deepdebug-local-agent 1.0.18 → 1.0.19
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/package.json +1 -1
- package/src/exec-utils.js +39 -21
- package/src/server.js +151 -12
package/package.json
CHANGED
package/src/exec-utils.js
CHANGED
|
@@ -47,20 +47,38 @@ export function run(cmd, args, cwd, timeoutMs = 10 * 60 * 1000) {
|
|
|
47
47
|
* @returns {Promise<{code: number, stdout: string, stderr: string, duration: number}>}
|
|
48
48
|
*/
|
|
49
49
|
export async function compileAndTest({ language, buildTool, cwd, skipTests = false }) {
|
|
50
|
-
console.log(
|
|
50
|
+
console.log(`[BUILD] [COMPILE] Language: ${language}, BuildTool: ${buildTool}, SkipTests: ${skipTests}`);
|
|
51
51
|
|
|
52
52
|
let result;
|
|
53
53
|
const start = Date.now();
|
|
54
54
|
|
|
55
55
|
try {
|
|
56
56
|
if (language === "java" && buildTool === "maven") {
|
|
57
|
-
//
|
|
57
|
+
// Prefer ./mvnw wrapper over system mvn
|
|
58
|
+
const fs = await import("fs");
|
|
59
|
+
const path = await import("path");
|
|
60
|
+
const hasMvnw = fs.existsSync(path.join(cwd, "mvnw"));
|
|
61
|
+
const mvnCmd = hasMvnw ? "./mvnw" : "mvn";
|
|
62
|
+
|
|
63
|
+
// Verify build tool is available before attempting
|
|
64
|
+
if (!hasMvnw) {
|
|
65
|
+
const check = await run("which mvn", [], cwd);
|
|
66
|
+
if (check.code !== 0) {
|
|
67
|
+
return {
|
|
68
|
+
code: 1,
|
|
69
|
+
stdout: "",
|
|
70
|
+
stderr: "Maven not found: neither ./mvnw nor mvn is available in this environment.",
|
|
71
|
+
duration: Date.now() - start
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
58
76
|
const args = skipTests
|
|
59
|
-
? ["
|
|
60
|
-
: ["
|
|
77
|
+
? ["compile", "-q", "-DskipTests"]
|
|
78
|
+
: ["test", "-q"];
|
|
61
79
|
|
|
62
|
-
console.log(
|
|
63
|
-
result = await run(
|
|
80
|
+
console.log(`[BUILD] [COMPILE] Running: ${mvnCmd} ${args.join(" ")}`);
|
|
81
|
+
result = await run(mvnCmd, args, cwd);
|
|
64
82
|
}
|
|
65
83
|
else if (language === "java" && buildTool === "gradle") {
|
|
66
84
|
// Gradle: clean build
|
|
@@ -68,12 +86,12 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
|
|
|
68
86
|
? ["clean", "build", "-x", "test"]
|
|
69
87
|
: ["clean", "build"];
|
|
70
88
|
|
|
71
|
-
console.log(
|
|
89
|
+
console.log(`[BUILD] [COMPILE] Running: ./gradlew ${args.join(" ")}`);
|
|
72
90
|
result = await run("./gradlew", args, cwd);
|
|
73
91
|
}
|
|
74
92
|
else if (language === "node" || buildTool === "npm") {
|
|
75
93
|
// Node/npm: install and optionally test
|
|
76
|
-
console.log(
|
|
94
|
+
console.log(`[BUILD] [COMPILE] Running: npm install`);
|
|
77
95
|
const installResult = await run("npm", ["install", "--silent"], cwd);
|
|
78
96
|
|
|
79
97
|
if (installResult.code !== 0) {
|
|
@@ -81,7 +99,7 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
|
|
|
81
99
|
}
|
|
82
100
|
|
|
83
101
|
if (!skipTests) {
|
|
84
|
-
console.log(
|
|
102
|
+
console.log(`[BUILD] [COMPILE] Running: npm test`);
|
|
85
103
|
result = await run("npm", ["test", "--silent"], cwd);
|
|
86
104
|
} else {
|
|
87
105
|
result = installResult;
|
|
@@ -89,7 +107,7 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
|
|
|
89
107
|
}
|
|
90
108
|
else if (language === "node" || buildTool === "yarn") {
|
|
91
109
|
// Yarn
|
|
92
|
-
console.log(
|
|
110
|
+
console.log(`[BUILD] [COMPILE] Running: yarn install`);
|
|
93
111
|
const installResult = await run("yarn", ["install", "--silent"], cwd);
|
|
94
112
|
|
|
95
113
|
if (installResult.code !== 0) {
|
|
@@ -97,7 +115,7 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
|
|
|
97
115
|
}
|
|
98
116
|
|
|
99
117
|
if (!skipTests) {
|
|
100
|
-
console.log(
|
|
118
|
+
console.log(`[BUILD] [COMPILE] Running: yarn test`);
|
|
101
119
|
result = await run("yarn", ["test", "--silent"], cwd);
|
|
102
120
|
} else {
|
|
103
121
|
result = installResult;
|
|
@@ -106,7 +124,7 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
|
|
|
106
124
|
else if (language === "python") {
|
|
107
125
|
// Python: pytest
|
|
108
126
|
if (!skipTests) {
|
|
109
|
-
console.log(
|
|
127
|
+
console.log(`[BUILD] [COMPILE] Running: pytest`);
|
|
110
128
|
result = await run("pytest", [], cwd);
|
|
111
129
|
} else {
|
|
112
130
|
// Python doesn't have a compile step, just return success
|
|
@@ -115,7 +133,7 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
|
|
|
115
133
|
}
|
|
116
134
|
else if (language === "go") {
|
|
117
135
|
// Go: build and optionally test
|
|
118
|
-
console.log(
|
|
136
|
+
console.log(`[BUILD] [COMPILE] Running: go build ./...`);
|
|
119
137
|
const buildResult = await run("go", ["build", "./..."], cwd);
|
|
120
138
|
|
|
121
139
|
if (buildResult.code !== 0) {
|
|
@@ -123,7 +141,7 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
|
|
|
123
141
|
}
|
|
124
142
|
|
|
125
143
|
if (!skipTests) {
|
|
126
|
-
console.log(
|
|
144
|
+
console.log(`[BUILD] [COMPILE] Running: go test ./...`);
|
|
127
145
|
result = await run("go", ["test", "./..."], cwd);
|
|
128
146
|
} else {
|
|
129
147
|
result = buildResult;
|
|
@@ -131,7 +149,7 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
|
|
|
131
149
|
}
|
|
132
150
|
else if (language === ".net" || language === "dotnet") {
|
|
133
151
|
// .NET: build and optionally test
|
|
134
|
-
console.log(
|
|
152
|
+
console.log(`[BUILD] [COMPILE] Running: dotnet build`);
|
|
135
153
|
const buildResult = await run("dotnet", ["build"], cwd);
|
|
136
154
|
|
|
137
155
|
if (buildResult.code !== 0) {
|
|
@@ -139,14 +157,14 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
|
|
|
139
157
|
}
|
|
140
158
|
|
|
141
159
|
if (!skipTests) {
|
|
142
|
-
console.log(
|
|
160
|
+
console.log(`[BUILD] [COMPILE] Running: dotnet test`);
|
|
143
161
|
result = await run("dotnet", ["test"], cwd);
|
|
144
162
|
} else {
|
|
145
163
|
result = buildResult;
|
|
146
164
|
}
|
|
147
165
|
}
|
|
148
166
|
else {
|
|
149
|
-
console.error(
|
|
167
|
+
console.error(`[ERR] [COMPILE] Unsupported: ${language}/${buildTool}`);
|
|
150
168
|
return {
|
|
151
169
|
code: 1,
|
|
152
170
|
stdout: "",
|
|
@@ -157,18 +175,18 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
|
|
|
157
175
|
|
|
158
176
|
// Log result
|
|
159
177
|
if (result.code === 0) {
|
|
160
|
-
console.log(
|
|
178
|
+
console.log(`[OK] [COMPILE] Success in ${result.duration}ms`);
|
|
161
179
|
} else {
|
|
162
|
-
console.error(
|
|
180
|
+
console.error(`[ERR] [COMPILE] Failed with code ${result.code}`);
|
|
163
181
|
if (result.stderr) {
|
|
164
|
-
console.error(
|
|
182
|
+
console.error(`[ERR] [COMPILE] Error: ${result.stderr.substring(0, 500)}`);
|
|
165
183
|
}
|
|
166
184
|
}
|
|
167
185
|
|
|
168
186
|
return result;
|
|
169
187
|
|
|
170
188
|
} catch (err) {
|
|
171
|
-
console.error(
|
|
189
|
+
console.error(`[ERR] [COMPILE] Exception: ${err.message}`);
|
|
172
190
|
return {
|
|
173
191
|
code: 1,
|
|
174
192
|
stdout: "",
|
package/src/server.js
CHANGED
|
@@ -1795,28 +1795,48 @@ app.post("/workspace/test-local/compile", async (req, res) => {
|
|
|
1795
1795
|
skipTests: true
|
|
1796
1796
|
});
|
|
1797
1797
|
|
|
1798
|
-
if (compileResult.
|
|
1798
|
+
if (!compileResult.success) {
|
|
1799
1799
|
TEST_LOCAL_STATE.status = "error";
|
|
1800
|
+
|
|
1801
|
+
const errorOutput = (compileResult.steps || [])
|
|
1802
|
+
.map(s => s.stderr || '')
|
|
1803
|
+
.filter(Boolean)
|
|
1804
|
+
.join('\n')
|
|
1805
|
+
.trim();
|
|
1806
|
+
|
|
1807
|
+
const stdoutOutput = (compileResult.steps || [])
|
|
1808
|
+
.map(s => s.stdout || '')
|
|
1809
|
+
.filter(Boolean)
|
|
1810
|
+
.join('\n')
|
|
1811
|
+
.trim();
|
|
1812
|
+
|
|
1813
|
+
const totalDuration = (compileResult.steps || [])
|
|
1814
|
+
.reduce((sum, s) => sum + (s.duration || 0), 0);
|
|
1815
|
+
|
|
1800
1816
|
TEST_LOCAL_STATE.compilationResult = {
|
|
1801
1817
|
success: false,
|
|
1802
|
-
error:
|
|
1803
|
-
duration:
|
|
1818
|
+
error: errorOutput || 'Compilation failed',
|
|
1819
|
+
duration: totalDuration
|
|
1804
1820
|
};
|
|
1805
1821
|
|
|
1806
1822
|
return res.json({
|
|
1807
1823
|
ok: false,
|
|
1808
|
-
error:
|
|
1809
|
-
stdout:
|
|
1810
|
-
|
|
1824
|
+
error: errorOutput || 'Compilation failed',
|
|
1825
|
+
stdout: stdoutOutput,
|
|
1826
|
+
steps: compileResult.steps,
|
|
1827
|
+
duration: totalDuration
|
|
1811
1828
|
});
|
|
1812
1829
|
}
|
|
1813
1830
|
|
|
1831
|
+
const totalDuration = (compileResult.steps || [])
|
|
1832
|
+
.reduce((sum, s) => sum + (s.duration || 0), 0);
|
|
1833
|
+
|
|
1814
1834
|
TEST_LOCAL_STATE.status = "compiled";
|
|
1815
1835
|
TEST_LOCAL_STATE.compilationResult = {
|
|
1816
1836
|
success: true,
|
|
1817
1837
|
language: meta.language,
|
|
1818
1838
|
buildTool: meta.buildTool,
|
|
1819
|
-
duration:
|
|
1839
|
+
duration: totalDuration
|
|
1820
1840
|
};
|
|
1821
1841
|
|
|
1822
1842
|
console.log("[TEST-LOCAL] Compilation successful");
|
|
@@ -1825,8 +1845,8 @@ app.post("/workspace/test-local/compile", async (req, res) => {
|
|
|
1825
1845
|
ok: true,
|
|
1826
1846
|
language: meta.language,
|
|
1827
1847
|
buildTool: meta.buildTool,
|
|
1828
|
-
duration:
|
|
1829
|
-
stdout: compileResult.stdout
|
|
1848
|
+
duration: totalDuration,
|
|
1849
|
+
stdout: (compileResult.steps || []).map(s => s.stdout || '').filter(Boolean).join('\n').trim()
|
|
1830
1850
|
});
|
|
1831
1851
|
} catch (err) {
|
|
1832
1852
|
console.error("[TEST-LOCAL] Compilation failed:", err.message);
|
|
@@ -5375,13 +5395,13 @@ app.post("/workspace/:workspaceId/run", async (req, res) => {
|
|
|
5375
5395
|
// Connects Local Agent to Gateway no public URL needed
|
|
5376
5396
|
// ============================================
|
|
5377
5397
|
async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
5378
|
-
if (!gatewayUrl || !
|
|
5379
|
-
console.log('WebSocket tunnel skipped: missing gatewayUrl
|
|
5398
|
+
if (!gatewayUrl || !tenantId) {
|
|
5399
|
+
console.log('WebSocket tunnel skipped: missing gatewayUrl or tenantId');
|
|
5380
5400
|
return;
|
|
5381
5401
|
}
|
|
5382
5402
|
|
|
5383
5403
|
const wsUrl = gatewayUrl.replace(/^https:\/\//, 'wss://').replace(/^http:\/\//, 'ws://');
|
|
5384
|
-
const fullUrl = `${wsUrl}/api/v1/agent/ws?tenantId=${encodeURIComponent(tenantId)}&apiKey
|
|
5404
|
+
const fullUrl = `${wsUrl}/api/v1/agent/ws?tenantId=${encodeURIComponent(tenantId)}${apiKey ? '&apiKey=' + encodeURIComponent(apiKey) : ''}`;
|
|
5385
5405
|
|
|
5386
5406
|
let reconnectDelay = 2000;
|
|
5387
5407
|
let isShuttingDown = false;
|
|
@@ -5792,6 +5812,125 @@ app.get("/setup/auth-status", async (req, res) => {
|
|
|
5792
5812
|
}
|
|
5793
5813
|
});
|
|
5794
5814
|
|
|
5815
|
+
// ============================================
|
|
5816
|
+
// SPRINT 1 S1-T2: POST /workspace/test-local/run-single
|
|
5817
|
+
// Run a single test class (faster than full suite)
|
|
5818
|
+
// ============================================
|
|
5819
|
+
|
|
5820
|
+
/**
|
|
5821
|
+
* POST /workspace/test-local/run-single
|
|
5822
|
+
*
|
|
5823
|
+
* Runs a single test class by name much faster than running the full suite.
|
|
5824
|
+
* Used by the agentic loop (tool: run_single_test) to validate a specific fix
|
|
5825
|
+
* without waiting for all tests to complete.
|
|
5826
|
+
*
|
|
5827
|
+
* Body:
|
|
5828
|
+
* { className: "BookingServiceTest" }
|
|
5829
|
+
* OR { testFile: "src/test/java/com/example/BookingServiceTest.java" }
|
|
5830
|
+
*
|
|
5831
|
+
* Response:
|
|
5832
|
+
* { ok, className, passed, failed, skipped, total, duration, output, failures[], exitCode }
|
|
5833
|
+
*/
|
|
5834
|
+
app.post("/workspace/test-local/run-single", async (req, res) => {
|
|
5835
|
+
const wsRoot = resolveWorkspaceRoot(req);
|
|
5836
|
+
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
5837
|
+
|
|
5838
|
+
const { className, testFile } = req.body || {};
|
|
5839
|
+
|
|
5840
|
+
// Resolve class name from either param
|
|
5841
|
+
let resolvedClass = className;
|
|
5842
|
+
if (!resolvedClass && testFile) {
|
|
5843
|
+
resolvedClass = path.basename(testFile, '.java');
|
|
5844
|
+
}
|
|
5845
|
+
|
|
5846
|
+
if (!resolvedClass || !resolvedClass.trim()) {
|
|
5847
|
+
return res.status(400).json({
|
|
5848
|
+
ok: false,
|
|
5849
|
+
error: "className or testFile is required. Example: { className: 'BookingServiceTest' }"
|
|
5850
|
+
});
|
|
5851
|
+
}
|
|
5852
|
+
|
|
5853
|
+
resolvedClass = resolvedClass.trim();
|
|
5854
|
+
console.log(`[RUN-SINGLE] Running test class: ${resolvedClass} in ${wsRoot}`);
|
|
5855
|
+
|
|
5856
|
+
const startTime = Date.now();
|
|
5857
|
+
|
|
5858
|
+
try {
|
|
5859
|
+
const meta = await detectProject(wsRoot);
|
|
5860
|
+
|
|
5861
|
+
let result;
|
|
5862
|
+
|
|
5863
|
+
if (meta.buildTool === 'maven') {
|
|
5864
|
+
// mvn test -Dtest=ClassName -q (sem clean para ser mais rapido)
|
|
5865
|
+
result = await run('mvn', ['test', `-Dtest=${resolvedClass}`, '-q'], wsRoot);
|
|
5866
|
+
} else if (meta.buildTool === 'gradle') {
|
|
5867
|
+
const gradleCmd = fs.existsSync(path.join(wsRoot, 'gradlew')) ? './gradlew' : 'gradle';
|
|
5868
|
+
result = await run(gradleCmd, ['test', '--tests', `*.${resolvedClass}`], wsRoot);
|
|
5869
|
+
} else if (meta.language === 'node') {
|
|
5870
|
+
result = await run('npx', ['jest', resolvedClass, '--no-coverage'], wsRoot);
|
|
5871
|
+
} else {
|
|
5872
|
+
return res.status(400).json({
|
|
5873
|
+
ok: false,
|
|
5874
|
+
error: `Single test not supported for buildTool: ${meta.buildTool}. Use /workspace/test-local/compile instead.`
|
|
5875
|
+
});
|
|
5876
|
+
}
|
|
5877
|
+
|
|
5878
|
+
const duration = Date.now() - startTime;
|
|
5879
|
+
const output = (result.stdout || '') + (result.stderr || '');
|
|
5880
|
+
|
|
5881
|
+
// Parse Maven Surefire / JUnit output
|
|
5882
|
+
const testsRunMatch = output.match(/Tests run: (\d+)/);
|
|
5883
|
+
const failuresMatch = output.match(/Failures: (\d+)/);
|
|
5884
|
+
const errorsMatch = output.match(/Errors: (\d+)/);
|
|
5885
|
+
const skippedMatch = output.match(/Skipped: (\d+)/);
|
|
5886
|
+
|
|
5887
|
+
const total = testsRunMatch ? parseInt(testsRunMatch[1]) : 0;
|
|
5888
|
+
const failures = failuresMatch ? parseInt(failuresMatch[1]) : 0;
|
|
5889
|
+
const errors = errorsMatch ? parseInt(errorsMatch[1]) : 0;
|
|
5890
|
+
const skipped = skippedMatch ? parseInt(skippedMatch[1]) : 0;
|
|
5891
|
+
const passed = total - failures - errors - skipped;
|
|
5892
|
+
const success = result.code === 0 && failures === 0 && errors === 0;
|
|
5893
|
+
|
|
5894
|
+
// Extract failure names from output
|
|
5895
|
+
const failureDetails = [];
|
|
5896
|
+
const failureRegex = /FAILED\s+([^\n]+)/g;
|
|
5897
|
+
let match;
|
|
5898
|
+
while ((match = failureRegex.exec(output)) !== null) {
|
|
5899
|
+
failureDetails.push(match[1].trim());
|
|
5900
|
+
}
|
|
5901
|
+
|
|
5902
|
+
console.log(`[RUN-SINGLE] ${resolvedClass}: ${success ? 'PASSED' : 'FAILED'} ` +
|
|
5903
|
+
`(${duration}ms, ${passed}/${total})`);
|
|
5904
|
+
|
|
5905
|
+
res.json({
|
|
5906
|
+
ok: success,
|
|
5907
|
+
className: resolvedClass,
|
|
5908
|
+
passed,
|
|
5909
|
+
failed: failures + errors,
|
|
5910
|
+
skipped,
|
|
5911
|
+
total,
|
|
5912
|
+
duration,
|
|
5913
|
+
output: output.length > 8000 ? output.substring(0, 8000) + '\n...[truncated]' : output,
|
|
5914
|
+
failures: failureDetails,
|
|
5915
|
+
exitCode: result.code
|
|
5916
|
+
});
|
|
5917
|
+
|
|
5918
|
+
} catch (err) {
|
|
5919
|
+
const duration = Date.now() - startTime;
|
|
5920
|
+
console.error(`[RUN-SINGLE] Failed for ${resolvedClass}:`, err.message);
|
|
5921
|
+
res.status(500).json({
|
|
5922
|
+
ok: false,
|
|
5923
|
+
className: resolvedClass,
|
|
5924
|
+
error: err.message,
|
|
5925
|
+
duration,
|
|
5926
|
+
passed: 0,
|
|
5927
|
+
failed: 1,
|
|
5928
|
+
skipped: 0,
|
|
5929
|
+
total: 0
|
|
5930
|
+
});
|
|
5931
|
+
}
|
|
5932
|
+
});
|
|
5933
|
+
|
|
5795
5934
|
// ============================================
|
|
5796
5935
|
// START SERVER
|
|
5797
5936
|
|