@wrongstack/core 0.265.1 → 0.267.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-DrkBxszZ.d.ts → agent-bridge-STJ3JwwK.d.ts} +1 -1
- package/dist/{agent-subagent-runner-DM2pP-B6.d.ts → agent-subagent-runner-CzPGP3jA.d.ts} +25 -7
- package/dist/{brain-BXd_61kQ.d.ts → brain-Cdg77tVN.d.ts} +73 -1
- package/dist/{compactor-B8pOf45Y.d.ts → compactor-iMZ84CXq.d.ts} +19 -1
- package/dist/{config-BMCj_XDs.d.ts → config-Du3pYYln.d.ts} +54 -3
- package/dist/{context-MRk5PhNv.d.ts → context-dT5Ueund.d.ts} +65 -1
- package/dist/coordination/index.d.ts +17 -17
- package/dist/coordination/index.js +138 -114
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +25 -25
- package/dist/defaults/index.js +1729 -781
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +15 -15
- package/dist/execution/index.js +1119 -229
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/{goal-preamble-DvHDSKSe.d.ts → goal-preamble-SulMTowG.d.ts} +28 -11
- package/dist/{goal-store-DtLMySNb.d.ts → goal-store-CABDwdFE.d.ts} +1 -1
- package/dist/{index-CEDeNodM.d.ts → index-Bms0m4oy.d.ts} +5 -5
- package/dist/{index-B-ch8K9C.d.ts → index-DtCVWel4.d.ts} +8 -8
- package/dist/index-IEuxQd-E.d.ts +82 -0
- package/dist/index.d.ts +118 -45
- package/dist/index.js +3083 -1602
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +72 -1
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-2x4w6Jn9.d.ts → mcp-servers-C2cBTxUR.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +30 -1
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-DmJlKuNp.d.ts → models-registry-BqGZNJQ-.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-DyCkCZnU.d.ts → multi-agent-coordinator-B8R43uPz.d.ts} +1 -1
- package/dist/{null-fleet-bus-CG9QY2aP.d.ts → null-fleet-bus-CnXa5oTH.d.ts} +14 -9
- package/dist/observability/index.d.ts +2 -2
- package/dist/{parallel-eternal-engine-Jw9uhEoT.d.ts → parallel-eternal-engine-DdNnw9BQ.d.ts} +11 -9
- package/dist/{path-resolver-Dy2ej-gE.d.ts → path-resolver-COIMLCQL.d.ts} +3 -3
- package/dist/{permission-B9SB45lp.d.ts → permission-B75JAi3-.d.ts} +1 -1
- package/dist/{permission-policy-CkjSXabK.d.ts → permission-policy-DlR9eJAM.d.ts} +2 -2
- package/dist/{pipeline-DPDxH_7m.d.ts → pipeline-BfD2k1rT.d.ts} +2 -2
- package/dist/{plan-templates-CzD9GnAU.d.ts → plan-templates-DSIKCXZN.d.ts} +5 -5
- package/dist/{llm-selector-C0tfTCUe.d.ts → provider-model-resolve-BNRsNuJx.d.ts} +40 -3
- package/dist/{provider-runner-DMa70ODu.d.ts → provider-runner-CX7iIvox.d.ts} +3 -3
- package/dist/{retry-policy-CN0khdlj.d.ts → retry-policy-BilV1ujH.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +12 -12
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-B2yw84VT.d.ts → secret-vault-gkvEZZfE.d.ts} +2 -2
- package/dist/security/index.d.ts +5 -67
- package/dist/security/index.js +96 -76
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-CzHh_igB.d.ts → selector-Bc7eWtT3.d.ts} +1 -1
- package/dist/{session-event-bridge-BUI6Jf-4.d.ts → session-event-bridge-D-araDEz.d.ts} +1 -1
- package/dist/{session-reader-CMgdMSRP.d.ts → session-reader-D7Dapswh.d.ts} +1 -1
- package/dist/storage/index.d.ts +11 -11
- package/dist/storage/index.js +81 -84
- package/dist/storage/index.js.map +1 -1
- package/dist/tools/index.d.ts +4 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/types/index.d.ts +19 -19
- package/dist/types/index.js +1265 -400
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +454 -406
- package/dist/utils/index.js +2191 -1201
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
package/dist/execution/index.js
CHANGED
|
@@ -1,11 +1,81 @@
|
|
|
1
|
+
import * as path3 from 'path';
|
|
1
2
|
import { randomUUID, randomBytes, createHash } from 'crypto';
|
|
2
3
|
import * as fs from 'fs/promises';
|
|
3
|
-
import * as path2 from 'path';
|
|
4
4
|
import * as os from 'os';
|
|
5
5
|
import { execFile } from 'child_process';
|
|
6
6
|
import { promisify } from 'util';
|
|
7
7
|
import { EventEmitter } from 'events';
|
|
8
8
|
|
|
9
|
+
// src/utils/tool-wire-compact.ts
|
|
10
|
+
var TOOL_DESCRIPTION_MAX_CHARS = 640;
|
|
11
|
+
var SCHEMA_DESCRIPTION_MAX_CHARS = 180;
|
|
12
|
+
var compactCache = /* @__PURE__ */ new WeakMap();
|
|
13
|
+
function compactToolDefinitionForWire(tool, opts = {}) {
|
|
14
|
+
const useDefaultOptions = opts.descriptionMaxChars === void 0 && opts.schemaDescriptionMaxChars === void 0;
|
|
15
|
+
if (useDefaultOptions && typeof tool === "object" && tool !== null) {
|
|
16
|
+
const cached = compactCache.get(tool);
|
|
17
|
+
if (cached) return cached;
|
|
18
|
+
}
|
|
19
|
+
const compact = {
|
|
20
|
+
name: tool.name,
|
|
21
|
+
description: compactDescription(
|
|
22
|
+
tool.description ?? "",
|
|
23
|
+
opts.descriptionMaxChars ?? TOOL_DESCRIPTION_MAX_CHARS
|
|
24
|
+
),
|
|
25
|
+
inputSchema: compactSchemaDescriptions(
|
|
26
|
+
tool.inputSchema,
|
|
27
|
+
opts.schemaDescriptionMaxChars ?? SCHEMA_DESCRIPTION_MAX_CHARS
|
|
28
|
+
)
|
|
29
|
+
};
|
|
30
|
+
if (useDefaultOptions && typeof tool === "object" && tool !== null) {
|
|
31
|
+
compactCache.set(tool, compact);
|
|
32
|
+
}
|
|
33
|
+
return compact;
|
|
34
|
+
}
|
|
35
|
+
function compactSchemaDescriptions(schema, maxDescriptionChars = SCHEMA_DESCRIPTION_MAX_CHARS) {
|
|
36
|
+
const compact = compactSchemaNode(schema, maxDescriptionChars);
|
|
37
|
+
return isRecord(compact) ? compact : { type: "object", properties: {} };
|
|
38
|
+
}
|
|
39
|
+
function compactSchemaNode(node, maxDescriptionChars) {
|
|
40
|
+
if (Array.isArray(node)) {
|
|
41
|
+
return node.map((item) => compactSchemaNode(item, maxDescriptionChars));
|
|
42
|
+
}
|
|
43
|
+
if (!isRecord(node)) return node;
|
|
44
|
+
const out = {};
|
|
45
|
+
for (const [key, value] of Object.entries(node)) {
|
|
46
|
+
if (key === "description" && typeof value === "string") {
|
|
47
|
+
out[key] = compactDescription(value, maxDescriptionChars);
|
|
48
|
+
} else {
|
|
49
|
+
out[key] = compactSchemaNode(value, maxDescriptionChars);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
function compactDescription(text, maxChars) {
|
|
55
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
56
|
+
if (normalized.length <= maxChars) return normalized;
|
|
57
|
+
if (maxChars <= 20) return normalized.slice(0, maxChars);
|
|
58
|
+
const hardLimit = maxChars - 12;
|
|
59
|
+
const boundary = findSemanticBoundary(normalized, hardLimit);
|
|
60
|
+
const head = normalized.slice(0, boundary > 0 ? boundary : hardLimit).trimEnd();
|
|
61
|
+
return `${head} ...`;
|
|
62
|
+
}
|
|
63
|
+
function findSemanticBoundary(text, limit) {
|
|
64
|
+
const punctuation = Math.max(
|
|
65
|
+
text.lastIndexOf(". ", limit),
|
|
66
|
+
text.lastIndexOf("; ", limit),
|
|
67
|
+
text.lastIndexOf(": ", limit)
|
|
68
|
+
);
|
|
69
|
+
if (punctuation >= Math.floor(limit * 0.45)) return punctuation + 1;
|
|
70
|
+
const comma = text.lastIndexOf(", ", limit);
|
|
71
|
+
if (comma >= Math.floor(limit * 0.6)) return comma + 1;
|
|
72
|
+
const space = text.lastIndexOf(" ", limit);
|
|
73
|
+
return space >= Math.floor(limit * 0.6) ? space : limit;
|
|
74
|
+
}
|
|
75
|
+
function isRecord(value) {
|
|
76
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
77
|
+
}
|
|
78
|
+
|
|
9
79
|
// src/utils/token-estimate.ts
|
|
10
80
|
var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
|
|
11
81
|
var CALIBRATION_GLOBAL_KEY = "__global__";
|
|
@@ -73,7 +143,8 @@ function estimateMessageTokens(messages) {
|
|
|
73
143
|
function estimateToolDefTokens(tool) {
|
|
74
144
|
const cached = tool._estDefTokens;
|
|
75
145
|
if (typeof cached === "number" && cached > 0) return cached;
|
|
76
|
-
|
|
146
|
+
const compact = compactToolDefinitionForWire(tool);
|
|
147
|
+
return RoughTokenEstimate(tool.name) + RoughTokenEstimate(compact.description) + RoughTokenEstimate(JSON.stringify(compact.inputSchema));
|
|
77
148
|
}
|
|
78
149
|
function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
79
150
|
let messagesTokens = 0;
|
|
@@ -251,6 +322,79 @@ function isEmptyMessage(msg) {
|
|
|
251
322
|
if (typeof msg.content === "string") return msg.content.trim().length === 0;
|
|
252
323
|
return msg.content.length === 0;
|
|
253
324
|
}
|
|
325
|
+
var MAX_DIGEST_CHARS = 4e3;
|
|
326
|
+
function createContextEvidenceState() {
|
|
327
|
+
return {
|
|
328
|
+
sessionGoals: [],
|
|
329
|
+
implicitFacts: [],
|
|
330
|
+
activeErrors: [],
|
|
331
|
+
toolCalls: [],
|
|
332
|
+
fileGraph: {},
|
|
333
|
+
repeatedReads: [],
|
|
334
|
+
updatedAt: Date.now()
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function buildContextEvidenceDigest(ctx) {
|
|
338
|
+
const state = ensureEvidence(ctx);
|
|
339
|
+
const lines = [];
|
|
340
|
+
if (state.currentIntent?.text) {
|
|
341
|
+
lines.push(`intent: ${state.currentIntent.text}`);
|
|
342
|
+
}
|
|
343
|
+
const goals = state.sessionGoals.slice(-3);
|
|
344
|
+
if (goals.length > 0) {
|
|
345
|
+
lines.push("session_goals:");
|
|
346
|
+
for (const goal of goals) lines.push(`- ${goal}`);
|
|
347
|
+
}
|
|
348
|
+
const activeErrors = state.activeErrors.slice(-5);
|
|
349
|
+
if (activeErrors.length > 0) {
|
|
350
|
+
lines.push("active_errors:");
|
|
351
|
+
for (const err of activeErrors) lines.push(`- ${err}`);
|
|
352
|
+
}
|
|
353
|
+
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);
|
|
354
|
+
if (files.length > 0) {
|
|
355
|
+
lines.push("dependency_graph:");
|
|
356
|
+
for (const file of files) {
|
|
357
|
+
const actions = [
|
|
358
|
+
file.reads > 0 ? `read ${file.reads}x` : "",
|
|
359
|
+
file.writes > 0 ? `write ${file.writes}x` : ""
|
|
360
|
+
].filter(Boolean).join(", ");
|
|
361
|
+
const refs = file.referenced ? "; referenced by assistant" : "";
|
|
362
|
+
const via = file.lastToolUseId ? `; last via ${file.lastToolUseId}` : "";
|
|
363
|
+
lines.push(`- ${file.path} (${actions || "seen"}${refs}${via})`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const referenced = state.toolCalls.filter((tool) => tool.status === "referenced").slice(-10);
|
|
367
|
+
const recentSeen = state.toolCalls.filter((tool) => tool.status === "seen").slice(-5);
|
|
368
|
+
const trail = [...referenced, ...recentSeen];
|
|
369
|
+
if (trail.length > 0) {
|
|
370
|
+
lines.push("tool_trail:");
|
|
371
|
+
for (const tool of trail) {
|
|
372
|
+
const size = tool.outputTokens ? `; ~${tool.outputTokens} tokens` : "";
|
|
373
|
+
const filesText = tool.files.length > 0 ? `; files=${tool.files.slice(0, 4).join(", ")}` : "";
|
|
374
|
+
const symbolsText = tool.symbols.length > 0 ? `; symbols=${tool.symbols.slice(0, 4).join(", ")}` : "";
|
|
375
|
+
lines.push(
|
|
376
|
+
`- ${tool.toolUseId} ${tool.toolName} ${tool.status}: ${tool.summary}${filesText}${symbolsText}${size}`
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const facts = state.implicitFacts.slice(-8);
|
|
381
|
+
if (facts.length > 0) {
|
|
382
|
+
lines.push("implicit_facts:");
|
|
383
|
+
for (const fact of facts) lines.push(`- ${fact}`);
|
|
384
|
+
}
|
|
385
|
+
const digest = lines.join("\n");
|
|
386
|
+
if (digest.length <= MAX_DIGEST_CHARS) return digest;
|
|
387
|
+
return `${digest.slice(0, MAX_DIGEST_CHARS)}... [+${digest.length - MAX_DIGEST_CHARS} chars]`;
|
|
388
|
+
}
|
|
389
|
+
function repeatedReadPressure(ctx) {
|
|
390
|
+
return ensureEvidence(ctx).repeatedReads.reduce((max, item) => Math.max(max, item.count), 0);
|
|
391
|
+
}
|
|
392
|
+
function ensureEvidence(ctx) {
|
|
393
|
+
if (!ctx.contextEvidence) {
|
|
394
|
+
ctx.contextEvidence = createContextEvidenceState();
|
|
395
|
+
}
|
|
396
|
+
return ctx.contextEvidence;
|
|
397
|
+
}
|
|
254
398
|
|
|
255
399
|
// src/types/blocks.ts
|
|
256
400
|
function isTextBlock(b) {
|
|
@@ -297,25 +441,26 @@ function findPreserveStart(messages, preserveK) {
|
|
|
297
441
|
preserveStart = i;
|
|
298
442
|
}
|
|
299
443
|
}
|
|
300
|
-
let
|
|
301
|
-
let
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
444
|
+
let pairRepairIterations = 0;
|
|
445
|
+
let pairRepairInnerIterations = 0;
|
|
446
|
+
while (preserveStart > 0) {
|
|
447
|
+
pairRepairIterations++;
|
|
448
|
+
const first = messages[preserveStart];
|
|
449
|
+
const prev = messages[preserveStart - 1];
|
|
450
|
+
if (!first || !prev || first.role !== "user" || prev.role !== "assistant") break;
|
|
451
|
+
if (typeof first.content === "string" || typeof prev.content === "string") break;
|
|
452
|
+
const resultIds = /* @__PURE__ */ new Set();
|
|
453
|
+
for (const block of first.content) {
|
|
454
|
+
pairRepairInnerIterations++;
|
|
455
|
+
if (block.type === "tool_result") resultIds.add(block.tool_use_id);
|
|
456
|
+
}
|
|
457
|
+
if (resultIds.size === 0) break;
|
|
458
|
+
const hasMatchingUse = prev.content.some((block) => {
|
|
459
|
+
pairRepairInnerIterations++;
|
|
460
|
+
return block.type === "tool_use" && resultIds.has(block.id);
|
|
309
461
|
});
|
|
310
|
-
if (
|
|
311
|
-
|
|
312
|
-
if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => {
|
|
313
|
-
forwardWalkInnerIterations++;
|
|
314
|
-
return b.type === "tool_result";
|
|
315
|
-
})) {
|
|
316
|
-
preserveStart = i + 1;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
462
|
+
if (!hasMatchingUse) break;
|
|
463
|
+
preserveStart--;
|
|
319
464
|
}
|
|
320
465
|
if (compactionDebugEnabled()) {
|
|
321
466
|
console.log(
|
|
@@ -325,9 +470,9 @@ function findPreserveStart(messages, preserveK) {
|
|
|
325
470
|
messageCount: messages.length,
|
|
326
471
|
preserveK,
|
|
327
472
|
preserveStart,
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
473
|
+
pairRepairIterations,
|
|
474
|
+
pairRepairInnerIterations,
|
|
475
|
+
pairRepairInnerPerOuter: pairRepairIterations > 0 ? pairRepairInnerIterations / pairRepairIterations : 0
|
|
331
476
|
})
|
|
332
477
|
);
|
|
333
478
|
}
|
|
@@ -344,7 +489,8 @@ function eliseOldToolResults(messages, opts) {
|
|
|
344
489
|
if (!msg || !Array.isArray(msg.content)) continue;
|
|
345
490
|
for (const b of msg.content) {
|
|
346
491
|
fastPathInnerIterations++;
|
|
347
|
-
|
|
492
|
+
const oversized = b.type === "tool_result" && estimateToolResultTokens(b.content) >= opts.eliseThreshold || b.type === "tool_use" && estimateToolInputTokens(b.input) >= opts.eliseThreshold;
|
|
493
|
+
if (oversized) {
|
|
348
494
|
hasOversized = true;
|
|
349
495
|
break;
|
|
350
496
|
}
|
|
@@ -378,6 +524,13 @@ function eliseOldToolResults(messages, opts) {
|
|
|
378
524
|
}
|
|
379
525
|
const original = msg.content;
|
|
380
526
|
const newContent = original.map((b) => {
|
|
527
|
+
if (b.type === "tool_use") {
|
|
528
|
+
const tokens2 = estimateToolInputTokens(b.input);
|
|
529
|
+
if (tokens2 < opts.eliseThreshold) return b;
|
|
530
|
+
const elidedInput = summarizeToolUseInputElision(b, tokens2);
|
|
531
|
+
saved += Math.max(0, tokens2 - estimateToolInputTokens(elidedInput));
|
|
532
|
+
return { ...b, input: elidedInput };
|
|
533
|
+
}
|
|
381
534
|
if (b.type !== "tool_result") return b;
|
|
382
535
|
const tokens = estimateToolResultTokens(b.content);
|
|
383
536
|
if (tokens < opts.eliseThreshold) return b;
|
|
@@ -385,7 +538,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
385
538
|
const elided = {
|
|
386
539
|
type: "tool_result",
|
|
387
540
|
tool_use_id: b.tool_use_id,
|
|
388
|
-
content:
|
|
541
|
+
content: summarizeToolResultElision(b, tokens),
|
|
389
542
|
is_error: b.is_error
|
|
390
543
|
};
|
|
391
544
|
return elided;
|
|
@@ -425,6 +578,65 @@ function eliseOldToolResults(messages, opts) {
|
|
|
425
578
|
});
|
|
426
579
|
return { messages: changed ? next : messages, saved, changed };
|
|
427
580
|
}
|
|
581
|
+
function summarizeToolUseInputElision(block, tokens) {
|
|
582
|
+
const fields = {};
|
|
583
|
+
for (const [key, value] of Object.entries(block.input ?? {})) {
|
|
584
|
+
fields[key] = summarizeToolUseInputValue(value);
|
|
585
|
+
}
|
|
586
|
+
return {
|
|
587
|
+
__elided_tool_input: `~${tokens} tokens; original arguments are in the session log`,
|
|
588
|
+
tool: block.name,
|
|
589
|
+
fields
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function summarizeToolUseInputValue(value) {
|
|
593
|
+
if (value === null || value === void 0) return value;
|
|
594
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
595
|
+
if (typeof value === "string") {
|
|
596
|
+
const oneLine = value.replace(/\s+/g, " ").trim();
|
|
597
|
+
return oneLine.length <= 160 ? oneLine : `${oneLine.slice(0, 120)}...(${oneLine.length} chars)`;
|
|
598
|
+
}
|
|
599
|
+
if (Array.isArray(value)) {
|
|
600
|
+
return `[array:${value.length}]`;
|
|
601
|
+
}
|
|
602
|
+
if (typeof value === "object") {
|
|
603
|
+
const keys = Object.keys(value);
|
|
604
|
+
return `[object:${keys.slice(0, 8).join(",")}${keys.length > 8 ? ",..." : ""}]`;
|
|
605
|
+
}
|
|
606
|
+
return String(value);
|
|
607
|
+
}
|
|
608
|
+
function summarizeToolResultElision(block, tokens) {
|
|
609
|
+
const parts = [`elided: ~${tokens} tokens`];
|
|
610
|
+
if (block.name) parts.push(`tool=${block.name}`);
|
|
611
|
+
const files = extractPathHints(block.content).slice(0, 5);
|
|
612
|
+
if (files.length > 0) parts.push(`files=${files.join(", ")}`);
|
|
613
|
+
const error = firstErrorLine(block.content);
|
|
614
|
+
if (error) parts.push(`error=${error}`);
|
|
615
|
+
return `[${parts.join("; ")}]`;
|
|
616
|
+
}
|
|
617
|
+
function extractPathHints(content) {
|
|
618
|
+
const text = typeof content === "string" ? content : JSON.stringify(content);
|
|
619
|
+
const out = /* @__PURE__ */ new Set();
|
|
620
|
+
const re = /(?:(?:[A-Za-z]:)?[./\\]?[\w@.-]+(?:[\\/][\w@(). -]+)+\.[A-Za-z0-9]{1,12})/g;
|
|
621
|
+
for (const match of text.matchAll(re)) {
|
|
622
|
+
const clean = match[0]?.replace(/\\/g, "/").replace(/^["'`]+|["'`),;:]+$/g, "");
|
|
623
|
+
if (clean && clean.length <= 220) out.add(clean);
|
|
624
|
+
if (out.size >= 5) break;
|
|
625
|
+
}
|
|
626
|
+
return [...out];
|
|
627
|
+
}
|
|
628
|
+
function firstErrorLine(content) {
|
|
629
|
+
const text = typeof content === "string" ? content : JSON.stringify(content);
|
|
630
|
+
for (const line of text.split(/\r?\n/)) {
|
|
631
|
+
if (!/\b(error|exception|failed|failure|fatal|panic|timeout|denied|enoent|eacces|eperm)\b/i.test(
|
|
632
|
+
line
|
|
633
|
+
))
|
|
634
|
+
continue;
|
|
635
|
+
const trimmed = line.replace(/\s+/g, " ").trim();
|
|
636
|
+
if (trimmed) return trimmed.slice(0, 180);
|
|
637
|
+
}
|
|
638
|
+
return void 0;
|
|
639
|
+
}
|
|
428
640
|
function buildLosslessDigest(messages) {
|
|
429
641
|
const lines = [];
|
|
430
642
|
for (const m of messages) {
|
|
@@ -535,15 +747,15 @@ function buildSmartDigest(messages) {
|
|
|
535
747
|
lines.push(`[${m.role}]: ${display}${marker}`);
|
|
536
748
|
}
|
|
537
749
|
if (noiseCount > 0) {
|
|
538
|
-
lines.push(
|
|
750
|
+
lines.push(
|
|
751
|
+
`[system]: ${noiseCount} low-importance turn(s) collapsed (repeated failures / pure tool I/O)`
|
|
752
|
+
);
|
|
539
753
|
}
|
|
540
754
|
return lines.join("\n");
|
|
541
755
|
}
|
|
542
756
|
function countToolBlocks(m) {
|
|
543
757
|
if (typeof m.content === "string") return 0;
|
|
544
|
-
return m.content.filter(
|
|
545
|
-
(b) => b.type === "tool_use" || b.type === "tool_result"
|
|
546
|
-
).length;
|
|
758
|
+
return m.content.filter((b) => b.type === "tool_use" || b.type === "tool_result").length;
|
|
547
759
|
}
|
|
548
760
|
function firstSentence(text) {
|
|
549
761
|
const trimmed = text.trim();
|
|
@@ -612,10 +824,12 @@ var HybridCompactor = class {
|
|
|
612
824
|
if (elide.changed) ctx.state.replaceMessages(elide.messages);
|
|
613
825
|
if (elide.saved > 0) reductions.push({ phase: "elision", saved: elide.saved });
|
|
614
826
|
let collapsedDigest;
|
|
827
|
+
let evidenceDigest;
|
|
615
828
|
if (opts.aggressive) {
|
|
616
829
|
const phase2 = this.collapseAncientTurns(ctx, preserveK);
|
|
617
830
|
if (phase2.saved > 0) reductions.push({ phase: "summary", saved: phase2.saved });
|
|
618
831
|
collapsedDigest = phase2.digest;
|
|
832
|
+
evidenceDigest = phase2.evidenceDigest;
|
|
619
833
|
}
|
|
620
834
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
621
835
|
if (repaired.report.changed) {
|
|
@@ -623,6 +837,11 @@ var HybridCompactor = class {
|
|
|
623
837
|
}
|
|
624
838
|
const afterTokens = estimateMessages(ctx.messages);
|
|
625
839
|
const afterFull = this.estimateFullRequest(ctx);
|
|
840
|
+
const quality = checkCompactionQuality(ctx, {
|
|
841
|
+
collapsedDigest,
|
|
842
|
+
evidenceDigest,
|
|
843
|
+
reduced: beforeTokens > afterTokens || beforeFull > afterFull
|
|
844
|
+
});
|
|
626
845
|
return {
|
|
627
846
|
before: beforeTokens,
|
|
628
847
|
after: afterTokens,
|
|
@@ -630,6 +849,8 @@ var HybridCompactor = class {
|
|
|
630
849
|
fullRequestTokensAfter: afterFull,
|
|
631
850
|
reductions,
|
|
632
851
|
collapsedDigest,
|
|
852
|
+
evidenceDigest,
|
|
853
|
+
quality,
|
|
633
854
|
repaired: repaired.report.changed ? {
|
|
634
855
|
removedToolUses: repaired.report.removedToolUses,
|
|
635
856
|
removedToolResults: repaired.report.removedToolResults,
|
|
@@ -671,7 +892,13 @@ var HybridCompactor = class {
|
|
|
671
892
|
if (boundary <= 0) return { saved: 0 };
|
|
672
893
|
const removed = messages.slice(0, boundary);
|
|
673
894
|
const removedTokens = estimateMessages(removed);
|
|
674
|
-
const
|
|
895
|
+
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)`;
|
|
896
|
+
const evidenceDigest = buildContextEvidenceDigest(ctx);
|
|
897
|
+
const digest = evidenceDigest ? `[context_state]
|
|
898
|
+
${evidenceDigest}
|
|
899
|
+
|
|
900
|
+
[prior_history]
|
|
901
|
+
${historyDigest}` : historyDigest;
|
|
675
902
|
const summaryMsg = {
|
|
676
903
|
role: "system",
|
|
677
904
|
content: `[prior_turns_digest: ${digest}]`
|
|
@@ -680,10 +907,29 @@ var HybridCompactor = class {
|
|
|
680
907
|
ctx.state.replaceMessages([summaryMsg, ...tail]);
|
|
681
908
|
return {
|
|
682
909
|
saved: Math.max(0, removedTokens - estimateMessages([summaryMsg])),
|
|
683
|
-
digest
|
|
910
|
+
digest,
|
|
911
|
+
evidenceDigest: evidenceDigest || void 0
|
|
684
912
|
};
|
|
685
913
|
}
|
|
686
914
|
};
|
|
915
|
+
function checkCompactionQuality(ctx, opts) {
|
|
916
|
+
const evidence = ctx.contextEvidence;
|
|
917
|
+
const digest = `${opts.collapsedDigest ?? ""}
|
|
918
|
+
${opts.evidenceDigest ?? ""}`;
|
|
919
|
+
const hasIntent = Boolean(evidence?.currentIntent?.text || /\b(intent|goal|session_goals)\b/i.test(digest));
|
|
920
|
+
const hasPathTrail = Boolean(
|
|
921
|
+
Object.keys(evidence?.fileGraph ?? {}).length > 0 || (evidence?.toolCalls.length ?? 0) > 0 || /\b(dependency_graph|tool_trail|files=)\b/i.test(digest)
|
|
922
|
+
);
|
|
923
|
+
const issues = [];
|
|
924
|
+
if (opts.reduced && !hasIntent) issues.push("missing intent anchor");
|
|
925
|
+
if (opts.reduced && !hasPathTrail) issues.push("missing tool/path trail");
|
|
926
|
+
return {
|
|
927
|
+
ok: issues.length === 0,
|
|
928
|
+
hasIntent,
|
|
929
|
+
hasPathTrail,
|
|
930
|
+
issues
|
|
931
|
+
};
|
|
932
|
+
}
|
|
687
933
|
function readContextWindowPolicy(ctx) {
|
|
688
934
|
const policy = ctx.meta?.["contextWindowPolicy"];
|
|
689
935
|
if (!policy || typeof policy !== "object") return null;
|
|
@@ -1042,10 +1288,10 @@ var SelectiveCompactor = class {
|
|
|
1042
1288
|
this.maxContext = opts.maxContext ?? 128e3;
|
|
1043
1289
|
this.preserveK = opts.preserveK ?? 4;
|
|
1044
1290
|
this.eliseThreshold = opts.eliseThreshold ?? 500;
|
|
1045
|
-
this.summarizerModel = opts.summarizerModel ?? opts.selectorModel
|
|
1046
|
-
if (this.summarizerModel ===
|
|
1291
|
+
this.summarizerModel = opts.summarizerModel ?? opts.selectorModel;
|
|
1292
|
+
if (this.summarizerModel === void 0 && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
|
|
1047
1293
|
console.warn(
|
|
1048
|
-
"[SelectiveCompactor] summarizerModel not set \u2014 will
|
|
1294
|
+
"[SelectiveCompactor] summarizerModel not set \u2014 will fall back to ctx.model at summarize time. Set `summarizerModel` explicitly to silence this warning."
|
|
1049
1295
|
);
|
|
1050
1296
|
}
|
|
1051
1297
|
this.summarizerPrompt = opts.summarizerPrompt ?? "You are a context summarizer. Given a list of messages, produce a concise summary that preserves all factual information, decisions, file changes, and state changes. Do not add commentary or opinions.";
|
|
@@ -1153,7 +1399,7 @@ var SelectiveCompactor = class {
|
|
|
1153
1399
|
Summarize the following message range:`;
|
|
1154
1400
|
const body = messages.map((m, i) => `[${i}] ${m.role}: ${this.messagePreview(m)}`).join("\n");
|
|
1155
1401
|
const req = {
|
|
1156
|
-
model: this.summarizerModel,
|
|
1402
|
+
model: this.summarizerModel ?? ctx.model,
|
|
1157
1403
|
system: [{ type: "text", text: systemText }],
|
|
1158
1404
|
messages: [{ role: "user", content: body }],
|
|
1159
1405
|
maxTokens: 512
|
|
@@ -1220,27 +1466,16 @@ Summarize the following message range:`;
|
|
|
1220
1466
|
if (typeof m.content === "string") return m.content.trim().length > 0;
|
|
1221
1467
|
return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
|
|
1222
1468
|
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Estimate message-array tokens via the shared `estimateMessages` primitive
|
|
1471
|
+
* so SelectiveCompactor's before/after/load figures agree with the
|
|
1472
|
+
* middleware threshold math and the other compactors. Previously this used a
|
|
1473
|
+
* private `ceil(len/3.5)` walk that diverged from the calibrated shared
|
|
1474
|
+
* estimator, causing the selective `load`/`targetBudget` comparison to mix
|
|
1475
|
+
* two incompatible token scales.
|
|
1476
|
+
*/
|
|
1223
1477
|
estimateTokens(messages) {
|
|
1224
|
-
|
|
1225
|
-
for (const m of messages) {
|
|
1226
|
-
if (typeof m.content === "string") {
|
|
1227
|
-
total += this.roughTokenEstimate(m.content);
|
|
1228
|
-
} else {
|
|
1229
|
-
for (const b of m.content) {
|
|
1230
|
-
if (b.type === "text") total += this.roughTokenEstimate(b.text);
|
|
1231
|
-
else if (b.type === "tool_use") total += this.roughTokenEstimate(JSON.stringify(b.input));
|
|
1232
|
-
else if (b.type === "tool_result") {
|
|
1233
|
-
total += this.roughTokenEstimate(
|
|
1234
|
-
typeof b.content === "string" ? b.content : JSON.stringify(b.content)
|
|
1235
|
-
);
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
return total;
|
|
1241
|
-
}
|
|
1242
|
-
roughTokenEstimate(text) {
|
|
1243
|
-
return Math.max(1, Math.ceil(text.length / 3.5));
|
|
1478
|
+
return estimateMessages(messages);
|
|
1244
1479
|
}
|
|
1245
1480
|
};
|
|
1246
1481
|
|
|
@@ -1312,10 +1547,19 @@ function readPolicy(ctx) {
|
|
|
1312
1547
|
if (typeof candidate.preserveK !== "number" || !candidate.thresholds) return null;
|
|
1313
1548
|
return candidate;
|
|
1314
1549
|
}
|
|
1550
|
+
|
|
1551
|
+
// src/utils/assert-never.ts
|
|
1552
|
+
function assertNever(x, message) {
|
|
1553
|
+
const err = new Error(
|
|
1554
|
+
`Unhandled case: ${JSON.stringify(x)}`
|
|
1555
|
+
);
|
|
1556
|
+
err.name = "AssertNeverError";
|
|
1557
|
+
throw err;
|
|
1558
|
+
}
|
|
1315
1559
|
async function atomicWrite(targetPath, content, opts = {}) {
|
|
1316
|
-
const dir =
|
|
1560
|
+
const dir = path3.dirname(targetPath);
|
|
1317
1561
|
await fs.mkdir(dir, { recursive: true });
|
|
1318
|
-
const tmp =
|
|
1562
|
+
const tmp = path3.join(dir, `.${path3.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
1319
1563
|
try {
|
|
1320
1564
|
if (typeof content === "string") {
|
|
1321
1565
|
await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
@@ -1368,7 +1612,7 @@ async function renameWithRetry(from, to) {
|
|
|
1368
1612
|
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
1369
1613
|
throw err;
|
|
1370
1614
|
}
|
|
1371
|
-
await new Promise((
|
|
1615
|
+
await new Promise((resolve3) => setTimeout(resolve3, delays[i]));
|
|
1372
1616
|
}
|
|
1373
1617
|
}
|
|
1374
1618
|
throw lastErr;
|
|
@@ -1379,137 +1623,17 @@ function toErrorMessage(err) {
|
|
|
1379
1623
|
return err instanceof Error ? err.message : String(err);
|
|
1380
1624
|
}
|
|
1381
1625
|
|
|
1382
|
-
// src/utils/string.ts
|
|
1383
|
-
function truncate(s, max) {
|
|
1384
|
-
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
1385
|
-
}
|
|
1386
|
-
function projectHash(absRoot) {
|
|
1387
|
-
return createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 12);
|
|
1388
|
-
}
|
|
1389
|
-
function projectSlug(absRoot) {
|
|
1390
|
-
const base = slugify(path2.basename(absRoot));
|
|
1391
|
-
const hash = createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 6);
|
|
1392
|
-
return `${base}-${hash}`;
|
|
1393
|
-
}
|
|
1394
|
-
function slugify(name) {
|
|
1395
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
|
|
1396
|
-
}
|
|
1397
|
-
function wstackGlobalRoot() {
|
|
1398
|
-
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
1399
|
-
if (fromEnv && fromEnv.trim().length > 0) return path2.resolve(fromEnv);
|
|
1400
|
-
return path2.join(os.homedir(), ".wrongstack");
|
|
1401
|
-
}
|
|
1402
|
-
function resolveWstackPaths(opts) {
|
|
1403
|
-
const globalRoot = opts.globalRoot ?? (opts.userHome ? path2.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
1404
|
-
const hash = projectHash(opts.projectRoot);
|
|
1405
|
-
const slug = projectSlug(opts.projectRoot);
|
|
1406
|
-
const projectDir = path2.join(globalRoot, "projects", slug);
|
|
1407
|
-
return {
|
|
1408
|
-
globalRoot,
|
|
1409
|
-
configDir: globalRoot,
|
|
1410
|
-
globalConfig: path2.join(globalRoot, "config.json"),
|
|
1411
|
-
secretsKey: path2.join(globalRoot, ".key"),
|
|
1412
|
-
globalMemory: path2.join(globalRoot, "memory.md"),
|
|
1413
|
-
globalSkills: path2.join(globalRoot, "skills"),
|
|
1414
|
-
globalPrompts: path2.join(globalRoot, "prompts"),
|
|
1415
|
-
cacheDir: path2.join(globalRoot, "cache"),
|
|
1416
|
-
modelsCache: path2.join(globalRoot, "cache", "models.dev.json"),
|
|
1417
|
-
modelsOverlayCache: path2.join(globalRoot, "cache", "models-overlay.json"),
|
|
1418
|
-
historyFile: path2.join(globalRoot, "history"),
|
|
1419
|
-
logFile: path2.join(globalRoot, "logs", "wrongstack.log"),
|
|
1420
|
-
projectDir,
|
|
1421
|
-
projectCodebaseIndex: path2.join(projectDir, "codebase-index"),
|
|
1422
|
-
projectMemory: path2.join(projectDir, "memory.md"),
|
|
1423
|
-
projectSessions: path2.join(projectDir, "sessions"),
|
|
1424
|
-
projectTrust: path2.join(projectDir, "trust.json"),
|
|
1425
|
-
projectMeta: path2.join(projectDir, "meta.json"),
|
|
1426
|
-
projectLocalConfig: path2.join(projectDir, "config.local.json"),
|
|
1427
|
-
inProjectConfig: path2.join(opts.projectRoot, ".wrongstack", "config.json"),
|
|
1428
|
-
inProjectAgentsFile: path2.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
|
|
1429
|
-
inProjectSkills: path2.join(opts.projectRoot, ".wrongstack", "skills"),
|
|
1430
|
-
inProjectWorktrees: path2.join(opts.projectRoot, ".wrongstack", "worktrees"),
|
|
1431
|
-
projectHash: hash,
|
|
1432
|
-
projectSlug: slug,
|
|
1433
|
-
projectGoal: path2.join(projectDir, "goal.json"),
|
|
1434
|
-
projectSpecs: path2.join(projectDir, "specs"),
|
|
1435
|
-
projectTaskGraphs: path2.join(projectDir, "task-graphs"),
|
|
1436
|
-
projectSddSession: path2.join(projectDir, "sdd-session.json"),
|
|
1437
|
-
projectPlan: path2.join(projectDir, "plan.json"),
|
|
1438
|
-
projectAutophase: path2.join(projectDir, "autophase"),
|
|
1439
|
-
syncConfig: path2.join(globalRoot, "sync.json"),
|
|
1440
|
-
projectStatus: (projectHash2) => path2.join(globalRoot, "projects", projectHash2, "status.json")
|
|
1441
|
-
};
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
// src/utils/sleep.ts
|
|
1445
|
-
function sleep(ms) {
|
|
1446
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
// src/utils/assert-never.ts
|
|
1450
|
-
function assertNever(x, message) {
|
|
1451
|
-
const err = new Error(
|
|
1452
|
-
`Unhandled case: ${JSON.stringify(x)}`
|
|
1453
|
-
);
|
|
1454
|
-
err.name = "AssertNeverError";
|
|
1455
|
-
throw err;
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
// src/utils/tool-output-serializer.ts
|
|
1459
|
-
function createToolOutputSerializer(opts = {}) {
|
|
1460
|
-
const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
|
|
1461
|
-
function serialize(value) {
|
|
1462
|
-
if (typeof value === "string") return value;
|
|
1463
|
-
if (value === null || value === void 0) return "";
|
|
1464
|
-
if (typeof value === "object") {
|
|
1465
|
-
if (Array.isArray(value)) return value.map(serialize).join("\n");
|
|
1466
|
-
if ("text" in value) {
|
|
1467
|
-
const t = value.text;
|
|
1468
|
-
return typeof t === "string" ? t : JSON.stringify(value, null, 2);
|
|
1469
|
-
}
|
|
1470
|
-
try {
|
|
1471
|
-
return JSON.stringify(value, null, 2);
|
|
1472
|
-
} catch {
|
|
1473
|
-
return String(value);
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
return String(value);
|
|
1477
|
-
}
|
|
1478
|
-
function enforceCap(text, remainingBudget) {
|
|
1479
|
-
if (remainingBudget <= 0) {
|
|
1480
|
-
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
1481
|
-
}
|
|
1482
|
-
const textBytes = Buffer.byteLength(text, "utf8");
|
|
1483
|
-
if (textBytes <= remainingBudget) {
|
|
1484
|
-
return { text, newBudget: remainingBudget - textBytes };
|
|
1485
|
-
}
|
|
1486
|
-
const marker = `
|
|
1487
|
-
\u2026[truncated ${textBytes - remainingBudget} bytes]\u2026
|
|
1488
|
-
`;
|
|
1489
|
-
const markerBytes = Buffer.byteLength(marker, "utf8");
|
|
1490
|
-
const available = remainingBudget - markerBytes;
|
|
1491
|
-
if (available <= 0) {
|
|
1492
|
-
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
1493
|
-
}
|
|
1494
|
-
const half = Math.floor(available / 2);
|
|
1495
|
-
const first = text.slice(0, half);
|
|
1496
|
-
const second = text.slice(text.length - half);
|
|
1497
|
-
return { text: `${first}${marker}${second}`, newBudget: 0 };
|
|
1498
|
-
}
|
|
1499
|
-
return { serialize, enforceCap, capBytes };
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
1626
|
// src/utils/json-schema-validate.ts
|
|
1503
1627
|
function validateAgainstSchema(value, schema) {
|
|
1504
1628
|
const errors = [];
|
|
1505
1629
|
walk(value, schema, "", errors);
|
|
1506
1630
|
return { ok: errors.length === 0, errors };
|
|
1507
1631
|
}
|
|
1508
|
-
function walk(value, schema,
|
|
1632
|
+
function walk(value, schema, path6, errors) {
|
|
1509
1633
|
if (schema.enum !== void 0) {
|
|
1510
1634
|
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
1511
1635
|
errors.push({
|
|
1512
|
-
path:
|
|
1636
|
+
path: path6 || "<root>",
|
|
1513
1637
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
1514
1638
|
});
|
|
1515
1639
|
return;
|
|
@@ -1518,7 +1642,7 @@ function walk(value, schema, path4, errors) {
|
|
|
1518
1642
|
if (typeof schema.type === "string") {
|
|
1519
1643
|
if (!checkType(value, schema.type)) {
|
|
1520
1644
|
errors.push({
|
|
1521
|
-
path:
|
|
1645
|
+
path: path6 || "<root>",
|
|
1522
1646
|
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
1523
1647
|
});
|
|
1524
1648
|
return;
|
|
@@ -1528,20 +1652,20 @@ function walk(value, schema, path4, errors) {
|
|
|
1528
1652
|
const obj = value;
|
|
1529
1653
|
for (const req of schema.required ?? []) {
|
|
1530
1654
|
if (!(req in obj)) {
|
|
1531
|
-
errors.push({ path: joinPath(
|
|
1655
|
+
errors.push({ path: joinPath(path6, req), message: "required property missing" });
|
|
1532
1656
|
}
|
|
1533
1657
|
}
|
|
1534
1658
|
if (schema.properties) {
|
|
1535
1659
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
1536
1660
|
if (key in obj) {
|
|
1537
|
-
walk(obj[key], subSchema, joinPath(
|
|
1661
|
+
walk(obj[key], subSchema, joinPath(path6, key), errors);
|
|
1538
1662
|
}
|
|
1539
1663
|
}
|
|
1540
1664
|
}
|
|
1541
1665
|
}
|
|
1542
1666
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
1543
1667
|
for (let i = 0; i < value.length; i++) {
|
|
1544
|
-
walk(value[i], schema.items, `${
|
|
1668
|
+
walk(value[i], schema.items, `${path6}[${i}]`, errors);
|
|
1545
1669
|
}
|
|
1546
1670
|
}
|
|
1547
1671
|
}
|
|
@@ -1631,6 +1755,714 @@ function compileUserRegex(pattern, flags) {
|
|
|
1631
1755
|
}
|
|
1632
1756
|
}
|
|
1633
1757
|
|
|
1758
|
+
// src/utils/sleep.ts
|
|
1759
|
+
function sleep(ms) {
|
|
1760
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
// src/utils/string.ts
|
|
1764
|
+
function truncate(s, max) {
|
|
1765
|
+
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
// src/utils/tool-output-serializer.ts
|
|
1769
|
+
var DEFAULT_LIST_LIMIT = 500;
|
|
1770
|
+
var LOG_ENTRY_LIMIT = 200;
|
|
1771
|
+
var INLINE_LIMIT = 240;
|
|
1772
|
+
var GREP_FILE_LIMIT = 80;
|
|
1773
|
+
var GREP_MATCHES_PER_FILE = 3;
|
|
1774
|
+
var DIFF_INLINE_LINE_LIMIT = 260;
|
|
1775
|
+
var DIFF_HUNK_LIMIT = 8;
|
|
1776
|
+
var DIFF_HUNK_CONTEXT = 14;
|
|
1777
|
+
function createToolOutputSerializer(opts = {}) {
|
|
1778
|
+
const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
|
|
1779
|
+
function serialize(value, context = {}) {
|
|
1780
|
+
if (typeof value === "string") return value;
|
|
1781
|
+
if (value === null || value === void 0) return "";
|
|
1782
|
+
if (typeof value === "object") {
|
|
1783
|
+
if (Array.isArray(value)) return value.map((item) => serialize(item)).join("\n");
|
|
1784
|
+
if (context.toolName) {
|
|
1785
|
+
const compact = renderToolObject(context.toolName, value, context.input);
|
|
1786
|
+
if (compact !== void 0) return compact;
|
|
1787
|
+
return renderGenericToolObject(context.toolName, value);
|
|
1788
|
+
}
|
|
1789
|
+
if ("text" in value) {
|
|
1790
|
+
const t = value.text;
|
|
1791
|
+
return typeof t === "string" ? t : JSON.stringify(value, null, 2);
|
|
1792
|
+
}
|
|
1793
|
+
try {
|
|
1794
|
+
return JSON.stringify(value, null, 2);
|
|
1795
|
+
} catch {
|
|
1796
|
+
return String(value);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
return String(value);
|
|
1800
|
+
}
|
|
1801
|
+
function enforceCap(text, remainingBudget) {
|
|
1802
|
+
if (remainingBudget <= 0) {
|
|
1803
|
+
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
1804
|
+
}
|
|
1805
|
+
const textBytes = Buffer.byteLength(text, "utf8");
|
|
1806
|
+
if (textBytes <= remainingBudget) {
|
|
1807
|
+
return { text, newBudget: remainingBudget - textBytes };
|
|
1808
|
+
}
|
|
1809
|
+
const marker = `
|
|
1810
|
+
\u2026[truncated ${textBytes - remainingBudget} bytes]\u2026
|
|
1811
|
+
`;
|
|
1812
|
+
const markerBytes = Buffer.byteLength(marker, "utf8");
|
|
1813
|
+
const available = remainingBudget - markerBytes;
|
|
1814
|
+
if (available <= 0) {
|
|
1815
|
+
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
1816
|
+
}
|
|
1817
|
+
const half = Math.floor(available / 2);
|
|
1818
|
+
const first = text.slice(0, half);
|
|
1819
|
+
const second = text.slice(text.length - half);
|
|
1820
|
+
return { text: `${first}${marker}${second}`, newBudget: 0 };
|
|
1821
|
+
}
|
|
1822
|
+
return { serialize, enforceCap, capBytes };
|
|
1823
|
+
}
|
|
1824
|
+
function renderToolObject(toolName, obj, input) {
|
|
1825
|
+
if (toolName === "read" && typeof obj["text"] === "string") {
|
|
1826
|
+
return joinSections([
|
|
1827
|
+
renderHeader(
|
|
1828
|
+
`read: ${stringFromInput(input, "path") ?? stringField(obj, "path") ?? "<unknown>"}`,
|
|
1829
|
+
{
|
|
1830
|
+
offset: numberFromInput(input, "offset"),
|
|
1831
|
+
limit: numberFromInput(input, "limit"),
|
|
1832
|
+
total_lines: obj["total_lines"],
|
|
1833
|
+
encoding: obj["encoding"],
|
|
1834
|
+
truncated: obj["truncated"],
|
|
1835
|
+
cached: obj["cached"],
|
|
1836
|
+
note: obj["note"]
|
|
1837
|
+
}
|
|
1838
|
+
),
|
|
1839
|
+
obj["text"]
|
|
1840
|
+
]);
|
|
1841
|
+
}
|
|
1842
|
+
if (toolName === "grep" && Array.isArray(obj["matches"])) {
|
|
1843
|
+
const matches = stringArrayField(obj, "matches");
|
|
1844
|
+
return joinSections([
|
|
1845
|
+
renderHeader(`grep: ${stringFromInput(input, "pattern") ?? "<pattern>"}`, {
|
|
1846
|
+
path: stringFromInput(input, "path"),
|
|
1847
|
+
glob: stringFromInput(input, "glob"),
|
|
1848
|
+
mode: stringFromInput(input, "output_mode"),
|
|
1849
|
+
count: obj["count"],
|
|
1850
|
+
shown: matches.length,
|
|
1851
|
+
truncated: obj["truncated"],
|
|
1852
|
+
used: obj["used"]
|
|
1853
|
+
}),
|
|
1854
|
+
renderGrepMatches(matches, stringFromInput(input, "output_mode"))
|
|
1855
|
+
]);
|
|
1856
|
+
}
|
|
1857
|
+
if (toolName === "patch" && Array.isArray(obj["files"])) {
|
|
1858
|
+
const files = stringArrayField(obj, "files");
|
|
1859
|
+
return joinSections([
|
|
1860
|
+
renderHeader("patch", {
|
|
1861
|
+
applied: obj["applied"],
|
|
1862
|
+
rejected: obj["rejected"],
|
|
1863
|
+
files: files.length,
|
|
1864
|
+
dry_run: obj["dry_run"]
|
|
1865
|
+
}),
|
|
1866
|
+
typeof obj["message"] === "string" ? `message:
|
|
1867
|
+
${obj["message"]}` : void 0,
|
|
1868
|
+
files.length > 0 ? `files:
|
|
1869
|
+
${renderStringList(files)}` : void 0
|
|
1870
|
+
]);
|
|
1871
|
+
}
|
|
1872
|
+
if (toolName === "glob" && Array.isArray(obj["files"])) {
|
|
1873
|
+
const files = stringArrayField(obj, "files");
|
|
1874
|
+
return joinSections([
|
|
1875
|
+
renderHeader(
|
|
1876
|
+
`${toolName}: ${stringFromInput(input, "pattern") ?? stringFromInput(input, "files") ?? stringFromInput(input, "path") ?? ""}`.trim(),
|
|
1877
|
+
{
|
|
1878
|
+
path: stringFromInput(input, "path"),
|
|
1879
|
+
files: files.length,
|
|
1880
|
+
truncated: obj["truncated"]
|
|
1881
|
+
}
|
|
1882
|
+
),
|
|
1883
|
+
renderStringList(files, "(no files)")
|
|
1884
|
+
]);
|
|
1885
|
+
}
|
|
1886
|
+
if (toolName === "tree" && typeof obj["tree"] === "string") {
|
|
1887
|
+
return joinSections([
|
|
1888
|
+
renderHeader(
|
|
1889
|
+
`tree: ${stringField(obj, "path") ?? stringFromInput(input, "path") ?? "<cwd>"}`,
|
|
1890
|
+
{
|
|
1891
|
+
total_files: obj["total_files"],
|
|
1892
|
+
total_dirs: obj["total_dirs"],
|
|
1893
|
+
truncated: obj["truncated"]
|
|
1894
|
+
}
|
|
1895
|
+
),
|
|
1896
|
+
obj["tree"]
|
|
1897
|
+
]);
|
|
1898
|
+
}
|
|
1899
|
+
if (toolName === "fetch" && typeof obj["content"] === "string") {
|
|
1900
|
+
return joinSections([
|
|
1901
|
+
renderHeader(
|
|
1902
|
+
`fetch: ${stringField(obj, "url") ?? stringFromInput(input, "url") ?? "<url>"}`,
|
|
1903
|
+
{
|
|
1904
|
+
status: obj["status"],
|
|
1905
|
+
content_type: obj["content_type"]
|
|
1906
|
+
}
|
|
1907
|
+
),
|
|
1908
|
+
obj["content"]
|
|
1909
|
+
]);
|
|
1910
|
+
}
|
|
1911
|
+
if (toolName === "replace" && Array.isArray(obj["results"])) {
|
|
1912
|
+
const results = obj["results"].filter(isRecord2);
|
|
1913
|
+
const sections = [
|
|
1914
|
+
renderHeader("replace", {
|
|
1915
|
+
files_modified: obj["files_modified"],
|
|
1916
|
+
total_replacements: obj["total_replacements"],
|
|
1917
|
+
dry_run: obj["dry_run"]
|
|
1918
|
+
})
|
|
1919
|
+
];
|
|
1920
|
+
for (const r of results.slice(0, DEFAULT_LIST_LIMIT)) {
|
|
1921
|
+
sections.push(
|
|
1922
|
+
joinSections([
|
|
1923
|
+
renderHeader(`file: ${stringField(r, "path") ?? "<unknown>"}`, {
|
|
1924
|
+
replacements: r["replacements"]
|
|
1925
|
+
}),
|
|
1926
|
+
typeof r["diff"] === "string" ? r["diff"] : void 0
|
|
1927
|
+
])
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
if (results.length > DEFAULT_LIST_LIMIT) {
|
|
1931
|
+
sections.push(`[serializer omitted ${results.length - DEFAULT_LIST_LIMIT} result item(s)]`);
|
|
1932
|
+
}
|
|
1933
|
+
return joinSections(sections);
|
|
1934
|
+
}
|
|
1935
|
+
if (typeof obj["diff"] === "string") {
|
|
1936
|
+
const diff = obj["diff"];
|
|
1937
|
+
return joinSections([
|
|
1938
|
+
renderHeader(toolName, {
|
|
1939
|
+
path: obj["path"],
|
|
1940
|
+
replacements: obj["replacements"],
|
|
1941
|
+
bytes_written: obj["bytes_written"],
|
|
1942
|
+
created: obj["created"],
|
|
1943
|
+
note: obj["note"],
|
|
1944
|
+
files: Array.isArray(obj["files"]) ? obj["files"].length : void 0,
|
|
1945
|
+
truncated: obj["truncated"],
|
|
1946
|
+
mode: obj["mode"]
|
|
1947
|
+
}),
|
|
1948
|
+
compactDiff(diff)
|
|
1949
|
+
]);
|
|
1950
|
+
}
|
|
1951
|
+
if (toolName === "test" && typeof obj["output"] === "string") {
|
|
1952
|
+
return renderTestOutput(obj, input);
|
|
1953
|
+
}
|
|
1954
|
+
if ((toolName === "typecheck" || toolName === "lint" || toolName === "format") && typeof obj["output"] === "string") {
|
|
1955
|
+
return renderVerifierOutput(toolName, obj, input);
|
|
1956
|
+
}
|
|
1957
|
+
if (hasCommandOutputShape(obj)) {
|
|
1958
|
+
return renderCommandOutput(toolName, obj, input);
|
|
1959
|
+
}
|
|
1960
|
+
if (toolName === "json" && typeof obj["formatted"] === "string") {
|
|
1961
|
+
return joinSections([
|
|
1962
|
+
renderHeader("json", {
|
|
1963
|
+
type: obj["type"],
|
|
1964
|
+
keys: Array.isArray(obj["keys"]) ? obj["keys"].length : void 0,
|
|
1965
|
+
query: stringFromInput(input, "query"),
|
|
1966
|
+
error: obj["error"]
|
|
1967
|
+
}),
|
|
1968
|
+
obj["formatted"]
|
|
1969
|
+
]);
|
|
1970
|
+
}
|
|
1971
|
+
if (toolName === "logs" && Array.isArray(obj["entries"])) {
|
|
1972
|
+
const entries = obj["entries"].filter(isRecord2);
|
|
1973
|
+
const lines = entries.slice(0, LOG_ENTRY_LIMIT).map((entry) => {
|
|
1974
|
+
const ts = stringField(entry, "timestamp") ?? "";
|
|
1975
|
+
const level = stringField(entry, "level") ?? "info";
|
|
1976
|
+
const message = stringField(entry, "message") ?? "";
|
|
1977
|
+
const source = stringField(entry, "source");
|
|
1978
|
+
return [ts, level, source, message].filter(Boolean).join(" ");
|
|
1979
|
+
});
|
|
1980
|
+
if (entries.length > LOG_ENTRY_LIMIT) {
|
|
1981
|
+
lines.push(`[serializer omitted ${entries.length - LOG_ENTRY_LIMIT} log entry item(s)]`);
|
|
1982
|
+
}
|
|
1983
|
+
return joinSections([
|
|
1984
|
+
renderHeader(`logs: ${stringField(obj, "source") ?? "<source>"}`, {
|
|
1985
|
+
total: obj["total"],
|
|
1986
|
+
shown: Math.min(entries.length, LOG_ENTRY_LIMIT),
|
|
1987
|
+
truncated: obj["truncated"],
|
|
1988
|
+
stream_mode: obj["stream_mode"]
|
|
1989
|
+
}),
|
|
1990
|
+
lines.length > 0 ? lines.join("\n") : "(no log entries)"
|
|
1991
|
+
]);
|
|
1992
|
+
}
|
|
1993
|
+
if (toolName === "audit" && Array.isArray(obj["vulnerabilities"])) {
|
|
1994
|
+
const vulns = obj["vulnerabilities"].filter(isRecord2);
|
|
1995
|
+
const lines = vulns.slice(0, DEFAULT_LIST_LIMIT).map((v) => {
|
|
1996
|
+
const severity = stringField(v, "severity") ?? "unknown";
|
|
1997
|
+
const pkg = stringField(v, "package") ?? "<package>";
|
|
1998
|
+
const title = stringField(v, "title") ?? "";
|
|
1999
|
+
const url = stringField(v, "url");
|
|
2000
|
+
return [severity, pkg, title, url].filter(Boolean).join(" | ");
|
|
2001
|
+
});
|
|
2002
|
+
if (vulns.length > DEFAULT_LIST_LIMIT) {
|
|
2003
|
+
lines.push(`[serializer omitted ${vulns.length - DEFAULT_LIST_LIMIT} vulnerability item(s)]`);
|
|
2004
|
+
}
|
|
2005
|
+
return joinSections([
|
|
2006
|
+
renderHeader("audit", {
|
|
2007
|
+
exit_code: obj["exit_code"],
|
|
2008
|
+
total: obj["total"],
|
|
2009
|
+
summary: obj["summary"],
|
|
2010
|
+
truncated: obj["truncated"]
|
|
2011
|
+
}),
|
|
2012
|
+
lines.length > 0 ? lines.join("\n") : stringField(obj, "output")
|
|
2013
|
+
]);
|
|
2014
|
+
}
|
|
2015
|
+
if (toolName === "outdated" && Array.isArray(obj["packages"])) {
|
|
2016
|
+
const packages = obj["packages"].filter(isRecord2);
|
|
2017
|
+
const lines = packages.slice(0, DEFAULT_LIST_LIMIT).map(
|
|
2018
|
+
(p) => [
|
|
2019
|
+
stringField(p, "name") ?? "<package>",
|
|
2020
|
+
`current=${stringField(p, "current") ?? "unknown"}`,
|
|
2021
|
+
`wanted=${stringField(p, "wanted") ?? "unknown"}`,
|
|
2022
|
+
`latest=${stringField(p, "latest") ?? "unknown"}`,
|
|
2023
|
+
stringField(p, "type")
|
|
2024
|
+
].filter(Boolean).join(" | ")
|
|
2025
|
+
);
|
|
2026
|
+
if (packages.length > DEFAULT_LIST_LIMIT) {
|
|
2027
|
+
lines.push(`[serializer omitted ${packages.length - DEFAULT_LIST_LIMIT} package item(s)]`);
|
|
2028
|
+
}
|
|
2029
|
+
return joinSections([
|
|
2030
|
+
renderHeader("outdated", {
|
|
2031
|
+
exit_code: obj["exit_code"],
|
|
2032
|
+
total: obj["total"],
|
|
2033
|
+
truncated: obj["truncated"]
|
|
2034
|
+
}),
|
|
2035
|
+
lines.length > 0 ? lines.join("\n") : stringField(obj, "output")
|
|
2036
|
+
]);
|
|
2037
|
+
}
|
|
2038
|
+
return void 0;
|
|
2039
|
+
}
|
|
2040
|
+
function renderTestOutput(obj, input) {
|
|
2041
|
+
const exitCode = numberField(obj, "exit_code") ?? 0;
|
|
2042
|
+
const failed = numberField(obj, "failed") ?? 0;
|
|
2043
|
+
const output = stringField(obj, "output") ?? "";
|
|
2044
|
+
const header = renderHeader(`test: ${stringField(obj, "runner") ?? "runner"}`, {
|
|
2045
|
+
exit_code: obj["exit_code"],
|
|
2046
|
+
tests_run: obj["tests_run"],
|
|
2047
|
+
passed: obj["passed"],
|
|
2048
|
+
failed: obj["failed"],
|
|
2049
|
+
duration_ms: obj["duration_ms"],
|
|
2050
|
+
truncated: obj["truncated"],
|
|
2051
|
+
files: inputListSummary(input, "files"),
|
|
2052
|
+
grep: stringFromInput(input, "grep")
|
|
2053
|
+
});
|
|
2054
|
+
if (exitCode === 0 && failed === 0) {
|
|
2055
|
+
return joinSections([
|
|
2056
|
+
header,
|
|
2057
|
+
joinSections([
|
|
2058
|
+
"report:",
|
|
2059
|
+
`status=passed`,
|
|
2060
|
+
`tests_run=${obj["tests_run"] ?? 0}`,
|
|
2061
|
+
`passed=${obj["passed"] ?? 0}`,
|
|
2062
|
+
`failed=${obj["failed"] ?? 0}`,
|
|
2063
|
+
`duration_ms=${obj["duration_ms"] ?? 0}`,
|
|
2064
|
+
extractSpoolNote(output)
|
|
2065
|
+
])
|
|
2066
|
+
]);
|
|
2067
|
+
}
|
|
2068
|
+
return joinSections([
|
|
2069
|
+
header,
|
|
2070
|
+
`error_context:
|
|
2071
|
+
${compactFailureOutput(output || "(no runner output)")}`
|
|
2072
|
+
]);
|
|
2073
|
+
}
|
|
2074
|
+
function renderVerifierOutput(toolName, obj, input) {
|
|
2075
|
+
const exitCode = numberField(obj, "exit_code") ?? 0;
|
|
2076
|
+
const errors = numberField(obj, "errors") ?? 0;
|
|
2077
|
+
const warnings = numberField(obj, "warnings") ?? 0;
|
|
2078
|
+
const output = stringField(obj, "output") ?? "";
|
|
2079
|
+
const changed = numberField(obj, "files_changed") ?? 0;
|
|
2080
|
+
const header = renderHeader(toolName, {
|
|
2081
|
+
exit_code: obj["exit_code"],
|
|
2082
|
+
errors: obj["errors"],
|
|
2083
|
+
warnings: obj["warnings"],
|
|
2084
|
+
files_checked: obj["files_checked"],
|
|
2085
|
+
files_changed: obj["files_changed"],
|
|
2086
|
+
fix_applied: obj["fix_applied"],
|
|
2087
|
+
fixer: obj["fixer"],
|
|
2088
|
+
linter: obj["linter"],
|
|
2089
|
+
project: obj["project"],
|
|
2090
|
+
truncated: obj["truncated"],
|
|
2091
|
+
files: inputListSummary(input, "files"),
|
|
2092
|
+
cwd: stringFromInput(input, "cwd")
|
|
2093
|
+
});
|
|
2094
|
+
if (exitCode === 0 && errors === 0 && (toolName !== "format" || changed === 0)) {
|
|
2095
|
+
return joinSections([
|
|
2096
|
+
header,
|
|
2097
|
+
joinSections([
|
|
2098
|
+
"report:",
|
|
2099
|
+
"status=passed",
|
|
2100
|
+
`errors=${errors}`,
|
|
2101
|
+
`warnings=${warnings}`,
|
|
2102
|
+
toolName === "format" ? `files_changed=${changed}` : void 0,
|
|
2103
|
+
extractSpoolNote(output)
|
|
2104
|
+
])
|
|
2105
|
+
]);
|
|
2106
|
+
}
|
|
2107
|
+
if (exitCode === 0 && toolName === "format") {
|
|
2108
|
+
return joinSections([
|
|
2109
|
+
header,
|
|
2110
|
+
joinSections([
|
|
2111
|
+
"report:",
|
|
2112
|
+
"status=changed",
|
|
2113
|
+
`files_changed=${changed}`,
|
|
2114
|
+
extractSpoolNote(output)
|
|
2115
|
+
])
|
|
2116
|
+
]);
|
|
2117
|
+
}
|
|
2118
|
+
return joinSections([
|
|
2119
|
+
header,
|
|
2120
|
+
`error_context:
|
|
2121
|
+
${compactFailureOutput(output || "(no verifier output)")}`
|
|
2122
|
+
]);
|
|
2123
|
+
}
|
|
2124
|
+
function renderGrepMatches(matches, mode) {
|
|
2125
|
+
if (matches.length === 0) return "(no matches)";
|
|
2126
|
+
if (mode === "files_with_matches") return renderStringList(matches, "(no files)");
|
|
2127
|
+
if (mode === "count") return renderStringList(matches, "(no counts)");
|
|
2128
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2129
|
+
const passthrough = [];
|
|
2130
|
+
for (const match of matches) {
|
|
2131
|
+
const parsed = parseGrepContentLine(match);
|
|
2132
|
+
if (!parsed) {
|
|
2133
|
+
passthrough.push(match);
|
|
2134
|
+
continue;
|
|
2135
|
+
}
|
|
2136
|
+
const list = groups.get(parsed.file) ?? [];
|
|
2137
|
+
list.push(`${parsed.line}:${parsed.text}`);
|
|
2138
|
+
groups.set(parsed.file, list);
|
|
2139
|
+
}
|
|
2140
|
+
if (groups.size === 0) return renderStringList(matches, "(no matches)");
|
|
2141
|
+
const sections = [];
|
|
2142
|
+
let fileIndex = 0;
|
|
2143
|
+
for (const [file, lines] of groups) {
|
|
2144
|
+
fileIndex++;
|
|
2145
|
+
if (fileIndex > GREP_FILE_LIMIT) break;
|
|
2146
|
+
const shown = lines.slice(0, GREP_MATCHES_PER_FILE);
|
|
2147
|
+
sections.push(
|
|
2148
|
+
`${file} (${lines.length} match(es), showing ${shown.length})
|
|
2149
|
+
${shown.join("\n")}`
|
|
2150
|
+
);
|
|
2151
|
+
}
|
|
2152
|
+
if (groups.size > GREP_FILE_LIMIT) {
|
|
2153
|
+
sections.push(`[serializer omitted ${groups.size - GREP_FILE_LIMIT} file group(s)]`);
|
|
2154
|
+
}
|
|
2155
|
+
if (passthrough.length > 0) {
|
|
2156
|
+
sections.push(`ungrouped:
|
|
2157
|
+
${renderStringList(passthrough, "", 50)}`);
|
|
2158
|
+
}
|
|
2159
|
+
return sections.join("\n");
|
|
2160
|
+
}
|
|
2161
|
+
function parseGrepContentLine(line) {
|
|
2162
|
+
const match = /^(.+?):(\d+):(.*)$/.exec(line);
|
|
2163
|
+
if (!match?.[1] || !match[2]) return void 0;
|
|
2164
|
+
return { file: match[1], line: match[2], text: match[3] ?? "" };
|
|
2165
|
+
}
|
|
2166
|
+
function compactDiff(diff) {
|
|
2167
|
+
const lines = diff.split(/\r?\n/);
|
|
2168
|
+
if (lines.length <= DIFF_INLINE_LINE_LIMIT) return diff;
|
|
2169
|
+
const fileCount = Math.max(
|
|
2170
|
+
new Set(
|
|
2171
|
+
lines.map(
|
|
2172
|
+
(line) => /^diff --git\s+a\/(.+?)\s+b\//.exec(line)?.[1] ?? /^---\s+(.+)/.exec(line)?.[1]
|
|
2173
|
+
).filter(Boolean)
|
|
2174
|
+
).size,
|
|
2175
|
+
0
|
|
2176
|
+
);
|
|
2177
|
+
const hunks = lines.filter((line) => line.startsWith("@@")).length;
|
|
2178
|
+
const added = lines.filter((line) => line.startsWith("+") && !line.startsWith("+++")).length;
|
|
2179
|
+
const removed = lines.filter((line) => line.startsWith("-") && !line.startsWith("---")).length;
|
|
2180
|
+
const selected = /* @__PURE__ */ new Set();
|
|
2181
|
+
let hunkCount = 0;
|
|
2182
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2183
|
+
const line = lines[i] ?? "";
|
|
2184
|
+
if (line.startsWith("diff --git") || line.startsWith("--- ") || line.startsWith("+++ ")) {
|
|
2185
|
+
selected.add(i);
|
|
2186
|
+
continue;
|
|
2187
|
+
}
|
|
2188
|
+
if (!line.startsWith("@@")) continue;
|
|
2189
|
+
if (hunkCount >= DIFF_HUNK_LIMIT) continue;
|
|
2190
|
+
hunkCount++;
|
|
2191
|
+
for (let j = i; j <= Math.min(lines.length - 1, i + DIFF_HUNK_CONTEXT); j++) {
|
|
2192
|
+
selected.add(j);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
if (selected.size === 0) {
|
|
2196
|
+
return joinSections([
|
|
2197
|
+
renderHeader("diff_summary", {
|
|
2198
|
+
files: fileCount,
|
|
2199
|
+
hunks,
|
|
2200
|
+
added,
|
|
2201
|
+
removed,
|
|
2202
|
+
lines: lines.length
|
|
2203
|
+
}),
|
|
2204
|
+
lines.slice(0, DIFF_INLINE_LINE_LIMIT).join("\n"),
|
|
2205
|
+
`[serializer omitted ${Math.max(0, lines.length - DIFF_INLINE_LINE_LIMIT)} diff line(s)]`
|
|
2206
|
+
]);
|
|
2207
|
+
}
|
|
2208
|
+
const excerpt = [];
|
|
2209
|
+
let previous = -1;
|
|
2210
|
+
for (const index of [...selected].sort((a, b) => a - b)) {
|
|
2211
|
+
if (index > previous + 1) {
|
|
2212
|
+
const omitted = previous === -1 ? index : index - previous - 1;
|
|
2213
|
+
excerpt.push(`[serializer omitted ${omitted} diff line(s)]`);
|
|
2214
|
+
}
|
|
2215
|
+
excerpt.push(lines[index] ?? "");
|
|
2216
|
+
previous = index;
|
|
2217
|
+
}
|
|
2218
|
+
const trailing = lines.length - previous - 1;
|
|
2219
|
+
if (trailing > 0) excerpt.push(`[serializer omitted ${trailing} trailing diff line(s)]`);
|
|
2220
|
+
return joinSections([
|
|
2221
|
+
renderHeader("diff_summary", {
|
|
2222
|
+
files: fileCount,
|
|
2223
|
+
hunks,
|
|
2224
|
+
shown_hunks: Math.min(hunks, DIFF_HUNK_LIMIT),
|
|
2225
|
+
added,
|
|
2226
|
+
removed,
|
|
2227
|
+
lines: lines.length
|
|
2228
|
+
}),
|
|
2229
|
+
excerpt.join("\n")
|
|
2230
|
+
]);
|
|
2231
|
+
}
|
|
2232
|
+
function compactFailureOutput(output) {
|
|
2233
|
+
const lines = output.split(/\r?\n/);
|
|
2234
|
+
if (lines.length <= 260) return output.trimEnd();
|
|
2235
|
+
const selected = /* @__PURE__ */ new Set();
|
|
2236
|
+
const marker = /\b(fail|failed|failure|error|exception|assertionerror|expected|received|actual|timeout|stack)\b/i;
|
|
2237
|
+
let markerHits = 0;
|
|
2238
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2239
|
+
if (!marker.test(lines[i] ?? "")) continue;
|
|
2240
|
+
markerHits++;
|
|
2241
|
+
for (let j = Math.max(0, i - 4); j <= Math.min(lines.length - 1, i + 10); j++) {
|
|
2242
|
+
selected.add(j);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
if (markerHits === 0) {
|
|
2246
|
+
return lines.slice(-220).join("\n").trimEnd();
|
|
2247
|
+
}
|
|
2248
|
+
const ordered = [...selected].sort((a, b) => a - b);
|
|
2249
|
+
const out = [];
|
|
2250
|
+
let previous = -1;
|
|
2251
|
+
for (const index of ordered) {
|
|
2252
|
+
if (index > previous + 1) {
|
|
2253
|
+
const omitted = previous === -1 ? index : index - previous - 1;
|
|
2254
|
+
out.push(`[serializer omitted ${omitted} line(s)]`);
|
|
2255
|
+
}
|
|
2256
|
+
out.push(lines[index] ?? "");
|
|
2257
|
+
previous = index;
|
|
2258
|
+
}
|
|
2259
|
+
return out.join("\n").trimEnd();
|
|
2260
|
+
}
|
|
2261
|
+
function extractSpoolNote(output) {
|
|
2262
|
+
return output.split(/\r?\n/).find((line) => line.startsWith("[output truncated") && line.includes("full"));
|
|
2263
|
+
}
|
|
2264
|
+
function hasCommandOutputShape(obj) {
|
|
2265
|
+
return typeof obj["stdout"] === "string" || typeof obj["stderr"] === "string" || typeof obj["output"] === "string" || typeof obj["exitCode"] === "number" || typeof obj["exit_code"] === "number";
|
|
2266
|
+
}
|
|
2267
|
+
function renderCommandOutput(toolName, obj, input) {
|
|
2268
|
+
const command = stringField(obj, "command") ?? stringFromInput(input, "command");
|
|
2269
|
+
const args = stringArrayField(obj, "args");
|
|
2270
|
+
const commandLine = command ? [command, ...args].join(" ") : void 0;
|
|
2271
|
+
const output = stringField(obj, "output");
|
|
2272
|
+
const stdout = stringField(obj, "stdout");
|
|
2273
|
+
const stderr = stringField(obj, "stderr");
|
|
2274
|
+
return joinSections([
|
|
2275
|
+
renderHeader(commandLine ? `${toolName}: ${commandLine}` : toolName, {
|
|
2276
|
+
exit_code: obj["exit_code"] ?? obj["exitCode"],
|
|
2277
|
+
timed_out: obj["timed_out"],
|
|
2278
|
+
pid: obj["pid"],
|
|
2279
|
+
allowed: obj["allowed"],
|
|
2280
|
+
truncated: obj["truncated"],
|
|
2281
|
+
runner: obj["runner"],
|
|
2282
|
+
linter: obj["linter"],
|
|
2283
|
+
fixer: obj["fixer"],
|
|
2284
|
+
project: obj["project"],
|
|
2285
|
+
tests_run: obj["tests_run"],
|
|
2286
|
+
passed: obj["passed"],
|
|
2287
|
+
failed: obj["failed"],
|
|
2288
|
+
duration_ms: obj["duration_ms"],
|
|
2289
|
+
errors: obj["errors"],
|
|
2290
|
+
warnings: obj["warnings"],
|
|
2291
|
+
files_checked: obj["files_checked"],
|
|
2292
|
+
files_changed: obj["files_changed"],
|
|
2293
|
+
fix_applied: obj["fix_applied"]
|
|
2294
|
+
}),
|
|
2295
|
+
stringField(obj, "error") ? `error:
|
|
2296
|
+
${stringField(obj, "error")}` : void 0,
|
|
2297
|
+
output ? `output:
|
|
2298
|
+
${output}` : void 0,
|
|
2299
|
+
stdout ? `stdout:
|
|
2300
|
+
${stdout}` : void 0,
|
|
2301
|
+
stderr ? `stderr:
|
|
2302
|
+
${stderr}` : void 0
|
|
2303
|
+
]);
|
|
2304
|
+
}
|
|
2305
|
+
function renderGenericToolObject(toolName, obj) {
|
|
2306
|
+
const scalars = {};
|
|
2307
|
+
const blocks = [];
|
|
2308
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
2309
|
+
if (value === void 0) continue;
|
|
2310
|
+
if (isScalar(value)) {
|
|
2311
|
+
const inline = String(value);
|
|
2312
|
+
if (inline.length <= INLINE_LIMIT && !inline.includes("\n")) {
|
|
2313
|
+
scalars[key] = value;
|
|
2314
|
+
} else {
|
|
2315
|
+
blocks.push(`${key}:
|
|
2316
|
+
${inline}`);
|
|
2317
|
+
}
|
|
2318
|
+
continue;
|
|
2319
|
+
}
|
|
2320
|
+
if (Array.isArray(value)) {
|
|
2321
|
+
if (value.every((item) => typeof item === "string")) {
|
|
2322
|
+
blocks.push(`${key}:
|
|
2323
|
+
${renderStringList(value)}`);
|
|
2324
|
+
} else {
|
|
2325
|
+
blocks.push(`${key}:
|
|
2326
|
+
${renderUnknownList(value)}`);
|
|
2327
|
+
}
|
|
2328
|
+
continue;
|
|
2329
|
+
}
|
|
2330
|
+
blocks.push(`${key}: ${clipInline(oneLineJson(value))}`);
|
|
2331
|
+
}
|
|
2332
|
+
return joinSections([renderHeader(toolName, scalars), ...blocks]);
|
|
2333
|
+
}
|
|
2334
|
+
function renderHeader(label, fields) {
|
|
2335
|
+
const parts = Object.entries(fields).filter(([, value]) => value !== void 0 && value !== null && value !== "").map(([key, value]) => `${key}=${clipInline(formatInlineValue(value))}`);
|
|
2336
|
+
return parts.length > 0 ? `${label} (${parts.join(" ")})` : label;
|
|
2337
|
+
}
|
|
2338
|
+
function renderStringList(items, empty = "", limit = DEFAULT_LIST_LIMIT) {
|
|
2339
|
+
if (items.length === 0) return empty;
|
|
2340
|
+
const shown = items.slice(0, limit);
|
|
2341
|
+
const omitted = items.length - shown.length;
|
|
2342
|
+
return [
|
|
2343
|
+
...shown,
|
|
2344
|
+
...omitted > 0 ? [`[serializer omitted ${omitted} item(s); narrow the request for more]`] : []
|
|
2345
|
+
].join("\n");
|
|
2346
|
+
}
|
|
2347
|
+
function renderUnknownList(items, limit = DEFAULT_LIST_LIMIT) {
|
|
2348
|
+
const shown = items.slice(0, limit).map((item) => clipInline(oneLineJson(item), 1e3));
|
|
2349
|
+
const omitted = items.length - shown.length;
|
|
2350
|
+
if (omitted > 0)
|
|
2351
|
+
shown.push(`[serializer omitted ${omitted} item(s); narrow the request for more]`);
|
|
2352
|
+
return shown.join("\n");
|
|
2353
|
+
}
|
|
2354
|
+
function joinSections(sections) {
|
|
2355
|
+
return sections.map((section) => typeof section === "string" ? section.trimEnd() : void 0).filter((section) => !!section).join("\n");
|
|
2356
|
+
}
|
|
2357
|
+
function formatInlineValue(value) {
|
|
2358
|
+
if (Array.isArray(value)) return `[${value.map(formatInlineValue).join(",")}]`;
|
|
2359
|
+
if (isScalar(value)) return String(value);
|
|
2360
|
+
return oneLineJson(value);
|
|
2361
|
+
}
|
|
2362
|
+
function clipInline(value, max = INLINE_LIMIT) {
|
|
2363
|
+
const compact = value.replace(/\s+/g, " ").trim();
|
|
2364
|
+
return compact.length <= max ? compact : `${compact.slice(0, max - 15)}...(${compact.length} chars)`;
|
|
2365
|
+
}
|
|
2366
|
+
function oneLineJson(value) {
|
|
2367
|
+
try {
|
|
2368
|
+
return JSON.stringify(value);
|
|
2369
|
+
} catch {
|
|
2370
|
+
return String(value);
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
function stringField(obj, key) {
|
|
2374
|
+
const value = obj[key];
|
|
2375
|
+
return typeof value === "string" ? value : void 0;
|
|
2376
|
+
}
|
|
2377
|
+
function numberField(obj, key) {
|
|
2378
|
+
const value = obj[key];
|
|
2379
|
+
return typeof value === "number" ? value : void 0;
|
|
2380
|
+
}
|
|
2381
|
+
function stringArrayField(obj, key) {
|
|
2382
|
+
const value = obj[key];
|
|
2383
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
2384
|
+
}
|
|
2385
|
+
function stringFromInput(input, key) {
|
|
2386
|
+
if (!isRecord2(input)) return void 0;
|
|
2387
|
+
const value = input[key];
|
|
2388
|
+
return typeof value === "string" ? value : void 0;
|
|
2389
|
+
}
|
|
2390
|
+
function numberFromInput(input, key) {
|
|
2391
|
+
if (!isRecord2(input)) return void 0;
|
|
2392
|
+
const value = input[key];
|
|
2393
|
+
return typeof value === "number" ? value : void 0;
|
|
2394
|
+
}
|
|
2395
|
+
function inputListSummary(input, key) {
|
|
2396
|
+
if (!isRecord2(input)) return void 0;
|
|
2397
|
+
const value = input[key];
|
|
2398
|
+
if (typeof value === "string") return value;
|
|
2399
|
+
if (Array.isArray(value)) return value.filter((item) => typeof item === "string").join(",");
|
|
2400
|
+
return void 0;
|
|
2401
|
+
}
|
|
2402
|
+
function isRecord2(value) {
|
|
2403
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
2404
|
+
}
|
|
2405
|
+
function isScalar(value) {
|
|
2406
|
+
return value === null || ["string", "number", "boolean"].includes(typeof value);
|
|
2407
|
+
}
|
|
2408
|
+
function projectHash(absRoot) {
|
|
2409
|
+
return createHash("sha256").update(path3.resolve(absRoot)).digest("hex").slice(0, 12);
|
|
2410
|
+
}
|
|
2411
|
+
function projectSlug(absRoot) {
|
|
2412
|
+
const base = slugify(path3.basename(absRoot));
|
|
2413
|
+
const hash = createHash("sha256").update(path3.resolve(absRoot)).digest("hex").slice(0, 6);
|
|
2414
|
+
return `${base}-${hash}`;
|
|
2415
|
+
}
|
|
2416
|
+
function slugify(name) {
|
|
2417
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
|
|
2418
|
+
}
|
|
2419
|
+
function wstackGlobalRoot() {
|
|
2420
|
+
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
2421
|
+
if (fromEnv && fromEnv.trim().length > 0) return path3.resolve(fromEnv);
|
|
2422
|
+
return path3.join(os.homedir(), ".wrongstack");
|
|
2423
|
+
}
|
|
2424
|
+
function resolveWstackPaths(opts) {
|
|
2425
|
+
const globalRoot = opts.globalRoot ?? (opts.userHome ? path3.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
2426
|
+
const hash = projectHash(opts.projectRoot);
|
|
2427
|
+
const slug = projectSlug(opts.projectRoot);
|
|
2428
|
+
const projectDir = path3.join(globalRoot, "projects", slug);
|
|
2429
|
+
return {
|
|
2430
|
+
globalRoot,
|
|
2431
|
+
configDir: globalRoot,
|
|
2432
|
+
globalConfig: path3.join(globalRoot, "config.json"),
|
|
2433
|
+
secretsKey: path3.join(globalRoot, ".key"),
|
|
2434
|
+
globalMemory: path3.join(globalRoot, "memory.md"),
|
|
2435
|
+
globalSkills: path3.join(globalRoot, "skills"),
|
|
2436
|
+
globalPrompts: path3.join(globalRoot, "prompts"),
|
|
2437
|
+
cacheDir: path3.join(globalRoot, "cache"),
|
|
2438
|
+
modelsCache: path3.join(globalRoot, "cache", "models.dev.json"),
|
|
2439
|
+
modelsOverlayCache: path3.join(globalRoot, "cache", "models-overlay.json"),
|
|
2440
|
+
historyFile: path3.join(globalRoot, "history"),
|
|
2441
|
+
logFile: path3.join(globalRoot, "logs", "wrongstack.log"),
|
|
2442
|
+
projectDir,
|
|
2443
|
+
projectCodebaseIndex: path3.join(projectDir, "codebase-index"),
|
|
2444
|
+
projectMemory: path3.join(projectDir, "memory.md"),
|
|
2445
|
+
projectSessions: path3.join(projectDir, "sessions"),
|
|
2446
|
+
projectTrust: path3.join(projectDir, "trust.json"),
|
|
2447
|
+
projectMeta: path3.join(projectDir, "meta.json"),
|
|
2448
|
+
projectLocalConfig: path3.join(projectDir, "config.local.json"),
|
|
2449
|
+
inProjectConfig: path3.join(opts.projectRoot, ".wrongstack", "config.json"),
|
|
2450
|
+
inProjectAgentsFile: path3.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
|
|
2451
|
+
inProjectSkills: path3.join(opts.projectRoot, ".wrongstack", "skills"),
|
|
2452
|
+
inProjectWorktrees: path3.join(opts.projectRoot, ".wrongstack", "worktrees"),
|
|
2453
|
+
projectHash: hash,
|
|
2454
|
+
projectSlug: slug,
|
|
2455
|
+
projectGoal: path3.join(projectDir, "goal.json"),
|
|
2456
|
+
projectSpecs: path3.join(projectDir, "specs"),
|
|
2457
|
+
projectTaskGraphs: path3.join(projectDir, "task-graphs"),
|
|
2458
|
+
projectSddSession: path3.join(projectDir, "sdd-session.json"),
|
|
2459
|
+
projectPlan: path3.join(projectDir, "plan.json"),
|
|
2460
|
+
projectAutophase: path3.join(projectDir, "autophase"),
|
|
2461
|
+
syncConfig: path3.join(globalRoot, "sync.json"),
|
|
2462
|
+
projectStatus: (projectHash2) => path3.join(globalRoot, "projects", projectHash2, "status.json")
|
|
2463
|
+
};
|
|
2464
|
+
}
|
|
2465
|
+
|
|
1634
2466
|
// src/types/errors.ts
|
|
1635
2467
|
var ERROR_CODES = {
|
|
1636
2468
|
// Provider
|
|
@@ -1834,15 +2666,20 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1834
2666
|
this._cachedMsgCount = msgCount;
|
|
1835
2667
|
this._cachedToolCount = toolCount;
|
|
1836
2668
|
}
|
|
1837
|
-
const
|
|
2669
|
+
const budget = computeContextWindowBudget(ctx, tokens, this._maxContext);
|
|
2670
|
+
const load = budget.load;
|
|
1838
2671
|
const policy = this.policyProvider?.(ctx);
|
|
1839
2672
|
const thresholds = policy?.thresholds ?? {
|
|
1840
2673
|
warn: this.warnThreshold,
|
|
1841
2674
|
soft: this.softThreshold,
|
|
1842
2675
|
hard: this.hardThreshold
|
|
1843
2676
|
};
|
|
2677
|
+
const repetition = repeatedReadPressure(ctx);
|
|
2678
|
+
const adaptiveThresholds = adaptThresholdsForSignals(thresholds, {
|
|
2679
|
+
repeatedReadCount: repetition
|
|
2680
|
+
});
|
|
1844
2681
|
const aggressiveOn = policy?.aggressiveOn ?? this.aggressiveOn;
|
|
1845
|
-
const level = load >=
|
|
2682
|
+
const level = load >= adaptiveThresholds.hard ? "hard" : load >= adaptiveThresholds.soft ? "soft" : load >= adaptiveThresholds.warn ? "warn" : null;
|
|
1846
2683
|
if (!level) {
|
|
1847
2684
|
this.lastNoopAttempt = null;
|
|
1848
2685
|
return next(ctx);
|
|
@@ -1851,7 +2688,13 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1851
2688
|
return next(ctx);
|
|
1852
2689
|
}
|
|
1853
2690
|
const aggressive = level === "hard" ? true : level === "soft" ? aggressiveOn !== "hard" : aggressiveOn === "warn";
|
|
1854
|
-
await this.compact(ctx, aggressive, {
|
|
2691
|
+
await this.compact(ctx, aggressive, {
|
|
2692
|
+
level,
|
|
2693
|
+
tokens,
|
|
2694
|
+
load,
|
|
2695
|
+
budget,
|
|
2696
|
+
signals: { repeatedReadCount: repetition }
|
|
2697
|
+
});
|
|
1855
2698
|
return next(ctx);
|
|
1856
2699
|
};
|
|
1857
2700
|
}
|
|
@@ -1904,6 +2747,8 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1904
2747
|
tokens: pressure.tokens,
|
|
1905
2748
|
load: pressure.load,
|
|
1906
2749
|
maxContext: this._maxContext,
|
|
2750
|
+
budget: pressure.budget,
|
|
2751
|
+
signals: pressure.signals,
|
|
1907
2752
|
report,
|
|
1908
2753
|
aggressive
|
|
1909
2754
|
});
|
|
@@ -1915,6 +2760,8 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1915
2760
|
level: pressure.level,
|
|
1916
2761
|
aggressive,
|
|
1917
2762
|
reductions: report.reductions?.map((r) => ({ phase: r.phase, saved: r.saved })),
|
|
2763
|
+
budget: pressure.budget,
|
|
2764
|
+
signals: pressure.signals,
|
|
1918
2765
|
// Record what was collapsed so the audit trail shows the preserved
|
|
1919
2766
|
// content, not just token counts. Bounded to keep the log line small;
|
|
1920
2767
|
// the full original turns are already in the session JSONL.
|
|
@@ -1930,6 +2777,8 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1930
2777
|
level: pressure.level,
|
|
1931
2778
|
tokens: pressure.tokens,
|
|
1932
2779
|
maxContext: this._maxContext,
|
|
2780
|
+
budget: pressure.budget,
|
|
2781
|
+
signals: pressure.signals,
|
|
1933
2782
|
load: pressure.load,
|
|
1934
2783
|
fatal
|
|
1935
2784
|
});
|
|
@@ -1949,6 +2798,37 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1949
2798
|
}
|
|
1950
2799
|
}
|
|
1951
2800
|
};
|
|
2801
|
+
function computeContextWindowBudget(ctx, inputTokens, maxContext) {
|
|
2802
|
+
const reservedOutputTokens = readPositiveMetaNumber(ctx, "contextOutputReserveTokens") ?? Math.floor(Math.min(8192, maxContext * 0.08));
|
|
2803
|
+
const reservedSafetyTokens = readPositiveMetaNumber(ctx, "contextSafetyBufferTokens") ?? Math.floor(Math.min(4096, maxContext * 0.02));
|
|
2804
|
+
const availableInputTokens = Math.max(
|
|
2805
|
+
1,
|
|
2806
|
+
maxContext - reservedOutputTokens - reservedSafetyTokens
|
|
2807
|
+
);
|
|
2808
|
+
const remainingInputTokens = availableInputTokens - inputTokens;
|
|
2809
|
+
return {
|
|
2810
|
+
maxContext,
|
|
2811
|
+
inputTokens,
|
|
2812
|
+
availableInputTokens,
|
|
2813
|
+
remainingInputTokens,
|
|
2814
|
+
reservedOutputTokens,
|
|
2815
|
+
reservedSafetyTokens,
|
|
2816
|
+
load: inputTokens / availableInputTokens,
|
|
2817
|
+
overflowTokens: Math.max(0, -remainingInputTokens)
|
|
2818
|
+
};
|
|
2819
|
+
}
|
|
2820
|
+
function readPositiveMetaNumber(ctx, key) {
|
|
2821
|
+
const value = ctx.meta?.[key];
|
|
2822
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : void 0;
|
|
2823
|
+
}
|
|
2824
|
+
function adaptThresholdsForSignals(thresholds, signals) {
|
|
2825
|
+
if (signals.repeatedReadCount < 3) return thresholds;
|
|
2826
|
+
return {
|
|
2827
|
+
warn: Math.max(0.25, thresholds.warn - 0.08),
|
|
2828
|
+
soft: Math.max(0.35, thresholds.soft - 0.04),
|
|
2829
|
+
hard: thresholds.hard
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
1952
2832
|
|
|
1953
2833
|
// src/security/capabilities.ts
|
|
1954
2834
|
var ToolCapabilities = {
|
|
@@ -2091,7 +2971,8 @@ ${errorDetails}`,
|
|
|
2091
2971
|
let effectivePermission = decision.permission;
|
|
2092
2972
|
const policy = this.opts.permissionPolicy;
|
|
2093
2973
|
const yolo = policy.getYolo?.() === true || policy.getYoloDestructive?.() === true;
|
|
2094
|
-
|
|
2974
|
+
const authoritativeAuto = decision.source === "yolo";
|
|
2975
|
+
if (toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo && !authoritativeAuto) {
|
|
2095
2976
|
effectivePermission = "confirm";
|
|
2096
2977
|
}
|
|
2097
2978
|
if (effectivePermission === "deny") {
|
|
@@ -2233,9 +3114,10 @@ ${post.additionalContext}`;
|
|
|
2233
3114
|
});
|
|
2234
3115
|
this.opts.renderer?.writeToolCall(tool.name, use.input);
|
|
2235
3116
|
const output = await this.runWithTimeout(tool, use.input, ctx.signal, ctx, use.id);
|
|
2236
|
-
const text = this.serializer.serialize(output);
|
|
3117
|
+
const text = this.serializer.serialize(output, { toolName: tool.name, input: use.input });
|
|
2237
3118
|
const scrubbed = this.opts.secretScrubber.scrub(text);
|
|
2238
|
-
const
|
|
3119
|
+
const withArtifact = await maybePersistLargeToolOutput(tool.name, scrubbed, budget);
|
|
3120
|
+
const { text: capped, newBudget } = this.serializer.enforceCap(withArtifact, budget);
|
|
2239
3121
|
this.opts.renderer?.writeToolResult(tool.name, capped, false);
|
|
2240
3122
|
return {
|
|
2241
3123
|
block: {
|
|
@@ -2260,38 +3142,27 @@ ${post.additionalContext}`;
|
|
|
2260
3142
|
tool.timeoutMs ?? this.iterationTimeoutMs,
|
|
2261
3143
|
this.maxToolTimeoutMs
|
|
2262
3144
|
);
|
|
2263
|
-
const
|
|
2264
|
-
const
|
|
2265
|
-
|
|
2266
|
-
let cleanupCalled = false;
|
|
2267
|
-
let caught = false;
|
|
3145
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
3146
|
+
const combined = AbortSignal.any([parentSignal, timeoutSignal]);
|
|
3147
|
+
let output;
|
|
2268
3148
|
try {
|
|
2269
|
-
|
|
2270
|
-
return await this.runStreamedTool(tool, input, ctx, combined, toolUseId);
|
|
2271
|
-
}
|
|
2272
|
-
return await tool.execute(input, ctx, { signal: combined });
|
|
3149
|
+
output = typeof tool.executeStream === "function" ? await this.runStreamedTool(tool, input, ctx, combined, toolUseId) : await tool.execute(input, ctx, { signal: combined });
|
|
2273
3150
|
} catch (err) {
|
|
2274
|
-
|
|
2275
|
-
if (combined.aborted && typeof tool.cleanup === "function") {
|
|
2276
|
-
cleanupCalled = true;
|
|
2277
|
-
try {
|
|
2278
|
-
await tool.cleanup(input, ctx);
|
|
2279
|
-
} catch {
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
3151
|
+
if (combined.aborted) await this.runToolCleanup(tool, input, ctx);
|
|
2282
3152
|
throw err;
|
|
2283
|
-
}
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
3153
|
+
}
|
|
3154
|
+
if (combined.aborted) {
|
|
3155
|
+
await this.runToolCleanup(tool, input, ctx);
|
|
3156
|
+
throw combined.reason instanceof Error ? combined.reason : new Error(typeof combined.reason === "string" ? combined.reason : "tool timeout");
|
|
3157
|
+
}
|
|
3158
|
+
return output;
|
|
3159
|
+
}
|
|
3160
|
+
/** Best-effort tool cleanup; never let it mask the original error. */
|
|
3161
|
+
async runToolCleanup(tool, input, ctx) {
|
|
3162
|
+
if (typeof tool.cleanup !== "function") return;
|
|
3163
|
+
try {
|
|
3164
|
+
await tool.cleanup(input, ctx);
|
|
3165
|
+
} catch {
|
|
2295
3166
|
}
|
|
2296
3167
|
}
|
|
2297
3168
|
async runStreamedTool(tool, input, ctx, signal, toolUseId) {
|
|
@@ -2460,6 +3331,25 @@ function extractMalformedRaw(input) {
|
|
|
2460
3331
|
return String(value);
|
|
2461
3332
|
}
|
|
2462
3333
|
}
|
|
3334
|
+
var TOOL_OUTPUT_ARTIFACT_THRESHOLD_BYTES = 64 * 1024;
|
|
3335
|
+
async function maybePersistLargeToolOutput(toolName, content, budget) {
|
|
3336
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
3337
|
+
if (bytes <= Math.min(TOOL_OUTPUT_ARTIFACT_THRESHOLD_BYTES, Math.max(0, budget))) {
|
|
3338
|
+
return content;
|
|
3339
|
+
}
|
|
3340
|
+
try {
|
|
3341
|
+
const dir = path3.join(wstackGlobalRoot(), "tool-output");
|
|
3342
|
+
await fs.mkdir(dir, { recursive: true });
|
|
3343
|
+
const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
|
|
3344
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3345
|
+
const filePath = path3.join(dir, `${stamp}-${safeTool}-${randomUUID()}.log`);
|
|
3346
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
3347
|
+
return content + `
|
|
3348
|
+
[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
|
|
3349
|
+
} catch {
|
|
3350
|
+
return content;
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
2463
3353
|
|
|
2464
3354
|
// src/execution/autonomous-runner.ts
|
|
2465
3355
|
var DoneConditionChecker = class {
|
|
@@ -3954,13 +4844,13 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
3954
4844
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
3955
4845
|
return Promise.resolve("stop");
|
|
3956
4846
|
}
|
|
3957
|
-
return new Promise((
|
|
4847
|
+
return new Promise((resolve3) => {
|
|
3958
4848
|
let resolved = false;
|
|
3959
4849
|
const respond = (d) => {
|
|
3960
4850
|
if (resolved) return;
|
|
3961
4851
|
resolved = true;
|
|
3962
4852
|
clearTimeout(fallback);
|
|
3963
|
-
|
|
4853
|
+
resolve3(d);
|
|
3964
4854
|
};
|
|
3965
4855
|
const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
|
|
3966
4856
|
bus.emit("budget.threshold_reached", {
|
|
@@ -7582,7 +8472,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7582
8472
|
taskIds.map((id) => {
|
|
7583
8473
|
const cached = this.completedResults.find((r) => r.taskId === id);
|
|
7584
8474
|
if (cached) return cached;
|
|
7585
|
-
return new Promise((
|
|
8475
|
+
return new Promise((resolve3, reject) => {
|
|
7586
8476
|
const timeout = setTimeout(() => {
|
|
7587
8477
|
this.off("task.completed", handler);
|
|
7588
8478
|
reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
|
|
@@ -7591,7 +8481,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7591
8481
|
if (result.taskId === id) {
|
|
7592
8482
|
clearTimeout(timeout);
|
|
7593
8483
|
this.off("task.completed", handler);
|
|
7594
|
-
|
|
8484
|
+
resolve3(result);
|
|
7595
8485
|
}
|
|
7596
8486
|
};
|
|
7597
8487
|
this.on("task.completed", handler);
|
|
@@ -7859,12 +8749,12 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7859
8749
|
}
|
|
7860
8750
|
return new Promise((resolveDecision) => {
|
|
7861
8751
|
let settled = false;
|
|
7862
|
-
const
|
|
8752
|
+
const resolve3 = (d) => {
|
|
7863
8753
|
if (settled) return;
|
|
7864
8754
|
settled = true;
|
|
7865
8755
|
resolveDecision(d);
|
|
7866
8756
|
};
|
|
7867
|
-
const fallback = setTimeout(() =>
|
|
8757
|
+
const fallback = setTimeout(() => resolve3("stop"), DECISION_TIMEOUT_MS);
|
|
7868
8758
|
budget._events?.emit("budget.threshold_reached", {
|
|
7869
8759
|
kind: "timeout",
|
|
7870
8760
|
used,
|
|
@@ -7880,11 +8770,11 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7880
8770
|
// disagreeing, resolves as a stop). Async grants still resolve.
|
|
7881
8771
|
extend: (extra) => {
|
|
7882
8772
|
clearTimeout(fallback);
|
|
7883
|
-
queueMicrotask(() =>
|
|
8773
|
+
queueMicrotask(() => resolve3({ extend: extra }));
|
|
7884
8774
|
},
|
|
7885
8775
|
deny: () => {
|
|
7886
8776
|
clearTimeout(fallback);
|
|
7887
|
-
|
|
8777
|
+
resolve3("stop");
|
|
7888
8778
|
}
|
|
7889
8779
|
});
|
|
7890
8780
|
});
|
|
@@ -8839,7 +9729,7 @@ var DefaultSkillLoader = class {
|
|
|
8839
9729
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
8840
9730
|
for (const e of entries) {
|
|
8841
9731
|
if (!e.isDirectory()) continue;
|
|
8842
|
-
const skillFile =
|
|
9732
|
+
const skillFile = path3.join(dir, e.name, "SKILL.md");
|
|
8843
9733
|
try {
|
|
8844
9734
|
const raw = await fs.readFile(skillFile, "utf8");
|
|
8845
9735
|
const meta = parseFrontmatter(raw);
|
|
@@ -8910,7 +9800,7 @@ var DefaultSkillLoader = class {
|
|
|
8910
9800
|
if (cached !== void 0) return cached;
|
|
8911
9801
|
const m = await this.find(name);
|
|
8912
9802
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
8913
|
-
const savePath =
|
|
9803
|
+
const savePath = path3.join(path3.dirname(m.path), "SKILL.save.md");
|
|
8914
9804
|
let result;
|
|
8915
9805
|
try {
|
|
8916
9806
|
result = await fs.readFile(savePath, "utf8");
|