@aexol/spectral 0.9.20 → 0.9.24
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/agents.d.ts +17 -1
- package/dist/agent/agents.d.ts.map +1 -1
- package/dist/agent/agents.js +42 -2
- package/dist/agent/index.d.ts +3 -1
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +40 -12
- package/dist/cli.js +9 -3
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +14 -5
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +2 -0
- package/dist/memory/tool-output-compressor.d.ts +61 -0
- package/dist/memory/tool-output-compressor.d.ts.map +1 -0
- package/dist/memory/tool-output-compressor.js +569 -0
- package/dist/relay/dispatcher.d.ts +3 -1
- package/dist/relay/dispatcher.d.ts.map +1 -1
- package/dist/relay/dispatcher.js +23 -0
- package/dist/sdk/coding-agent/config.d.ts.map +1 -1
- package/dist/sdk/coding-agent/config.js +10 -1
- package/dist/server/agent-bridge.d.ts +8 -0
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +29 -1
- package/dist/server/handlers/agent-settings.d.ts +49 -0
- package/dist/server/handlers/agent-settings.d.ts.map +1 -0
- package/dist/server/handlers/agent-settings.js +109 -0
- package/dist/server/handlers/settings-lock.d.ts +36 -0
- package/dist/server/handlers/settings-lock.d.ts.map +1 -0
- package/dist/server/handlers/settings-lock.js +89 -0
- package/dist/server/handlers/settings.d.ts +0 -11
- package/dist/server/handlers/settings.d.ts.map +1 -1
- package/dist/server/handlers/settings.js +5 -85
- package/package.json +1 -1
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Output Compressor — RTK-inspired pre-processing that intercepts
|
|
3
|
+
* tool results and compresses raw output before it enters the LLM context.
|
|
4
|
+
*
|
|
5
|
+
* This is complementary to the conversation-level compaction pipeline:
|
|
6
|
+
* - Compressor: compresses raw tool output (pre-context)
|
|
7
|
+
* - Compaction: summarizes + prunes conversation history (post-context)
|
|
8
|
+
* - Unified compaction: narrative summary + observation extraction (at compaction boundary)
|
|
9
|
+
*
|
|
10
|
+
* Strategies (mirroring RTK's four-pronged approach):
|
|
11
|
+
*
|
|
12
|
+
* 1. SMART FILTERING — strip noise from structured outputs
|
|
13
|
+
* - Code: remove comments, collapse whitespace, skip blank lines
|
|
14
|
+
* - Git: strip progress meters, hints, verbose enumerations
|
|
15
|
+
* - Shell: remove ANSI escape codes, carriage returns
|
|
16
|
+
*
|
|
17
|
+
* 2. GROUPING — aggregate similar items
|
|
18
|
+
* - ls/find: group files by directory
|
|
19
|
+
* - grep/rg: group matches by file, collapse repeated patterns
|
|
20
|
+
* - Lint errors: group by rule + file
|
|
21
|
+
*
|
|
22
|
+
* 3. TRUNCATION — keep relevant context, cut redundancy
|
|
23
|
+
* - Read: keep N lines with line-number markers
|
|
24
|
+
* - Bash: keep first + last N lines of long output
|
|
25
|
+
* - Test runners: keep only failures + summary
|
|
26
|
+
*
|
|
27
|
+
* 4. DEDUPLICATION — collapse repeated lines with counts
|
|
28
|
+
* - Log output: deduplicate repeated lines
|
|
29
|
+
* - Docker/build output: collapse progress lines
|
|
30
|
+
*/
|
|
31
|
+
import { debugLog } from "./debug-log.js";
|
|
32
|
+
export const DEFAULT_COMPRESSOR_CONFIG = {
|
|
33
|
+
enabled: true,
|
|
34
|
+
maxResultChars: 8000,
|
|
35
|
+
maxReadLines: 500,
|
|
36
|
+
maxLsLines: 100,
|
|
37
|
+
maxSearchLines: 200,
|
|
38
|
+
stripAnsi: true,
|
|
39
|
+
stripComments: true,
|
|
40
|
+
testFailuresOnly: true,
|
|
41
|
+
compactGit: true,
|
|
42
|
+
};
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// ANSI Stripping
|
|
45
|
+
// ============================================================================
|
|
46
|
+
const ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]/g;
|
|
47
|
+
const CARRIAGE_RETURN_RE = /\r/g;
|
|
48
|
+
function stripAnsi(input) {
|
|
49
|
+
return input.replace(ANSI_RE, "").replace(CARRIAGE_RETURN_RE, "");
|
|
50
|
+
}
|
|
51
|
+
const COMMENT_PATTERNS = {
|
|
52
|
+
ts: { line: "//", blockStart: "/*", blockEnd: "*/" },
|
|
53
|
+
tsx: { line: "//", blockStart: "/*", blockEnd: "*/" },
|
|
54
|
+
js: { line: "//", blockStart: "/*", blockEnd: "*/" },
|
|
55
|
+
jsx: { line: "//", blockStart: "/*", blockEnd: "*/" },
|
|
56
|
+
mjs: { line: "//", blockStart: "/*", blockEnd: "*/" },
|
|
57
|
+
cjs: { line: "//", blockStart: "/*", blockEnd: "*/" },
|
|
58
|
+
py: { line: "#", blockStart: '"""', blockEnd: '"""' },
|
|
59
|
+
pyw: { line: "#", blockStart: '"""', blockEnd: '"""' },
|
|
60
|
+
rs: { line: "//", blockStart: "/*", blockEnd: "*/" },
|
|
61
|
+
go: { line: "//", blockStart: "/*", blockEnd: "*/" },
|
|
62
|
+
java: { line: "//", blockStart: "/*", blockEnd: "*/" },
|
|
63
|
+
rb: { line: "#", blockStart: "=begin", blockEnd: "=end" },
|
|
64
|
+
sh: { line: "#", blockStart: null, blockEnd: null },
|
|
65
|
+
bash: { line: "#", blockStart: null, blockEnd: null },
|
|
66
|
+
zsh: { line: "#", blockStart: null, blockEnd: null },
|
|
67
|
+
c: { line: "//", blockStart: "/*", blockEnd: "*/" },
|
|
68
|
+
h: { line: "//", blockStart: "/*", blockEnd: "*/" },
|
|
69
|
+
cpp: { line: "//", blockStart: "/*", blockEnd: "*/" },
|
|
70
|
+
hpp: { line: "//", blockStart: "/*", blockEnd: "*/" },
|
|
71
|
+
};
|
|
72
|
+
/** Data formats that must never have comment stripping applied */
|
|
73
|
+
const DATA_EXTENSIONS = new Set([
|
|
74
|
+
"json", "jsonc", "json5", "yaml", "yml", "toml", "xml", "csv",
|
|
75
|
+
"tsv", "graphql", "gql", "sql", "md", "markdown", "txt",
|
|
76
|
+
"env", "lock", "css", "scss", "less", "html", "htm",
|
|
77
|
+
]);
|
|
78
|
+
function getExtension(filePath) {
|
|
79
|
+
const dot = filePath.lastIndexOf(".");
|
|
80
|
+
if (dot < 0)
|
|
81
|
+
return "";
|
|
82
|
+
return filePath.slice(dot + 1).toLowerCase();
|
|
83
|
+
}
|
|
84
|
+
function stripComments(content, ext) {
|
|
85
|
+
if (!ext || DATA_EXTENSIONS.has(ext))
|
|
86
|
+
return content;
|
|
87
|
+
const patterns = COMMENT_PATTERNS[ext];
|
|
88
|
+
if (!patterns)
|
|
89
|
+
return content;
|
|
90
|
+
const lines = content.split("\n");
|
|
91
|
+
const result = [];
|
|
92
|
+
let inBlockComment = false;
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
const trimmed = line.trim();
|
|
95
|
+
// Handle block comments
|
|
96
|
+
if (patterns.blockStart && patterns.blockEnd) {
|
|
97
|
+
if (!inBlockComment && trimmed.includes(patterns.blockStart)) {
|
|
98
|
+
inBlockComment = true;
|
|
99
|
+
}
|
|
100
|
+
if (inBlockComment) {
|
|
101
|
+
if (trimmed.includes(patterns.blockEnd)) {
|
|
102
|
+
inBlockComment = false;
|
|
103
|
+
}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Skip line comments
|
|
108
|
+
if (patterns.line && trimmed.startsWith(patterns.line)) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
// Skip blank lines
|
|
112
|
+
if (!trimmed)
|
|
113
|
+
continue;
|
|
114
|
+
// Remove trailing whitespace
|
|
115
|
+
result.push(line.replace(/\s+$/, ""));
|
|
116
|
+
}
|
|
117
|
+
return result.join("\n");
|
|
118
|
+
}
|
|
119
|
+
// ============================================================================
|
|
120
|
+
// Smart Truncation
|
|
121
|
+
// ============================================================================
|
|
122
|
+
function smartTruncate(text, maxChars) {
|
|
123
|
+
if (text.length <= maxChars)
|
|
124
|
+
return text;
|
|
125
|
+
const half = Math.floor(maxChars / 2);
|
|
126
|
+
const head = text.slice(0, half);
|
|
127
|
+
const tail = text.slice(text.length - half);
|
|
128
|
+
const omitted = text.length - head.length - tail.length;
|
|
129
|
+
return `${head}\n\n[... ${omitted} characters truncated ...]\n\n${tail}`;
|
|
130
|
+
}
|
|
131
|
+
function smartTruncateLines(text, maxLines) {
|
|
132
|
+
const lines = text.split("\n");
|
|
133
|
+
if (lines.length <= maxLines)
|
|
134
|
+
return text;
|
|
135
|
+
const keepFirst = Math.floor(maxLines / 3);
|
|
136
|
+
const keepLast = Math.floor(maxLines / 3);
|
|
137
|
+
const middle = maxLines - keepFirst - keepLast - 1;
|
|
138
|
+
const head = lines.slice(0, keepFirst);
|
|
139
|
+
const tail = lines.slice(lines.length - keepLast);
|
|
140
|
+
const omitted = lines.length - keepFirst - keepLast;
|
|
141
|
+
return [
|
|
142
|
+
...head,
|
|
143
|
+
`[... ${omitted} lines omitted ...]`,
|
|
144
|
+
...tail,
|
|
145
|
+
].join("\n");
|
|
146
|
+
}
|
|
147
|
+
function deduplicateRepeatedLines(text) {
|
|
148
|
+
const lines = text.split("\n");
|
|
149
|
+
const stats = { totalLines: 0, uniqueLines: 0, collapsedRuns: 0 };
|
|
150
|
+
if (lines.length === 0)
|
|
151
|
+
return { result: text, stats };
|
|
152
|
+
const result = [];
|
|
153
|
+
let runCount = 0;
|
|
154
|
+
let lastLine = "";
|
|
155
|
+
for (const line of lines) {
|
|
156
|
+
stats.totalLines++;
|
|
157
|
+
if (line === lastLine) {
|
|
158
|
+
runCount++;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
if (runCount > 2) {
|
|
162
|
+
// Collapse: replace the repeated run with a count marker
|
|
163
|
+
result.pop(); // Remove the last duplicate
|
|
164
|
+
result.push(`${lastLine} [repeated ${runCount - 1} more times]`);
|
|
165
|
+
stats.collapsedRuns++;
|
|
166
|
+
}
|
|
167
|
+
result.push(line);
|
|
168
|
+
stats.uniqueLines++;
|
|
169
|
+
runCount = 1;
|
|
170
|
+
lastLine = line;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Handle trailing run
|
|
174
|
+
if (runCount > 2) {
|
|
175
|
+
result.pop();
|
|
176
|
+
result.push(`${lastLine} [repeated ${runCount - 1} more times]`);
|
|
177
|
+
stats.collapsedRuns++;
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
result: result.join("\n"),
|
|
181
|
+
stats,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
// ============================================================================
|
|
185
|
+
// Read Compressor
|
|
186
|
+
// ============================================================================
|
|
187
|
+
function compressReadOutput(event, config) {
|
|
188
|
+
let content = event.content
|
|
189
|
+
.filter((c) => c.type === "text")
|
|
190
|
+
.map((c) => c.text)
|
|
191
|
+
.join("\n");
|
|
192
|
+
if (!content)
|
|
193
|
+
return "";
|
|
194
|
+
// Strip ANSI
|
|
195
|
+
if (config.stripAnsi) {
|
|
196
|
+
content = stripAnsi(content);
|
|
197
|
+
}
|
|
198
|
+
// Strip comments for code files
|
|
199
|
+
if (config.stripComments) {
|
|
200
|
+
const filePath = event.input?.path ?? "";
|
|
201
|
+
const ext = getExtension(filePath);
|
|
202
|
+
if (ext && !DATA_EXTENSIONS.has(ext)) {
|
|
203
|
+
content = stripComments(content, ext);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Truncate long reads
|
|
207
|
+
const lines = content.split("\n");
|
|
208
|
+
if (lines.length > config.maxReadLines) {
|
|
209
|
+
content = smartTruncateLines(content, config.maxReadLines);
|
|
210
|
+
}
|
|
211
|
+
// Truncate by char count
|
|
212
|
+
if (content.length > config.maxResultChars) {
|
|
213
|
+
content = smartTruncate(content, config.maxResultChars);
|
|
214
|
+
}
|
|
215
|
+
return content;
|
|
216
|
+
}
|
|
217
|
+
// ============================================================================
|
|
218
|
+
// Bash Compressor
|
|
219
|
+
// ============================================================================
|
|
220
|
+
/** Commands whose output should be filtered to failures only */
|
|
221
|
+
const TEST_COMMANDS = [
|
|
222
|
+
"jest", "vitest", "pytest", "go test", "cargo test",
|
|
223
|
+
"rspec", "rake test", "playwright test", "npx playwright",
|
|
224
|
+
"npm test", "npm run test", "yarn test", "pnpm test",
|
|
225
|
+
"bun test",
|
|
226
|
+
];
|
|
227
|
+
/** Commands where git-like compacting should apply */
|
|
228
|
+
const GIT_COMMANDS = [
|
|
229
|
+
"git status", "git diff", "git log", "git show",
|
|
230
|
+
"git push", "git pull", "git fetch", "git add",
|
|
231
|
+
"git commit", "git branch", "git stash",
|
|
232
|
+
];
|
|
233
|
+
/** Patterns to strip from git output */
|
|
234
|
+
const GIT_NOISE_PATTERNS = [
|
|
235
|
+
/^Enumerating objects:/,
|
|
236
|
+
/^Counting objects:/,
|
|
237
|
+
/^Compressing objects:/,
|
|
238
|
+
/^Writing objects:/,
|
|
239
|
+
/^Delta compression using/,
|
|
240
|
+
/^Total \d+/,
|
|
241
|
+
/^\(use "git/,
|
|
242
|
+
/^\(create\/copy files/,
|
|
243
|
+
];
|
|
244
|
+
function detectCommandType(command) {
|
|
245
|
+
const base = command.split(/\s+/).slice(0, 3).join(" ").toLowerCase();
|
|
246
|
+
for (const testCmd of TEST_COMMANDS) {
|
|
247
|
+
if (base.startsWith(testCmd))
|
|
248
|
+
return "test";
|
|
249
|
+
}
|
|
250
|
+
for (const gitCmd of GIT_COMMANDS) {
|
|
251
|
+
if (base.startsWith(gitCmd))
|
|
252
|
+
return "git";
|
|
253
|
+
}
|
|
254
|
+
if (/\b(build|compile|tsc|esbuild|webpack|vite build|next build)\b/.test(base)) {
|
|
255
|
+
return "build";
|
|
256
|
+
}
|
|
257
|
+
return "other";
|
|
258
|
+
}
|
|
259
|
+
function filterGitOutput(content) {
|
|
260
|
+
const lines = content.split("\n");
|
|
261
|
+
const filtered = [];
|
|
262
|
+
for (const line of lines) {
|
|
263
|
+
const trimmed = line.trim();
|
|
264
|
+
if (!trimmed)
|
|
265
|
+
continue;
|
|
266
|
+
// Skip noise lines
|
|
267
|
+
if (GIT_NOISE_PATTERNS.some((p) => p.test(trimmed)))
|
|
268
|
+
continue;
|
|
269
|
+
filtered.push(line);
|
|
270
|
+
}
|
|
271
|
+
if (filtered.length === 0)
|
|
272
|
+
return "ok";
|
|
273
|
+
return filtered.join("\n");
|
|
274
|
+
}
|
|
275
|
+
function filterTestOutput(content) {
|
|
276
|
+
const lines = content.split("\n");
|
|
277
|
+
const result = [];
|
|
278
|
+
let failureSection = false;
|
|
279
|
+
let summarySection = false;
|
|
280
|
+
for (const line of lines) {
|
|
281
|
+
// Start of failure section
|
|
282
|
+
if (/^FAIL|^\s*●|^\s*\d+\)|^\s*FAILED|^AssertionError|^Error:/i.test(line)) {
|
|
283
|
+
failureSection = true;
|
|
284
|
+
}
|
|
285
|
+
// Start of summary section
|
|
286
|
+
if (/^(Test Suites|Tests|Snapshots|Time|Ran all):/i.test(line)) {
|
|
287
|
+
summarySection = true;
|
|
288
|
+
failureSection = false;
|
|
289
|
+
}
|
|
290
|
+
// Keep lines in failure or summary sections
|
|
291
|
+
if (failureSection || summarySection) {
|
|
292
|
+
result.push(line);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// If no failures found, just return a summary
|
|
296
|
+
if (result.length === 0) {
|
|
297
|
+
const summaryLines = lines.filter((l) => /^(Tests|Test Suites|Snapshots|Time|Ran):/i.test(l.trim()));
|
|
298
|
+
if (summaryLines.length > 0) {
|
|
299
|
+
return summaryLines.join("\n");
|
|
300
|
+
}
|
|
301
|
+
return "All tests passed.";
|
|
302
|
+
}
|
|
303
|
+
return result.join("\n");
|
|
304
|
+
}
|
|
305
|
+
function compressBashOutput(event, config) {
|
|
306
|
+
let content = event.content
|
|
307
|
+
.filter((c) => c.type === "text")
|
|
308
|
+
.map((c) => c.text)
|
|
309
|
+
.join("\n");
|
|
310
|
+
if (!content)
|
|
311
|
+
return "";
|
|
312
|
+
// Strip ANSI
|
|
313
|
+
if (config.stripAnsi) {
|
|
314
|
+
content = stripAnsi(content);
|
|
315
|
+
}
|
|
316
|
+
// Detect command type and apply specialized filters
|
|
317
|
+
const command = event.input?.command ?? "";
|
|
318
|
+
const cmdType = detectCommandType(command);
|
|
319
|
+
switch (cmdType) {
|
|
320
|
+
case "git":
|
|
321
|
+
if (config.compactGit) {
|
|
322
|
+
content = filterGitOutput(content);
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
case "test":
|
|
326
|
+
if (config.testFailuresOnly) {
|
|
327
|
+
content = filterTestOutput(content);
|
|
328
|
+
}
|
|
329
|
+
break;
|
|
330
|
+
case "build":
|
|
331
|
+
// For build output, deduplicate repeated progress lines
|
|
332
|
+
{
|
|
333
|
+
const deduped = deduplicateRepeatedLines(content);
|
|
334
|
+
content = deduped.result;
|
|
335
|
+
}
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
// Deduplicate repeated lines for all outputs
|
|
339
|
+
{
|
|
340
|
+
const deduped = deduplicateRepeatedLines(content);
|
|
341
|
+
if (deduped.stats.collapsedRuns > 0) {
|
|
342
|
+
content = deduped.result;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// Truncate by char count
|
|
346
|
+
if (content.length > config.maxResultChars) {
|
|
347
|
+
content = smartTruncate(content, config.maxResultChars);
|
|
348
|
+
}
|
|
349
|
+
return content;
|
|
350
|
+
}
|
|
351
|
+
// ============================================================================
|
|
352
|
+
// Ls Compressor
|
|
353
|
+
// ============================================================================
|
|
354
|
+
function compressLsOutput(event, config) {
|
|
355
|
+
let content = event.content
|
|
356
|
+
.filter((c) => c.type === "text")
|
|
357
|
+
.map((c) => c.text)
|
|
358
|
+
.join("\n");
|
|
359
|
+
if (!content)
|
|
360
|
+
return "";
|
|
361
|
+
if (config.stripAnsi) {
|
|
362
|
+
content = stripAnsi(content);
|
|
363
|
+
}
|
|
364
|
+
const lines = content.split("\n");
|
|
365
|
+
if (lines.length > config.maxLsLines) {
|
|
366
|
+
// Group by directory when listing many files
|
|
367
|
+
const groups = new Map();
|
|
368
|
+
for (const line of lines) {
|
|
369
|
+
const trimmed = line.trim();
|
|
370
|
+
if (!trimmed)
|
|
371
|
+
continue;
|
|
372
|
+
const slashIdx = trimmed.lastIndexOf("/");
|
|
373
|
+
const dir = slashIdx >= 0 ? trimmed.slice(0, slashIdx) : ".";
|
|
374
|
+
groups.set(dir, (groups.get(dir) ?? 0) + 1);
|
|
375
|
+
}
|
|
376
|
+
const grouped = [];
|
|
377
|
+
for (const [dir, count] of [...groups.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
378
|
+
grouped.push(`${dir}/ (${count} files)`);
|
|
379
|
+
}
|
|
380
|
+
return grouped.join("\n");
|
|
381
|
+
}
|
|
382
|
+
return content;
|
|
383
|
+
}
|
|
384
|
+
// ============================================================================
|
|
385
|
+
// Grep Compressor
|
|
386
|
+
// ============================================================================
|
|
387
|
+
function compressGrepOutput(event, config) {
|
|
388
|
+
let content = event.content
|
|
389
|
+
.filter((c) => c.type === "text")
|
|
390
|
+
.map((c) => c.text)
|
|
391
|
+
.join("\n");
|
|
392
|
+
if (!content)
|
|
393
|
+
return "";
|
|
394
|
+
if (config.stripAnsi) {
|
|
395
|
+
content = stripAnsi(content);
|
|
396
|
+
}
|
|
397
|
+
// Group matches by file
|
|
398
|
+
const lines = content.split("\n");
|
|
399
|
+
const byFile = new Map();
|
|
400
|
+
for (const line of lines) {
|
|
401
|
+
const trimmed = line.trim();
|
|
402
|
+
if (!trimmed)
|
|
403
|
+
continue;
|
|
404
|
+
// Standard grep format: "file:line:text"
|
|
405
|
+
const colonIdx = trimmed.indexOf(":");
|
|
406
|
+
if (colonIdx > 0) {
|
|
407
|
+
const file = trimmed.slice(0, colonIdx);
|
|
408
|
+
byFile.set(file, (byFile.get(file) ?? 0) + 1);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (byFile.size > 1 && lines.length > config.maxSearchLines) {
|
|
412
|
+
// Group by file with count, show first few matches per file
|
|
413
|
+
const grouped = [];
|
|
414
|
+
for (const [file, count] of [...byFile.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
415
|
+
const fileMatches = lines.filter((l) => l.startsWith(file));
|
|
416
|
+
const preview = fileMatches.slice(0, 3).join("\n ");
|
|
417
|
+
grouped.push(`${file} (${count} matches):\n ${preview}${count > 3 ? `\n ... +${count - 3} more` : ""}`);
|
|
418
|
+
}
|
|
419
|
+
return grouped.join("\n\n");
|
|
420
|
+
}
|
|
421
|
+
return content;
|
|
422
|
+
}
|
|
423
|
+
// ============================================================================
|
|
424
|
+
// Find Compressor
|
|
425
|
+
// ============================================================================
|
|
426
|
+
function compressFindOutput(event, config) {
|
|
427
|
+
let content = event.content
|
|
428
|
+
.filter((c) => c.type === "text")
|
|
429
|
+
.map((c) => c.text)
|
|
430
|
+
.join("\n");
|
|
431
|
+
if (!content)
|
|
432
|
+
return "";
|
|
433
|
+
if (config.stripAnsi) {
|
|
434
|
+
content = stripAnsi(content);
|
|
435
|
+
}
|
|
436
|
+
// Group by directory
|
|
437
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
438
|
+
if (lines.length > config.maxSearchLines) {
|
|
439
|
+
const groups = new Map();
|
|
440
|
+
for (const line of lines) {
|
|
441
|
+
const trimmed = line.trim();
|
|
442
|
+
if (!trimmed)
|
|
443
|
+
continue;
|
|
444
|
+
const slashIdx = trimmed.lastIndexOf("/");
|
|
445
|
+
const dir = slashIdx >= 0 ? trimmed.slice(0, slashIdx) : ".";
|
|
446
|
+
if (!groups.has(dir))
|
|
447
|
+
groups.set(dir, []);
|
|
448
|
+
groups.get(dir).push(slashIdx >= 0 ? trimmed.slice(slashIdx + 1) : trimmed);
|
|
449
|
+
}
|
|
450
|
+
const grouped = [];
|
|
451
|
+
for (const [dir, files] of [...groups.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
452
|
+
if (files.length === 1) {
|
|
453
|
+
grouped.push(`${dir}/${files[0]}`);
|
|
454
|
+
}
|
|
455
|
+
else if (files.length <= 5) {
|
|
456
|
+
grouped.push(`${dir}/ (${files.length} files: ${files.join(", ")})`);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
grouped.push(`${dir}/ (${files.length} files: ${files.slice(0, 3).join(", ")}... +${files.length - 3} more)`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return grouped.join("\n");
|
|
463
|
+
}
|
|
464
|
+
return content;
|
|
465
|
+
}
|
|
466
|
+
export function registerToolOutputCompressor(ext, runtime) {
|
|
467
|
+
const config = { ...DEFAULT_COMPRESSOR_CONFIG };
|
|
468
|
+
ext.on("tool_result", (_event, _ctx) => {
|
|
469
|
+
if (!config.enabled)
|
|
470
|
+
return;
|
|
471
|
+
// Only apply compression for known tool types
|
|
472
|
+
if (_event.toolName === "read") {
|
|
473
|
+
const event = _event;
|
|
474
|
+
const original = event.content
|
|
475
|
+
.filter((c) => c.type === "text")
|
|
476
|
+
.map((c) => c.text)
|
|
477
|
+
.join("\n");
|
|
478
|
+
const compressed = compressReadOutput(event, config);
|
|
479
|
+
if (compressed && compressed !== original) {
|
|
480
|
+
debugLog("compressor.read", {
|
|
481
|
+
originalLen: original.length,
|
|
482
|
+
compressedLen: compressed.length,
|
|
483
|
+
savings: ((1 - compressed.length / Math.max(1, original.length)) * 100).toFixed(1) + "%",
|
|
484
|
+
});
|
|
485
|
+
return {
|
|
486
|
+
content: [...event.content.filter((c) => c.type !== "text"), { type: "text", text: compressed }],
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (_event.toolName === "bash") {
|
|
492
|
+
const event = _event;
|
|
493
|
+
const original = event.content
|
|
494
|
+
.filter((c) => c.type === "text")
|
|
495
|
+
.map((c) => c.text)
|
|
496
|
+
.join("\n");
|
|
497
|
+
const compressed = compressBashOutput(event, config);
|
|
498
|
+
if (compressed && compressed !== original) {
|
|
499
|
+
const command = event.input?.command ?? "";
|
|
500
|
+
debugLog("compressor.bash", {
|
|
501
|
+
command: command.slice(0, 80),
|
|
502
|
+
originalLen: original.length,
|
|
503
|
+
compressedLen: compressed.length,
|
|
504
|
+
savings: ((1 - compressed.length / Math.max(1, original.length)) * 100).toFixed(1) + "%",
|
|
505
|
+
});
|
|
506
|
+
return {
|
|
507
|
+
content: [...event.content.filter((c) => c.type !== "text"), { type: "text", text: compressed }],
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (_event.toolName === "ls") {
|
|
513
|
+
const event = _event;
|
|
514
|
+
const original = event.content
|
|
515
|
+
.filter((c) => c.type === "text")
|
|
516
|
+
.map((c) => c.text)
|
|
517
|
+
.join("\n");
|
|
518
|
+
const compressed = compressLsOutput(event, config);
|
|
519
|
+
if (compressed && compressed !== original) {
|
|
520
|
+
debugLog("compressor.ls", {
|
|
521
|
+
originalLen: original.length,
|
|
522
|
+
compressedLen: compressed.length,
|
|
523
|
+
});
|
|
524
|
+
return {
|
|
525
|
+
content: [...event.content.filter((c) => c.type !== "text"), { type: "text", text: compressed }],
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (_event.toolName === "grep") {
|
|
531
|
+
const event = _event;
|
|
532
|
+
const original = event.content
|
|
533
|
+
.filter((c) => c.type === "text")
|
|
534
|
+
.map((c) => c.text)
|
|
535
|
+
.join("\n");
|
|
536
|
+
const compressed = compressGrepOutput(event, config);
|
|
537
|
+
if (compressed && compressed !== original) {
|
|
538
|
+
debugLog("compressor.grep", {
|
|
539
|
+
originalLen: original.length,
|
|
540
|
+
compressedLen: compressed.length,
|
|
541
|
+
});
|
|
542
|
+
return {
|
|
543
|
+
content: [...event.content.filter((c) => c.type !== "text"), { type: "text", text: compressed }],
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
if (_event.toolName === "find") {
|
|
549
|
+
const event = _event;
|
|
550
|
+
const original = event.content
|
|
551
|
+
.filter((c) => c.type === "text")
|
|
552
|
+
.map((c) => c.text)
|
|
553
|
+
.join("\n");
|
|
554
|
+
const compressed = compressFindOutput(event, config);
|
|
555
|
+
if (compressed && compressed !== original) {
|
|
556
|
+
debugLog("compressor.find", {
|
|
557
|
+
originalLen: original.length,
|
|
558
|
+
compressedLen: compressed.length,
|
|
559
|
+
});
|
|
560
|
+
return {
|
|
561
|
+
content: [...event.content.filter((c) => c.type !== "text"), { type: "text", text: compressed }],
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
// Unknown tool types: do nothing
|
|
567
|
+
return;
|
|
568
|
+
});
|
|
569
|
+
}
|
|
@@ -48,7 +48,7 @@ import type { RelayClient } from "./client.js";
|
|
|
48
48
|
* `id` is populated when the route had an `:id` placeholder.
|
|
49
49
|
*/
|
|
50
50
|
interface RouteMatch {
|
|
51
|
-
route: "list_projects" | "create_project" | "update_project" | "delete_project" | "bind_studio" | "get_project_observations" | "list_project_sessions" | "create_session" | "get_session" | "update_session" | "delete_session" | "get_session_memory" | "get_session_memory_details" | "compact_session" | "remember_and_delete_session" | "fork_session" | "list_path_autocomplete" | "pick_directory" | "search_project_files" | "enqueue_prompt" | "get_prompt_queue" | "remove_prompt" | "clear_prompt_queue" | "list_mcp_status" | "get_settings" | "put_settings";
|
|
51
|
+
route: "list_projects" | "create_project" | "update_project" | "delete_project" | "bind_studio" | "get_project_observations" | "list_project_sessions" | "create_session" | "get_session" | "update_session" | "delete_session" | "get_session_memory" | "get_session_memory_details" | "compact_session" | "remember_and_delete_session" | "fork_session" | "list_path_autocomplete" | "pick_directory" | "search_project_files" | "enqueue_prompt" | "get_prompt_queue" | "remove_prompt" | "clear_prompt_queue" | "list_mcp_status" | "get_settings" | "put_settings" | "get_agent_settings" | "put_agent_settings" | "list_agents";
|
|
52
52
|
id?: string;
|
|
53
53
|
/** Parsed query params, if the path carried a `?...` suffix. */
|
|
54
54
|
query?: URLSearchParams;
|
|
@@ -79,6 +79,8 @@ export interface RestRequestDeps {
|
|
|
79
79
|
manager: SessionStreamManager;
|
|
80
80
|
/** Working directory for this machine. Used by settings persistence. Defaults to homedir. */
|
|
81
81
|
cwd?: string;
|
|
82
|
+
/** Agent config dir (`~/.spectral/agent` by default). Passed through to agent-settings persistence. */
|
|
83
|
+
agentDir?: string;
|
|
82
84
|
/** Optional logger for unexpected errors. Defaults to console.error. */
|
|
83
85
|
logger?: {
|
|
84
86
|
error: (...args: unknown[]) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/relay/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAIH,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACf,MAAM,uBAAuB,CAAC;AAI/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/relay/dispatcher.ts"],"names":[],"mappings":"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"}
|
package/dist/relay/dispatcher.js
CHANGED
|
@@ -47,6 +47,7 @@ import { handleSearchProjectFiles } from "../server/handlers/files-search.js";
|
|
|
47
47
|
import { handleBindStudioProject, handleCreateProject, handleDeleteProject, handleGetProjectObservations, handleListProjects, handleListSessionsByProject, handleUpdateProject, } from "../server/handlers/projects.js";
|
|
48
48
|
import { handleCompactSession, handleCreateSession, handleDeleteSession, handleForkSession, handleRememberAndDeleteSession, handleGetSessionDetail, handleGetSessionMemoryDetails, handleGetSessionMemoryStatus, handleUpdateSession, } from "../server/handlers/sessions.js";
|
|
49
49
|
import { handleClearPromptQueue, handleEnqueuePrompt, handleGetPromptQueue, handleRemovePrompt, } from "../server/handlers/queue.js";
|
|
50
|
+
import { handleGetAgentSettings, handleListAgents, handlePutAgentSettings, } from "../server/handlers/agent-settings.js";
|
|
50
51
|
import { handleGetSettings, handlePutSettings, } from "../server/handlers/settings.js";
|
|
51
52
|
import { shutdownState } from "../server/shutdown.js";
|
|
52
53
|
import { handleAutoResearch } from "./auto-research.js";
|
|
@@ -76,6 +77,20 @@ export function matchRoute(method, path) {
|
|
|
76
77
|
return { route: "put_settings" };
|
|
77
78
|
return null;
|
|
78
79
|
}
|
|
80
|
+
// /api/agent-settings
|
|
81
|
+
if (cleanPath === "/api/agent-settings") {
|
|
82
|
+
if (method === "GET")
|
|
83
|
+
return { route: "get_agent_settings" };
|
|
84
|
+
if (method === "PUT")
|
|
85
|
+
return { route: "put_agent_settings" };
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
// /api/agents
|
|
89
|
+
if (cleanPath === "/api/agents") {
|
|
90
|
+
if (method === "GET")
|
|
91
|
+
return { route: "list_agents", query };
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
79
94
|
// /api/paths/autocomplete
|
|
80
95
|
if (cleanPath === "/api/paths/autocomplete") {
|
|
81
96
|
if (method === "GET")
|
|
@@ -451,6 +466,14 @@ async function dispatchRoute(match, body, deps) {
|
|
|
451
466
|
return handleGetSettings(deps.cwd ?? homedir());
|
|
452
467
|
case "put_settings":
|
|
453
468
|
return handlePutSettings(deps.cwd ?? homedir(), asObject(body));
|
|
469
|
+
case "get_agent_settings":
|
|
470
|
+
return handleGetAgentSettings(deps.cwd ?? homedir(), deps.agentDir);
|
|
471
|
+
case "put_agent_settings":
|
|
472
|
+
return handlePutAgentSettings(deps.cwd ?? homedir(), asObject(body), deps.agentDir);
|
|
473
|
+
case "list_agents": {
|
|
474
|
+
const agentCwd = match.query?.get("cwd") || deps.cwd || homedir();
|
|
475
|
+
return handleListAgents(agentCwd, deps.agentDir);
|
|
476
|
+
}
|
|
454
477
|
}
|
|
455
478
|
}
|
|
456
479
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/sdk/coding-agent/config.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAEhE,UAAU,qBAAqB;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAkB,SAAQ,qBAAqB;IAC/D,KAAK,CAAC,EAAE,qBAAqB,EAAE,CAAC;CAChC;AAsBD,wBAAgB,mBAAmB,IAAI,aAAa,CAcnD;AAyKD,wBAAgB,oBAAoB,CACnC,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,EAAE,EACrB,iBAAiB,SAAc,GAC7B,iBAAiB,GAAG,SAAS,CAO/B;AAED,wBAAgB,mCAAmC,CAClD,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,EAAE,EACrB,iBAAiB,SAAc,GAC7B,MAAM,CAUR;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAOhE;AAMD;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAiBtC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AAED;;;;+BAI+B;AAC/B,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,4BAA4B;AAC5B,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,iCAAiC;AACjC,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,qCAAqC;AACrC,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,+BAA+B;AAC/B,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/sdk/coding-agent/config.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAEhE,UAAU,qBAAqB;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAkB,SAAQ,qBAAqB;IAC/D,KAAK,CAAC,EAAE,qBAAqB,EAAE,CAAC;CAChC;AAsBD,wBAAgB,mBAAmB,IAAI,aAAa,CAcnD;AAyKD,wBAAgB,oBAAoB,CACnC,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,EAAE,EACrB,iBAAiB,SAAc,GAC7B,iBAAiB,GAAG,SAAS,CAO/B;AAED,wBAAgB,mCAAmC,CAClD,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,EAAE,EACrB,iBAAiB,SAAc,GAC7B,MAAM,CAUR;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAOhE;AAMD;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAiBtC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AAED;;;;+BAI+B;AAC/B,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,4BAA4B;AAC5B,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,iCAAiC;AACjC,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,qCAAqC;AACrC,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,+BAA+B;AAC/B,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AA4BD,eAAO,MAAM,YAAY,EAAE,MAA+B,CAAC;AAC3D,eAAO,MAAM,QAAQ,EAAE,MAAyC,CAAC;AACjE,eAAO,MAAM,SAAS,EAAE,MAAmD,CAAC;AAC5E,eAAO,MAAM,eAAe,EAAE,MAAqD,CAAC;AACpF,eAAO,MAAM,OAAO,EAAE,MAA+B,CAAC;AAGtD,eAAO,MAAM,aAAa,QAA+C,CAAC;AAC1E,eAAO,MAAM,eAAe,QAAuD,CAAC;AAEpF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAID,6CAA6C;AAC7C,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGxD;AAMD,gEAAgE;AAChE,wBAAgB,WAAW,IAAI,MAAM,CAMpC;AAED,iDAAiD;AACjD,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,8BAA8B;AAC9B,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,4BAA4B;AAC5B,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,gCAAgC;AAChC,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,kCAAkC;AAClC,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,sDAAsD;AACtD,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,6CAA6C;AAC7C,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,qCAAqC;AACrC,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,iCAAiC;AACjC,wBAAgB,eAAe,IAAI,MAAM,CAExC"}
|
|
@@ -268,7 +268,16 @@ export function getExamplesPath() {
|
|
|
268
268
|
export function getChangelogPath() {
|
|
269
269
|
return resolve(join(getPackageDir(), "CHANGELOG.md"));
|
|
270
270
|
}
|
|
271
|
-
|
|
271
|
+
function readPackageJson() {
|
|
272
|
+
try {
|
|
273
|
+
return JSON.parse(readFileSync(getPackageJsonPath(), "utf-8"));
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
// Standalone Bun binaries do not carry the source package.json.
|
|
277
|
+
return {};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const pkg = readPackageJson();
|
|
272
281
|
const spectralConfigName = pkg.spectralConfig?.name;
|
|
273
282
|
export const PACKAGE_NAME = pkg.name || "index.ts";
|
|
274
283
|
export const APP_NAME = spectralConfigName || "spectral";
|