@wrongstack/core 0.265.1 → 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-DrkBxszZ.d.ts → agent-bridge-STJ3JwwK.d.ts} +1 -1
- package/dist/{agent-subagent-runner-DM2pP-B6.d.ts → agent-subagent-runner-CzPGP3jA.d.ts} +25 -7
- package/dist/{brain-BXd_61kQ.d.ts → brain-Cdg77tVN.d.ts} +73 -1
- package/dist/{compactor-B8pOf45Y.d.ts → compactor-iMZ84CXq.d.ts} +19 -1
- package/dist/{config-BMCj_XDs.d.ts → config-Du3pYYln.d.ts} +54 -3
- package/dist/{context-MRk5PhNv.d.ts → context-dT5Ueund.d.ts} +65 -1
- package/dist/coordination/index.d.ts +17 -17
- package/dist/coordination/index.js +138 -114
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +25 -25
- package/dist/defaults/index.js +1729 -781
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +15 -15
- package/dist/execution/index.js +1119 -229
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/{goal-preamble-DvHDSKSe.d.ts → goal-preamble-SulMTowG.d.ts} +28 -11
- package/dist/{goal-store-DtLMySNb.d.ts → goal-store-CABDwdFE.d.ts} +1 -1
- package/dist/{index-CEDeNodM.d.ts → index-Bms0m4oy.d.ts} +5 -5
- package/dist/{index-B-ch8K9C.d.ts → index-DtCVWel4.d.ts} +8 -8
- package/dist/index-IEuxQd-E.d.ts +82 -0
- package/dist/index.d.ts +118 -45
- package/dist/index.js +3083 -1602
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +72 -1
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-2x4w6Jn9.d.ts → mcp-servers-C2cBTxUR.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +30 -1
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-DmJlKuNp.d.ts → models-registry-BqGZNJQ-.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-DyCkCZnU.d.ts → multi-agent-coordinator-B8R43uPz.d.ts} +1 -1
- package/dist/{null-fleet-bus-CG9QY2aP.d.ts → null-fleet-bus-CnXa5oTH.d.ts} +14 -9
- package/dist/observability/index.d.ts +2 -2
- package/dist/{parallel-eternal-engine-Jw9uhEoT.d.ts → parallel-eternal-engine-DdNnw9BQ.d.ts} +11 -9
- package/dist/{path-resolver-Dy2ej-gE.d.ts → path-resolver-COIMLCQL.d.ts} +3 -3
- package/dist/{permission-B9SB45lp.d.ts → permission-B75JAi3-.d.ts} +1 -1
- package/dist/{permission-policy-CkjSXabK.d.ts → permission-policy-DlR9eJAM.d.ts} +2 -2
- package/dist/{pipeline-DPDxH_7m.d.ts → pipeline-BfD2k1rT.d.ts} +2 -2
- package/dist/{plan-templates-CzD9GnAU.d.ts → plan-templates-DSIKCXZN.d.ts} +5 -5
- package/dist/{llm-selector-C0tfTCUe.d.ts → provider-model-resolve-BNRsNuJx.d.ts} +40 -3
- package/dist/{provider-runner-DMa70ODu.d.ts → provider-runner-CX7iIvox.d.ts} +3 -3
- package/dist/{retry-policy-CN0khdlj.d.ts → retry-policy-BilV1ujH.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +12 -12
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-B2yw84VT.d.ts → secret-vault-gkvEZZfE.d.ts} +2 -2
- package/dist/security/index.d.ts +5 -67
- package/dist/security/index.js +96 -76
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-CzHh_igB.d.ts → selector-Bc7eWtT3.d.ts} +1 -1
- package/dist/{session-event-bridge-BUI6Jf-4.d.ts → session-event-bridge-D-araDEz.d.ts} +1 -1
- package/dist/{session-reader-CMgdMSRP.d.ts → session-reader-D7Dapswh.d.ts} +1 -1
- package/dist/storage/index.d.ts +11 -11
- package/dist/storage/index.js +81 -84
- 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 +19 -19
- package/dist/types/index.js +1265 -400
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +454 -406
- package/dist/utils/index.js +2191 -1201
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
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,48 +268,381 @@ 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
|
-
|
|
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;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
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;
|
|
309
|
+
}
|
|
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" });
|
|
232
316
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
317
|
+
}
|
|
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
|
+
}
|
|
237
323
|
}
|
|
238
324
|
}
|
|
239
|
-
return String(value);
|
|
240
325
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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);
|
|
244
329
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
330
|
+
}
|
|
331
|
+
}
|
|
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;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function isPlainObject(v) {
|
|
353
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
354
|
+
}
|
|
355
|
+
function describeType(v) {
|
|
356
|
+
if (v === null) return "null";
|
|
357
|
+
if (Array.isArray(v)) return "array";
|
|
358
|
+
return typeof v;
|
|
359
|
+
}
|
|
360
|
+
function joinPath(parent, key) {
|
|
361
|
+
if (!parent) return key;
|
|
362
|
+
return `${parent}.${key}`;
|
|
363
|
+
}
|
|
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]));
|
|
370
|
+
}
|
|
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;
|
|
380
|
+
}
|
|
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);
|
|
387
|
+
}
|
|
388
|
+
for (const [id, ovProvider] of Object.entries(overlay)) {
|
|
389
|
+
const existing = out[id];
|
|
390
|
+
out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
|
|
391
|
+
}
|
|
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 };
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
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
|
|
415
|
+
};
|
|
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
|
+
}
|
|
444
|
+
|
|
445
|
+
// src/utils/message-invariants.ts
|
|
446
|
+
function repairToolUseAdjacency(messages) {
|
|
447
|
+
const removedToolUses = [];
|
|
448
|
+
const removedToolResults = [];
|
|
449
|
+
let removedMessages = 0;
|
|
450
|
+
let changed = false;
|
|
451
|
+
const out = [];
|
|
452
|
+
for (let i = 0; i < messages.length; i++) {
|
|
453
|
+
const original = expectDefined(messages[i]);
|
|
454
|
+
let msg = original;
|
|
455
|
+
if (hasToolUse(msg)) {
|
|
456
|
+
const nextIds = toolResultIds(messages[i + 1]);
|
|
457
|
+
const filtered = mapContent(msg, (blocks) => {
|
|
458
|
+
const next = [];
|
|
459
|
+
for (const block of blocks) {
|
|
460
|
+
if (block.type === "tool_use" && !nextIds.has(block.id)) {
|
|
461
|
+
removedToolUses.push(block.id);
|
|
462
|
+
changed = true;
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
next.push(block);
|
|
466
|
+
}
|
|
467
|
+
return next;
|
|
468
|
+
});
|
|
469
|
+
msg = filtered ?? msg;
|
|
248
470
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
471
|
+
if (hasToolResult(msg)) {
|
|
472
|
+
const allowed = toolUseIds(out[out.length - 1]);
|
|
473
|
+
const filtered = mapContent(msg, (blocks) => {
|
|
474
|
+
const next = [];
|
|
475
|
+
for (const block of blocks) {
|
|
476
|
+
if (block.type === "tool_result" && !allowed.has(block.tool_use_id)) {
|
|
477
|
+
removedToolResults.push(block.tool_use_id);
|
|
478
|
+
changed = true;
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
next.push(block);
|
|
482
|
+
}
|
|
483
|
+
return next;
|
|
484
|
+
});
|
|
485
|
+
msg = filtered ?? msg;
|
|
256
486
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
487
|
+
if (isEmptyMessage(msg)) {
|
|
488
|
+
removedMessages++;
|
|
489
|
+
changed = true;
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
out.push(msg);
|
|
261
493
|
}
|
|
262
|
-
return {
|
|
494
|
+
return {
|
|
495
|
+
messages: changed ? out : messages,
|
|
496
|
+
report: { changed, removedToolUses, removedToolResults, removedMessages }
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function hasToolUse(msg) {
|
|
500
|
+
return contentBlocks(msg).some((b) => b.type === "tool_use");
|
|
501
|
+
}
|
|
502
|
+
function hasToolResult(msg) {
|
|
503
|
+
return contentBlocks(msg).some((b) => b.type === "tool_result");
|
|
504
|
+
}
|
|
505
|
+
function toolUseIds(msg) {
|
|
506
|
+
const ids = /* @__PURE__ */ new Set();
|
|
507
|
+
if (!msg || msg.role !== "assistant") return ids;
|
|
508
|
+
for (const block of contentBlocks(msg)) {
|
|
509
|
+
if (block.type === "tool_use") ids.add(block.id);
|
|
510
|
+
}
|
|
511
|
+
return ids;
|
|
512
|
+
}
|
|
513
|
+
function toolResultIds(msg) {
|
|
514
|
+
const ids = /* @__PURE__ */ new Set();
|
|
515
|
+
if (!msg || msg.role !== "user") return ids;
|
|
516
|
+
for (const block of contentBlocks(msg)) {
|
|
517
|
+
if (block.type === "tool_result") ids.add(block.tool_use_id);
|
|
518
|
+
}
|
|
519
|
+
return ids;
|
|
520
|
+
}
|
|
521
|
+
function contentBlocks(msg) {
|
|
522
|
+
return msg && Array.isArray(msg.content) ? msg.content : [];
|
|
523
|
+
}
|
|
524
|
+
function mapContent(msg, fn) {
|
|
525
|
+
if (!Array.isArray(msg.content)) return msg;
|
|
526
|
+
const next = fn(msg.content);
|
|
527
|
+
if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {
|
|
528
|
+
return msg;
|
|
529
|
+
}
|
|
530
|
+
return { ...msg, content: next };
|
|
531
|
+
}
|
|
532
|
+
function isEmptyMessage(msg) {
|
|
533
|
+
if (typeof msg.content === "string") return msg.content.trim().length === 0;
|
|
534
|
+
return msg.content.length === 0;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// src/utils/regex-guard.ts
|
|
538
|
+
var MAX_PATTERN_LEN = 512;
|
|
539
|
+
var DANGEROUS_PATTERNS = [
|
|
540
|
+
/(\([^)]*[+*][^)]*\))[+*]/,
|
|
541
|
+
// (a+)+, (.*)+, etc
|
|
542
|
+
/(\(\?:[^)]*[+*][^)]*\))[+*]/
|
|
543
|
+
// same, with non-capturing group
|
|
544
|
+
];
|
|
545
|
+
function compileUserRegex(pattern, flags) {
|
|
546
|
+
if (typeof pattern !== "string") {
|
|
547
|
+
return { ok: false, reason: "pattern must be a string" };
|
|
548
|
+
}
|
|
549
|
+
if (pattern.length === 0) {
|
|
550
|
+
return { ok: false, reason: "pattern is empty" };
|
|
551
|
+
}
|
|
552
|
+
if (pattern.length > MAX_PATTERN_LEN) {
|
|
553
|
+
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
554
|
+
}
|
|
555
|
+
for (const rx of DANGEROUS_PATTERNS) {
|
|
556
|
+
if (rx.test(pattern)) {
|
|
557
|
+
return {
|
|
558
|
+
ok: false,
|
|
559
|
+
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
try {
|
|
564
|
+
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
565
|
+
} catch (err) {
|
|
566
|
+
return {
|
|
567
|
+
ok: false,
|
|
568
|
+
reason: err instanceof Error ? err.message : "invalid regex"
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// src/utils/string.ts
|
|
574
|
+
function truncate(s, max) {
|
|
575
|
+
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/utils/tool-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);
|
|
263
646
|
}
|
|
264
647
|
|
|
265
648
|
// src/utils/token-estimate.ts
|
|
@@ -328,7 +711,8 @@ function estimateMessageTokens(messages) {
|
|
|
328
711
|
function estimateToolDefTokens(tool) {
|
|
329
712
|
const cached = tool._estDefTokens;
|
|
330
713
|
if (typeof cached === "number" && cached > 0) return cached;
|
|
331
|
-
|
|
714
|
+
const compact = compactToolDefinitionForWire(tool);
|
|
715
|
+
return RoughTokenEstimate(tool.name) + RoughTokenEstimate(compact.description) + RoughTokenEstimate(JSON.stringify(compact.inputSchema));
|
|
332
716
|
}
|
|
333
717
|
function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
334
718
|
let messagesTokens = 0;
|
|
@@ -383,291 +767,650 @@ function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = C
|
|
|
383
767
|
};
|
|
384
768
|
}
|
|
385
769
|
|
|
386
|
-
// src/utils/
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
+
}
|
|
411
800
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if (block.type === "tool_result" && !allowed.has(block.tool_use_id)) {
|
|
418
|
-
removedToolResults.push(block.tool_use_id);
|
|
419
|
-
changed = true;
|
|
420
|
-
continue;
|
|
421
|
-
}
|
|
422
|
-
next.push(block);
|
|
423
|
-
}
|
|
424
|
-
return next;
|
|
425
|
-
});
|
|
426
|
-
msg = filtered ?? msg;
|
|
801
|
+
return String(value);
|
|
802
|
+
}
|
|
803
|
+
function enforceCap(text, remainingBudget) {
|
|
804
|
+
if (remainingBudget <= 0) {
|
|
805
|
+
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
427
806
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
continue;
|
|
807
|
+
const textBytes = Buffer.byteLength(text, "utf8");
|
|
808
|
+
if (textBytes <= remainingBudget) {
|
|
809
|
+
return { text, newBudget: remainingBudget - textBytes };
|
|
432
810
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
function toolUseIds(msg) {
|
|
447
|
-
const ids = /* @__PURE__ */ new Set();
|
|
448
|
-
if (!msg || msg.role !== "assistant") return ids;
|
|
449
|
-
for (const block of contentBlocks(msg)) {
|
|
450
|
-
if (block.type === "tool_use") ids.add(block.id);
|
|
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 };
|
|
451
823
|
}
|
|
452
|
-
return
|
|
824
|
+
return { serialize, enforceCap, capBytes };
|
|
453
825
|
}
|
|
454
|
-
function
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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;
|
|
461
1041
|
}
|
|
462
|
-
function
|
|
463
|
-
|
|
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
|
+
]);
|
|
464
1075
|
}
|
|
465
|
-
function
|
|
466
|
-
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
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
|
+
);
|
|
470
1153
|
}
|
|
471
|
-
|
|
1154
|
+
if (groups.size > GREP_FILE_LIMIT) {
|
|
1155
|
+
sections.push(`[serializer omitted ${groups.size - GREP_FILE_LIMIT} file group(s)]`);
|
|
1156
|
+
}
|
|
1157
|
+
if (passthrough.length > 0) {
|
|
1158
|
+
sections.push(`ungrouped:
|
|
1159
|
+
${renderStringList(passthrough, "", 50)}`);
|
|
1160
|
+
}
|
|
1161
|
+
return sections.join("\n");
|
|
472
1162
|
}
|
|
473
|
-
function
|
|
474
|
-
|
|
475
|
-
return
|
|
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] ?? "" };
|
|
476
1167
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
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
|
+
}
|
|
1196
|
+
}
|
|
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
|
|
1230
|
+
}),
|
|
1231
|
+
excerpt.join("\n")
|
|
1232
|
+
]);
|
|
483
1233
|
}
|
|
484
|
-
function
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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);
|
|
492
1245
|
}
|
|
493
1246
|
}
|
|
494
|
-
if (
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
1247
|
+
if (markerHits === 0) {
|
|
1248
|
+
return lines.slice(-220).join("\n").trimEnd();
|
|
1249
|
+
}
|
|
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)]`);
|
|
501
1257
|
}
|
|
1258
|
+
out.push(lines[index] ?? "");
|
|
1259
|
+
previous = index;
|
|
502
1260
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
1261
|
+
return out.join("\n").trimEnd();
|
|
1262
|
+
}
|
|
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}`);
|
|
508
1319
|
}
|
|
1320
|
+
continue;
|
|
509
1321
|
}
|
|
510
|
-
if (
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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)}`);
|
|
515
1329
|
}
|
|
1330
|
+
continue;
|
|
516
1331
|
}
|
|
1332
|
+
blocks.push(`${key}: ${clipInline(oneLineJson(value))}`);
|
|
517
1333
|
}
|
|
518
|
-
|
|
519
|
-
for (let i = 0; i < value.length; i++) {
|
|
520
|
-
walk(value[i], schema.items, `${path7}[${i}]`, errors);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
1334
|
+
return joinSections([renderHeader(toolName, scalars), ...blocks]);
|
|
523
1335
|
}
|
|
524
|
-
function
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
return typeof value === "string";
|
|
528
|
-
case "number":
|
|
529
|
-
return typeof value === "number" && !Number.isNaN(value);
|
|
530
|
-
case "integer":
|
|
531
|
-
return typeof value === "number" && Number.isInteger(value);
|
|
532
|
-
case "boolean":
|
|
533
|
-
return typeof value === "boolean";
|
|
534
|
-
case "null":
|
|
535
|
-
return value === null;
|
|
536
|
-
case "array":
|
|
537
|
-
return Array.isArray(value);
|
|
538
|
-
case "object":
|
|
539
|
-
return isPlainObject(value);
|
|
540
|
-
default:
|
|
541
|
-
return true;
|
|
542
|
-
}
|
|
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;
|
|
543
1339
|
}
|
|
544
|
-
function
|
|
545
|
-
|
|
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");
|
|
546
1348
|
}
|
|
547
|
-
function
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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");
|
|
551
1355
|
}
|
|
552
|
-
function
|
|
553
|
-
|
|
554
|
-
return `${parent}.${key}`;
|
|
1356
|
+
function joinSections(sections) {
|
|
1357
|
+
return sections.map((section) => typeof section === "string" ? section.trimEnd() : void 0).filter((section) => !!section).join("\n");
|
|
555
1358
|
}
|
|
556
|
-
function
|
|
557
|
-
if (
|
|
558
|
-
if (
|
|
559
|
-
|
|
560
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
561
|
-
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
562
|
-
}
|
|
563
|
-
if (typeof a === "object" && typeof b === "object") {
|
|
564
|
-
const ak = Object.keys(a);
|
|
565
|
-
const bk = Object.keys(b);
|
|
566
|
-
if (ak.length !== bk.length) return false;
|
|
567
|
-
return ak.every(
|
|
568
|
-
(k) => deepEqual(a[k], b[k])
|
|
569
|
-
);
|
|
570
|
-
}
|
|
571
|
-
return false;
|
|
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);
|
|
572
1363
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
// (a+)+, (.*)+, etc
|
|
579
|
-
/(\(\?:[^)]*[+*][^)]*\))[+*]/
|
|
580
|
-
// same, with non-capturing group
|
|
581
|
-
];
|
|
582
|
-
function compileUserRegex(pattern, flags) {
|
|
583
|
-
if (typeof pattern !== "string") {
|
|
584
|
-
return { ok: false, reason: "pattern must be a string" };
|
|
585
|
-
}
|
|
586
|
-
if (pattern.length === 0) {
|
|
587
|
-
return { ok: false, reason: "pattern is empty" };
|
|
588
|
-
}
|
|
589
|
-
if (pattern.length > MAX_PATTERN_LEN) {
|
|
590
|
-
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
591
|
-
}
|
|
592
|
-
for (const rx of DANGEROUS_PATTERNS) {
|
|
593
|
-
if (rx.test(pattern)) {
|
|
594
|
-
return {
|
|
595
|
-
ok: false,
|
|
596
|
-
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
}
|
|
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) {
|
|
600
1369
|
try {
|
|
601
|
-
return
|
|
602
|
-
} catch
|
|
603
|
-
return
|
|
604
|
-
ok: false,
|
|
605
|
-
reason: err instanceof Error ? err.message : "invalid regex"
|
|
606
|
-
};
|
|
1370
|
+
return JSON.stringify(value);
|
|
1371
|
+
} catch {
|
|
1372
|
+
return String(value);
|
|
607
1373
|
}
|
|
608
1374
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
const out = {};
|
|
613
|
-
for (const [id, provider] of Object.entries(base)) {
|
|
614
|
-
out[id] = cloneProvider(provider);
|
|
615
|
-
}
|
|
616
|
-
for (const [id, ovProvider] of Object.entries(overlay)) {
|
|
617
|
-
const existing = out[id];
|
|
618
|
-
out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
|
|
619
|
-
}
|
|
620
|
-
return out;
|
|
1375
|
+
function stringField(obj, key) {
|
|
1376
|
+
const value = obj[key];
|
|
1377
|
+
return typeof value === "string" ? value : void 0;
|
|
621
1378
|
}
|
|
622
|
-
function
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
models[mid] = { ...m };
|
|
626
|
-
}
|
|
627
|
-
for (const [mid, ovModel] of Object.entries(overlay.models ?? {})) {
|
|
628
|
-
const existing = models[mid];
|
|
629
|
-
models[mid] = existing ? mergeModel(existing, ovModel) : { ...ovModel };
|
|
630
|
-
}
|
|
631
|
-
return {
|
|
632
|
-
...base,
|
|
633
|
-
// Overlay scalar fields win when explicitly provided; otherwise keep base.
|
|
634
|
-
...stripUndefined({
|
|
635
|
-
id: overlay.id,
|
|
636
|
-
name: overlay.name,
|
|
637
|
-
npm: overlay.npm,
|
|
638
|
-
api: overlay.api,
|
|
639
|
-
env: overlay.env,
|
|
640
|
-
doc: overlay.doc
|
|
641
|
-
}),
|
|
642
|
-
models
|
|
643
|
-
};
|
|
1379
|
+
function numberField(obj, key) {
|
|
1380
|
+
const value = obj[key];
|
|
1381
|
+
return typeof value === "number" ? value : void 0;
|
|
644
1382
|
}
|
|
645
|
-
function
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
merged.limit = { ...base.limit, ...overlay.limit };
|
|
649
|
-
}
|
|
650
|
-
if (base.cost || overlay.cost) {
|
|
651
|
-
merged.cost = { ...base.cost, ...overlay.cost };
|
|
652
|
-
}
|
|
653
|
-
if (base.modalities || overlay.modalities) {
|
|
654
|
-
merged.modalities = { ...base.modalities, ...overlay.modalities };
|
|
655
|
-
}
|
|
656
|
-
return merged;
|
|
1383
|
+
function stringArrayField(obj, key) {
|
|
1384
|
+
const value = obj[key];
|
|
1385
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
657
1386
|
}
|
|
658
|
-
function
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
}
|
|
663
|
-
return { ...p, models };
|
|
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;
|
|
664
1391
|
}
|
|
665
|
-
function
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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");
|
|
671
1414
|
}
|
|
672
1415
|
|
|
673
1416
|
// src/types/errors.ts
|
|
@@ -1150,7 +1893,7 @@ var DefaultSecretVault = class {
|
|
|
1150
1893
|
KEY_FILE_MAGIC.copy(keyFileBuf, 0);
|
|
1151
1894
|
keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
|
|
1152
1895
|
newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
|
|
1153
|
-
fs2.mkdirSync(
|
|
1896
|
+
fs2.mkdirSync(path6.dirname(this.keyFile), { recursive: true });
|
|
1154
1897
|
fs2.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
|
|
1155
1898
|
checkKeyFilePermissions(this.keyFile);
|
|
1156
1899
|
this.key = newKey;
|
|
@@ -1198,7 +1941,7 @@ var DefaultSecretVault = class {
|
|
|
1198
1941
|
} catch (err) {
|
|
1199
1942
|
if (err.code !== "ENOENT") throw err;
|
|
1200
1943
|
}
|
|
1201
|
-
fs2.mkdirSync(
|
|
1944
|
+
fs2.mkdirSync(path6.dirname(this.keyFile), { recursive: true });
|
|
1202
1945
|
const key = randomBytes(KEY_BYTES);
|
|
1203
1946
|
try {
|
|
1204
1947
|
fs2.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
|
|
@@ -1288,7 +2031,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
|
1288
2031
|
}
|
|
1289
2032
|
const merged = deepMerge(current, patch ?? {});
|
|
1290
2033
|
const encrypted = encryptConfigSecrets(merged, vault);
|
|
1291
|
-
await fs.mkdir(
|
|
2034
|
+
await fs.mkdir(path6.dirname(configPath), { recursive: true });
|
|
1292
2035
|
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
1293
2036
|
await restrictFilePermissions(configPath);
|
|
1294
2037
|
}
|
|
@@ -1334,8 +2077,13 @@ async function rotateConfigKeys(configPath, vault, logger) {
|
|
|
1334
2077
|
warn(`[secret-vault] Config file ${configPath} is not valid JSON \u2014 skipping rotation`);
|
|
1335
2078
|
return { rotated: 0, oldVersion: vault.keyVersion, newVersion: vault.keyVersion, file: configPath };
|
|
1336
2079
|
}
|
|
1337
|
-
const counter = { n: 0 };
|
|
2080
|
+
const counter = { n: 0, failed: [] };
|
|
1338
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
|
+
}
|
|
1339
2087
|
if (counter.n === 0) {
|
|
1340
2088
|
const { oldVersion: oldVersion2, newVersion: newVersion2 } = vault.rotateKey();
|
|
1341
2089
|
log(`[secret-vault] Key rotated (v${oldVersion2} \u2192 v${newVersion2}) \u2014 no encrypted fields to re-encrypt`);
|
|
@@ -1348,23 +2096,27 @@ async function rotateConfigKeys(configPath, vault, logger) {
|
|
|
1348
2096
|
log(`[secret-vault] Key rotated (v${oldVersion} \u2192 v${newVersion}) \u2014 re-encrypted ${counter.n} field(s)`);
|
|
1349
2097
|
return { rotated: counter.n, oldVersion, newVersion, file: configPath };
|
|
1350
2098
|
}
|
|
1351
|
-
function walkDecryptCount(node, vault, counter) {
|
|
2099
|
+
function walkDecryptCount(node, vault, counter, pathPrefix = "") {
|
|
1352
2100
|
if (node === null || node === void 0) return node;
|
|
1353
2101
|
if (typeof node !== "object") return node;
|
|
1354
2102
|
if (Array.isArray(node)) {
|
|
1355
|
-
return node.map(
|
|
2103
|
+
return node.map(
|
|
2104
|
+
(item, i) => walkDecryptCount(item, vault, counter, `${pathPrefix}[${i}]`)
|
|
2105
|
+
);
|
|
1356
2106
|
}
|
|
1357
2107
|
const out = /* @__PURE__ */ Object.create(null);
|
|
1358
2108
|
for (const [k, v] of Object.entries(node)) {
|
|
2109
|
+
const keyPath = pathPrefix ? `${pathPrefix}.${k}` : k;
|
|
1359
2110
|
if (typeof v === "string" && vault.isEncrypted(v)) {
|
|
1360
2111
|
try {
|
|
1361
2112
|
out[k] = vault.decrypt(v);
|
|
1362
2113
|
counter.n++;
|
|
1363
2114
|
} catch {
|
|
2115
|
+
counter.failed.push(keyPath);
|
|
1364
2116
|
out[k] = v;
|
|
1365
2117
|
}
|
|
1366
2118
|
} else if (typeof v === "object" && v !== null) {
|
|
1367
|
-
out[k] = walkDecryptCount(v, vault, counter);
|
|
2119
|
+
out[k] = walkDecryptCount(v, vault, counter, keyPath);
|
|
1368
2120
|
} else {
|
|
1369
2121
|
out[k] = v;
|
|
1370
2122
|
}
|
|
@@ -1477,7 +2229,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
1477
2229
|
this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
|
|
1478
2230
|
if (this.file) {
|
|
1479
2231
|
try {
|
|
1480
|
-
fs2.mkdirSync(
|
|
2232
|
+
fs2.mkdirSync(path6.dirname(this.file), { recursive: true });
|
|
1481
2233
|
} catch {
|
|
1482
2234
|
}
|
|
1483
2235
|
}
|
|
@@ -1778,25 +2530,26 @@ function findPreserveStart(messages, preserveK) {
|
|
|
1778
2530
|
preserveStart = i;
|
|
1779
2531
|
}
|
|
1780
2532
|
}
|
|
1781
|
-
let
|
|
1782
|
-
let
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
const
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => {
|
|
1794
|
-
forwardWalkInnerIterations++;
|
|
1795
|
-
return b.type === "tool_result";
|
|
1796
|
-
})) {
|
|
1797
|
-
preserveStart = i + 1;
|
|
1798
|
-
}
|
|
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);
|
|
1799
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--;
|
|
1800
2553
|
}
|
|
1801
2554
|
if (compactionDebugEnabled()) {
|
|
1802
2555
|
console.log(
|
|
@@ -1806,9 +2559,9 @@ function findPreserveStart(messages, preserveK) {
|
|
|
1806
2559
|
messageCount: messages.length,
|
|
1807
2560
|
preserveK,
|
|
1808
2561
|
preserveStart,
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
2562
|
+
pairRepairIterations,
|
|
2563
|
+
pairRepairInnerIterations,
|
|
2564
|
+
pairRepairInnerPerOuter: pairRepairIterations > 0 ? pairRepairInnerIterations / pairRepairIterations : 0
|
|
1812
2565
|
})
|
|
1813
2566
|
);
|
|
1814
2567
|
}
|
|
@@ -1825,7 +2578,8 @@ function eliseOldToolResults(messages, opts) {
|
|
|
1825
2578
|
if (!msg || !Array.isArray(msg.content)) continue;
|
|
1826
2579
|
for (const b of msg.content) {
|
|
1827
2580
|
fastPathInnerIterations++;
|
|
1828
|
-
|
|
2581
|
+
const oversized = b.type === "tool_result" && estimateToolResultTokens(b.content) >= opts.eliseThreshold || b.type === "tool_use" && estimateToolInputTokens(b.input) >= opts.eliseThreshold;
|
|
2582
|
+
if (oversized) {
|
|
1829
2583
|
hasOversized = true;
|
|
1830
2584
|
break;
|
|
1831
2585
|
}
|
|
@@ -1859,6 +2613,13 @@ function eliseOldToolResults(messages, opts) {
|
|
|
1859
2613
|
}
|
|
1860
2614
|
const original = msg.content;
|
|
1861
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
|
+
}
|
|
1862
2623
|
if (b.type !== "tool_result") return b;
|
|
1863
2624
|
const tokens = estimateToolResultTokens(b.content);
|
|
1864
2625
|
if (tokens < opts.eliseThreshold) return b;
|
|
@@ -1866,7 +2627,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
1866
2627
|
const elided = {
|
|
1867
2628
|
type: "tool_result",
|
|
1868
2629
|
tool_use_id: b.tool_use_id,
|
|
1869
|
-
content:
|
|
2630
|
+
content: summarizeToolResultElision(b, tokens),
|
|
1870
2631
|
is_error: b.is_error
|
|
1871
2632
|
};
|
|
1872
2633
|
return elided;
|
|
@@ -1906,6 +2667,65 @@ function eliseOldToolResults(messages, opts) {
|
|
|
1906
2667
|
});
|
|
1907
2668
|
return { messages: changed ? next : messages, saved, changed };
|
|
1908
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
|
+
}
|
|
1909
2729
|
function buildLosslessDigest(messages) {
|
|
1910
2730
|
const lines = [];
|
|
1911
2731
|
for (const m of messages) {
|
|
@@ -2016,15 +2836,15 @@ function buildSmartDigest(messages) {
|
|
|
2016
2836
|
lines.push(`[${m.role}]: ${display}${marker}`);
|
|
2017
2837
|
}
|
|
2018
2838
|
if (noiseCount > 0) {
|
|
2019
|
-
lines.push(
|
|
2839
|
+
lines.push(
|
|
2840
|
+
`[system]: ${noiseCount} low-importance turn(s) collapsed (repeated failures / pure tool I/O)`
|
|
2841
|
+
);
|
|
2020
2842
|
}
|
|
2021
2843
|
return lines.join("\n");
|
|
2022
2844
|
}
|
|
2023
2845
|
function countToolBlocks(m) {
|
|
2024
2846
|
if (typeof m.content === "string") return 0;
|
|
2025
|
-
return m.content.filter(
|
|
2026
|
-
(b) => b.type === "tool_use" || b.type === "tool_result"
|
|
2027
|
-
).length;
|
|
2847
|
+
return m.content.filter((b) => b.type === "tool_use" || b.type === "tool_result").length;
|
|
2028
2848
|
}
|
|
2029
2849
|
function firstSentence(text) {
|
|
2030
2850
|
const trimmed = text.trim();
|
|
@@ -2070,10 +2890,12 @@ var HybridCompactor = class {
|
|
|
2070
2890
|
if (elide.changed) ctx.state.replaceMessages(elide.messages);
|
|
2071
2891
|
if (elide.saved > 0) reductions.push({ phase: "elision", saved: elide.saved });
|
|
2072
2892
|
let collapsedDigest;
|
|
2893
|
+
let evidenceDigest;
|
|
2073
2894
|
if (opts.aggressive) {
|
|
2074
2895
|
const phase2 = this.collapseAncientTurns(ctx, preserveK);
|
|
2075
2896
|
if (phase2.saved > 0) reductions.push({ phase: "summary", saved: phase2.saved });
|
|
2076
2897
|
collapsedDigest = phase2.digest;
|
|
2898
|
+
evidenceDigest = phase2.evidenceDigest;
|
|
2077
2899
|
}
|
|
2078
2900
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
2079
2901
|
if (repaired.report.changed) {
|
|
@@ -2081,6 +2903,11 @@ var HybridCompactor = class {
|
|
|
2081
2903
|
}
|
|
2082
2904
|
const afterTokens = estimateMessages(ctx.messages);
|
|
2083
2905
|
const afterFull = this.estimateFullRequest(ctx);
|
|
2906
|
+
const quality = checkCompactionQuality(ctx, {
|
|
2907
|
+
collapsedDigest,
|
|
2908
|
+
evidenceDigest,
|
|
2909
|
+
reduced: beforeTokens > afterTokens || beforeFull > afterFull
|
|
2910
|
+
});
|
|
2084
2911
|
return {
|
|
2085
2912
|
before: beforeTokens,
|
|
2086
2913
|
after: afterTokens,
|
|
@@ -2088,6 +2915,8 @@ var HybridCompactor = class {
|
|
|
2088
2915
|
fullRequestTokensAfter: afterFull,
|
|
2089
2916
|
reductions,
|
|
2090
2917
|
collapsedDigest,
|
|
2918
|
+
evidenceDigest,
|
|
2919
|
+
quality,
|
|
2091
2920
|
repaired: repaired.report.changed ? {
|
|
2092
2921
|
removedToolUses: repaired.report.removedToolUses,
|
|
2093
2922
|
removedToolResults: repaired.report.removedToolResults,
|
|
@@ -2129,7 +2958,13 @@ var HybridCompactor = class {
|
|
|
2129
2958
|
if (boundary <= 0) return { saved: 0 };
|
|
2130
2959
|
const removed = messages.slice(0, boundary);
|
|
2131
2960
|
const removedTokens = estimateMessages(removed);
|
|
2132
|
-
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;
|
|
2133
2968
|
const summaryMsg = {
|
|
2134
2969
|
role: "system",
|
|
2135
2970
|
content: `[prior_turns_digest: ${digest}]`
|
|
@@ -2138,10 +2973,29 @@ var HybridCompactor = class {
|
|
|
2138
2973
|
ctx.state.replaceMessages([summaryMsg, ...tail]);
|
|
2139
2974
|
return {
|
|
2140
2975
|
saved: Math.max(0, removedTokens - estimateMessages([summaryMsg])),
|
|
2141
|
-
digest
|
|
2976
|
+
digest,
|
|
2977
|
+
evidenceDigest: evidenceDigest || void 0
|
|
2142
2978
|
};
|
|
2143
2979
|
}
|
|
2144
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
|
+
}
|
|
2145
2999
|
function readContextWindowPolicy(ctx) {
|
|
2146
3000
|
const policy = ctx.meta?.["contextWindowPolicy"];
|
|
2147
3001
|
if (!policy || typeof policy !== "object") return null;
|
|
@@ -2168,52 +3022,52 @@ var DefaultPathResolver = class {
|
|
|
2168
3022
|
projectRoot;
|
|
2169
3023
|
cwd;
|
|
2170
3024
|
constructor(cwd = process.cwd()) {
|
|
2171
|
-
this.cwd =
|
|
3025
|
+
this.cwd = path6.resolve(cwd);
|
|
2172
3026
|
this.projectRoot = this.detectProjectRoot(this.cwd);
|
|
2173
3027
|
}
|
|
2174
3028
|
detectProjectRoot(start) {
|
|
2175
|
-
let dir =
|
|
2176
|
-
const root =
|
|
2177
|
-
const home =
|
|
2178
|
-
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);
|
|
2179
3033
|
while (dir !== root) {
|
|
2180
3034
|
if (dir === home && dir !== startPath) {
|
|
2181
3035
|
break;
|
|
2182
3036
|
}
|
|
2183
3037
|
for (const marker of PROJECT_MARKERS) {
|
|
2184
3038
|
try {
|
|
2185
|
-
fs2.accessSync(
|
|
3039
|
+
fs2.accessSync(path6.join(dir, marker));
|
|
2186
3040
|
return dir;
|
|
2187
3041
|
} catch {
|
|
2188
3042
|
}
|
|
2189
3043
|
}
|
|
2190
|
-
const parent =
|
|
3044
|
+
const parent = path6.dirname(dir);
|
|
2191
3045
|
if (parent === dir) break;
|
|
2192
3046
|
dir = parent;
|
|
2193
3047
|
}
|
|
2194
3048
|
return startPath;
|
|
2195
3049
|
}
|
|
2196
3050
|
resolve(input) {
|
|
2197
|
-
const abs =
|
|
3051
|
+
const abs = path6.isAbsolute(input) ? input : path6.resolve(this.cwd, input);
|
|
2198
3052
|
let real;
|
|
2199
3053
|
try {
|
|
2200
3054
|
real = fs2.realpathSync(abs);
|
|
2201
3055
|
} catch {
|
|
2202
|
-
real =
|
|
3056
|
+
real = path6.normalize(abs);
|
|
2203
3057
|
}
|
|
2204
3058
|
return real;
|
|
2205
3059
|
}
|
|
2206
3060
|
isInsideRoot(absPath) {
|
|
2207
|
-
const normalized =
|
|
2208
|
-
const root =
|
|
3061
|
+
const normalized = path6.normalize(absPath);
|
|
3062
|
+
const root = path6.normalize(this.projectRoot);
|
|
2209
3063
|
if (normalized === root) return true;
|
|
2210
|
-
const rel =
|
|
2211
|
-
return !rel.startsWith("..") && !
|
|
3064
|
+
const rel = path6.relative(root, normalized);
|
|
3065
|
+
return !rel.startsWith("..") && !path6.isAbsolute(rel);
|
|
2212
3066
|
}
|
|
2213
3067
|
ensureInsideRoot(absPath) {
|
|
2214
3068
|
const resolved = this.resolve(absPath);
|
|
2215
3069
|
if (!this.isInsideRoot(resolved)) {
|
|
2216
|
-
const display =
|
|
3070
|
+
const display = path6.isAbsolute(absPath) ? path6.basename(absPath) : absPath;
|
|
2217
3071
|
const err = new Error(`Path "${display}" resolves outside the project root`);
|
|
2218
3072
|
err.fullPath = absPath;
|
|
2219
3073
|
err.projectRoot = this.projectRoot;
|
|
@@ -2592,7 +3446,7 @@ var DefaultModelsRegistry = class {
|
|
|
2592
3446
|
this.overlay = opts.overlay;
|
|
2593
3447
|
this.overlayUrl = opts.overlayUrl;
|
|
2594
3448
|
this.overlayFile = opts.overlayFile;
|
|
2595
|
-
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);
|
|
2596
3450
|
}
|
|
2597
3451
|
async load(opts = {}) {
|
|
2598
3452
|
if (this.payload && !opts.force) return this.payload;
|
|
@@ -2805,7 +3659,7 @@ var DefaultModelsRegistry = class {
|
|
|
2805
3659
|
}
|
|
2806
3660
|
/** Used by `wstack models refresh` to expose where the cache lives. */
|
|
2807
3661
|
cacheLocation() {
|
|
2808
|
-
return
|
|
3662
|
+
return path6.resolve(this.cacheFile);
|
|
2809
3663
|
}
|
|
2810
3664
|
};
|
|
2811
3665
|
function hasEntries(payload) {
|
|
@@ -3303,7 +4157,7 @@ var InMemoryAgentBridge = class {
|
|
|
3303
4157
|
});
|
|
3304
4158
|
}
|
|
3305
4159
|
this.inflightGuards.add(correlationId);
|
|
3306
|
-
return new Promise((
|
|
4160
|
+
return new Promise((resolve6, reject) => {
|
|
3307
4161
|
const timer = setTimeout(() => {
|
|
3308
4162
|
this.inflightGuards.delete(correlationId);
|
|
3309
4163
|
this.pendingRequests.delete(correlationId);
|
|
@@ -3322,7 +4176,7 @@ var InMemoryAgentBridge = class {
|
|
|
3322
4176
|
return;
|
|
3323
4177
|
}
|
|
3324
4178
|
this.pendingRequests.set(correlationId, {
|
|
3325
|
-
resolve:
|
|
4179
|
+
resolve: resolve6,
|
|
3326
4180
|
reject,
|
|
3327
4181
|
timer
|
|
3328
4182
|
});
|
|
@@ -3612,7 +4466,8 @@ ${errorDetails}`,
|
|
|
3612
4466
|
let effectivePermission = decision.permission;
|
|
3613
4467
|
const policy = this.opts.permissionPolicy;
|
|
3614
4468
|
const yolo = policy.getYolo?.() === true || policy.getYoloDestructive?.() === true;
|
|
3615
|
-
|
|
4469
|
+
const authoritativeAuto = decision.source === "yolo";
|
|
4470
|
+
if (toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo && !authoritativeAuto) {
|
|
3616
4471
|
effectivePermission = "confirm";
|
|
3617
4472
|
}
|
|
3618
4473
|
if (effectivePermission === "deny") {
|
|
@@ -3754,9 +4609,10 @@ ${post.additionalContext}`;
|
|
|
3754
4609
|
});
|
|
3755
4610
|
this.opts.renderer?.writeToolCall(tool.name, use.input);
|
|
3756
4611
|
const output = await this.runWithTimeout(tool, use.input, ctx.signal, ctx, use.id);
|
|
3757
|
-
const text = this.serializer.serialize(output);
|
|
4612
|
+
const text = this.serializer.serialize(output, { toolName: tool.name, input: use.input });
|
|
3758
4613
|
const scrubbed = this.opts.secretScrubber.scrub(text);
|
|
3759
|
-
const
|
|
4614
|
+
const withArtifact = await maybePersistLargeToolOutput(tool.name, scrubbed, budget);
|
|
4615
|
+
const { text: capped, newBudget } = this.serializer.enforceCap(withArtifact, budget);
|
|
3760
4616
|
this.opts.renderer?.writeToolResult(tool.name, capped, false);
|
|
3761
4617
|
return {
|
|
3762
4618
|
block: {
|
|
@@ -3781,38 +4637,27 @@ ${post.additionalContext}`;
|
|
|
3781
4637
|
tool.timeoutMs ?? this.iterationTimeoutMs,
|
|
3782
4638
|
this.maxToolTimeoutMs
|
|
3783
4639
|
);
|
|
3784
|
-
const
|
|
3785
|
-
const
|
|
3786
|
-
|
|
3787
|
-
let cleanupCalled = false;
|
|
3788
|
-
let caught = false;
|
|
4640
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
4641
|
+
const combined = AbortSignal.any([parentSignal, timeoutSignal]);
|
|
4642
|
+
let output;
|
|
3789
4643
|
try {
|
|
3790
|
-
|
|
3791
|
-
return await this.runStreamedTool(tool, input, ctx, combined, toolUseId);
|
|
3792
|
-
}
|
|
3793
|
-
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 });
|
|
3794
4645
|
} catch (err) {
|
|
3795
|
-
|
|
3796
|
-
if (combined.aborted && typeof tool.cleanup === "function") {
|
|
3797
|
-
cleanupCalled = true;
|
|
3798
|
-
try {
|
|
3799
|
-
await tool.cleanup(input, ctx);
|
|
3800
|
-
} catch {
|
|
3801
|
-
}
|
|
3802
|
-
}
|
|
4646
|
+
if (combined.aborted) await this.runToolCleanup(tool, input, ctx);
|
|
3803
4647
|
throw err;
|
|
3804
|
-
}
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
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 {
|
|
3816
4661
|
}
|
|
3817
4662
|
}
|
|
3818
4663
|
async runStreamedTool(tool, input, ctx, signal, toolUseId) {
|
|
@@ -3981,6 +4826,25 @@ function extractMalformedRaw(input) {
|
|
|
3981
4826
|
return String(value);
|
|
3982
4827
|
}
|
|
3983
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
|
+
}
|
|
3984
4848
|
|
|
3985
4849
|
// src/core/conversation-state.ts
|
|
3986
4850
|
var ConversationState = class {
|
|
@@ -4099,6 +4963,7 @@ var Context = class {
|
|
|
4099
4963
|
todos = [];
|
|
4100
4964
|
readFiles = /* @__PURE__ */ new Set();
|
|
4101
4965
|
fileMtimes = /* @__PURE__ */ new Map();
|
|
4966
|
+
contextEvidence = createContextEvidenceState();
|
|
4102
4967
|
systemPrompt;
|
|
4103
4968
|
provider;
|
|
4104
4969
|
session;
|
|
@@ -4230,11 +5095,11 @@ var Context = class {
|
|
|
4230
5095
|
* Returns the resolved absolute path.
|
|
4231
5096
|
*/
|
|
4232
5097
|
setWorkingDir(dir) {
|
|
4233
|
-
const resolved =
|
|
5098
|
+
const resolved = path6.isAbsolute(dir) ? path6.resolve(dir) : path6.resolve(this.projectRoot, dir);
|
|
4234
5099
|
if (!this.allowOutsideProjectRoot) {
|
|
4235
|
-
const root =
|
|
4236
|
-
const rel =
|
|
4237
|
-
if (rel.startsWith("..") ||
|
|
5100
|
+
const root = path6.resolve(this.projectRoot);
|
|
5101
|
+
const rel = path6.relative(root, resolved);
|
|
5102
|
+
if (rel.startsWith("..") || path6.isAbsolute(rel)) {
|
|
4238
5103
|
throw new Error(
|
|
4239
5104
|
`Working directory "${resolved}" is outside project root "${root}"`
|
|
4240
5105
|
);
|