@wrongstack/core 0.264.0 → 0.267.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-D8sa1vtv.d.ts → agent-bridge-STJ3JwwK.d.ts} +1 -1
- package/dist/{agent-subagent-runner-c9DLkaas.d.ts → agent-subagent-runner-CzPGP3jA.d.ts} +131 -11
- package/dist/{brain-O1IdKPaK.d.ts → brain-Cdg77tVN.d.ts} +103 -2
- package/dist/{compactor-BBy0rCtB.d.ts → compactor-iMZ84CXq.d.ts} +19 -1
- package/dist/{config-Dz2F3H2K.d.ts → config-Du3pYYln.d.ts} +132 -13
- package/dist/{context-BGSpZNSE.d.ts → context-dT5Ueund.d.ts} +90 -12
- package/dist/coordination/index.d.ts +78 -22
- package/dist/coordination/index.js +695 -273
- 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 +2327 -965
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +16 -16
- package/dist/execution/index.js +1500 -371
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +2 -2
- package/dist/execution/prompt-enhancer.js +1 -1
- package/dist/execution/prompt-enhancer.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/{goal-preamble-DzjFuN3p.d.ts → goal-preamble-SulMTowG.d.ts} +33 -12
- package/dist/{goal-store-CxWmCGbH.d.ts → goal-store-CABDwdFE.d.ts} +1 -1
- package/dist/{index-CbLSI66_.d.ts → index-Bms0m4oy.d.ts} +5 -5
- package/dist/{index-CYIQrXVF.d.ts → index-DtCVWel4.d.ts} +8 -8
- package/dist/index-IEuxQd-E.d.ts +82 -0
- package/dist/index.d.ts +261 -57
- package/dist/index.js +4799 -2212
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +84 -9
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js +1 -1
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-DC4QRPUI.d.ts → mcp-servers-C2cBTxUR.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +104 -31
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-B_siPxqN.d.ts → models-registry-BqGZNJQ-.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-CK5Jdj9K.d.ts → multi-agent-coordinator-B8R43uPz.d.ts} +1 -1
- package/dist/{null-fleet-bus-DgvD4SCO.d.ts → null-fleet-bus-CnXa5oTH.d.ts} +14 -9
- package/dist/observability/index.d.ts +2 -2
- package/dist/{parallel-eternal-engine-bK0JQBR_.d.ts → parallel-eternal-engine-DdNnw9BQ.d.ts} +11 -9
- package/dist/{path-resolver-BPEDlN38.d.ts → path-resolver-COIMLCQL.d.ts} +3 -3
- package/dist/{permission-4yvGmMRB.d.ts → permission-B75JAi3-.d.ts} +1 -1
- package/dist/{permission-policy-C6XpsBOy.d.ts → permission-policy-DlR9eJAM.d.ts} +2 -2
- package/dist/{pipeline-CXCeMz8J.d.ts → pipeline-BfD2k1rT.d.ts} +3 -3
- package/dist/{plan-templates-BvzRBkJc.d.ts → plan-templates-DSIKCXZN.d.ts} +32 -8
- package/dist/provider-model-resolve-BNRsNuJx.d.ts +107 -0
- package/dist/{provider-runner-C5aQpDWE.d.ts → provider-runner-CX7iIvox.d.ts} +3 -3
- package/dist/{retry-policy-CFhdtRzz.d.ts → retry-policy-BilV1ujH.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +286 -105
- package/dist/sdd/index.js.map +1 -1
- package/dist/secret-vault-BAKpgFw_.d.ts +57 -0
- package/dist/{secret-vault-CxiVLbt1.d.ts → secret-vault-gkvEZZfE.d.ts} +43 -4
- package/dist/security/index.d.ts +6 -68
- package/dist/security/index.js +296 -95
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-gIuhRTkN.d.ts → selector-Bc7eWtT3.d.ts} +1 -1
- package/dist/{session-event-bridge-DkvvrpDt.d.ts → session-event-bridge-D-araDEz.d.ts} +1 -1
- package/dist/{session-reader-KdfVwkKP.d.ts → session-reader-D7Dapswh.d.ts} +1 -1
- package/dist/storage/index.d.ts +112 -15
- package/dist/storage/index.js +491 -156
- package/dist/storage/index.js.map +1 -1
- package/dist/tools/index.d.ts +4 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/types/index.d.ts +21 -21
- package/dist/types/index.js +1523 -450
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +455 -407
- package/dist/utils/index.js +2191 -1203
- 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 +1 -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/llm-selector-DzxuZnNz.d.ts +0 -58
- package/dist/secret-vault-BJDY28ev.d.ts +0 -25
package/dist/types/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { randomBytes, createCipheriv, createDecipheriv, randomUUID } from 'crypto';
|
|
2
2
|
import * as fs from 'fs/promises';
|
|
3
|
-
import * as
|
|
4
|
-
import * as fs2 from 'fs';
|
|
3
|
+
import * as path6 from 'path';
|
|
5
4
|
import * as os from 'os';
|
|
5
|
+
import * as fs2 from 'fs';
|
|
6
6
|
|
|
7
7
|
// src/types/blocks.ts
|
|
8
8
|
function isTextBlock(b) {
|
|
@@ -30,9 +30,9 @@ function asText(content) {
|
|
|
30
30
|
return content.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
31
31
|
}
|
|
32
32
|
async function atomicWrite(targetPath, content, opts = {}) {
|
|
33
|
-
const dir =
|
|
33
|
+
const dir = path6.dirname(targetPath);
|
|
34
34
|
await fs.mkdir(dir, { recursive: true });
|
|
35
|
-
const tmp =
|
|
35
|
+
const tmp = path6.join(dir, `.${path6.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
36
36
|
try {
|
|
37
37
|
if (typeof content === "string") {
|
|
38
38
|
await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
@@ -85,17 +85,12 @@ async function renameWithRetry(from, to) {
|
|
|
85
85
|
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
86
86
|
throw err;
|
|
87
87
|
}
|
|
88
|
-
await new Promise((
|
|
88
|
+
await new Promise((resolve6) => setTimeout(resolve6, delays[i]));
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
throw lastErr;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
// src/utils/error.ts
|
|
95
|
-
function toErrorMessage(err) {
|
|
96
|
-
return err instanceof Error ? err.message : String(err);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
94
|
// src/utils/term.ts
|
|
100
95
|
var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
|
|
101
96
|
function isStdoutTTY() {
|
|
@@ -143,20 +138,75 @@ var color = {
|
|
|
143
138
|
bgRed: wrap("41", "49"),
|
|
144
139
|
bgGreen: wrap("42", "49")
|
|
145
140
|
};
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
141
|
+
var MAX_DIGEST_CHARS = 4e3;
|
|
142
|
+
function createContextEvidenceState() {
|
|
143
|
+
return {
|
|
144
|
+
sessionGoals: [],
|
|
145
|
+
implicitFacts: [],
|
|
146
|
+
activeErrors: [],
|
|
147
|
+
toolCalls: [],
|
|
148
|
+
fileGraph: {},
|
|
149
|
+
repeatedReads: [],
|
|
150
|
+
updatedAt: Date.now()
|
|
151
|
+
};
|
|
150
152
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
function buildContextEvidenceDigest(ctx) {
|
|
154
|
+
const state = ensureEvidence(ctx);
|
|
155
|
+
const lines = [];
|
|
156
|
+
if (state.currentIntent?.text) {
|
|
157
|
+
lines.push(`intent: ${state.currentIntent.text}`);
|
|
158
|
+
}
|
|
159
|
+
const goals = state.sessionGoals.slice(-3);
|
|
160
|
+
if (goals.length > 0) {
|
|
161
|
+
lines.push("session_goals:");
|
|
162
|
+
for (const goal of goals) lines.push(`- ${goal}`);
|
|
163
|
+
}
|
|
164
|
+
const activeErrors = state.activeErrors.slice(-5);
|
|
165
|
+
if (activeErrors.length > 0) {
|
|
166
|
+
lines.push("active_errors:");
|
|
167
|
+
for (const err of activeErrors) lines.push(`- ${err}`);
|
|
168
|
+
}
|
|
169
|
+
const files = Object.values(state.fileGraph).sort((a, b) => b.writes - a.writes || b.reads - a.reads || a.path.localeCompare(b.path)).slice(0, 12);
|
|
170
|
+
if (files.length > 0) {
|
|
171
|
+
lines.push("dependency_graph:");
|
|
172
|
+
for (const file of files) {
|
|
173
|
+
const actions = [
|
|
174
|
+
file.reads > 0 ? `read ${file.reads}x` : "",
|
|
175
|
+
file.writes > 0 ? `write ${file.writes}x` : ""
|
|
176
|
+
].filter(Boolean).join(", ");
|
|
177
|
+
const refs = file.referenced ? "; referenced by assistant" : "";
|
|
178
|
+
const via = file.lastToolUseId ? `; last via ${file.lastToolUseId}` : "";
|
|
179
|
+
lines.push(`- ${file.path} (${actions || "seen"}${refs}${via})`);
|
|
180
|
+
}
|
|
158
181
|
}
|
|
159
|
-
|
|
182
|
+
const referenced = state.toolCalls.filter((tool) => tool.status === "referenced").slice(-10);
|
|
183
|
+
const recentSeen = state.toolCalls.filter((tool) => tool.status === "seen").slice(-5);
|
|
184
|
+
const trail = [...referenced, ...recentSeen];
|
|
185
|
+
if (trail.length > 0) {
|
|
186
|
+
lines.push("tool_trail:");
|
|
187
|
+
for (const tool of trail) {
|
|
188
|
+
const size = tool.outputTokens ? `; ~${tool.outputTokens} tokens` : "";
|
|
189
|
+
const filesText = tool.files.length > 0 ? `; files=${tool.files.slice(0, 4).join(", ")}` : "";
|
|
190
|
+
const symbolsText = tool.symbols.length > 0 ? `; symbols=${tool.symbols.slice(0, 4).join(", ")}` : "";
|
|
191
|
+
lines.push(
|
|
192
|
+
`- ${tool.toolUseId} ${tool.toolName} ${tool.status}: ${tool.summary}${filesText}${symbolsText}${size}`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const facts = state.implicitFacts.slice(-8);
|
|
197
|
+
if (facts.length > 0) {
|
|
198
|
+
lines.push("implicit_facts:");
|
|
199
|
+
for (const fact of facts) lines.push(`- ${fact}`);
|
|
200
|
+
}
|
|
201
|
+
const digest = lines.join("\n");
|
|
202
|
+
if (digest.length <= MAX_DIGEST_CHARS) return digest;
|
|
203
|
+
return `${digest.slice(0, MAX_DIGEST_CHARS)}... [+${digest.length - MAX_DIGEST_CHARS} chars]`;
|
|
204
|
+
}
|
|
205
|
+
function ensureEvidence(ctx) {
|
|
206
|
+
if (!ctx.contextEvidence) {
|
|
207
|
+
ctx.contextEvidence = createContextEvidenceState();
|
|
208
|
+
}
|
|
209
|
+
return ctx.contextEvidence;
|
|
160
210
|
}
|
|
161
211
|
|
|
162
212
|
// src/utils/deep-merge.ts
|
|
@@ -218,173 +268,179 @@ function deepMerge(base, patch, options = {}) {
|
|
|
218
268
|
return out;
|
|
219
269
|
}
|
|
220
270
|
|
|
221
|
-
// src/utils/
|
|
222
|
-
function
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
271
|
+
// src/utils/error.ts
|
|
272
|
+
function toErrorMessage(err) {
|
|
273
|
+
return err instanceof Error ? err.message : String(err);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/utils/expect-defined.ts
|
|
277
|
+
function expectDefined(value, label) {
|
|
278
|
+
if (value === null || value === void 0) {
|
|
279
|
+
const err = new Error("Expected value to be defined");
|
|
280
|
+
err.name = "ExpectDefinedError";
|
|
281
|
+
throw err;
|
|
282
|
+
}
|
|
283
|
+
return value;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/utils/json-schema-validate.ts
|
|
287
|
+
function validateAgainstSchema(value, schema) {
|
|
288
|
+
const errors = [];
|
|
289
|
+
walk(value, schema, "", errors);
|
|
290
|
+
return { ok: errors.length === 0, errors };
|
|
291
|
+
}
|
|
292
|
+
function walk(value, schema, path10, errors) {
|
|
293
|
+
if (schema.enum !== void 0) {
|
|
294
|
+
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
295
|
+
errors.push({
|
|
296
|
+
path: path10 || "<root>",
|
|
297
|
+
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
298
|
+
});
|
|
299
|
+
return;
|
|
238
300
|
}
|
|
239
|
-
return String(value);
|
|
240
301
|
}
|
|
241
|
-
|
|
242
|
-
if (
|
|
243
|
-
|
|
302
|
+
if (typeof schema.type === "string") {
|
|
303
|
+
if (!checkType(value, schema.type)) {
|
|
304
|
+
errors.push({
|
|
305
|
+
path: path10 || "<root>",
|
|
306
|
+
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
307
|
+
});
|
|
308
|
+
return;
|
|
244
309
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
310
|
+
}
|
|
311
|
+
if (schema.type === "object" && isPlainObject(value)) {
|
|
312
|
+
const obj = value;
|
|
313
|
+
for (const req of schema.required ?? []) {
|
|
314
|
+
if (!(req in obj)) {
|
|
315
|
+
errors.push({ path: joinPath(path10, req), message: "required property missing" });
|
|
316
|
+
}
|
|
248
317
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
318
|
+
if (schema.properties) {
|
|
319
|
+
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
320
|
+
if (key in obj) {
|
|
321
|
+
walk(obj[key], subSchema, joinPath(path10, key), errors);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
256
324
|
}
|
|
257
|
-
const half = Math.floor(available / 2);
|
|
258
|
-
const first = text.slice(0, half);
|
|
259
|
-
const second = text.slice(text.length - half);
|
|
260
|
-
return { text: `${first}${marker}${second}`, newBudget: 0 };
|
|
261
|
-
}
|
|
262
|
-
return { serialize, enforceCap, capBytes };
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// src/utils/token-estimate.ts
|
|
266
|
-
var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
|
|
267
|
-
var CALIBRATION_GLOBAL_KEY = "__global__";
|
|
268
|
-
var _cals = /* @__PURE__ */ new Map();
|
|
269
|
-
function calState(key) {
|
|
270
|
-
let state = _cals.get(key);
|
|
271
|
-
if (!state) {
|
|
272
|
-
state = { ratio: 1, count: 0, prevEst: 0 };
|
|
273
|
-
_cals.set(key, state);
|
|
274
325
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
let evicted = 0;
|
|
284
|
-
const maxEvict = Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4);
|
|
285
|
-
for (const k of ESTIMATE_CACHE.keys()) {
|
|
286
|
-
if (evicted >= maxEvict) break;
|
|
287
|
-
ESTIMATE_CACHE.delete(k);
|
|
288
|
-
evicted++;
|
|
326
|
+
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
327
|
+
for (let i = 0; i < value.length; i++) {
|
|
328
|
+
walk(value[i], schema.items, `${path10}[${i}]`, errors);
|
|
289
329
|
}
|
|
290
330
|
}
|
|
291
|
-
const estimate = compute(key);
|
|
292
|
-
ESTIMATE_CACHE.set(key, estimate);
|
|
293
|
-
return estimate;
|
|
294
331
|
}
|
|
295
|
-
function
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
332
|
+
function checkType(value, type) {
|
|
333
|
+
switch (type) {
|
|
334
|
+
case "string":
|
|
335
|
+
return typeof value === "string";
|
|
336
|
+
case "number":
|
|
337
|
+
return typeof value === "number" && !Number.isNaN(value);
|
|
338
|
+
case "integer":
|
|
339
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
340
|
+
case "boolean":
|
|
341
|
+
return typeof value === "boolean";
|
|
342
|
+
case "null":
|
|
343
|
+
return value === null;
|
|
344
|
+
case "array":
|
|
345
|
+
return Array.isArray(value);
|
|
346
|
+
case "object":
|
|
347
|
+
return isPlainObject(value);
|
|
348
|
+
default:
|
|
349
|
+
return true;
|
|
299
350
|
}
|
|
300
|
-
return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
|
|
301
351
|
}
|
|
302
|
-
function
|
|
303
|
-
|
|
304
|
-
return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
|
|
352
|
+
function isPlainObject(v) {
|
|
353
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
305
354
|
}
|
|
306
|
-
function
|
|
307
|
-
|
|
355
|
+
function describeType(v) {
|
|
356
|
+
if (v === null) return "null";
|
|
357
|
+
if (Array.isArray(v)) return "array";
|
|
358
|
+
return typeof v;
|
|
308
359
|
}
|
|
309
|
-
function
|
|
310
|
-
if (
|
|
311
|
-
|
|
312
|
-
for (const b of msg.content) {
|
|
313
|
-
if (b.type === "text") total += estimateTextTokens(b.text);
|
|
314
|
-
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
315
|
-
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
316
|
-
else total += RoughTokenEstimate(JSON.stringify(b));
|
|
317
|
-
}
|
|
318
|
-
return total;
|
|
360
|
+
function joinPath(parent, key) {
|
|
361
|
+
if (!parent) return key;
|
|
362
|
+
return `${parent}.${key}`;
|
|
319
363
|
}
|
|
320
|
-
function
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
total += computeMessageTokens(m);
|
|
364
|
+
function deepEqual(a, b) {
|
|
365
|
+
if (a === b) return true;
|
|
366
|
+
if (typeof a !== typeof b) return false;
|
|
367
|
+
if (a === null || b === null) return a === b;
|
|
368
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
369
|
+
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
328
370
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
371
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
372
|
+
const ak = Object.keys(a);
|
|
373
|
+
const bk = Object.keys(b);
|
|
374
|
+
if (ak.length !== bk.length) return false;
|
|
375
|
+
return ak.every(
|
|
376
|
+
(k) => deepEqual(a[k], b[k])
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return false;
|
|
335
380
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (typeof m === "object" && m !== null && "content" in m) {
|
|
343
|
-
const cached = m._estTokens;
|
|
344
|
-
if (typeof cached === "number" && cached > 0) {
|
|
345
|
-
messagesTokens += cached;
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
const content = m.content;
|
|
349
|
-
if (typeof content === "string") {
|
|
350
|
-
messagesTokens += RoughTokenEstimate(content);
|
|
351
|
-
} else if (Array.isArray(content)) {
|
|
352
|
-
for (const b of content) {
|
|
353
|
-
if (typeof b === "object" && b !== null) {
|
|
354
|
-
if (b.type === "text") {
|
|
355
|
-
messagesTokens += RoughTokenEstimate(b.text);
|
|
356
|
-
} else {
|
|
357
|
-
messagesTokens += RoughTokenEstimate(JSON.stringify(b));
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
381
|
+
|
|
382
|
+
// src/utils/merge-models-payload.ts
|
|
383
|
+
function mergeModelsPayload(base, overlay) {
|
|
384
|
+
const out = {};
|
|
385
|
+
for (const [id, provider] of Object.entries(base)) {
|
|
386
|
+
out[id] = cloneProvider(provider);
|
|
364
387
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
} else if (Array.isArray(systemPrompt)) {
|
|
369
|
-
for (const b of systemPrompt) {
|
|
370
|
-
if (typeof b === "object" && b !== null && b.type === "text") {
|
|
371
|
-
systemTokens += RoughTokenEstimate(b.text);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
388
|
+
for (const [id, ovProvider] of Object.entries(overlay)) {
|
|
389
|
+
const existing = out[id];
|
|
390
|
+
out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
|
|
374
391
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
392
|
+
return out;
|
|
393
|
+
}
|
|
394
|
+
function mergeProvider(base, overlay) {
|
|
395
|
+
const models = {};
|
|
396
|
+
for (const [mid, m] of Object.entries(base.models ?? {})) {
|
|
397
|
+
models[mid] = { ...m };
|
|
398
|
+
}
|
|
399
|
+
for (const [mid, ovModel] of Object.entries(overlay.models ?? {})) {
|
|
400
|
+
const existing = models[mid];
|
|
401
|
+
models[mid] = existing ? mergeModel(existing, ovModel) : { ...ovModel };
|
|
378
402
|
}
|
|
379
|
-
const total = messagesTokens + systemTokens + toolsTokens;
|
|
380
|
-
calState(calibrationKey).prevEst = total;
|
|
381
403
|
return {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
404
|
+
...base,
|
|
405
|
+
// Overlay scalar fields win when explicitly provided; otherwise keep base.
|
|
406
|
+
...stripUndefined({
|
|
407
|
+
id: overlay.id,
|
|
408
|
+
name: overlay.name,
|
|
409
|
+
npm: overlay.npm,
|
|
410
|
+
api: overlay.api,
|
|
411
|
+
env: overlay.env,
|
|
412
|
+
doc: overlay.doc
|
|
413
|
+
}),
|
|
414
|
+
models
|
|
386
415
|
};
|
|
387
416
|
}
|
|
417
|
+
function mergeModel(base, overlay) {
|
|
418
|
+
const merged = { ...base, ...overlay };
|
|
419
|
+
if (base.limit || overlay.limit) {
|
|
420
|
+
merged.limit = { ...base.limit, ...overlay.limit };
|
|
421
|
+
}
|
|
422
|
+
if (base.cost || overlay.cost) {
|
|
423
|
+
merged.cost = { ...base.cost, ...overlay.cost };
|
|
424
|
+
}
|
|
425
|
+
if (base.modalities || overlay.modalities) {
|
|
426
|
+
merged.modalities = { ...base.modalities, ...overlay.modalities };
|
|
427
|
+
}
|
|
428
|
+
return merged;
|
|
429
|
+
}
|
|
430
|
+
function cloneProvider(p) {
|
|
431
|
+
const models = {};
|
|
432
|
+
for (const [mid, m] of Object.entries(p.models ?? {})) {
|
|
433
|
+
models[mid] = { ...m };
|
|
434
|
+
}
|
|
435
|
+
return { ...p, models };
|
|
436
|
+
}
|
|
437
|
+
function stripUndefined(obj) {
|
|
438
|
+
const out = {};
|
|
439
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
440
|
+
if (v !== void 0) out[k] = v;
|
|
441
|
+
}
|
|
442
|
+
return out;
|
|
443
|
+
}
|
|
388
444
|
|
|
389
445
|
// src/utils/message-invariants.ts
|
|
390
446
|
function repairToolUseAdjacency(messages) {
|
|
@@ -478,102 +534,6 @@ function isEmptyMessage(msg) {
|
|
|
478
534
|
return msg.content.length === 0;
|
|
479
535
|
}
|
|
480
536
|
|
|
481
|
-
// src/utils/json-schema-validate.ts
|
|
482
|
-
function validateAgainstSchema(value, schema) {
|
|
483
|
-
const errors = [];
|
|
484
|
-
walk(value, schema, "", errors);
|
|
485
|
-
return { ok: errors.length === 0, errors };
|
|
486
|
-
}
|
|
487
|
-
function walk(value, schema, path7, errors) {
|
|
488
|
-
if (schema.enum !== void 0) {
|
|
489
|
-
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
490
|
-
errors.push({
|
|
491
|
-
path: path7 || "<root>",
|
|
492
|
-
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
493
|
-
});
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
if (typeof schema.type === "string") {
|
|
498
|
-
if (!checkType(value, schema.type)) {
|
|
499
|
-
errors.push({
|
|
500
|
-
path: path7 || "<root>",
|
|
501
|
-
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
502
|
-
});
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
if (schema.type === "object" && isPlainObject(value)) {
|
|
507
|
-
const obj = value;
|
|
508
|
-
for (const req of schema.required ?? []) {
|
|
509
|
-
if (!(req in obj)) {
|
|
510
|
-
errors.push({ path: joinPath(path7, req), message: "required property missing" });
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
if (schema.properties) {
|
|
514
|
-
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
515
|
-
if (key in obj) {
|
|
516
|
-
walk(obj[key], subSchema, joinPath(path7, key), errors);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
522
|
-
for (let i = 0; i < value.length; i++) {
|
|
523
|
-
walk(value[i], schema.items, `${path7}[${i}]`, errors);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
function checkType(value, type) {
|
|
528
|
-
switch (type) {
|
|
529
|
-
case "string":
|
|
530
|
-
return typeof value === "string";
|
|
531
|
-
case "number":
|
|
532
|
-
return typeof value === "number" && !Number.isNaN(value);
|
|
533
|
-
case "integer":
|
|
534
|
-
return typeof value === "number" && Number.isInteger(value);
|
|
535
|
-
case "boolean":
|
|
536
|
-
return typeof value === "boolean";
|
|
537
|
-
case "null":
|
|
538
|
-
return value === null;
|
|
539
|
-
case "array":
|
|
540
|
-
return Array.isArray(value);
|
|
541
|
-
case "object":
|
|
542
|
-
return isPlainObject(value);
|
|
543
|
-
default:
|
|
544
|
-
return true;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
function isPlainObject(v) {
|
|
548
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
549
|
-
}
|
|
550
|
-
function describeType(v) {
|
|
551
|
-
if (v === null) return "null";
|
|
552
|
-
if (Array.isArray(v)) return "array";
|
|
553
|
-
return typeof v;
|
|
554
|
-
}
|
|
555
|
-
function joinPath(parent, key) {
|
|
556
|
-
if (!parent) return key;
|
|
557
|
-
return `${parent}.${key}`;
|
|
558
|
-
}
|
|
559
|
-
function deepEqual(a, b) {
|
|
560
|
-
if (a === b) return true;
|
|
561
|
-
if (typeof a !== typeof b) return false;
|
|
562
|
-
if (a === null || b === null) return a === b;
|
|
563
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
564
|
-
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
565
|
-
}
|
|
566
|
-
if (typeof a === "object" && typeof b === "object") {
|
|
567
|
-
const ak = Object.keys(a);
|
|
568
|
-
const bk = Object.keys(b);
|
|
569
|
-
if (ak.length !== bk.length) return false;
|
|
570
|
-
return ak.every(
|
|
571
|
-
(k) => deepEqual(a[k], b[k])
|
|
572
|
-
);
|
|
573
|
-
}
|
|
574
|
-
return false;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
537
|
// src/utils/regex-guard.ts
|
|
578
538
|
var MAX_PATTERN_LEN = 512;
|
|
579
539
|
var DANGEROUS_PATTERNS = [
|
|
@@ -599,78 +559,858 @@ function compileUserRegex(pattern, flags) {
|
|
|
599
559
|
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
600
560
|
};
|
|
601
561
|
}
|
|
602
|
-
}
|
|
603
|
-
try {
|
|
604
|
-
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
605
|
-
} catch (err) {
|
|
606
|
-
return {
|
|
607
|
-
ok: false,
|
|
608
|
-
reason: err instanceof Error ? err.message : "invalid regex"
|
|
609
|
-
};
|
|
610
|
-
}
|
|
562
|
+
}
|
|
563
|
+
try {
|
|
564
|
+
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
565
|
+
} catch (err) {
|
|
566
|
+
return {
|
|
567
|
+
ok: false,
|
|
568
|
+
reason: err instanceof Error ? err.message : "invalid regex"
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// src/utils/string.ts
|
|
574
|
+
function truncate(s, max) {
|
|
575
|
+
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/utils/tool-wire-compact.ts
|
|
579
|
+
var TOOL_DESCRIPTION_MAX_CHARS = 640;
|
|
580
|
+
var SCHEMA_DESCRIPTION_MAX_CHARS = 180;
|
|
581
|
+
var compactCache = /* @__PURE__ */ new WeakMap();
|
|
582
|
+
function compactToolDefinitionForWire(tool, opts = {}) {
|
|
583
|
+
const useDefaultOptions = opts.descriptionMaxChars === void 0 && opts.schemaDescriptionMaxChars === void 0;
|
|
584
|
+
if (useDefaultOptions && typeof tool === "object" && tool !== null) {
|
|
585
|
+
const cached = compactCache.get(tool);
|
|
586
|
+
if (cached) return cached;
|
|
587
|
+
}
|
|
588
|
+
const compact = {
|
|
589
|
+
name: tool.name,
|
|
590
|
+
description: compactDescription(
|
|
591
|
+
tool.description ?? "",
|
|
592
|
+
opts.descriptionMaxChars ?? TOOL_DESCRIPTION_MAX_CHARS
|
|
593
|
+
),
|
|
594
|
+
inputSchema: compactSchemaDescriptions(
|
|
595
|
+
tool.inputSchema,
|
|
596
|
+
opts.schemaDescriptionMaxChars ?? SCHEMA_DESCRIPTION_MAX_CHARS
|
|
597
|
+
)
|
|
598
|
+
};
|
|
599
|
+
if (useDefaultOptions && typeof tool === "object" && tool !== null) {
|
|
600
|
+
compactCache.set(tool, compact);
|
|
601
|
+
}
|
|
602
|
+
return compact;
|
|
603
|
+
}
|
|
604
|
+
function compactSchemaDescriptions(schema, maxDescriptionChars = SCHEMA_DESCRIPTION_MAX_CHARS) {
|
|
605
|
+
const compact = compactSchemaNode(schema, maxDescriptionChars);
|
|
606
|
+
return isRecord(compact) ? compact : { type: "object", properties: {} };
|
|
607
|
+
}
|
|
608
|
+
function compactSchemaNode(node, maxDescriptionChars) {
|
|
609
|
+
if (Array.isArray(node)) {
|
|
610
|
+
return node.map((item) => compactSchemaNode(item, maxDescriptionChars));
|
|
611
|
+
}
|
|
612
|
+
if (!isRecord(node)) return node;
|
|
613
|
+
const out = {};
|
|
614
|
+
for (const [key, value] of Object.entries(node)) {
|
|
615
|
+
if (key === "description" && typeof value === "string") {
|
|
616
|
+
out[key] = compactDescription(value, maxDescriptionChars);
|
|
617
|
+
} else {
|
|
618
|
+
out[key] = compactSchemaNode(value, maxDescriptionChars);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return out;
|
|
622
|
+
}
|
|
623
|
+
function compactDescription(text, maxChars) {
|
|
624
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
625
|
+
if (normalized.length <= maxChars) return normalized;
|
|
626
|
+
if (maxChars <= 20) return normalized.slice(0, maxChars);
|
|
627
|
+
const hardLimit = maxChars - 12;
|
|
628
|
+
const boundary = findSemanticBoundary(normalized, hardLimit);
|
|
629
|
+
const head = normalized.slice(0, boundary > 0 ? boundary : hardLimit).trimEnd();
|
|
630
|
+
return `${head} ...`;
|
|
631
|
+
}
|
|
632
|
+
function findSemanticBoundary(text, limit) {
|
|
633
|
+
const punctuation = Math.max(
|
|
634
|
+
text.lastIndexOf(". ", limit),
|
|
635
|
+
text.lastIndexOf("; ", limit),
|
|
636
|
+
text.lastIndexOf(": ", limit)
|
|
637
|
+
);
|
|
638
|
+
if (punctuation >= Math.floor(limit * 0.45)) return punctuation + 1;
|
|
639
|
+
const comma = text.lastIndexOf(", ", limit);
|
|
640
|
+
if (comma >= Math.floor(limit * 0.6)) return comma + 1;
|
|
641
|
+
const space = text.lastIndexOf(" ", limit);
|
|
642
|
+
return space >= Math.floor(limit * 0.6) ? space : limit;
|
|
643
|
+
}
|
|
644
|
+
function isRecord(value) {
|
|
645
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// src/utils/token-estimate.ts
|
|
649
|
+
var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
|
|
650
|
+
var CALIBRATION_GLOBAL_KEY = "__global__";
|
|
651
|
+
var _cals = /* @__PURE__ */ new Map();
|
|
652
|
+
function calState(key) {
|
|
653
|
+
let state = _cals.get(key);
|
|
654
|
+
if (!state) {
|
|
655
|
+
state = { ratio: 1, count: 0, prevEst: 0 };
|
|
656
|
+
_cals.set(key, state);
|
|
657
|
+
}
|
|
658
|
+
return state;
|
|
659
|
+
}
|
|
660
|
+
var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
|
|
661
|
+
var ESTIMATE_CACHE_MAX_SIZE = 1e4;
|
|
662
|
+
function getCachedEstimate(key, compute) {
|
|
663
|
+
const existing = ESTIMATE_CACHE.get(key);
|
|
664
|
+
if (existing !== void 0) return existing;
|
|
665
|
+
if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
|
|
666
|
+
for (const k of ESTIMATE_CACHE.keys()) {
|
|
667
|
+
if (ESTIMATE_CACHE.size <= Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) break;
|
|
668
|
+
ESTIMATE_CACHE.delete(k);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
const estimate = compute(key);
|
|
672
|
+
ESTIMATE_CACHE.set(key, estimate);
|
|
673
|
+
return estimate;
|
|
674
|
+
}
|
|
675
|
+
function estimateToolInputTokens(input) {
|
|
676
|
+
if (typeof input === "string") return RoughTokenEstimate(input);
|
|
677
|
+
if (input === null || typeof input !== "object") {
|
|
678
|
+
return RoughTokenEstimate(String(input));
|
|
679
|
+
}
|
|
680
|
+
return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
|
|
681
|
+
}
|
|
682
|
+
function estimateToolResultTokens(content) {
|
|
683
|
+
if (typeof content === "string") return RoughTokenEstimate(content);
|
|
684
|
+
return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
|
|
685
|
+
}
|
|
686
|
+
function estimateTextTokens(text) {
|
|
687
|
+
return RoughTokenEstimate(text);
|
|
688
|
+
}
|
|
689
|
+
function computeMessageTokens(msg) {
|
|
690
|
+
if (typeof msg.content === "string") return estimateTextTokens(msg.content);
|
|
691
|
+
let total = 0;
|
|
692
|
+
for (const b of msg.content) {
|
|
693
|
+
if (b.type === "text") total += estimateTextTokens(b.text);
|
|
694
|
+
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
695
|
+
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
696
|
+
else total += RoughTokenEstimate(JSON.stringify(b));
|
|
697
|
+
}
|
|
698
|
+
return total;
|
|
699
|
+
}
|
|
700
|
+
function estimateMessageTokens(messages) {
|
|
701
|
+
let total = 0;
|
|
702
|
+
for (const m of messages) {
|
|
703
|
+
if (typeof m._estTokens === "number" && m._estTokens > 0) {
|
|
704
|
+
total += m._estTokens;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
total += computeMessageTokens(m);
|
|
708
|
+
}
|
|
709
|
+
return total;
|
|
710
|
+
}
|
|
711
|
+
function estimateToolDefTokens(tool) {
|
|
712
|
+
const cached = tool._estDefTokens;
|
|
713
|
+
if (typeof cached === "number" && cached > 0) return cached;
|
|
714
|
+
const compact = compactToolDefinitionForWire(tool);
|
|
715
|
+
return RoughTokenEstimate(tool.name) + RoughTokenEstimate(compact.description) + RoughTokenEstimate(JSON.stringify(compact.inputSchema));
|
|
716
|
+
}
|
|
717
|
+
function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
718
|
+
let messagesTokens = 0;
|
|
719
|
+
if (typeof messages === "string") {
|
|
720
|
+
messagesTokens = RoughTokenEstimate(messages);
|
|
721
|
+
} else if (Array.isArray(messages)) {
|
|
722
|
+
for (const m of messages) {
|
|
723
|
+
if (typeof m === "object" && m !== null && "content" in m) {
|
|
724
|
+
const cached = m._estTokens;
|
|
725
|
+
if (typeof cached === "number" && cached > 0) {
|
|
726
|
+
messagesTokens += cached;
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
const content = m.content;
|
|
730
|
+
if (typeof content === "string") {
|
|
731
|
+
messagesTokens += RoughTokenEstimate(content);
|
|
732
|
+
} else if (Array.isArray(content)) {
|
|
733
|
+
for (const b of content) {
|
|
734
|
+
if (typeof b === "object" && b !== null) {
|
|
735
|
+
if (b.type === "text") {
|
|
736
|
+
messagesTokens += RoughTokenEstimate(b.text);
|
|
737
|
+
} else {
|
|
738
|
+
messagesTokens += RoughTokenEstimate(JSON.stringify(b));
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
let systemTokens = 0;
|
|
747
|
+
if (typeof systemPrompt === "string") {
|
|
748
|
+
systemTokens = RoughTokenEstimate(systemPrompt);
|
|
749
|
+
} else if (Array.isArray(systemPrompt)) {
|
|
750
|
+
for (const b of systemPrompt) {
|
|
751
|
+
if (typeof b === "object" && b !== null && b.type === "text") {
|
|
752
|
+
systemTokens += RoughTokenEstimate(b.text);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
let toolsTokens = 0;
|
|
757
|
+
for (const t of tools) {
|
|
758
|
+
toolsTokens += estimateToolDefTokens(t);
|
|
759
|
+
}
|
|
760
|
+
const total = messagesTokens + systemTokens + toolsTokens;
|
|
761
|
+
calState(calibrationKey).prevEst = total;
|
|
762
|
+
return {
|
|
763
|
+
messages: messagesTokens,
|
|
764
|
+
systemPrompt: systemTokens,
|
|
765
|
+
tools: toolsTokens,
|
|
766
|
+
total
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// src/utils/tool-output-serializer.ts
|
|
771
|
+
var DEFAULT_LIST_LIMIT = 500;
|
|
772
|
+
var LOG_ENTRY_LIMIT = 200;
|
|
773
|
+
var INLINE_LIMIT = 240;
|
|
774
|
+
var GREP_FILE_LIMIT = 80;
|
|
775
|
+
var GREP_MATCHES_PER_FILE = 3;
|
|
776
|
+
var DIFF_INLINE_LINE_LIMIT = 260;
|
|
777
|
+
var DIFF_HUNK_LIMIT = 8;
|
|
778
|
+
var DIFF_HUNK_CONTEXT = 14;
|
|
779
|
+
function createToolOutputSerializer(opts = {}) {
|
|
780
|
+
const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
|
|
781
|
+
function serialize(value, context = {}) {
|
|
782
|
+
if (typeof value === "string") return value;
|
|
783
|
+
if (value === null || value === void 0) return "";
|
|
784
|
+
if (typeof value === "object") {
|
|
785
|
+
if (Array.isArray(value)) return value.map((item) => serialize(item)).join("\n");
|
|
786
|
+
if (context.toolName) {
|
|
787
|
+
const compact = renderToolObject(context.toolName, value, context.input);
|
|
788
|
+
if (compact !== void 0) return compact;
|
|
789
|
+
return renderGenericToolObject(context.toolName, value);
|
|
790
|
+
}
|
|
791
|
+
if ("text" in value) {
|
|
792
|
+
const t = value.text;
|
|
793
|
+
return typeof t === "string" ? t : JSON.stringify(value, null, 2);
|
|
794
|
+
}
|
|
795
|
+
try {
|
|
796
|
+
return JSON.stringify(value, null, 2);
|
|
797
|
+
} catch {
|
|
798
|
+
return String(value);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
return String(value);
|
|
802
|
+
}
|
|
803
|
+
function enforceCap(text, remainingBudget) {
|
|
804
|
+
if (remainingBudget <= 0) {
|
|
805
|
+
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
806
|
+
}
|
|
807
|
+
const textBytes = Buffer.byteLength(text, "utf8");
|
|
808
|
+
if (textBytes <= remainingBudget) {
|
|
809
|
+
return { text, newBudget: remainingBudget - textBytes };
|
|
810
|
+
}
|
|
811
|
+
const marker = `
|
|
812
|
+
\u2026[truncated ${textBytes - remainingBudget} bytes]\u2026
|
|
813
|
+
`;
|
|
814
|
+
const markerBytes = Buffer.byteLength(marker, "utf8");
|
|
815
|
+
const available = remainingBudget - markerBytes;
|
|
816
|
+
if (available <= 0) {
|
|
817
|
+
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
818
|
+
}
|
|
819
|
+
const half = Math.floor(available / 2);
|
|
820
|
+
const first = text.slice(0, half);
|
|
821
|
+
const second = text.slice(text.length - half);
|
|
822
|
+
return { text: `${first}${marker}${second}`, newBudget: 0 };
|
|
823
|
+
}
|
|
824
|
+
return { serialize, enforceCap, capBytes };
|
|
825
|
+
}
|
|
826
|
+
function renderToolObject(toolName, obj, input) {
|
|
827
|
+
if (toolName === "read" && typeof obj["text"] === "string") {
|
|
828
|
+
return joinSections([
|
|
829
|
+
renderHeader(
|
|
830
|
+
`read: ${stringFromInput(input, "path") ?? stringField(obj, "path") ?? "<unknown>"}`,
|
|
831
|
+
{
|
|
832
|
+
offset: numberFromInput(input, "offset"),
|
|
833
|
+
limit: numberFromInput(input, "limit"),
|
|
834
|
+
total_lines: obj["total_lines"],
|
|
835
|
+
encoding: obj["encoding"],
|
|
836
|
+
truncated: obj["truncated"],
|
|
837
|
+
cached: obj["cached"],
|
|
838
|
+
note: obj["note"]
|
|
839
|
+
}
|
|
840
|
+
),
|
|
841
|
+
obj["text"]
|
|
842
|
+
]);
|
|
843
|
+
}
|
|
844
|
+
if (toolName === "grep" && Array.isArray(obj["matches"])) {
|
|
845
|
+
const matches = stringArrayField(obj, "matches");
|
|
846
|
+
return joinSections([
|
|
847
|
+
renderHeader(`grep: ${stringFromInput(input, "pattern") ?? "<pattern>"}`, {
|
|
848
|
+
path: stringFromInput(input, "path"),
|
|
849
|
+
glob: stringFromInput(input, "glob"),
|
|
850
|
+
mode: stringFromInput(input, "output_mode"),
|
|
851
|
+
count: obj["count"],
|
|
852
|
+
shown: matches.length,
|
|
853
|
+
truncated: obj["truncated"],
|
|
854
|
+
used: obj["used"]
|
|
855
|
+
}),
|
|
856
|
+
renderGrepMatches(matches, stringFromInput(input, "output_mode"))
|
|
857
|
+
]);
|
|
858
|
+
}
|
|
859
|
+
if (toolName === "patch" && Array.isArray(obj["files"])) {
|
|
860
|
+
const files = stringArrayField(obj, "files");
|
|
861
|
+
return joinSections([
|
|
862
|
+
renderHeader("patch", {
|
|
863
|
+
applied: obj["applied"],
|
|
864
|
+
rejected: obj["rejected"],
|
|
865
|
+
files: files.length,
|
|
866
|
+
dry_run: obj["dry_run"]
|
|
867
|
+
}),
|
|
868
|
+
typeof obj["message"] === "string" ? `message:
|
|
869
|
+
${obj["message"]}` : void 0,
|
|
870
|
+
files.length > 0 ? `files:
|
|
871
|
+
${renderStringList(files)}` : void 0
|
|
872
|
+
]);
|
|
873
|
+
}
|
|
874
|
+
if (toolName === "glob" && Array.isArray(obj["files"])) {
|
|
875
|
+
const files = stringArrayField(obj, "files");
|
|
876
|
+
return joinSections([
|
|
877
|
+
renderHeader(
|
|
878
|
+
`${toolName}: ${stringFromInput(input, "pattern") ?? stringFromInput(input, "files") ?? stringFromInput(input, "path") ?? ""}`.trim(),
|
|
879
|
+
{
|
|
880
|
+
path: stringFromInput(input, "path"),
|
|
881
|
+
files: files.length,
|
|
882
|
+
truncated: obj["truncated"]
|
|
883
|
+
}
|
|
884
|
+
),
|
|
885
|
+
renderStringList(files, "(no files)")
|
|
886
|
+
]);
|
|
887
|
+
}
|
|
888
|
+
if (toolName === "tree" && typeof obj["tree"] === "string") {
|
|
889
|
+
return joinSections([
|
|
890
|
+
renderHeader(
|
|
891
|
+
`tree: ${stringField(obj, "path") ?? stringFromInput(input, "path") ?? "<cwd>"}`,
|
|
892
|
+
{
|
|
893
|
+
total_files: obj["total_files"],
|
|
894
|
+
total_dirs: obj["total_dirs"],
|
|
895
|
+
truncated: obj["truncated"]
|
|
896
|
+
}
|
|
897
|
+
),
|
|
898
|
+
obj["tree"]
|
|
899
|
+
]);
|
|
900
|
+
}
|
|
901
|
+
if (toolName === "fetch" && typeof obj["content"] === "string") {
|
|
902
|
+
return joinSections([
|
|
903
|
+
renderHeader(
|
|
904
|
+
`fetch: ${stringField(obj, "url") ?? stringFromInput(input, "url") ?? "<url>"}`,
|
|
905
|
+
{
|
|
906
|
+
status: obj["status"],
|
|
907
|
+
content_type: obj["content_type"]
|
|
908
|
+
}
|
|
909
|
+
),
|
|
910
|
+
obj["content"]
|
|
911
|
+
]);
|
|
912
|
+
}
|
|
913
|
+
if (toolName === "replace" && Array.isArray(obj["results"])) {
|
|
914
|
+
const results = obj["results"].filter(isRecord2);
|
|
915
|
+
const sections = [
|
|
916
|
+
renderHeader("replace", {
|
|
917
|
+
files_modified: obj["files_modified"],
|
|
918
|
+
total_replacements: obj["total_replacements"],
|
|
919
|
+
dry_run: obj["dry_run"]
|
|
920
|
+
})
|
|
921
|
+
];
|
|
922
|
+
for (const r of results.slice(0, DEFAULT_LIST_LIMIT)) {
|
|
923
|
+
sections.push(
|
|
924
|
+
joinSections([
|
|
925
|
+
renderHeader(`file: ${stringField(r, "path") ?? "<unknown>"}`, {
|
|
926
|
+
replacements: r["replacements"]
|
|
927
|
+
}),
|
|
928
|
+
typeof r["diff"] === "string" ? r["diff"] : void 0
|
|
929
|
+
])
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
if (results.length > DEFAULT_LIST_LIMIT) {
|
|
933
|
+
sections.push(`[serializer omitted ${results.length - DEFAULT_LIST_LIMIT} result item(s)]`);
|
|
934
|
+
}
|
|
935
|
+
return joinSections(sections);
|
|
936
|
+
}
|
|
937
|
+
if (typeof obj["diff"] === "string") {
|
|
938
|
+
const diff = obj["diff"];
|
|
939
|
+
return joinSections([
|
|
940
|
+
renderHeader(toolName, {
|
|
941
|
+
path: obj["path"],
|
|
942
|
+
replacements: obj["replacements"],
|
|
943
|
+
bytes_written: obj["bytes_written"],
|
|
944
|
+
created: obj["created"],
|
|
945
|
+
note: obj["note"],
|
|
946
|
+
files: Array.isArray(obj["files"]) ? obj["files"].length : void 0,
|
|
947
|
+
truncated: obj["truncated"],
|
|
948
|
+
mode: obj["mode"]
|
|
949
|
+
}),
|
|
950
|
+
compactDiff(diff)
|
|
951
|
+
]);
|
|
952
|
+
}
|
|
953
|
+
if (toolName === "test" && typeof obj["output"] === "string") {
|
|
954
|
+
return renderTestOutput(obj, input);
|
|
955
|
+
}
|
|
956
|
+
if ((toolName === "typecheck" || toolName === "lint" || toolName === "format") && typeof obj["output"] === "string") {
|
|
957
|
+
return renderVerifierOutput(toolName, obj, input);
|
|
958
|
+
}
|
|
959
|
+
if (hasCommandOutputShape(obj)) {
|
|
960
|
+
return renderCommandOutput(toolName, obj, input);
|
|
961
|
+
}
|
|
962
|
+
if (toolName === "json" && typeof obj["formatted"] === "string") {
|
|
963
|
+
return joinSections([
|
|
964
|
+
renderHeader("json", {
|
|
965
|
+
type: obj["type"],
|
|
966
|
+
keys: Array.isArray(obj["keys"]) ? obj["keys"].length : void 0,
|
|
967
|
+
query: stringFromInput(input, "query"),
|
|
968
|
+
error: obj["error"]
|
|
969
|
+
}),
|
|
970
|
+
obj["formatted"]
|
|
971
|
+
]);
|
|
972
|
+
}
|
|
973
|
+
if (toolName === "logs" && Array.isArray(obj["entries"])) {
|
|
974
|
+
const entries = obj["entries"].filter(isRecord2);
|
|
975
|
+
const lines = entries.slice(0, LOG_ENTRY_LIMIT).map((entry) => {
|
|
976
|
+
const ts = stringField(entry, "timestamp") ?? "";
|
|
977
|
+
const level = stringField(entry, "level") ?? "info";
|
|
978
|
+
const message = stringField(entry, "message") ?? "";
|
|
979
|
+
const source = stringField(entry, "source");
|
|
980
|
+
return [ts, level, source, message].filter(Boolean).join(" ");
|
|
981
|
+
});
|
|
982
|
+
if (entries.length > LOG_ENTRY_LIMIT) {
|
|
983
|
+
lines.push(`[serializer omitted ${entries.length - LOG_ENTRY_LIMIT} log entry item(s)]`);
|
|
984
|
+
}
|
|
985
|
+
return joinSections([
|
|
986
|
+
renderHeader(`logs: ${stringField(obj, "source") ?? "<source>"}`, {
|
|
987
|
+
total: obj["total"],
|
|
988
|
+
shown: Math.min(entries.length, LOG_ENTRY_LIMIT),
|
|
989
|
+
truncated: obj["truncated"],
|
|
990
|
+
stream_mode: obj["stream_mode"]
|
|
991
|
+
}),
|
|
992
|
+
lines.length > 0 ? lines.join("\n") : "(no log entries)"
|
|
993
|
+
]);
|
|
994
|
+
}
|
|
995
|
+
if (toolName === "audit" && Array.isArray(obj["vulnerabilities"])) {
|
|
996
|
+
const vulns = obj["vulnerabilities"].filter(isRecord2);
|
|
997
|
+
const lines = vulns.slice(0, DEFAULT_LIST_LIMIT).map((v) => {
|
|
998
|
+
const severity = stringField(v, "severity") ?? "unknown";
|
|
999
|
+
const pkg = stringField(v, "package") ?? "<package>";
|
|
1000
|
+
const title = stringField(v, "title") ?? "";
|
|
1001
|
+
const url = stringField(v, "url");
|
|
1002
|
+
return [severity, pkg, title, url].filter(Boolean).join(" | ");
|
|
1003
|
+
});
|
|
1004
|
+
if (vulns.length > DEFAULT_LIST_LIMIT) {
|
|
1005
|
+
lines.push(`[serializer omitted ${vulns.length - DEFAULT_LIST_LIMIT} vulnerability item(s)]`);
|
|
1006
|
+
}
|
|
1007
|
+
return joinSections([
|
|
1008
|
+
renderHeader("audit", {
|
|
1009
|
+
exit_code: obj["exit_code"],
|
|
1010
|
+
total: obj["total"],
|
|
1011
|
+
summary: obj["summary"],
|
|
1012
|
+
truncated: obj["truncated"]
|
|
1013
|
+
}),
|
|
1014
|
+
lines.length > 0 ? lines.join("\n") : stringField(obj, "output")
|
|
1015
|
+
]);
|
|
1016
|
+
}
|
|
1017
|
+
if (toolName === "outdated" && Array.isArray(obj["packages"])) {
|
|
1018
|
+
const packages = obj["packages"].filter(isRecord2);
|
|
1019
|
+
const lines = packages.slice(0, DEFAULT_LIST_LIMIT).map(
|
|
1020
|
+
(p) => [
|
|
1021
|
+
stringField(p, "name") ?? "<package>",
|
|
1022
|
+
`current=${stringField(p, "current") ?? "unknown"}`,
|
|
1023
|
+
`wanted=${stringField(p, "wanted") ?? "unknown"}`,
|
|
1024
|
+
`latest=${stringField(p, "latest") ?? "unknown"}`,
|
|
1025
|
+
stringField(p, "type")
|
|
1026
|
+
].filter(Boolean).join(" | ")
|
|
1027
|
+
);
|
|
1028
|
+
if (packages.length > DEFAULT_LIST_LIMIT) {
|
|
1029
|
+
lines.push(`[serializer omitted ${packages.length - DEFAULT_LIST_LIMIT} package item(s)]`);
|
|
1030
|
+
}
|
|
1031
|
+
return joinSections([
|
|
1032
|
+
renderHeader("outdated", {
|
|
1033
|
+
exit_code: obj["exit_code"],
|
|
1034
|
+
total: obj["total"],
|
|
1035
|
+
truncated: obj["truncated"]
|
|
1036
|
+
}),
|
|
1037
|
+
lines.length > 0 ? lines.join("\n") : stringField(obj, "output")
|
|
1038
|
+
]);
|
|
1039
|
+
}
|
|
1040
|
+
return void 0;
|
|
611
1041
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const
|
|
616
|
-
|
|
617
|
-
|
|
1042
|
+
function renderTestOutput(obj, input) {
|
|
1043
|
+
const exitCode = numberField(obj, "exit_code") ?? 0;
|
|
1044
|
+
const failed = numberField(obj, "failed") ?? 0;
|
|
1045
|
+
const output = stringField(obj, "output") ?? "";
|
|
1046
|
+
const header = renderHeader(`test: ${stringField(obj, "runner") ?? "runner"}`, {
|
|
1047
|
+
exit_code: obj["exit_code"],
|
|
1048
|
+
tests_run: obj["tests_run"],
|
|
1049
|
+
passed: obj["passed"],
|
|
1050
|
+
failed: obj["failed"],
|
|
1051
|
+
duration_ms: obj["duration_ms"],
|
|
1052
|
+
truncated: obj["truncated"],
|
|
1053
|
+
files: inputListSummary(input, "files"),
|
|
1054
|
+
grep: stringFromInput(input, "grep")
|
|
1055
|
+
});
|
|
1056
|
+
if (exitCode === 0 && failed === 0) {
|
|
1057
|
+
return joinSections([
|
|
1058
|
+
header,
|
|
1059
|
+
joinSections([
|
|
1060
|
+
"report:",
|
|
1061
|
+
`status=passed`,
|
|
1062
|
+
`tests_run=${obj["tests_run"] ?? 0}`,
|
|
1063
|
+
`passed=${obj["passed"] ?? 0}`,
|
|
1064
|
+
`failed=${obj["failed"] ?? 0}`,
|
|
1065
|
+
`duration_ms=${obj["duration_ms"] ?? 0}`,
|
|
1066
|
+
extractSpoolNote(output)
|
|
1067
|
+
])
|
|
1068
|
+
]);
|
|
1069
|
+
}
|
|
1070
|
+
return joinSections([
|
|
1071
|
+
header,
|
|
1072
|
+
`error_context:
|
|
1073
|
+
${compactFailureOutput(output || "(no runner output)")}`
|
|
1074
|
+
]);
|
|
1075
|
+
}
|
|
1076
|
+
function renderVerifierOutput(toolName, obj, input) {
|
|
1077
|
+
const exitCode = numberField(obj, "exit_code") ?? 0;
|
|
1078
|
+
const errors = numberField(obj, "errors") ?? 0;
|
|
1079
|
+
const warnings = numberField(obj, "warnings") ?? 0;
|
|
1080
|
+
const output = stringField(obj, "output") ?? "";
|
|
1081
|
+
const changed = numberField(obj, "files_changed") ?? 0;
|
|
1082
|
+
const header = renderHeader(toolName, {
|
|
1083
|
+
exit_code: obj["exit_code"],
|
|
1084
|
+
errors: obj["errors"],
|
|
1085
|
+
warnings: obj["warnings"],
|
|
1086
|
+
files_checked: obj["files_checked"],
|
|
1087
|
+
files_changed: obj["files_changed"],
|
|
1088
|
+
fix_applied: obj["fix_applied"],
|
|
1089
|
+
fixer: obj["fixer"],
|
|
1090
|
+
linter: obj["linter"],
|
|
1091
|
+
project: obj["project"],
|
|
1092
|
+
truncated: obj["truncated"],
|
|
1093
|
+
files: inputListSummary(input, "files"),
|
|
1094
|
+
cwd: stringFromInput(input, "cwd")
|
|
1095
|
+
});
|
|
1096
|
+
if (exitCode === 0 && errors === 0 && (toolName !== "format" || changed === 0)) {
|
|
1097
|
+
return joinSections([
|
|
1098
|
+
header,
|
|
1099
|
+
joinSections([
|
|
1100
|
+
"report:",
|
|
1101
|
+
"status=passed",
|
|
1102
|
+
`errors=${errors}`,
|
|
1103
|
+
`warnings=${warnings}`,
|
|
1104
|
+
toolName === "format" ? `files_changed=${changed}` : void 0,
|
|
1105
|
+
extractSpoolNote(output)
|
|
1106
|
+
])
|
|
1107
|
+
]);
|
|
1108
|
+
}
|
|
1109
|
+
if (exitCode === 0 && toolName === "format") {
|
|
1110
|
+
return joinSections([
|
|
1111
|
+
header,
|
|
1112
|
+
joinSections([
|
|
1113
|
+
"report:",
|
|
1114
|
+
"status=changed",
|
|
1115
|
+
`files_changed=${changed}`,
|
|
1116
|
+
extractSpoolNote(output)
|
|
1117
|
+
])
|
|
1118
|
+
]);
|
|
1119
|
+
}
|
|
1120
|
+
return joinSections([
|
|
1121
|
+
header,
|
|
1122
|
+
`error_context:
|
|
1123
|
+
${compactFailureOutput(output || "(no verifier output)")}`
|
|
1124
|
+
]);
|
|
1125
|
+
}
|
|
1126
|
+
function renderGrepMatches(matches, mode) {
|
|
1127
|
+
if (matches.length === 0) return "(no matches)";
|
|
1128
|
+
if (mode === "files_with_matches") return renderStringList(matches, "(no files)");
|
|
1129
|
+
if (mode === "count") return renderStringList(matches, "(no counts)");
|
|
1130
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1131
|
+
const passthrough = [];
|
|
1132
|
+
for (const match of matches) {
|
|
1133
|
+
const parsed = parseGrepContentLine(match);
|
|
1134
|
+
if (!parsed) {
|
|
1135
|
+
passthrough.push(match);
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
const list = groups.get(parsed.file) ?? [];
|
|
1139
|
+
list.push(`${parsed.line}:${parsed.text}`);
|
|
1140
|
+
groups.set(parsed.file, list);
|
|
1141
|
+
}
|
|
1142
|
+
if (groups.size === 0) return renderStringList(matches, "(no matches)");
|
|
1143
|
+
const sections = [];
|
|
1144
|
+
let fileIndex = 0;
|
|
1145
|
+
for (const [file, lines] of groups) {
|
|
1146
|
+
fileIndex++;
|
|
1147
|
+
if (fileIndex > GREP_FILE_LIMIT) break;
|
|
1148
|
+
const shown = lines.slice(0, GREP_MATCHES_PER_FILE);
|
|
1149
|
+
sections.push(
|
|
1150
|
+
`${file} (${lines.length} match(es), showing ${shown.length})
|
|
1151
|
+
${shown.join("\n")}`
|
|
1152
|
+
);
|
|
618
1153
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
|
|
1154
|
+
if (groups.size > GREP_FILE_LIMIT) {
|
|
1155
|
+
sections.push(`[serializer omitted ${groups.size - GREP_FILE_LIMIT} file group(s)]`);
|
|
622
1156
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const models = {};
|
|
627
|
-
for (const [mid, m] of Object.entries(base.models ?? {})) {
|
|
628
|
-
models[mid] = { ...m };
|
|
1157
|
+
if (passthrough.length > 0) {
|
|
1158
|
+
sections.push(`ungrouped:
|
|
1159
|
+
${renderStringList(passthrough, "", 50)}`);
|
|
629
1160
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1161
|
+
return sections.join("\n");
|
|
1162
|
+
}
|
|
1163
|
+
function parseGrepContentLine(line) {
|
|
1164
|
+
const match = /^(.+?):(\d+):(.*)$/.exec(line);
|
|
1165
|
+
if (!match?.[1] || !match[2]) return void 0;
|
|
1166
|
+
return { file: match[1], line: match[2], text: match[3] ?? "" };
|
|
1167
|
+
}
|
|
1168
|
+
function compactDiff(diff) {
|
|
1169
|
+
const lines = diff.split(/\r?\n/);
|
|
1170
|
+
if (lines.length <= DIFF_INLINE_LINE_LIMIT) return diff;
|
|
1171
|
+
const fileCount = Math.max(
|
|
1172
|
+
new Set(
|
|
1173
|
+
lines.map(
|
|
1174
|
+
(line) => /^diff --git\s+a\/(.+?)\s+b\//.exec(line)?.[1] ?? /^---\s+(.+)/.exec(line)?.[1]
|
|
1175
|
+
).filter(Boolean)
|
|
1176
|
+
).size,
|
|
1177
|
+
0
|
|
1178
|
+
);
|
|
1179
|
+
const hunks = lines.filter((line) => line.startsWith("@@")).length;
|
|
1180
|
+
const added = lines.filter((line) => line.startsWith("+") && !line.startsWith("+++")).length;
|
|
1181
|
+
const removed = lines.filter((line) => line.startsWith("-") && !line.startsWith("---")).length;
|
|
1182
|
+
const selected = /* @__PURE__ */ new Set();
|
|
1183
|
+
let hunkCount = 0;
|
|
1184
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1185
|
+
const line = lines[i] ?? "";
|
|
1186
|
+
if (line.startsWith("diff --git") || line.startsWith("--- ") || line.startsWith("+++ ")) {
|
|
1187
|
+
selected.add(i);
|
|
1188
|
+
continue;
|
|
1189
|
+
}
|
|
1190
|
+
if (!line.startsWith("@@")) continue;
|
|
1191
|
+
if (hunkCount >= DIFF_HUNK_LIMIT) continue;
|
|
1192
|
+
hunkCount++;
|
|
1193
|
+
for (let j = i; j <= Math.min(lines.length - 1, i + DIFF_HUNK_CONTEXT); j++) {
|
|
1194
|
+
selected.add(j);
|
|
1195
|
+
}
|
|
633
1196
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
1197
|
+
if (selected.size === 0) {
|
|
1198
|
+
return joinSections([
|
|
1199
|
+
renderHeader("diff_summary", {
|
|
1200
|
+
files: fileCount,
|
|
1201
|
+
hunks,
|
|
1202
|
+
added,
|
|
1203
|
+
removed,
|
|
1204
|
+
lines: lines.length
|
|
1205
|
+
}),
|
|
1206
|
+
lines.slice(0, DIFF_INLINE_LINE_LIMIT).join("\n"),
|
|
1207
|
+
`[serializer omitted ${Math.max(0, lines.length - DIFF_INLINE_LINE_LIMIT)} diff line(s)]`
|
|
1208
|
+
]);
|
|
1209
|
+
}
|
|
1210
|
+
const excerpt = [];
|
|
1211
|
+
let previous = -1;
|
|
1212
|
+
for (const index of [...selected].sort((a, b) => a - b)) {
|
|
1213
|
+
if (index > previous + 1) {
|
|
1214
|
+
const omitted = previous === -1 ? index : index - previous - 1;
|
|
1215
|
+
excerpt.push(`[serializer omitted ${omitted} diff line(s)]`);
|
|
1216
|
+
}
|
|
1217
|
+
excerpt.push(lines[index] ?? "");
|
|
1218
|
+
previous = index;
|
|
1219
|
+
}
|
|
1220
|
+
const trailing = lines.length - previous - 1;
|
|
1221
|
+
if (trailing > 0) excerpt.push(`[serializer omitted ${trailing} trailing diff line(s)]`);
|
|
1222
|
+
return joinSections([
|
|
1223
|
+
renderHeader("diff_summary", {
|
|
1224
|
+
files: fileCount,
|
|
1225
|
+
hunks,
|
|
1226
|
+
shown_hunks: Math.min(hunks, DIFF_HUNK_LIMIT),
|
|
1227
|
+
added,
|
|
1228
|
+
removed,
|
|
1229
|
+
lines: lines.length
|
|
644
1230
|
}),
|
|
645
|
-
|
|
646
|
-
|
|
1231
|
+
excerpt.join("\n")
|
|
1232
|
+
]);
|
|
647
1233
|
}
|
|
648
|
-
function
|
|
649
|
-
const
|
|
650
|
-
if (
|
|
651
|
-
|
|
1234
|
+
function compactFailureOutput(output) {
|
|
1235
|
+
const lines = output.split(/\r?\n/);
|
|
1236
|
+
if (lines.length <= 260) return output.trimEnd();
|
|
1237
|
+
const selected = /* @__PURE__ */ new Set();
|
|
1238
|
+
const marker = /\b(fail|failed|failure|error|exception|assertionerror|expected|received|actual|timeout|stack)\b/i;
|
|
1239
|
+
let markerHits = 0;
|
|
1240
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1241
|
+
if (!marker.test(lines[i] ?? "")) continue;
|
|
1242
|
+
markerHits++;
|
|
1243
|
+
for (let j = Math.max(0, i - 4); j <= Math.min(lines.length - 1, i + 10); j++) {
|
|
1244
|
+
selected.add(j);
|
|
1245
|
+
}
|
|
652
1246
|
}
|
|
653
|
-
if (
|
|
654
|
-
|
|
1247
|
+
if (markerHits === 0) {
|
|
1248
|
+
return lines.slice(-220).join("\n").trimEnd();
|
|
655
1249
|
}
|
|
656
|
-
|
|
657
|
-
|
|
1250
|
+
const ordered = [...selected].sort((a, b) => a - b);
|
|
1251
|
+
const out = [];
|
|
1252
|
+
let previous = -1;
|
|
1253
|
+
for (const index of ordered) {
|
|
1254
|
+
if (index > previous + 1) {
|
|
1255
|
+
const omitted = previous === -1 ? index : index - previous - 1;
|
|
1256
|
+
out.push(`[serializer omitted ${omitted} line(s)]`);
|
|
1257
|
+
}
|
|
1258
|
+
out.push(lines[index] ?? "");
|
|
1259
|
+
previous = index;
|
|
658
1260
|
}
|
|
659
|
-
return
|
|
1261
|
+
return out.join("\n").trimEnd();
|
|
660
1262
|
}
|
|
661
|
-
function
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
1263
|
+
function extractSpoolNote(output) {
|
|
1264
|
+
return output.split(/\r?\n/).find((line) => line.startsWith("[output truncated") && line.includes("full"));
|
|
1265
|
+
}
|
|
1266
|
+
function hasCommandOutputShape(obj) {
|
|
1267
|
+
return typeof obj["stdout"] === "string" || typeof obj["stderr"] === "string" || typeof obj["output"] === "string" || typeof obj["exitCode"] === "number" || typeof obj["exit_code"] === "number";
|
|
1268
|
+
}
|
|
1269
|
+
function renderCommandOutput(toolName, obj, input) {
|
|
1270
|
+
const command = stringField(obj, "command") ?? stringFromInput(input, "command");
|
|
1271
|
+
const args = stringArrayField(obj, "args");
|
|
1272
|
+
const commandLine = command ? [command, ...args].join(" ") : void 0;
|
|
1273
|
+
const output = stringField(obj, "output");
|
|
1274
|
+
const stdout = stringField(obj, "stdout");
|
|
1275
|
+
const stderr = stringField(obj, "stderr");
|
|
1276
|
+
return joinSections([
|
|
1277
|
+
renderHeader(commandLine ? `${toolName}: ${commandLine}` : toolName, {
|
|
1278
|
+
exit_code: obj["exit_code"] ?? obj["exitCode"],
|
|
1279
|
+
timed_out: obj["timed_out"],
|
|
1280
|
+
pid: obj["pid"],
|
|
1281
|
+
allowed: obj["allowed"],
|
|
1282
|
+
truncated: obj["truncated"],
|
|
1283
|
+
runner: obj["runner"],
|
|
1284
|
+
linter: obj["linter"],
|
|
1285
|
+
fixer: obj["fixer"],
|
|
1286
|
+
project: obj["project"],
|
|
1287
|
+
tests_run: obj["tests_run"],
|
|
1288
|
+
passed: obj["passed"],
|
|
1289
|
+
failed: obj["failed"],
|
|
1290
|
+
duration_ms: obj["duration_ms"],
|
|
1291
|
+
errors: obj["errors"],
|
|
1292
|
+
warnings: obj["warnings"],
|
|
1293
|
+
files_checked: obj["files_checked"],
|
|
1294
|
+
files_changed: obj["files_changed"],
|
|
1295
|
+
fix_applied: obj["fix_applied"]
|
|
1296
|
+
}),
|
|
1297
|
+
stringField(obj, "error") ? `error:
|
|
1298
|
+
${stringField(obj, "error")}` : void 0,
|
|
1299
|
+
output ? `output:
|
|
1300
|
+
${output}` : void 0,
|
|
1301
|
+
stdout ? `stdout:
|
|
1302
|
+
${stdout}` : void 0,
|
|
1303
|
+
stderr ? `stderr:
|
|
1304
|
+
${stderr}` : void 0
|
|
1305
|
+
]);
|
|
1306
|
+
}
|
|
1307
|
+
function renderGenericToolObject(toolName, obj) {
|
|
1308
|
+
const scalars = {};
|
|
1309
|
+
const blocks = [];
|
|
1310
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1311
|
+
if (value === void 0) continue;
|
|
1312
|
+
if (isScalar(value)) {
|
|
1313
|
+
const inline = String(value);
|
|
1314
|
+
if (inline.length <= INLINE_LIMIT && !inline.includes("\n")) {
|
|
1315
|
+
scalars[key] = value;
|
|
1316
|
+
} else {
|
|
1317
|
+
blocks.push(`${key}:
|
|
1318
|
+
${inline}`);
|
|
1319
|
+
}
|
|
1320
|
+
continue;
|
|
1321
|
+
}
|
|
1322
|
+
if (Array.isArray(value)) {
|
|
1323
|
+
if (value.every((item) => typeof item === "string")) {
|
|
1324
|
+
blocks.push(`${key}:
|
|
1325
|
+
${renderStringList(value)}`);
|
|
1326
|
+
} else {
|
|
1327
|
+
blocks.push(`${key}:
|
|
1328
|
+
${renderUnknownList(value)}`);
|
|
1329
|
+
}
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
blocks.push(`${key}: ${clipInline(oneLineJson(value))}`);
|
|
665
1333
|
}
|
|
666
|
-
return
|
|
1334
|
+
return joinSections([renderHeader(toolName, scalars), ...blocks]);
|
|
667
1335
|
}
|
|
668
|
-
function
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
1336
|
+
function renderHeader(label, fields) {
|
|
1337
|
+
const parts = Object.entries(fields).filter(([, value]) => value !== void 0 && value !== null && value !== "").map(([key, value]) => `${key}=${clipInline(formatInlineValue(value))}`);
|
|
1338
|
+
return parts.length > 0 ? `${label} (${parts.join(" ")})` : label;
|
|
1339
|
+
}
|
|
1340
|
+
function renderStringList(items, empty = "", limit = DEFAULT_LIST_LIMIT) {
|
|
1341
|
+
if (items.length === 0) return empty;
|
|
1342
|
+
const shown = items.slice(0, limit);
|
|
1343
|
+
const omitted = items.length - shown.length;
|
|
1344
|
+
return [
|
|
1345
|
+
...shown,
|
|
1346
|
+
...omitted > 0 ? [`[serializer omitted ${omitted} item(s); narrow the request for more]`] : []
|
|
1347
|
+
].join("\n");
|
|
1348
|
+
}
|
|
1349
|
+
function renderUnknownList(items, limit = DEFAULT_LIST_LIMIT) {
|
|
1350
|
+
const shown = items.slice(0, limit).map((item) => clipInline(oneLineJson(item), 1e3));
|
|
1351
|
+
const omitted = items.length - shown.length;
|
|
1352
|
+
if (omitted > 0)
|
|
1353
|
+
shown.push(`[serializer omitted ${omitted} item(s); narrow the request for more]`);
|
|
1354
|
+
return shown.join("\n");
|
|
1355
|
+
}
|
|
1356
|
+
function joinSections(sections) {
|
|
1357
|
+
return sections.map((section) => typeof section === "string" ? section.trimEnd() : void 0).filter((section) => !!section).join("\n");
|
|
1358
|
+
}
|
|
1359
|
+
function formatInlineValue(value) {
|
|
1360
|
+
if (Array.isArray(value)) return `[${value.map(formatInlineValue).join(",")}]`;
|
|
1361
|
+
if (isScalar(value)) return String(value);
|
|
1362
|
+
return oneLineJson(value);
|
|
1363
|
+
}
|
|
1364
|
+
function clipInline(value, max = INLINE_LIMIT) {
|
|
1365
|
+
const compact = value.replace(/\s+/g, " ").trim();
|
|
1366
|
+
return compact.length <= max ? compact : `${compact.slice(0, max - 15)}...(${compact.length} chars)`;
|
|
1367
|
+
}
|
|
1368
|
+
function oneLineJson(value) {
|
|
1369
|
+
try {
|
|
1370
|
+
return JSON.stringify(value);
|
|
1371
|
+
} catch {
|
|
1372
|
+
return String(value);
|
|
672
1373
|
}
|
|
673
|
-
|
|
1374
|
+
}
|
|
1375
|
+
function stringField(obj, key) {
|
|
1376
|
+
const value = obj[key];
|
|
1377
|
+
return typeof value === "string" ? value : void 0;
|
|
1378
|
+
}
|
|
1379
|
+
function numberField(obj, key) {
|
|
1380
|
+
const value = obj[key];
|
|
1381
|
+
return typeof value === "number" ? value : void 0;
|
|
1382
|
+
}
|
|
1383
|
+
function stringArrayField(obj, key) {
|
|
1384
|
+
const value = obj[key];
|
|
1385
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
1386
|
+
}
|
|
1387
|
+
function stringFromInput(input, key) {
|
|
1388
|
+
if (!isRecord2(input)) return void 0;
|
|
1389
|
+
const value = input[key];
|
|
1390
|
+
return typeof value === "string" ? value : void 0;
|
|
1391
|
+
}
|
|
1392
|
+
function numberFromInput(input, key) {
|
|
1393
|
+
if (!isRecord2(input)) return void 0;
|
|
1394
|
+
const value = input[key];
|
|
1395
|
+
return typeof value === "number" ? value : void 0;
|
|
1396
|
+
}
|
|
1397
|
+
function inputListSummary(input, key) {
|
|
1398
|
+
if (!isRecord2(input)) return void 0;
|
|
1399
|
+
const value = input[key];
|
|
1400
|
+
if (typeof value === "string") return value;
|
|
1401
|
+
if (Array.isArray(value)) return value.filter((item) => typeof item === "string").join(",");
|
|
1402
|
+
return void 0;
|
|
1403
|
+
}
|
|
1404
|
+
function isRecord2(value) {
|
|
1405
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1406
|
+
}
|
|
1407
|
+
function isScalar(value) {
|
|
1408
|
+
return value === null || ["string", "number", "boolean"].includes(typeof value);
|
|
1409
|
+
}
|
|
1410
|
+
function wstackGlobalRoot() {
|
|
1411
|
+
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
1412
|
+
if (fromEnv && fromEnv.trim().length > 0) return path6.resolve(fromEnv);
|
|
1413
|
+
return path6.join(os.homedir(), ".wrongstack");
|
|
674
1414
|
}
|
|
675
1415
|
|
|
676
1416
|
// src/types/errors.ts
|
|
@@ -998,6 +1738,20 @@ function providerStatusToCode(status, type) {
|
|
|
998
1738
|
return ERROR_CODES.PROVIDER_INVALID_REQUEST;
|
|
999
1739
|
}
|
|
1000
1740
|
|
|
1741
|
+
// src/types/config.ts
|
|
1742
|
+
function normalizeTokenSavingTier(val) {
|
|
1743
|
+
if (val === void 0) return "off";
|
|
1744
|
+
if (typeof val === "boolean") return val ? "medium" : "off";
|
|
1745
|
+
const validTiers = /* @__PURE__ */ new Set([
|
|
1746
|
+
"off",
|
|
1747
|
+
"minimal",
|
|
1748
|
+
"light",
|
|
1749
|
+
"medium",
|
|
1750
|
+
"aggressive"
|
|
1751
|
+
]);
|
|
1752
|
+
return validTiers.has(val) ? val : "off";
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1001
1755
|
// src/types/default-config.ts
|
|
1002
1756
|
var DEFAULT_TOOLS_CONFIG = Object.freeze({
|
|
1003
1757
|
defaultExecutionStrategy: "smart",
|
|
@@ -1005,7 +1759,8 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
|
|
|
1005
1759
|
iterationTimeoutMs: 3e5,
|
|
1006
1760
|
sessionTimeoutMs: 18e5,
|
|
1007
1761
|
perIterationOutputCapBytes: 1e5,
|
|
1008
|
-
autoExtendLimit: true
|
|
1762
|
+
autoExtendLimit: true,
|
|
1763
|
+
restrictToProjectRoot: false
|
|
1009
1764
|
});
|
|
1010
1765
|
var DEFAULT_CONTEXT_CONFIG = Object.freeze({
|
|
1011
1766
|
preserveK: 10,
|
|
@@ -1014,6 +1769,10 @@ var DEFAULT_CONTEXT_CONFIG = Object.freeze({
|
|
|
1014
1769
|
var DEFAULT_AUTONOMY_CONFIG = Object.freeze({
|
|
1015
1770
|
autoProceedDelayMs: 45e3
|
|
1016
1771
|
});
|
|
1772
|
+
var DEFAULT_CIRCUIT_BREAKER_CONFIG = Object.freeze({
|
|
1773
|
+
enabled: false,
|
|
1774
|
+
autoKillResetMs: 6e4
|
|
1775
|
+
});
|
|
1017
1776
|
var DEFAULT_SESSION_LOGGING_CONFIG = Object.freeze({
|
|
1018
1777
|
auditLevel: "standard",
|
|
1019
1778
|
sampling: {
|
|
@@ -1025,7 +1784,10 @@ var DEFAULT_SESSION_LOGGING_CONFIG = Object.freeze({
|
|
|
1025
1784
|
var DEFAULT_SESSION_PRUNE_DAYS = 30;
|
|
1026
1785
|
|
|
1027
1786
|
// src/types/secret-vault.ts
|
|
1028
|
-
var
|
|
1787
|
+
var ENCRYPTED_PREFIX_PATTERN = /^enc:v(\d+):/;
|
|
1788
|
+
function encryptedPrefixForVersion(version) {
|
|
1789
|
+
return `enc:v${version}:`;
|
|
1790
|
+
}
|
|
1029
1791
|
|
|
1030
1792
|
// src/security/secret-vault.ts
|
|
1031
1793
|
var KEY_BYTES = 32;
|
|
@@ -1033,6 +1795,8 @@ var IV_BYTES = 12;
|
|
|
1033
1795
|
var TAG_BYTES = 16;
|
|
1034
1796
|
var ALGO = "aes-256-gcm";
|
|
1035
1797
|
var KEY_FILE_MODE = 384;
|
|
1798
|
+
var KEY_FILE_MAGIC = Buffer.from("WSKV", "ascii");
|
|
1799
|
+
var VERSIONED_KEY_FILE_SIZE = KEY_FILE_MAGIC.length + 1 + KEY_BYTES;
|
|
1036
1800
|
function checkKeyFilePermissions(keyFile) {
|
|
1037
1801
|
if (process.platform === "win32") return;
|
|
1038
1802
|
try {
|
|
@@ -1055,11 +1819,17 @@ function checkKeyFilePermissions(keyFile) {
|
|
|
1055
1819
|
var DefaultSecretVault = class {
|
|
1056
1820
|
keyFile;
|
|
1057
1821
|
key;
|
|
1822
|
+
_keyVersion = 1;
|
|
1058
1823
|
constructor(opts) {
|
|
1059
1824
|
this.keyFile = opts.keyFile;
|
|
1060
1825
|
}
|
|
1826
|
+
/** Current key version. Starts at 1; incremented by rotateKey(). */
|
|
1827
|
+
get keyVersion() {
|
|
1828
|
+
if (!this.key) this.loadOrCreateKey();
|
|
1829
|
+
return this._keyVersion;
|
|
1830
|
+
}
|
|
1061
1831
|
isEncrypted(value) {
|
|
1062
|
-
return typeof value === "string" &&
|
|
1832
|
+
return typeof value === "string" && ENCRYPTED_PREFIX_PATTERN.test(value);
|
|
1063
1833
|
}
|
|
1064
1834
|
encrypt(plaintext) {
|
|
1065
1835
|
if (this.isEncrypted(plaintext)) return plaintext;
|
|
@@ -1068,11 +1838,20 @@ var DefaultSecretVault = class {
|
|
|
1068
1838
|
const cipher = createCipheriv(ALGO, key, iv);
|
|
1069
1839
|
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
1070
1840
|
const tag = cipher.getAuthTag();
|
|
1071
|
-
|
|
1841
|
+
const prefix = encryptedPrefixForVersion(this._keyVersion);
|
|
1842
|
+
return `${prefix}${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
|
|
1072
1843
|
}
|
|
1073
1844
|
decrypt(value) {
|
|
1074
1845
|
if (!this.isEncrypted(value)) return value;
|
|
1075
|
-
const
|
|
1846
|
+
const prefixMatch = value.match(ENCRYPTED_PREFIX_PATTERN);
|
|
1847
|
+
if (!prefixMatch) {
|
|
1848
|
+
throw new ConfigError({
|
|
1849
|
+
message: "SecretVault: malformed encrypted value",
|
|
1850
|
+
code: ERROR_CODES.CONFIG_PARSE_FAILED,
|
|
1851
|
+
context: { field: "encrypted_value" }
|
|
1852
|
+
});
|
|
1853
|
+
}
|
|
1854
|
+
const rest = value.slice(prefixMatch[0].length);
|
|
1076
1855
|
const parts = rest.split(":");
|
|
1077
1856
|
if (parts.length !== 3) {
|
|
1078
1857
|
throw new ConfigError({
|
|
@@ -1101,42 +1880,104 @@ var DefaultSecretVault = class {
|
|
|
1101
1880
|
const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
1102
1881
|
return pt.toString("utf8");
|
|
1103
1882
|
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Generate a new encryption key, write it to disk, and increment the key version.
|
|
1885
|
+
* After rotation, encrypt() emits the new version prefix (e.g. enc:v2:).
|
|
1886
|
+
* The caller must re-encrypt existing config values (see rotateConfigKeys()).
|
|
1887
|
+
*/
|
|
1888
|
+
rotateKey() {
|
|
1889
|
+
const oldVersion = this._keyVersion;
|
|
1890
|
+
const newKey = randomBytes(KEY_BYTES);
|
|
1891
|
+
const newVersion = oldVersion + 1;
|
|
1892
|
+
const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
|
|
1893
|
+
KEY_FILE_MAGIC.copy(keyFileBuf, 0);
|
|
1894
|
+
keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
|
|
1895
|
+
newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
|
|
1896
|
+
fs2.mkdirSync(path6.dirname(this.keyFile), { recursive: true });
|
|
1897
|
+
fs2.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
|
|
1898
|
+
checkKeyFilePermissions(this.keyFile);
|
|
1899
|
+
this.key = newKey;
|
|
1900
|
+
this._keyVersion = newVersion;
|
|
1901
|
+
return { oldVersion, newVersion };
|
|
1902
|
+
}
|
|
1104
1903
|
loadOrCreateKey() {
|
|
1105
1904
|
if (this.key) return this.key;
|
|
1106
1905
|
try {
|
|
1107
1906
|
const buf = fs2.readFileSync(this.keyFile);
|
|
1108
|
-
if (buf.length
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1907
|
+
if (buf.length === KEY_BYTES) {
|
|
1908
|
+
this.key = buf;
|
|
1909
|
+
this._keyVersion = 1;
|
|
1910
|
+
checkKeyFilePermissions(this.keyFile);
|
|
1911
|
+
return this.key;
|
|
1912
|
+
}
|
|
1913
|
+
if (buf.length === VERSIONED_KEY_FILE_SIZE) {
|
|
1914
|
+
const magic = buf.subarray(0, KEY_FILE_MAGIC.length);
|
|
1915
|
+
if (!magic.equals(KEY_FILE_MAGIC)) {
|
|
1916
|
+
throw new ConfigError({
|
|
1917
|
+
message: `SecretVault: key file ${this.keyFile} has invalid magic header`,
|
|
1918
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
1919
|
+
context: { keyFile: this.keyFile }
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
const version = buf[KEY_FILE_MAGIC.length];
|
|
1923
|
+
const key2 = buf.subarray(KEY_FILE_MAGIC.length + 1);
|
|
1924
|
+
if (key2.length !== KEY_BYTES) {
|
|
1925
|
+
throw new ConfigError({
|
|
1926
|
+
message: `SecretVault: key file ${this.keyFile} has wrong key size (${key2.length} bytes, expected ${KEY_BYTES})`,
|
|
1927
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
1928
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: key2.length }
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
this.key = Buffer.from(key2);
|
|
1932
|
+
this._keyVersion = version;
|
|
1933
|
+
checkKeyFilePermissions(this.keyFile);
|
|
1934
|
+
return this.key;
|
|
1114
1935
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1936
|
+
throw new ConfigError({
|
|
1937
|
+
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.`,
|
|
1938
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
1939
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
|
|
1940
|
+
});
|
|
1118
1941
|
} catch (err) {
|
|
1119
1942
|
if (err.code !== "ENOENT") throw err;
|
|
1120
1943
|
}
|
|
1121
|
-
fs2.mkdirSync(
|
|
1944
|
+
fs2.mkdirSync(path6.dirname(this.keyFile), { recursive: true });
|
|
1122
1945
|
const key = randomBytes(KEY_BYTES);
|
|
1123
1946
|
try {
|
|
1124
1947
|
fs2.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
|
|
1125
1948
|
} catch (err) {
|
|
1126
1949
|
if (err.code !== "EEXIST") throw err;
|
|
1127
1950
|
const buf = fs2.readFileSync(this.keyFile);
|
|
1128
|
-
if (buf.length
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1951
|
+
if (buf.length === KEY_BYTES) {
|
|
1952
|
+
this.key = buf;
|
|
1953
|
+
this._keyVersion = 1;
|
|
1954
|
+
checkKeyFilePermissions(this.keyFile);
|
|
1955
|
+
return this.key;
|
|
1956
|
+
}
|
|
1957
|
+
if (buf.length === VERSIONED_KEY_FILE_SIZE) {
|
|
1958
|
+
const magic = buf.subarray(0, KEY_FILE_MAGIC.length);
|
|
1959
|
+
if (!magic.equals(KEY_FILE_MAGIC)) {
|
|
1960
|
+
throw new ConfigError({
|
|
1961
|
+
message: `SecretVault: key file ${this.keyFile} has invalid magic header`,
|
|
1962
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
1963
|
+
context: { keyFile: this.keyFile }
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
const version = buf[KEY_FILE_MAGIC.length];
|
|
1967
|
+
const winnerKey = buf.subarray(KEY_FILE_MAGIC.length + 1);
|
|
1968
|
+
this.key = Buffer.from(winnerKey);
|
|
1969
|
+
this._keyVersion = version;
|
|
1970
|
+
checkKeyFilePermissions(this.keyFile);
|
|
1971
|
+
return this.key;
|
|
1134
1972
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1973
|
+
throw new ConfigError({
|
|
1974
|
+
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.`,
|
|
1975
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
1976
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
|
|
1977
|
+
});
|
|
1138
1978
|
}
|
|
1139
1979
|
this.key = key;
|
|
1980
|
+
this._keyVersion = 1;
|
|
1140
1981
|
return key;
|
|
1141
1982
|
}
|
|
1142
1983
|
};
|
|
@@ -1190,7 +2031,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
|
1190
2031
|
}
|
|
1191
2032
|
const merged = deepMerge(current, patch ?? {});
|
|
1192
2033
|
const encrypted = encryptConfigSecrets(merged, vault);
|
|
1193
|
-
await fs.mkdir(
|
|
2034
|
+
await fs.mkdir(path6.dirname(configPath), { recursive: true });
|
|
1194
2035
|
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
1195
2036
|
await restrictFilePermissions(configPath);
|
|
1196
2037
|
}
|
|
@@ -1217,6 +2058,89 @@ async function migratePlaintextSecrets(configPath, vault, logger) {
|
|
|
1217
2058
|
);
|
|
1218
2059
|
return { migrated: counter.n, file: configPath };
|
|
1219
2060
|
}
|
|
2061
|
+
async function rotateConfigKeys(configPath, vault, logger) {
|
|
2062
|
+
const log = logger?.info ?? (() => {
|
|
2063
|
+
});
|
|
2064
|
+
const warn = logger?.warn ?? ((msg) => console.warn(msg));
|
|
2065
|
+
let raw;
|
|
2066
|
+
try {
|
|
2067
|
+
raw = await fs.readFile(configPath, "utf8");
|
|
2068
|
+
} catch {
|
|
2069
|
+
const { oldVersion: oldVersion2, newVersion: newVersion2 } = vault.rotateKey();
|
|
2070
|
+
log(`[secret-vault] Key rotated (v${oldVersion2} \u2192 v${newVersion2}) \u2014 no config file to re-encrypt`);
|
|
2071
|
+
return { rotated: 0, oldVersion: oldVersion2, newVersion: newVersion2, file: configPath };
|
|
2072
|
+
}
|
|
2073
|
+
let parsed;
|
|
2074
|
+
try {
|
|
2075
|
+
parsed = JSON.parse(raw);
|
|
2076
|
+
} catch {
|
|
2077
|
+
warn(`[secret-vault] Config file ${configPath} is not valid JSON \u2014 skipping rotation`);
|
|
2078
|
+
return { rotated: 0, oldVersion: vault.keyVersion, newVersion: vault.keyVersion, file: configPath };
|
|
2079
|
+
}
|
|
2080
|
+
const counter = { n: 0, failed: [] };
|
|
2081
|
+
const decrypted = walkDecryptCount(parsed, vault, counter);
|
|
2082
|
+
if (counter.failed.length > 0) {
|
|
2083
|
+
throw new Error(
|
|
2084
|
+
`[secret-vault] Aborting key rotation: ${counter.failed.length} field(s) could not be decrypted with the current key and would be permanently lost on rotation: ${counter.failed.join(", ")}. Restore or remove these fields before rotating.`
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
if (counter.n === 0) {
|
|
2088
|
+
const { oldVersion: oldVersion2, newVersion: newVersion2 } = vault.rotateKey();
|
|
2089
|
+
log(`[secret-vault] Key rotated (v${oldVersion2} \u2192 v${newVersion2}) \u2014 no encrypted fields to re-encrypt`);
|
|
2090
|
+
return { rotated: 0, oldVersion: oldVersion2, newVersion: newVersion2, file: configPath };
|
|
2091
|
+
}
|
|
2092
|
+
const { oldVersion, newVersion } = vault.rotateKey();
|
|
2093
|
+
const reencrypted = walkReencrypt(decrypted, vault);
|
|
2094
|
+
await atomicWrite(configPath, JSON.stringify(reencrypted, null, 2), { mode: 384 });
|
|
2095
|
+
await restrictFilePermissions(configPath, { warn });
|
|
2096
|
+
log(`[secret-vault] Key rotated (v${oldVersion} \u2192 v${newVersion}) \u2014 re-encrypted ${counter.n} field(s)`);
|
|
2097
|
+
return { rotated: counter.n, oldVersion, newVersion, file: configPath };
|
|
2098
|
+
}
|
|
2099
|
+
function walkDecryptCount(node, vault, counter, pathPrefix = "") {
|
|
2100
|
+
if (node === null || node === void 0) return node;
|
|
2101
|
+
if (typeof node !== "object") return node;
|
|
2102
|
+
if (Array.isArray(node)) {
|
|
2103
|
+
return node.map(
|
|
2104
|
+
(item, i) => walkDecryptCount(item, vault, counter, `${pathPrefix}[${i}]`)
|
|
2105
|
+
);
|
|
2106
|
+
}
|
|
2107
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
2108
|
+
for (const [k, v] of Object.entries(node)) {
|
|
2109
|
+
const keyPath = pathPrefix ? `${pathPrefix}.${k}` : k;
|
|
2110
|
+
if (typeof v === "string" && vault.isEncrypted(v)) {
|
|
2111
|
+
try {
|
|
2112
|
+
out[k] = vault.decrypt(v);
|
|
2113
|
+
counter.n++;
|
|
2114
|
+
} catch {
|
|
2115
|
+
counter.failed.push(keyPath);
|
|
2116
|
+
out[k] = v;
|
|
2117
|
+
}
|
|
2118
|
+
} else if (typeof v === "object" && v !== null) {
|
|
2119
|
+
out[k] = walkDecryptCount(v, vault, counter, keyPath);
|
|
2120
|
+
} else {
|
|
2121
|
+
out[k] = v;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
return out;
|
|
2125
|
+
}
|
|
2126
|
+
function walkReencrypt(node, vault) {
|
|
2127
|
+
if (node === null || node === void 0) return node;
|
|
2128
|
+
if (typeof node !== "object") return node;
|
|
2129
|
+
if (Array.isArray(node)) {
|
|
2130
|
+
return node.map((item) => walkReencrypt(item, vault));
|
|
2131
|
+
}
|
|
2132
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
2133
|
+
for (const [k, v] of Object.entries(node)) {
|
|
2134
|
+
if (typeof v === "string" && isSecretField(k) && v.length > 0 && !vault.isEncrypted(v)) {
|
|
2135
|
+
out[k] = vault.encrypt(v);
|
|
2136
|
+
} else if (typeof v === "object" && v !== null) {
|
|
2137
|
+
out[k] = walkReencrypt(v, vault);
|
|
2138
|
+
} else {
|
|
2139
|
+
out[k] = v;
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
return out;
|
|
2143
|
+
}
|
|
1220
2144
|
async function restrictFilePermissions(filePath, opts) {
|
|
1221
2145
|
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
1222
2146
|
if (process.platform === "win32") {
|
|
@@ -1305,7 +2229,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
1305
2229
|
this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
|
|
1306
2230
|
if (this.file) {
|
|
1307
2231
|
try {
|
|
1308
|
-
fs2.mkdirSync(
|
|
2232
|
+
fs2.mkdirSync(path6.dirname(this.file), { recursive: true });
|
|
1309
2233
|
} catch {
|
|
1310
2234
|
}
|
|
1311
2235
|
}
|
|
@@ -1567,7 +2491,11 @@ var MEMORY_TYPE_LABELS = {
|
|
|
1567
2491
|
};
|
|
1568
2492
|
|
|
1569
2493
|
// src/execution/compaction-core.ts
|
|
2494
|
+
function compactionDebugEnabled() {
|
|
2495
|
+
return process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1";
|
|
2496
|
+
}
|
|
1570
2497
|
function emitCompactionMetrics(event, metrics) {
|
|
2498
|
+
if (!compactionDebugEnabled()) return;
|
|
1571
2499
|
console.log(
|
|
1572
2500
|
JSON.stringify({
|
|
1573
2501
|
level: "debug",
|
|
@@ -1602,38 +2530,41 @@ function findPreserveStart(messages, preserveK) {
|
|
|
1602
2530
|
preserveStart = i;
|
|
1603
2531
|
}
|
|
1604
2532
|
}
|
|
1605
|
-
let
|
|
1606
|
-
let
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
const
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => {
|
|
1618
|
-
forwardWalkInnerIterations++;
|
|
1619
|
-
return b.type === "tool_result";
|
|
1620
|
-
})) {
|
|
1621
|
-
preserveStart = i + 1;
|
|
1622
|
-
}
|
|
2533
|
+
let pairRepairIterations = 0;
|
|
2534
|
+
let pairRepairInnerIterations = 0;
|
|
2535
|
+
while (preserveStart > 0) {
|
|
2536
|
+
pairRepairIterations++;
|
|
2537
|
+
const first = messages[preserveStart];
|
|
2538
|
+
const prev = messages[preserveStart - 1];
|
|
2539
|
+
if (!first || !prev || first.role !== "user" || prev.role !== "assistant") break;
|
|
2540
|
+
if (typeof first.content === "string" || typeof prev.content === "string") break;
|
|
2541
|
+
const resultIds = /* @__PURE__ */ new Set();
|
|
2542
|
+
for (const block of first.content) {
|
|
2543
|
+
pairRepairInnerIterations++;
|
|
2544
|
+
if (block.type === "tool_result") resultIds.add(block.tool_use_id);
|
|
1623
2545
|
}
|
|
2546
|
+
if (resultIds.size === 0) break;
|
|
2547
|
+
const hasMatchingUse = prev.content.some((block) => {
|
|
2548
|
+
pairRepairInnerIterations++;
|
|
2549
|
+
return block.type === "tool_use" && resultIds.has(block.id);
|
|
2550
|
+
});
|
|
2551
|
+
if (!hasMatchingUse) break;
|
|
2552
|
+
preserveStart--;
|
|
2553
|
+
}
|
|
2554
|
+
if (compactionDebugEnabled()) {
|
|
2555
|
+
console.log(
|
|
2556
|
+
JSON.stringify({
|
|
2557
|
+
level: "debug",
|
|
2558
|
+
event: "compaction.find_preserve_start.ended",
|
|
2559
|
+
messageCount: messages.length,
|
|
2560
|
+
preserveK,
|
|
2561
|
+
preserveStart,
|
|
2562
|
+
pairRepairIterations,
|
|
2563
|
+
pairRepairInnerIterations,
|
|
2564
|
+
pairRepairInnerPerOuter: pairRepairIterations > 0 ? pairRepairInnerIterations / pairRepairIterations : 0
|
|
2565
|
+
})
|
|
2566
|
+
);
|
|
1624
2567
|
}
|
|
1625
|
-
console.log(
|
|
1626
|
-
JSON.stringify({
|
|
1627
|
-
level: "debug",
|
|
1628
|
-
event: "compaction.find_preserve_start.ended",
|
|
1629
|
-
messageCount: messages.length,
|
|
1630
|
-
preserveK,
|
|
1631
|
-
preserveStart,
|
|
1632
|
-
forwardWalkIterations,
|
|
1633
|
-
forwardWalkInnerIterations,
|
|
1634
|
-
forwardWalkInnerPerOuter: forwardWalkIterations > 0 ? forwardWalkInnerIterations / forwardWalkIterations : 0
|
|
1635
|
-
})
|
|
1636
|
-
);
|
|
1637
2568
|
return preserveStart;
|
|
1638
2569
|
}
|
|
1639
2570
|
function eliseOldToolResults(messages, opts) {
|
|
@@ -1647,7 +2578,8 @@ function eliseOldToolResults(messages, opts) {
|
|
|
1647
2578
|
if (!msg || !Array.isArray(msg.content)) continue;
|
|
1648
2579
|
for (const b of msg.content) {
|
|
1649
2580
|
fastPathInnerIterations++;
|
|
1650
|
-
|
|
2581
|
+
const oversized = b.type === "tool_result" && estimateToolResultTokens(b.content) >= opts.eliseThreshold || b.type === "tool_use" && estimateToolInputTokens(b.input) >= opts.eliseThreshold;
|
|
2582
|
+
if (oversized) {
|
|
1651
2583
|
hasOversized = true;
|
|
1652
2584
|
break;
|
|
1653
2585
|
}
|
|
@@ -1681,6 +2613,13 @@ function eliseOldToolResults(messages, opts) {
|
|
|
1681
2613
|
}
|
|
1682
2614
|
const original = msg.content;
|
|
1683
2615
|
const newContent = original.map((b) => {
|
|
2616
|
+
if (b.type === "tool_use") {
|
|
2617
|
+
const tokens2 = estimateToolInputTokens(b.input);
|
|
2618
|
+
if (tokens2 < opts.eliseThreshold) return b;
|
|
2619
|
+
const elidedInput = summarizeToolUseInputElision(b, tokens2);
|
|
2620
|
+
saved += Math.max(0, tokens2 - estimateToolInputTokens(elidedInput));
|
|
2621
|
+
return { ...b, input: elidedInput };
|
|
2622
|
+
}
|
|
1684
2623
|
if (b.type !== "tool_result") return b;
|
|
1685
2624
|
const tokens = estimateToolResultTokens(b.content);
|
|
1686
2625
|
if (tokens < opts.eliseThreshold) return b;
|
|
@@ -1688,7 +2627,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
1688
2627
|
const elided = {
|
|
1689
2628
|
type: "tool_result",
|
|
1690
2629
|
tool_use_id: b.tool_use_id,
|
|
1691
|
-
content:
|
|
2630
|
+
content: summarizeToolResultElision(b, tokens),
|
|
1692
2631
|
is_error: b.is_error
|
|
1693
2632
|
};
|
|
1694
2633
|
return elided;
|
|
@@ -1700,7 +2639,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
1700
2639
|
changed = true;
|
|
1701
2640
|
}
|
|
1702
2641
|
fullPassInnerIterations += original.length;
|
|
1703
|
-
if (
|
|
2642
|
+
if (compactionDebugEnabled()) {
|
|
1704
2643
|
const ratio = fullPassInnerIterations / fullPassIterations;
|
|
1705
2644
|
if (ratio > 10) {
|
|
1706
2645
|
console.error(
|
|
@@ -1728,6 +2667,65 @@ function eliseOldToolResults(messages, opts) {
|
|
|
1728
2667
|
});
|
|
1729
2668
|
return { messages: changed ? next : messages, saved, changed };
|
|
1730
2669
|
}
|
|
2670
|
+
function summarizeToolUseInputElision(block, tokens) {
|
|
2671
|
+
const fields = {};
|
|
2672
|
+
for (const [key, value] of Object.entries(block.input ?? {})) {
|
|
2673
|
+
fields[key] = summarizeToolUseInputValue(value);
|
|
2674
|
+
}
|
|
2675
|
+
return {
|
|
2676
|
+
__elided_tool_input: `~${tokens} tokens; original arguments are in the session log`,
|
|
2677
|
+
tool: block.name,
|
|
2678
|
+
fields
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
function summarizeToolUseInputValue(value) {
|
|
2682
|
+
if (value === null || value === void 0) return value;
|
|
2683
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
2684
|
+
if (typeof value === "string") {
|
|
2685
|
+
const oneLine = value.replace(/\s+/g, " ").trim();
|
|
2686
|
+
return oneLine.length <= 160 ? oneLine : `${oneLine.slice(0, 120)}...(${oneLine.length} chars)`;
|
|
2687
|
+
}
|
|
2688
|
+
if (Array.isArray(value)) {
|
|
2689
|
+
return `[array:${value.length}]`;
|
|
2690
|
+
}
|
|
2691
|
+
if (typeof value === "object") {
|
|
2692
|
+
const keys = Object.keys(value);
|
|
2693
|
+
return `[object:${keys.slice(0, 8).join(",")}${keys.length > 8 ? ",..." : ""}]`;
|
|
2694
|
+
}
|
|
2695
|
+
return String(value);
|
|
2696
|
+
}
|
|
2697
|
+
function summarizeToolResultElision(block, tokens) {
|
|
2698
|
+
const parts = [`elided: ~${tokens} tokens`];
|
|
2699
|
+
if (block.name) parts.push(`tool=${block.name}`);
|
|
2700
|
+
const files = extractPathHints(block.content).slice(0, 5);
|
|
2701
|
+
if (files.length > 0) parts.push(`files=${files.join(", ")}`);
|
|
2702
|
+
const error = firstErrorLine(block.content);
|
|
2703
|
+
if (error) parts.push(`error=${error}`);
|
|
2704
|
+
return `[${parts.join("; ")}]`;
|
|
2705
|
+
}
|
|
2706
|
+
function extractPathHints(content) {
|
|
2707
|
+
const text = typeof content === "string" ? content : JSON.stringify(content);
|
|
2708
|
+
const out = /* @__PURE__ */ new Set();
|
|
2709
|
+
const re = /(?:(?:[A-Za-z]:)?[./\\]?[\w@.-]+(?:[\\/][\w@(). -]+)+\.[A-Za-z0-9]{1,12})/g;
|
|
2710
|
+
for (const match of text.matchAll(re)) {
|
|
2711
|
+
const clean = match[0]?.replace(/\\/g, "/").replace(/^["'`]+|["'`),;:]+$/g, "");
|
|
2712
|
+
if (clean && clean.length <= 220) out.add(clean);
|
|
2713
|
+
if (out.size >= 5) break;
|
|
2714
|
+
}
|
|
2715
|
+
return [...out];
|
|
2716
|
+
}
|
|
2717
|
+
function firstErrorLine(content) {
|
|
2718
|
+
const text = typeof content === "string" ? content : JSON.stringify(content);
|
|
2719
|
+
for (const line of text.split(/\r?\n/)) {
|
|
2720
|
+
if (!/\b(error|exception|failed|failure|fatal|panic|timeout|denied|enoent|eacces|eperm)\b/i.test(
|
|
2721
|
+
line
|
|
2722
|
+
))
|
|
2723
|
+
continue;
|
|
2724
|
+
const trimmed = line.replace(/\s+/g, " ").trim();
|
|
2725
|
+
if (trimmed) return trimmed.slice(0, 180);
|
|
2726
|
+
}
|
|
2727
|
+
return void 0;
|
|
2728
|
+
}
|
|
1731
2729
|
function buildLosslessDigest(messages) {
|
|
1732
2730
|
const lines = [];
|
|
1733
2731
|
for (const m of messages) {
|
|
@@ -1838,15 +2836,15 @@ function buildSmartDigest(messages) {
|
|
|
1838
2836
|
lines.push(`[${m.role}]: ${display}${marker}`);
|
|
1839
2837
|
}
|
|
1840
2838
|
if (noiseCount > 0) {
|
|
1841
|
-
lines.push(
|
|
2839
|
+
lines.push(
|
|
2840
|
+
`[system]: ${noiseCount} low-importance turn(s) collapsed (repeated failures / pure tool I/O)`
|
|
2841
|
+
);
|
|
1842
2842
|
}
|
|
1843
2843
|
return lines.join("\n");
|
|
1844
2844
|
}
|
|
1845
2845
|
function countToolBlocks(m) {
|
|
1846
2846
|
if (typeof m.content === "string") return 0;
|
|
1847
|
-
return m.content.filter(
|
|
1848
|
-
(b) => b.type === "tool_use" || b.type === "tool_result"
|
|
1849
|
-
).length;
|
|
2847
|
+
return m.content.filter((b) => b.type === "tool_use" || b.type === "tool_result").length;
|
|
1850
2848
|
}
|
|
1851
2849
|
function firstSentence(text) {
|
|
1852
2850
|
const trimmed = text.trim();
|
|
@@ -1892,10 +2890,12 @@ var HybridCompactor = class {
|
|
|
1892
2890
|
if (elide.changed) ctx.state.replaceMessages(elide.messages);
|
|
1893
2891
|
if (elide.saved > 0) reductions.push({ phase: "elision", saved: elide.saved });
|
|
1894
2892
|
let collapsedDigest;
|
|
2893
|
+
let evidenceDigest;
|
|
1895
2894
|
if (opts.aggressive) {
|
|
1896
2895
|
const phase2 = this.collapseAncientTurns(ctx, preserveK);
|
|
1897
2896
|
if (phase2.saved > 0) reductions.push({ phase: "summary", saved: phase2.saved });
|
|
1898
2897
|
collapsedDigest = phase2.digest;
|
|
2898
|
+
evidenceDigest = phase2.evidenceDigest;
|
|
1899
2899
|
}
|
|
1900
2900
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
1901
2901
|
if (repaired.report.changed) {
|
|
@@ -1903,6 +2903,11 @@ var HybridCompactor = class {
|
|
|
1903
2903
|
}
|
|
1904
2904
|
const afterTokens = estimateMessages(ctx.messages);
|
|
1905
2905
|
const afterFull = this.estimateFullRequest(ctx);
|
|
2906
|
+
const quality = checkCompactionQuality(ctx, {
|
|
2907
|
+
collapsedDigest,
|
|
2908
|
+
evidenceDigest,
|
|
2909
|
+
reduced: beforeTokens > afterTokens || beforeFull > afterFull
|
|
2910
|
+
});
|
|
1906
2911
|
return {
|
|
1907
2912
|
before: beforeTokens,
|
|
1908
2913
|
after: afterTokens,
|
|
@@ -1910,6 +2915,8 @@ var HybridCompactor = class {
|
|
|
1910
2915
|
fullRequestTokensAfter: afterFull,
|
|
1911
2916
|
reductions,
|
|
1912
2917
|
collapsedDigest,
|
|
2918
|
+
evidenceDigest,
|
|
2919
|
+
quality,
|
|
1913
2920
|
repaired: repaired.report.changed ? {
|
|
1914
2921
|
removedToolUses: repaired.report.removedToolUses,
|
|
1915
2922
|
removedToolResults: repaired.report.removedToolResults,
|
|
@@ -1951,7 +2958,13 @@ var HybridCompactor = class {
|
|
|
1951
2958
|
if (boundary <= 0) return { saved: 0 };
|
|
1952
2959
|
const removed = messages.slice(0, boundary);
|
|
1953
2960
|
const removedTokens = estimateMessages(removed);
|
|
1954
|
-
const
|
|
2961
|
+
const historyDigest = this.smart ? buildSmartDigest(removed) || `${removed.length} earlier turns (no textual content; tool I/O omitted; see session log)` : buildLosslessDigest(removed) || `${removed.length} earlier turns (no textual content; tool I/O omitted; see session log)`;
|
|
2962
|
+
const evidenceDigest = buildContextEvidenceDigest(ctx);
|
|
2963
|
+
const digest = evidenceDigest ? `[context_state]
|
|
2964
|
+
${evidenceDigest}
|
|
2965
|
+
|
|
2966
|
+
[prior_history]
|
|
2967
|
+
${historyDigest}` : historyDigest;
|
|
1955
2968
|
const summaryMsg = {
|
|
1956
2969
|
role: "system",
|
|
1957
2970
|
content: `[prior_turns_digest: ${digest}]`
|
|
@@ -1960,10 +2973,29 @@ var HybridCompactor = class {
|
|
|
1960
2973
|
ctx.state.replaceMessages([summaryMsg, ...tail]);
|
|
1961
2974
|
return {
|
|
1962
2975
|
saved: Math.max(0, removedTokens - estimateMessages([summaryMsg])),
|
|
1963
|
-
digest
|
|
2976
|
+
digest,
|
|
2977
|
+
evidenceDigest: evidenceDigest || void 0
|
|
1964
2978
|
};
|
|
1965
2979
|
}
|
|
1966
2980
|
};
|
|
2981
|
+
function checkCompactionQuality(ctx, opts) {
|
|
2982
|
+
const evidence = ctx.contextEvidence;
|
|
2983
|
+
const digest = `${opts.collapsedDigest ?? ""}
|
|
2984
|
+
${opts.evidenceDigest ?? ""}`;
|
|
2985
|
+
const hasIntent = Boolean(evidence?.currentIntent?.text || /\b(intent|goal|session_goals)\b/i.test(digest));
|
|
2986
|
+
const hasPathTrail = Boolean(
|
|
2987
|
+
Object.keys(evidence?.fileGraph ?? {}).length > 0 || (evidence?.toolCalls.length ?? 0) > 0 || /\b(dependency_graph|tool_trail|files=)\b/i.test(digest)
|
|
2988
|
+
);
|
|
2989
|
+
const issues = [];
|
|
2990
|
+
if (opts.reduced && !hasIntent) issues.push("missing intent anchor");
|
|
2991
|
+
if (opts.reduced && !hasPathTrail) issues.push("missing tool/path trail");
|
|
2992
|
+
return {
|
|
2993
|
+
ok: issues.length === 0,
|
|
2994
|
+
hasIntent,
|
|
2995
|
+
hasPathTrail,
|
|
2996
|
+
issues
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
1967
2999
|
function readContextWindowPolicy(ctx) {
|
|
1968
3000
|
const policy = ctx.meta?.["contextWindowPolicy"];
|
|
1969
3001
|
if (!policy || typeof policy !== "object") return null;
|
|
@@ -1990,52 +3022,52 @@ var DefaultPathResolver = class {
|
|
|
1990
3022
|
projectRoot;
|
|
1991
3023
|
cwd;
|
|
1992
3024
|
constructor(cwd = process.cwd()) {
|
|
1993
|
-
this.cwd =
|
|
3025
|
+
this.cwd = path6.resolve(cwd);
|
|
1994
3026
|
this.projectRoot = this.detectProjectRoot(this.cwd);
|
|
1995
3027
|
}
|
|
1996
3028
|
detectProjectRoot(start) {
|
|
1997
|
-
let dir =
|
|
1998
|
-
const root =
|
|
1999
|
-
const home =
|
|
2000
|
-
const startPath =
|
|
3029
|
+
let dir = path6.resolve(start);
|
|
3030
|
+
const root = path6.parse(dir).root;
|
|
3031
|
+
const home = path6.resolve(os.homedir());
|
|
3032
|
+
const startPath = path6.resolve(start);
|
|
2001
3033
|
while (dir !== root) {
|
|
2002
3034
|
if (dir === home && dir !== startPath) {
|
|
2003
3035
|
break;
|
|
2004
3036
|
}
|
|
2005
3037
|
for (const marker of PROJECT_MARKERS) {
|
|
2006
3038
|
try {
|
|
2007
|
-
fs2.accessSync(
|
|
3039
|
+
fs2.accessSync(path6.join(dir, marker));
|
|
2008
3040
|
return dir;
|
|
2009
3041
|
} catch {
|
|
2010
3042
|
}
|
|
2011
3043
|
}
|
|
2012
|
-
const parent =
|
|
3044
|
+
const parent = path6.dirname(dir);
|
|
2013
3045
|
if (parent === dir) break;
|
|
2014
3046
|
dir = parent;
|
|
2015
3047
|
}
|
|
2016
3048
|
return startPath;
|
|
2017
3049
|
}
|
|
2018
3050
|
resolve(input) {
|
|
2019
|
-
const abs =
|
|
3051
|
+
const abs = path6.isAbsolute(input) ? input : path6.resolve(this.cwd, input);
|
|
2020
3052
|
let real;
|
|
2021
3053
|
try {
|
|
2022
3054
|
real = fs2.realpathSync(abs);
|
|
2023
3055
|
} catch {
|
|
2024
|
-
real =
|
|
3056
|
+
real = path6.normalize(abs);
|
|
2025
3057
|
}
|
|
2026
3058
|
return real;
|
|
2027
3059
|
}
|
|
2028
3060
|
isInsideRoot(absPath) {
|
|
2029
|
-
const normalized =
|
|
2030
|
-
const root =
|
|
3061
|
+
const normalized = path6.normalize(absPath);
|
|
3062
|
+
const root = path6.normalize(this.projectRoot);
|
|
2031
3063
|
if (normalized === root) return true;
|
|
2032
|
-
const rel =
|
|
2033
|
-
return !rel.startsWith("..") && !
|
|
3064
|
+
const rel = path6.relative(root, normalized);
|
|
3065
|
+
return !rel.startsWith("..") && !path6.isAbsolute(rel);
|
|
2034
3066
|
}
|
|
2035
3067
|
ensureInsideRoot(absPath) {
|
|
2036
3068
|
const resolved = this.resolve(absPath);
|
|
2037
3069
|
if (!this.isInsideRoot(resolved)) {
|
|
2038
|
-
const display =
|
|
3070
|
+
const display = path6.isAbsolute(absPath) ? path6.basename(absPath) : absPath;
|
|
2039
3071
|
const err = new Error(`Path "${display}" resolves outside the project root`);
|
|
2040
3072
|
err.fullPath = absPath;
|
|
2041
3073
|
err.projectRoot = this.projectRoot;
|
|
@@ -2234,6 +3266,27 @@ var PATTERNS = [
|
|
|
2234
3266
|
{ type: "postgres_uri", regex: /postgres(?:ql)?:\/\/[^\s"'`]+/g },
|
|
2235
3267
|
{ type: "mysql_uri", regex: /mysql:\/\/[^\s"'`]+/g },
|
|
2236
3268
|
{ type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g },
|
|
3269
|
+
// AI/ML provider keys — modern LLM services with well-known prefixes
|
|
3270
|
+
{
|
|
3271
|
+
type: "huggingface_token",
|
|
3272
|
+
// HuggingFace tokens: hf_ followed by 34 alphanumeric chars
|
|
3273
|
+
regex: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{34}(?![A-Za-z0-9])/g
|
|
3274
|
+
},
|
|
3275
|
+
{
|
|
3276
|
+
type: "replicate_token",
|
|
3277
|
+
// Replicate tokens: r8_ followed by 40+ alphanumeric chars
|
|
3278
|
+
regex: /(?<![A-Za-z0-9])r8_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
|
|
3279
|
+
},
|
|
3280
|
+
{
|
|
3281
|
+
type: "perplexity_key",
|
|
3282
|
+
// Perplexity API keys: pplx- followed by 40+ alphanumeric chars
|
|
3283
|
+
regex: /(?<![A-Za-z0-9])pplx-[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
|
|
3284
|
+
},
|
|
3285
|
+
{
|
|
3286
|
+
type: "groq_key",
|
|
3287
|
+
// Groq API keys: gsk_ followed by 40+ alphanumeric chars
|
|
3288
|
+
regex: /(?<![A-Za-z0-9])gsk_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
|
|
3289
|
+
},
|
|
2237
3290
|
{
|
|
2238
3291
|
type: "bearer_token",
|
|
2239
3292
|
// Anchored with alternation instead of negative lookahead — avoids V8
|
|
@@ -2267,6 +3320,10 @@ function hasCredentialAnchors(text) {
|
|
|
2267
3320
|
text.includes("xox") || // Slack token (xoxa/xoxb/xoxp/xoxo/xoxs)
|
|
2268
3321
|
text.includes("Bearer ") || // Bearer token (space suffix reduces false positives)
|
|
2269
3322
|
text.includes("/bot") || // Telegram bot token (URL path pattern)
|
|
3323
|
+
text.includes("hf_") || // HuggingFace token
|
|
3324
|
+
text.includes("r8_") || // Replicate token
|
|
3325
|
+
text.includes("pplx-") || // Perplexity API key
|
|
3326
|
+
text.includes("gsk_") || // Groq API key
|
|
2270
3327
|
text.includes("_KEY=") || // High-entropy env vars: API_KEY=, SECRET_KEY=, ...
|
|
2271
3328
|
text.includes("_TOKEN=") || // ACCESS_TOKEN=, AUTH_TOKEN=, ...
|
|
2272
3329
|
text.includes("_SECRET=") || // API_SECRET=, CLIENT_SECRET=, ...
|
|
@@ -2389,7 +3446,7 @@ var DefaultModelsRegistry = class {
|
|
|
2389
3446
|
this.overlay = opts.overlay;
|
|
2390
3447
|
this.overlayUrl = opts.overlayUrl;
|
|
2391
3448
|
this.overlayFile = opts.overlayFile;
|
|
2392
|
-
this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ?
|
|
3449
|
+
this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path6.join(path6.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
|
|
2393
3450
|
}
|
|
2394
3451
|
async load(opts = {}) {
|
|
2395
3452
|
if (this.payload && !opts.force) return this.payload;
|
|
@@ -2602,7 +3659,7 @@ var DefaultModelsRegistry = class {
|
|
|
2602
3659
|
}
|
|
2603
3660
|
/** Used by `wstack models refresh` to expose where the cache lives. */
|
|
2604
3661
|
cacheLocation() {
|
|
2605
|
-
return
|
|
3662
|
+
return path6.resolve(this.cacheFile);
|
|
2606
3663
|
}
|
|
2607
3664
|
};
|
|
2608
3665
|
function hasEntries(payload) {
|
|
@@ -3100,7 +4157,7 @@ var InMemoryAgentBridge = class {
|
|
|
3100
4157
|
});
|
|
3101
4158
|
}
|
|
3102
4159
|
this.inflightGuards.add(correlationId);
|
|
3103
|
-
return new Promise((
|
|
4160
|
+
return new Promise((resolve6, reject) => {
|
|
3104
4161
|
const timer = setTimeout(() => {
|
|
3105
4162
|
this.inflightGuards.delete(correlationId);
|
|
3106
4163
|
this.pendingRequests.delete(correlationId);
|
|
@@ -3119,7 +4176,7 @@ var InMemoryAgentBridge = class {
|
|
|
3119
4176
|
return;
|
|
3120
4177
|
}
|
|
3121
4178
|
this.pendingRequests.set(correlationId, {
|
|
3122
|
-
resolve:
|
|
4179
|
+
resolve: resolve6,
|
|
3123
4180
|
reject,
|
|
3124
4181
|
timer
|
|
3125
4182
|
});
|
|
@@ -3409,7 +4466,8 @@ ${errorDetails}`,
|
|
|
3409
4466
|
let effectivePermission = decision.permission;
|
|
3410
4467
|
const policy = this.opts.permissionPolicy;
|
|
3411
4468
|
const yolo = policy.getYolo?.() === true || policy.getYoloDestructive?.() === true;
|
|
3412
|
-
|
|
4469
|
+
const authoritativeAuto = decision.source === "yolo";
|
|
4470
|
+
if (toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo && !authoritativeAuto) {
|
|
3413
4471
|
effectivePermission = "confirm";
|
|
3414
4472
|
}
|
|
3415
4473
|
if (effectivePermission === "deny") {
|
|
@@ -3551,9 +4609,10 @@ ${post.additionalContext}`;
|
|
|
3551
4609
|
});
|
|
3552
4610
|
this.opts.renderer?.writeToolCall(tool.name, use.input);
|
|
3553
4611
|
const output = await this.runWithTimeout(tool, use.input, ctx.signal, ctx, use.id);
|
|
3554
|
-
const text = this.serializer.serialize(output);
|
|
4612
|
+
const text = this.serializer.serialize(output, { toolName: tool.name, input: use.input });
|
|
3555
4613
|
const scrubbed = this.opts.secretScrubber.scrub(text);
|
|
3556
|
-
const
|
|
4614
|
+
const withArtifact = await maybePersistLargeToolOutput(tool.name, scrubbed, budget);
|
|
4615
|
+
const { text: capped, newBudget } = this.serializer.enforceCap(withArtifact, budget);
|
|
3557
4616
|
this.opts.renderer?.writeToolResult(tool.name, capped, false);
|
|
3558
4617
|
return {
|
|
3559
4618
|
block: {
|
|
@@ -3578,38 +4637,27 @@ ${post.additionalContext}`;
|
|
|
3578
4637
|
tool.timeoutMs ?? this.iterationTimeoutMs,
|
|
3579
4638
|
this.maxToolTimeoutMs
|
|
3580
4639
|
);
|
|
3581
|
-
const
|
|
3582
|
-
const
|
|
3583
|
-
|
|
3584
|
-
let cleanupCalled = false;
|
|
3585
|
-
let caught = false;
|
|
4640
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
4641
|
+
const combined = AbortSignal.any([parentSignal, timeoutSignal]);
|
|
4642
|
+
let output;
|
|
3586
4643
|
try {
|
|
3587
|
-
|
|
3588
|
-
return await this.runStreamedTool(tool, input, ctx, combined, toolUseId);
|
|
3589
|
-
}
|
|
3590
|
-
return await tool.execute(input, ctx, { signal: combined });
|
|
4644
|
+
output = typeof tool.executeStream === "function" ? await this.runStreamedTool(tool, input, ctx, combined, toolUseId) : await tool.execute(input, ctx, { signal: combined });
|
|
3591
4645
|
} catch (err) {
|
|
3592
|
-
|
|
3593
|
-
if (combined.aborted && typeof tool.cleanup === "function") {
|
|
3594
|
-
cleanupCalled = true;
|
|
3595
|
-
try {
|
|
3596
|
-
await tool.cleanup(input, ctx);
|
|
3597
|
-
} catch {
|
|
3598
|
-
}
|
|
3599
|
-
}
|
|
4646
|
+
if (combined.aborted) await this.runToolCleanup(tool, input, ctx);
|
|
3600
4647
|
throw err;
|
|
3601
|
-
}
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
4648
|
+
}
|
|
4649
|
+
if (combined.aborted) {
|
|
4650
|
+
await this.runToolCleanup(tool, input, ctx);
|
|
4651
|
+
throw combined.reason instanceof Error ? combined.reason : new Error(typeof combined.reason === "string" ? combined.reason : "tool timeout");
|
|
4652
|
+
}
|
|
4653
|
+
return output;
|
|
4654
|
+
}
|
|
4655
|
+
/** Best-effort tool cleanup; never let it mask the original error. */
|
|
4656
|
+
async runToolCleanup(tool, input, ctx) {
|
|
4657
|
+
if (typeof tool.cleanup !== "function") return;
|
|
4658
|
+
try {
|
|
4659
|
+
await tool.cleanup(input, ctx);
|
|
4660
|
+
} catch {
|
|
3613
4661
|
}
|
|
3614
4662
|
}
|
|
3615
4663
|
async runStreamedTool(tool, input, ctx, signal, toolUseId) {
|
|
@@ -3778,6 +4826,25 @@ function extractMalformedRaw(input) {
|
|
|
3778
4826
|
return String(value);
|
|
3779
4827
|
}
|
|
3780
4828
|
}
|
|
4829
|
+
var TOOL_OUTPUT_ARTIFACT_THRESHOLD_BYTES = 64 * 1024;
|
|
4830
|
+
async function maybePersistLargeToolOutput(toolName, content, budget) {
|
|
4831
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
4832
|
+
if (bytes <= Math.min(TOOL_OUTPUT_ARTIFACT_THRESHOLD_BYTES, Math.max(0, budget))) {
|
|
4833
|
+
return content;
|
|
4834
|
+
}
|
|
4835
|
+
try {
|
|
4836
|
+
const dir = path6.join(wstackGlobalRoot(), "tool-output");
|
|
4837
|
+
await fs.mkdir(dir, { recursive: true });
|
|
4838
|
+
const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
|
|
4839
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4840
|
+
const filePath = path6.join(dir, `${stamp}-${safeTool}-${randomUUID()}.log`);
|
|
4841
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
4842
|
+
return content + `
|
|
4843
|
+
[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
|
|
4844
|
+
} catch {
|
|
4845
|
+
return content;
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
3781
4848
|
|
|
3782
4849
|
// src/core/conversation-state.ts
|
|
3783
4850
|
var ConversationState = class {
|
|
@@ -3896,6 +4963,7 @@ var Context = class {
|
|
|
3896
4963
|
todos = [];
|
|
3897
4964
|
readFiles = /* @__PURE__ */ new Set();
|
|
3898
4965
|
fileMtimes = /* @__PURE__ */ new Map();
|
|
4966
|
+
contextEvidence = createContextEvidenceState();
|
|
3899
4967
|
systemPrompt;
|
|
3900
4968
|
provider;
|
|
3901
4969
|
session;
|
|
@@ -3905,6 +4973,13 @@ var Context = class {
|
|
|
3905
4973
|
projectRoot;
|
|
3906
4974
|
/** Mutable working directory — starts as `cwd`. Change via `setWorkingDir()`. */
|
|
3907
4975
|
workingDir;
|
|
4976
|
+
/**
|
|
4977
|
+
* When true, file tools (via `_util.ts`) and `setWorkingDir()` reject paths
|
|
4978
|
+
* outside `projectRoot`. When false, those boundary checks are bypassed so
|
|
4979
|
+
* tools may reach paths outside the project (still gated by permission
|
|
4980
|
+
* tiers). Mutable so `/settings` can toggle it live on the running session.
|
|
4981
|
+
*/
|
|
4982
|
+
allowOutsideProjectRoot;
|
|
3908
4983
|
model;
|
|
3909
4984
|
tools = [];
|
|
3910
4985
|
meta = {};
|
|
@@ -3918,11 +4993,6 @@ var Context = class {
|
|
|
3918
4993
|
* so storage operations can include it in `storage.*` events.
|
|
3919
4994
|
*/
|
|
3920
4995
|
traceId;
|
|
3921
|
-
/**
|
|
3922
|
-
* When true, tools can access any path on the filesystem.
|
|
3923
|
-
* When false or undefined, tools are restricted to the project root.
|
|
3924
|
-
*/
|
|
3925
|
-
allowOutsideProjectRoot;
|
|
3926
4996
|
/** Callbacks fired when `setWorkingDir()` changes the working directory. */
|
|
3927
4997
|
_onWorkingDirChanged = [];
|
|
3928
4998
|
/**
|
|
@@ -3954,12 +5024,13 @@ var Context = class {
|
|
|
3954
5024
|
this.cwd = init.cwd;
|
|
3955
5025
|
this.projectRoot = init.projectRoot;
|
|
3956
5026
|
this.workingDir = init.workingDir ?? init.cwd;
|
|
5027
|
+
this.allowOutsideProjectRoot = init.allowOutsideProjectRoot ?? false;
|
|
3957
5028
|
this.model = init.model;
|
|
3958
5029
|
this.tools = init.tools ?? [];
|
|
3959
5030
|
this.agentId = init.agentId ?? "unknown";
|
|
3960
5031
|
this.agentName = init.agentName ?? "Unknown Agent";
|
|
3961
5032
|
this.traceId = init.traceId;
|
|
3962
|
-
this.allowOutsideProjectRoot = init.allowOutsideProjectRoot ??
|
|
5033
|
+
this.allowOutsideProjectRoot = init.allowOutsideProjectRoot ?? false;
|
|
3963
5034
|
this.session.traceId = init.traceId;
|
|
3964
5035
|
}
|
|
3965
5036
|
/**
|
|
@@ -4024,13 +5095,15 @@ var Context = class {
|
|
|
4024
5095
|
* Returns the resolved absolute path.
|
|
4025
5096
|
*/
|
|
4026
5097
|
setWorkingDir(dir) {
|
|
4027
|
-
const resolved =
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
5098
|
+
const resolved = path6.isAbsolute(dir) ? path6.resolve(dir) : path6.resolve(this.projectRoot, dir);
|
|
5099
|
+
if (!this.allowOutsideProjectRoot) {
|
|
5100
|
+
const root = path6.resolve(this.projectRoot);
|
|
5101
|
+
const rel = path6.relative(root, resolved);
|
|
5102
|
+
if (rel.startsWith("..") || path6.isAbsolute(rel)) {
|
|
5103
|
+
throw new Error(
|
|
5104
|
+
`Working directory "${resolved}" is outside project root "${root}"`
|
|
5105
|
+
);
|
|
5106
|
+
}
|
|
4034
5107
|
}
|
|
4035
5108
|
const old = this.workingDir;
|
|
4036
5109
|
this.workingDir = resolved;
|
|
@@ -4336,6 +5409,6 @@ function renderPlainText(meta, events) {
|
|
|
4336
5409
|
return lines.join("\n");
|
|
4337
5410
|
}
|
|
4338
5411
|
|
|
4339
|
-
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 };
|
|
5412
|
+
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 };
|
|
4340
5413
|
//# sourceMappingURL=index.js.map
|
|
4341
5414
|
//# sourceMappingURL=index.js.map
|