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