@kernel.chat/kbot 2.9.0 → 2.10.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.d.ts +2 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +1 -1
- package/dist/agent.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +17 -12
- package/dist/auth.js.map +1 -1
- package/dist/cli.js +194 -2
- package/dist/cli.js.map +1 -1
- package/dist/cloud-sync.d.ts.map +1 -1
- package/dist/cloud-sync.js +15 -4
- package/dist/cloud-sync.js.map +1 -1
- package/dist/confidence.d.ts.map +1 -1
- package/dist/confidence.js +4 -1
- package/dist/confidence.js.map +1 -1
- package/dist/context-manager.d.ts.map +1 -1
- package/dist/context-manager.js +31 -5
- package/dist/context-manager.js.map +1 -1
- package/dist/context-manager.test.d.ts +2 -0
- package/dist/context-manager.test.d.ts.map +1 -0
- package/dist/context-manager.test.js +121 -0
- package/dist/context-manager.test.js.map +1 -0
- package/dist/export.d.ts +20 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +301 -0
- package/dist/export.js.map +1 -0
- package/dist/ide/acp-server.js +5 -4
- package/dist/ide/acp-server.js.map +1 -1
- package/dist/ide/lsp-bridge.d.ts.map +1 -1
- package/dist/ide/lsp-bridge.js +11 -5
- package/dist/ide/lsp-bridge.js.map +1 -1
- package/dist/marketplace.d.ts +25 -0
- package/dist/marketplace.d.ts.map +1 -0
- package/dist/marketplace.js +327 -0
- package/dist/marketplace.js.map +1 -0
- package/dist/planner.d.ts.map +1 -1
- package/dist/planner.js +3 -2
- package/dist/planner.js.map +1 -1
- package/dist/rate-limiter.d.ts +45 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiter.js +200 -0
- package/dist/rate-limiter.js.map +1 -0
- package/dist/sessions.test.d.ts +2 -0
- package/dist/sessions.test.d.ts.map +1 -0
- package/dist/sessions.test.js +55 -0
- package/dist/sessions.test.js.map +1 -0
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js +105 -26
- package/dist/streaming.js.map +1 -1
- package/dist/streaming.test.d.ts +2 -0
- package/dist/streaming.test.d.ts.map +1 -0
- package/dist/streaming.test.js +41 -0
- package/dist/streaming.test.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +22 -10
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/test-runner.d.ts +2 -0
- package/dist/tools/test-runner.d.ts.map +1 -0
- package/dist/tools/test-runner.js +550 -0
- package/dist/tools/test-runner.js.map +1 -0
- package/dist/ui.js +1 -1
- package/dist/voice.d.ts +25 -0
- package/dist/voice.d.ts.map +1 -0
- package/dist/voice.js +336 -0
- package/dist/voice.js.map +1 -0
- package/dist/watch.d.ts +37 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +369 -0
- package/dist/watch.js.map +1 -0
- package/package.json +1 -1
package/dist/tools/index.js
CHANGED
|
@@ -100,15 +100,25 @@ export async function executeTool(call) {
|
|
|
100
100
|
const maxResult = tool.maxResultSize ?? DEFAULT_MAX_RESULT;
|
|
101
101
|
const startTime = Date.now();
|
|
102
102
|
try {
|
|
103
|
-
//
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
103
|
+
// Execute tool with AbortController-based timeout (prevents leaked promises)
|
|
104
|
+
const controller = new AbortController();
|
|
105
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
106
|
+
try {
|
|
107
|
+
const result = await Promise.race([
|
|
108
|
+
tool.execute(call.arguments),
|
|
109
|
+
new Promise((_, reject) => {
|
|
110
|
+
controller.signal.addEventListener('abort', () => reject(new Error(`Tool '${call.name}' timed out after ${timeout / 1000}s`)));
|
|
111
|
+
}),
|
|
112
|
+
]);
|
|
113
|
+
clearTimeout(timer);
|
|
114
|
+
const durationMs = Date.now() - startTime;
|
|
115
|
+
const truncated = truncateResult(result, maxResult);
|
|
116
|
+
recordMetrics(call.name, durationMs, false);
|
|
117
|
+
return { tool_call_id: call.id, result: truncated, duration_ms: durationMs };
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
clearTimeout(timer);
|
|
121
|
+
}
|
|
112
122
|
}
|
|
113
123
|
catch (err) {
|
|
114
124
|
const durationMs = Date.now() - startTime;
|
|
@@ -124,7 +134,7 @@ export async function executeTool(call) {
|
|
|
124
134
|
/** Register all built-in tools. Call once at startup. Uses parallel imports for speed. */
|
|
125
135
|
export async function registerAllTools(opts) {
|
|
126
136
|
// Parallel import all tool modules at once
|
|
127
|
-
const [{ registerFileTools }, { registerBashTools }, { registerGitTools }, { registerSearchTools }, { registerFetchTools }, { registerGitHubTools }, { registerMatrixTools }, { registerParallelTools }, { registerMcpClientTools }, { registerTaskTools }, { registerNotebookTools }, { registerBackgroundTools }, { registerSandboxTools }, { registerBuildMatrixTools }, { registerSubagentTools }, { registerWorktreeTools }, { registerOpenClawTools }, { registerQualityTools }, { registerMemoryTools }, { registerBrowserTools }, { registerE2bTools }, { registerLspTools }, { registerMcpPluginTools }, { registerGraphMemoryTools }, { registerConfidenceTools }, { registerAgentProtocolTools }, { registerTemporalTools }, { registerReasoningTools }, { registerIntentionalityTools },] = await Promise.all([
|
|
137
|
+
const [{ registerFileTools }, { registerBashTools }, { registerGitTools }, { registerSearchTools }, { registerFetchTools }, { registerGitHubTools }, { registerMatrixTools }, { registerParallelTools }, { registerMcpClientTools }, { registerTaskTools }, { registerNotebookTools }, { registerBackgroundTools }, { registerSandboxTools }, { registerBuildMatrixTools }, { registerSubagentTools }, { registerWorktreeTools }, { registerOpenClawTools }, { registerQualityTools }, { registerMemoryTools }, { registerBrowserTools }, { registerE2bTools }, { registerLspTools }, { registerMcpPluginTools }, { registerGraphMemoryTools }, { registerConfidenceTools }, { registerAgentProtocolTools }, { registerTemporalTools }, { registerReasoningTools }, { registerIntentionalityTools }, { registerTestRunnerTools },] = await Promise.all([
|
|
128
138
|
import('./files.js'),
|
|
129
139
|
import('./bash.js'),
|
|
130
140
|
import('./git.js'),
|
|
@@ -154,6 +164,7 @@ export async function registerAllTools(opts) {
|
|
|
154
164
|
import('../temporal.js'),
|
|
155
165
|
import('../reasoning.js'),
|
|
156
166
|
import('../intentionality.js'),
|
|
167
|
+
import('./test-runner.js'),
|
|
157
168
|
]);
|
|
158
169
|
// Register all tools (synchronous, fast)
|
|
159
170
|
registerFileTools();
|
|
@@ -185,6 +196,7 @@ export async function registerAllTools(opts) {
|
|
|
185
196
|
registerTemporalTools();
|
|
186
197
|
registerReasoningTools();
|
|
187
198
|
registerIntentionalityTools();
|
|
199
|
+
registerTestRunnerTools();
|
|
188
200
|
// Computer use tools — opt-in only via --computer-use flag
|
|
189
201
|
if (opts?.computerUse) {
|
|
190
202
|
const { registerComputerTools } = await import('./computer.js');
|
package/dist/tools/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-runner.d.ts","sourceRoot":"","sources":["../../src/tools/test-runner.ts"],"names":[],"mappings":"AA8iBA,wBAAgB,uBAAuB,IAAI,IAAI,CAqD9C"}
|
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
import { registerTool } from './index.js';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
function detectFramework(projectPath) {
|
|
6
|
+
// 1. Vitest — explicit config or vite config with test section
|
|
7
|
+
const vitestConfigs = [
|
|
8
|
+
'vitest.config.ts',
|
|
9
|
+
'vitest.config.js',
|
|
10
|
+
'vitest.config.mts',
|
|
11
|
+
'vitest.config.mjs',
|
|
12
|
+
];
|
|
13
|
+
for (const cfg of vitestConfigs) {
|
|
14
|
+
if (existsSync(join(projectPath, cfg)))
|
|
15
|
+
return 'vitest';
|
|
16
|
+
}
|
|
17
|
+
// Check vite.config.* for a `test` key (lightweight heuristic)
|
|
18
|
+
const viteConfigs = [
|
|
19
|
+
'vite.config.ts',
|
|
20
|
+
'vite.config.js',
|
|
21
|
+
'vite.config.mts',
|
|
22
|
+
'vite.config.mjs',
|
|
23
|
+
];
|
|
24
|
+
for (const cfg of viteConfigs) {
|
|
25
|
+
const p = join(projectPath, cfg);
|
|
26
|
+
if (existsSync(p)) {
|
|
27
|
+
try {
|
|
28
|
+
const content = readFileSync(p, 'utf-8');
|
|
29
|
+
if (/\btest\s*[:{]/.test(content))
|
|
30
|
+
return 'vitest';
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// ignore read errors
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// 2. Jest — explicit config or "jest" key in package.json
|
|
38
|
+
const jestConfigs = [
|
|
39
|
+
'jest.config.ts',
|
|
40
|
+
'jest.config.js',
|
|
41
|
+
'jest.config.mjs',
|
|
42
|
+
'jest.config.cjs',
|
|
43
|
+
'jest.config.json',
|
|
44
|
+
];
|
|
45
|
+
for (const cfg of jestConfigs) {
|
|
46
|
+
if (existsSync(join(projectPath, cfg)))
|
|
47
|
+
return 'jest';
|
|
48
|
+
}
|
|
49
|
+
const pkgPath = join(projectPath, 'package.json');
|
|
50
|
+
if (existsSync(pkgPath)) {
|
|
51
|
+
try {
|
|
52
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
53
|
+
if (pkg.jest)
|
|
54
|
+
return 'jest';
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// ignore parse errors
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// 3. Pytest
|
|
61
|
+
if (existsSync(join(projectPath, 'pytest.ini')))
|
|
62
|
+
return 'pytest';
|
|
63
|
+
if (existsSync(join(projectPath, 'setup.cfg'))) {
|
|
64
|
+
try {
|
|
65
|
+
const content = readFileSync(join(projectPath, 'setup.cfg'), 'utf-8');
|
|
66
|
+
if (/\[tool:pytest\]/.test(content))
|
|
67
|
+
return 'pytest';
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// ignore
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (existsSync(join(projectPath, 'pyproject.toml'))) {
|
|
74
|
+
try {
|
|
75
|
+
const content = readFileSync(join(projectPath, 'pyproject.toml'), 'utf-8');
|
|
76
|
+
if (/\[tool\.pytest/.test(content))
|
|
77
|
+
return 'pytest';
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// ignore
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// 4. Cargo (Rust)
|
|
84
|
+
if (existsSync(join(projectPath, 'Cargo.toml')))
|
|
85
|
+
return 'cargo';
|
|
86
|
+
// 5. Go
|
|
87
|
+
if (existsSync(join(projectPath, 'go.mod')))
|
|
88
|
+
return 'go';
|
|
89
|
+
// 6. Fallback — npm test script
|
|
90
|
+
if (existsSync(pkgPath)) {
|
|
91
|
+
try {
|
|
92
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
93
|
+
if (pkg.scripts?.test && pkg.scripts.test !== 'echo "Error: no test specified" && exit 1') {
|
|
94
|
+
return 'npm';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// ignore
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Command builders
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
function buildCommand(framework, opts) {
|
|
107
|
+
switch (framework) {
|
|
108
|
+
case 'vitest': {
|
|
109
|
+
let cmd = 'npx vitest run --reporter=json';
|
|
110
|
+
if (opts.file)
|
|
111
|
+
cmd += ` ${opts.file}`;
|
|
112
|
+
else if (opts.pattern)
|
|
113
|
+
cmd += ` --testPathPattern="${opts.pattern}"`;
|
|
114
|
+
return cmd;
|
|
115
|
+
}
|
|
116
|
+
case 'jest': {
|
|
117
|
+
let cmd = 'npx jest --json';
|
|
118
|
+
if (opts.file)
|
|
119
|
+
cmd += ` ${opts.file}`;
|
|
120
|
+
else if (opts.pattern)
|
|
121
|
+
cmd += ` --testPathPattern="${opts.pattern}"`;
|
|
122
|
+
return cmd;
|
|
123
|
+
}
|
|
124
|
+
case 'pytest': {
|
|
125
|
+
let cmd = 'python -m pytest --tb=short -q';
|
|
126
|
+
if (opts.file)
|
|
127
|
+
cmd += ` ${opts.file}`;
|
|
128
|
+
else if (opts.pattern)
|
|
129
|
+
cmd += ` -k "${opts.pattern}"`;
|
|
130
|
+
if (opts.verbose)
|
|
131
|
+
cmd += ' -v';
|
|
132
|
+
return cmd;
|
|
133
|
+
}
|
|
134
|
+
case 'cargo': {
|
|
135
|
+
let cmd = 'cargo test';
|
|
136
|
+
if (opts.file)
|
|
137
|
+
cmd += ` --test ${opts.file}`;
|
|
138
|
+
else if (opts.pattern)
|
|
139
|
+
cmd += ` ${opts.pattern}`;
|
|
140
|
+
cmd += ' 2>&1';
|
|
141
|
+
return cmd;
|
|
142
|
+
}
|
|
143
|
+
case 'go': {
|
|
144
|
+
let cmd = 'go test';
|
|
145
|
+
if (opts.file)
|
|
146
|
+
cmd += ` ${opts.file}`;
|
|
147
|
+
else
|
|
148
|
+
cmd += ' ./...';
|
|
149
|
+
if (opts.verbose)
|
|
150
|
+
cmd += ' -v';
|
|
151
|
+
cmd += ' 2>&1';
|
|
152
|
+
return cmd;
|
|
153
|
+
}
|
|
154
|
+
case 'npm': {
|
|
155
|
+
return 'npm test 2>&1';
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Output parsers
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
function parseVitestJson(raw) {
|
|
163
|
+
const result = {
|
|
164
|
+
framework: 'vitest',
|
|
165
|
+
total: 0,
|
|
166
|
+
passed: 0,
|
|
167
|
+
failed: 0,
|
|
168
|
+
skipped: 0,
|
|
169
|
+
failures: [],
|
|
170
|
+
};
|
|
171
|
+
try {
|
|
172
|
+
// vitest --reporter=json may prefix non-JSON output; find the JSON object
|
|
173
|
+
const jsonStart = raw.indexOf('{');
|
|
174
|
+
const jsonEnd = raw.lastIndexOf('}');
|
|
175
|
+
if (jsonStart === -1 || jsonEnd === -1)
|
|
176
|
+
return { ...result, raw };
|
|
177
|
+
const data = JSON.parse(raw.substring(jsonStart, jsonEnd + 1));
|
|
178
|
+
result.total = data.numTotalTests ?? 0;
|
|
179
|
+
result.passed = data.numPassedTests ?? 0;
|
|
180
|
+
result.failed = data.numFailedTests ?? 0;
|
|
181
|
+
result.skipped = (data.numPendingTests ?? 0) + (data.numTodoTests ?? 0);
|
|
182
|
+
if (Array.isArray(data.testResults)) {
|
|
183
|
+
for (const suite of data.testResults) {
|
|
184
|
+
if (suite.status === 'failed' && Array.isArray(suite.assertionResults)) {
|
|
185
|
+
for (const assertion of suite.assertionResults) {
|
|
186
|
+
if (assertion.status === 'failed') {
|
|
187
|
+
result.failures.push({
|
|
188
|
+
name: assertion.ancestorTitles
|
|
189
|
+
? [...assertion.ancestorTitles, assertion.title].join(' > ')
|
|
190
|
+
: assertion.fullName ?? assertion.title ?? 'unknown',
|
|
191
|
+
message: Array.isArray(assertion.failureMessages)
|
|
192
|
+
? assertion.failureMessages.join('\n').slice(0, 500)
|
|
193
|
+
: String(assertion.failureMessages ?? '').slice(0, 500),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
result.raw = raw;
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
function parseJestJson(raw) {
|
|
207
|
+
const result = {
|
|
208
|
+
framework: 'jest',
|
|
209
|
+
total: 0,
|
|
210
|
+
passed: 0,
|
|
211
|
+
failed: 0,
|
|
212
|
+
skipped: 0,
|
|
213
|
+
failures: [],
|
|
214
|
+
};
|
|
215
|
+
try {
|
|
216
|
+
const jsonStart = raw.indexOf('{');
|
|
217
|
+
const jsonEnd = raw.lastIndexOf('}');
|
|
218
|
+
if (jsonStart === -1 || jsonEnd === -1)
|
|
219
|
+
return { ...result, raw };
|
|
220
|
+
const data = JSON.parse(raw.substring(jsonStart, jsonEnd + 1));
|
|
221
|
+
result.total = data.numTotalTests ?? 0;
|
|
222
|
+
result.passed = data.numPassedTests ?? 0;
|
|
223
|
+
result.failed = data.numFailedTests ?? 0;
|
|
224
|
+
result.skipped = data.numPendingTests ?? 0;
|
|
225
|
+
if (Array.isArray(data.testResults)) {
|
|
226
|
+
for (const suite of data.testResults) {
|
|
227
|
+
if (Array.isArray(suite.assertionResults)) {
|
|
228
|
+
for (const assertion of suite.assertionResults) {
|
|
229
|
+
if (assertion.status === 'failed') {
|
|
230
|
+
result.failures.push({
|
|
231
|
+
name: assertion.ancestorTitles
|
|
232
|
+
? [...assertion.ancestorTitles, assertion.title].join(' > ')
|
|
233
|
+
: assertion.fullName ?? assertion.title ?? 'unknown',
|
|
234
|
+
message: Array.isArray(assertion.failureMessages)
|
|
235
|
+
? assertion.failureMessages.join('\n').slice(0, 500)
|
|
236
|
+
: String(assertion.failureMessages ?? '').slice(0, 500),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
result.raw = raw;
|
|
246
|
+
}
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
function parsePytestOutput(raw) {
|
|
250
|
+
const result = {
|
|
251
|
+
framework: 'pytest',
|
|
252
|
+
total: 0,
|
|
253
|
+
passed: 0,
|
|
254
|
+
failed: 0,
|
|
255
|
+
skipped: 0,
|
|
256
|
+
failures: [],
|
|
257
|
+
};
|
|
258
|
+
// Match summary line: "X passed, Y failed, Z skipped" etc.
|
|
259
|
+
const summaryMatch = raw.match(/(\d+)\s+passed|(\d+)\s+failed|(\d+)\s+error|(\d+)\s+skipped|(\d+)\s+warning/g);
|
|
260
|
+
if (summaryMatch) {
|
|
261
|
+
for (const part of summaryMatch) {
|
|
262
|
+
const numMatch = part.match(/(\d+)\s+(\w+)/);
|
|
263
|
+
if (!numMatch)
|
|
264
|
+
continue;
|
|
265
|
+
const count = parseInt(numMatch[1], 10);
|
|
266
|
+
const label = numMatch[2];
|
|
267
|
+
if (label === 'passed')
|
|
268
|
+
result.passed = count;
|
|
269
|
+
else if (label === 'failed' || label === 'error')
|
|
270
|
+
result.failed += count;
|
|
271
|
+
else if (label === 'skipped')
|
|
272
|
+
result.skipped = count;
|
|
273
|
+
}
|
|
274
|
+
result.total = result.passed + result.failed + result.skipped;
|
|
275
|
+
}
|
|
276
|
+
// Extract FAILED lines
|
|
277
|
+
const failedLines = raw.match(/FAILED\s+(.+?)(?:\s+-\s+(.+))?$/gm);
|
|
278
|
+
if (failedLines) {
|
|
279
|
+
for (const line of failedLines) {
|
|
280
|
+
const m = line.match(/FAILED\s+(.+?)(?:\s+-\s+(.+))?$/);
|
|
281
|
+
if (m) {
|
|
282
|
+
result.failures.push({
|
|
283
|
+
name: m[1].trim(),
|
|
284
|
+
message: m[2]?.trim() ?? '',
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (result.total === 0)
|
|
290
|
+
result.raw = raw;
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
function parseCargoOutput(raw) {
|
|
294
|
+
const result = {
|
|
295
|
+
framework: 'cargo',
|
|
296
|
+
total: 0,
|
|
297
|
+
passed: 0,
|
|
298
|
+
failed: 0,
|
|
299
|
+
skipped: 0,
|
|
300
|
+
failures: [],
|
|
301
|
+
};
|
|
302
|
+
// Match "test result: ok/FAILED. X passed; Y failed; Z ignored"
|
|
303
|
+
const summaryMatch = raw.match(/test result:\s*\w+\.\s*(\d+)\s+passed;\s*(\d+)\s+failed;\s*(\d+)\s+ignored/);
|
|
304
|
+
if (summaryMatch) {
|
|
305
|
+
result.passed = parseInt(summaryMatch[1], 10);
|
|
306
|
+
result.failed = parseInt(summaryMatch[2], 10);
|
|
307
|
+
result.skipped = parseInt(summaryMatch[3], 10);
|
|
308
|
+
result.total = result.passed + result.failed + result.skipped;
|
|
309
|
+
}
|
|
310
|
+
// Extract failures: lines matching "---- <test_name> stdout ----" followed by failure text
|
|
311
|
+
const failureBlocks = raw.split(/----\s+(.+?)\s+stdout\s+----/);
|
|
312
|
+
for (let i = 1; i < failureBlocks.length; i += 2) {
|
|
313
|
+
const testName = failureBlocks[i];
|
|
314
|
+
const body = failureBlocks[i + 1] ?? '';
|
|
315
|
+
// Only include if there's an assertion failure
|
|
316
|
+
if (body.includes("thread '") && body.includes('panicked at')) {
|
|
317
|
+
const msgMatch = body.match(/panicked at\s+'([^']+)'/);
|
|
318
|
+
result.failures.push({
|
|
319
|
+
name: testName,
|
|
320
|
+
message: msgMatch ? msgMatch[1].slice(0, 500) : body.slice(0, 500),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (result.total === 0)
|
|
325
|
+
result.raw = raw;
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
function parseGoOutput(raw) {
|
|
329
|
+
const result = {
|
|
330
|
+
framework: 'go',
|
|
331
|
+
total: 0,
|
|
332
|
+
passed: 0,
|
|
333
|
+
failed: 0,
|
|
334
|
+
skipped: 0,
|
|
335
|
+
failures: [],
|
|
336
|
+
};
|
|
337
|
+
// Count pass/fail from "--- PASS:" and "--- FAIL:" lines
|
|
338
|
+
const passMatches = raw.match(/---\s+PASS:/g);
|
|
339
|
+
const failMatches = raw.match(/---\s+FAIL:/g);
|
|
340
|
+
const skipMatches = raw.match(/---\s+SKIP:/g);
|
|
341
|
+
result.passed = passMatches?.length ?? 0;
|
|
342
|
+
result.failed = failMatches?.length ?? 0;
|
|
343
|
+
result.skipped = skipMatches?.length ?? 0;
|
|
344
|
+
result.total = result.passed + result.failed + result.skipped;
|
|
345
|
+
// Extract failure details
|
|
346
|
+
const failLines = raw.match(/---\s+FAIL:\s+(\S+)\s+\([\d.]+s\)/g);
|
|
347
|
+
if (failLines) {
|
|
348
|
+
for (const line of failLines) {
|
|
349
|
+
const m = line.match(/---\s+FAIL:\s+(\S+)/);
|
|
350
|
+
if (m) {
|
|
351
|
+
// Try to find the error message above this FAIL line
|
|
352
|
+
const idx = raw.indexOf(line);
|
|
353
|
+
const preceding = raw.substring(Math.max(0, idx - 500), idx);
|
|
354
|
+
const errLines = preceding
|
|
355
|
+
.split('\n')
|
|
356
|
+
.filter((l) => l.includes('Error') || l.includes('expected') || l.includes('got'))
|
|
357
|
+
.slice(-3);
|
|
358
|
+
result.failures.push({
|
|
359
|
+
name: m[1],
|
|
360
|
+
message: errLines.join('\n').slice(0, 500) || 'Test failed',
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// Fallback: check for "FAIL" at package level if no individual tests found
|
|
366
|
+
if (result.total === 0) {
|
|
367
|
+
const pkgFail = raw.match(/^FAIL\s+\S+/gm);
|
|
368
|
+
const pkgOk = raw.match(/^ok\s+\S+/gm);
|
|
369
|
+
if (pkgFail || pkgOk) {
|
|
370
|
+
result.failed = pkgFail?.length ?? 0;
|
|
371
|
+
result.passed = pkgOk?.length ?? 0;
|
|
372
|
+
result.total = result.passed + result.failed;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (result.total === 0)
|
|
376
|
+
result.raw = raw;
|
|
377
|
+
return result;
|
|
378
|
+
}
|
|
379
|
+
function parseGenericOutput(raw) {
|
|
380
|
+
const result = {
|
|
381
|
+
framework: 'npm',
|
|
382
|
+
total: 0,
|
|
383
|
+
passed: 0,
|
|
384
|
+
failed: 0,
|
|
385
|
+
skipped: 0,
|
|
386
|
+
failures: [],
|
|
387
|
+
raw,
|
|
388
|
+
};
|
|
389
|
+
// Try common patterns
|
|
390
|
+
// "X passing", "Y failing", "Z pending"
|
|
391
|
+
const passingMatch = raw.match(/(\d+)\s+passing/i);
|
|
392
|
+
const failingMatch = raw.match(/(\d+)\s+failing/i);
|
|
393
|
+
const pendingMatch = raw.match(/(\d+)\s+pending/i);
|
|
394
|
+
if (passingMatch)
|
|
395
|
+
result.passed = parseInt(passingMatch[1], 10);
|
|
396
|
+
if (failingMatch)
|
|
397
|
+
result.failed = parseInt(failingMatch[1], 10);
|
|
398
|
+
if (pendingMatch)
|
|
399
|
+
result.skipped = parseInt(pendingMatch[1], 10);
|
|
400
|
+
// "Tests: X passed, Y failed, Z total"
|
|
401
|
+
const testsLine = raw.match(/Tests:\s*(\d+)\s+passed.*?(\d+)\s+failed.*?(\d+)\s+total/i);
|
|
402
|
+
if (testsLine) {
|
|
403
|
+
result.passed = parseInt(testsLine[1], 10);
|
|
404
|
+
result.failed = parseInt(testsLine[2], 10);
|
|
405
|
+
result.total = parseInt(testsLine[3], 10);
|
|
406
|
+
}
|
|
407
|
+
if (result.total === 0) {
|
|
408
|
+
result.total = result.passed + result.failed + result.skipped;
|
|
409
|
+
}
|
|
410
|
+
return result;
|
|
411
|
+
}
|
|
412
|
+
function parseOutput(framework, raw) {
|
|
413
|
+
switch (framework) {
|
|
414
|
+
case 'vitest':
|
|
415
|
+
return parseVitestJson(raw);
|
|
416
|
+
case 'jest':
|
|
417
|
+
return parseJestJson(raw);
|
|
418
|
+
case 'pytest':
|
|
419
|
+
return parsePytestOutput(raw);
|
|
420
|
+
case 'cargo':
|
|
421
|
+
return parseCargoOutput(raw);
|
|
422
|
+
case 'go':
|
|
423
|
+
return parseGoOutput(raw);
|
|
424
|
+
case 'npm':
|
|
425
|
+
return parseGenericOutput(raw);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// ---------------------------------------------------------------------------
|
|
429
|
+
// Format result for agent consumption
|
|
430
|
+
// ---------------------------------------------------------------------------
|
|
431
|
+
function formatResult(result) {
|
|
432
|
+
const lines = [];
|
|
433
|
+
lines.push(`Framework: ${result.framework}`);
|
|
434
|
+
lines.push(`Total: ${result.total} | Passed: ${result.passed} | Failed: ${result.failed}${result.skipped ? ` | Skipped: ${result.skipped}` : ''}`);
|
|
435
|
+
if (result.failures.length > 0) {
|
|
436
|
+
lines.push('');
|
|
437
|
+
lines.push('Failures:');
|
|
438
|
+
for (let i = 0; i < result.failures.length; i++) {
|
|
439
|
+
const f = result.failures[i];
|
|
440
|
+
lines.push(` ${i + 1}. ${f.name}`);
|
|
441
|
+
if (f.message) {
|
|
442
|
+
const msgLines = f.message.split('\n').filter(Boolean);
|
|
443
|
+
for (const ml of msgLines) {
|
|
444
|
+
lines.push(` ${ml.trim()}`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (result.failed === 0 && result.total > 0) {
|
|
450
|
+
lines.push('');
|
|
451
|
+
lines.push('All tests passed.');
|
|
452
|
+
}
|
|
453
|
+
if (result.raw && result.total === 0) {
|
|
454
|
+
lines.push('');
|
|
455
|
+
lines.push('Could not parse test output. Raw output:');
|
|
456
|
+
lines.push(result.raw.slice(0, 2000));
|
|
457
|
+
}
|
|
458
|
+
return lines.join('\n');
|
|
459
|
+
}
|
|
460
|
+
// ---------------------------------------------------------------------------
|
|
461
|
+
// Run tests
|
|
462
|
+
// ---------------------------------------------------------------------------
|
|
463
|
+
function runTests(projectPath, opts = {}) {
|
|
464
|
+
const resolvedPath = projectPath || process.cwd();
|
|
465
|
+
if (!existsSync(resolvedPath)) {
|
|
466
|
+
return `Error: path does not exist: ${resolvedPath}`;
|
|
467
|
+
}
|
|
468
|
+
const framework = detectFramework(resolvedPath);
|
|
469
|
+
if (!framework) {
|
|
470
|
+
return ('No test framework detected. Looked for:\n' +
|
|
471
|
+
' - vitest.config.* or vite.config.* with test section\n' +
|
|
472
|
+
' - jest.config.* or "jest" in package.json\n' +
|
|
473
|
+
' - pytest.ini, pyproject.toml with [tool.pytest]\n' +
|
|
474
|
+
' - Cargo.toml\n' +
|
|
475
|
+
' - go.mod\n' +
|
|
476
|
+
' - package.json with "test" script');
|
|
477
|
+
}
|
|
478
|
+
const cmd = buildCommand(framework, opts);
|
|
479
|
+
let stdout;
|
|
480
|
+
try {
|
|
481
|
+
stdout = execSync(cmd, {
|
|
482
|
+
cwd: resolvedPath,
|
|
483
|
+
timeout: 120_000,
|
|
484
|
+
maxBuffer: 10 * 1024 * 1024, // 10 MB
|
|
485
|
+
encoding: 'utf-8',
|
|
486
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
487
|
+
env: { ...process.env, FORCE_COLOR: '0', CI: 'true' },
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
catch (err) {
|
|
491
|
+
// Test failures cause non-zero exit codes — capture stdout anyway
|
|
492
|
+
const execErr = err;
|
|
493
|
+
stdout = execErr.stdout ?? execErr.stderr ?? execErr.message ?? 'Unknown error';
|
|
494
|
+
}
|
|
495
|
+
const result = parseOutput(framework, stdout);
|
|
496
|
+
return formatResult(result);
|
|
497
|
+
}
|
|
498
|
+
// ---------------------------------------------------------------------------
|
|
499
|
+
// Registration
|
|
500
|
+
// ---------------------------------------------------------------------------
|
|
501
|
+
export function registerTestRunnerTools() {
|
|
502
|
+
registerTool({
|
|
503
|
+
name: 'run_tests',
|
|
504
|
+
description: 'Run project tests. Auto-detects the test framework (vitest, jest, pytest, cargo, go, npm) by checking config files, runs tests, and returns structured pass/fail results with failure details the agent can use to auto-fix issues.',
|
|
505
|
+
parameters: {
|
|
506
|
+
path: { type: 'string', description: 'Project root path. Defaults to current working directory.' },
|
|
507
|
+
pattern: { type: 'string', description: 'Test file pattern or filter to narrow which tests to run.' },
|
|
508
|
+
verbose: { type: 'boolean', description: 'Enable verbose output from the test runner.' },
|
|
509
|
+
},
|
|
510
|
+
tier: 'free',
|
|
511
|
+
async execute(args) {
|
|
512
|
+
return runTests(String(args.path || process.cwd()), {
|
|
513
|
+
pattern: args.pattern ? String(args.pattern) : undefined,
|
|
514
|
+
verbose: args.verbose === true,
|
|
515
|
+
});
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
registerTool({
|
|
519
|
+
name: 'test_file',
|
|
520
|
+
description: 'Run tests for a specific file. Auto-detects the test framework and runs only the specified test file, returning structured results with failure details.',
|
|
521
|
+
parameters: {
|
|
522
|
+
path: { type: 'string', description: 'Absolute or relative path to the test file to run.', required: true },
|
|
523
|
+
},
|
|
524
|
+
tier: 'free',
|
|
525
|
+
async execute(args) {
|
|
526
|
+
const args_path = String(args.path || '');
|
|
527
|
+
if (!args_path) {
|
|
528
|
+
return 'Error: path is required. Provide the path to the test file.';
|
|
529
|
+
}
|
|
530
|
+
// Derive project root by walking up from the test file to find a config
|
|
531
|
+
const { dirname } = await import('node:path');
|
|
532
|
+
let dir = dirname(args_path.startsWith('/') ? args_path : join(process.cwd(), args_path));
|
|
533
|
+
let projectRoot = process.cwd();
|
|
534
|
+
// Walk up to find project root (look for package.json, Cargo.toml, go.mod, etc.)
|
|
535
|
+
const rootMarkers = ['package.json', 'Cargo.toml', 'go.mod', 'pyproject.toml', 'pytest.ini'];
|
|
536
|
+
for (let i = 0; i < 10; i++) {
|
|
537
|
+
if (rootMarkers.some((marker) => existsSync(join(dir, marker)))) {
|
|
538
|
+
projectRoot = dir;
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
const parent = dirname(dir);
|
|
542
|
+
if (parent === dir)
|
|
543
|
+
break;
|
|
544
|
+
dir = parent;
|
|
545
|
+
}
|
|
546
|
+
return runTests(projectRoot, { file: args_path });
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
//# sourceMappingURL=test-runner.js.map
|