@lasso-ai/cli 1.3.1 → 1.3.2
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/cli/bridge.js +180 -0
- package/dist/overlay.js +1 -1
- package/package.json +1 -1
package/dist/cli/bridge.js
CHANGED
|
@@ -147,6 +147,164 @@ function proposalMatchesCurrentSource(cwd, changes) {
|
|
|
147
147
|
return false;
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
+
async function triggerBuildFix(cwd, taskId, buildErrors, buildCommand, originalChanges, resolvedLassoKey, agentConfig, collabConfig, socket, taskControllers, taskSnapshots, buildErrorContext) {
|
|
151
|
+
const MAX_RETRIES = 3;
|
|
152
|
+
const context = buildErrorContext.get(taskId);
|
|
153
|
+
if (!context)
|
|
154
|
+
return;
|
|
155
|
+
context.retryCount += 1;
|
|
156
|
+
if (context.retryCount > MAX_RETRIES) {
|
|
157
|
+
// Max retries reached - revert changes and show error
|
|
158
|
+
for (const snapshot of taskSnapshots.get(taskId) || []) {
|
|
159
|
+
if (snapshot.content === null) {
|
|
160
|
+
if (node_fs_1.default.existsSync(snapshot.filePath))
|
|
161
|
+
node_fs_1.default.unlinkSync(snapshot.filePath);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
node_fs_1.default.writeFileSync(snapshot.filePath, snapshot.content);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
taskSnapshots.delete(taskId);
|
|
168
|
+
buildErrorContext.delete(taskId);
|
|
169
|
+
socket.send(JSON.stringify({
|
|
170
|
+
type: "agent_status",
|
|
171
|
+
taskId,
|
|
172
|
+
status: "error",
|
|
173
|
+
message: `⚠️ Build validation failed after ${MAX_RETRIES} attempts. The change was reverted. Errors:\n${buildErrors.slice(0, 5).join("\n")}${buildErrors.length > 5 ? `\n... and ${buildErrors.length - 5} more errors` : ""}`
|
|
174
|
+
}));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// Re-apply the original changes
|
|
178
|
+
try {
|
|
179
|
+
const planned = new Map();
|
|
180
|
+
for (const change of originalChanges) {
|
|
181
|
+
const isNew = !change.oldString;
|
|
182
|
+
const filePath = resolveProposedFile(cwd, change.filePath, true);
|
|
183
|
+
const exists = node_fs_1.default.existsSync(filePath);
|
|
184
|
+
const content = exists ? node_fs_1.default.readFileSync(filePath, "utf8") : "";
|
|
185
|
+
const prepared = (!exists || isNew)
|
|
186
|
+
? { start: 0, end: content.length, oldString: content, newString: change.newString }
|
|
187
|
+
: prepareChange(content, change);
|
|
188
|
+
const fileChanges = planned.get(filePath) || [];
|
|
189
|
+
if (fileChanges.some((item) => prepared.start < item.end && item.start < prepared.end)) {
|
|
190
|
+
throw new Error(`Could not safely apply ${change.filePath}. Proposed changes overlap.`);
|
|
191
|
+
}
|
|
192
|
+
fileChanges.push({ filePath, content, exists, ...prepared });
|
|
193
|
+
planned.set(filePath, fileChanges);
|
|
194
|
+
}
|
|
195
|
+
for (const [filePath, changes] of planned) {
|
|
196
|
+
const exists = changes[0].exists;
|
|
197
|
+
const content = changes[0].content;
|
|
198
|
+
const nextContent = exists
|
|
199
|
+
? [...changes]
|
|
200
|
+
.sort((a, b) => b.start - a.start)
|
|
201
|
+
.reduce((value, change) => value.slice(0, change.start) + change.newString + value.slice(change.end), content)
|
|
202
|
+
: changes[0].newString;
|
|
203
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(filePath), { recursive: true });
|
|
204
|
+
node_fs_1.default.writeFileSync(filePath, nextContent);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch (applyError) {
|
|
208
|
+
socket.send(JSON.stringify({
|
|
209
|
+
type: "agent_status",
|
|
210
|
+
taskId,
|
|
211
|
+
status: "error",
|
|
212
|
+
message: `Failed to re-apply original changes: ${applyError instanceof Error ? applyError.message : "Unknown error"}`
|
|
213
|
+
}));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
// Run agent to fix build errors
|
|
217
|
+
const { runOneShotAgent } = await import("./oneshot.js");
|
|
218
|
+
const fixPrompt = `The previous change caused build errors. Please fix them. Build command: ${buildCommand}\n\nBuild errors:\n${buildErrors.slice(0, 10).join("\n")}${buildErrors.length > 10 ? `\n... and ${buildErrors.length - 10} more errors` : ""}\n\nFix these errors by making minimal changes to the files. Attempt ${context.retryCount} of ${MAX_RETRIES}.`;
|
|
219
|
+
const controller = new AbortController();
|
|
220
|
+
taskControllers.set(taskId, controller);
|
|
221
|
+
socket.send(JSON.stringify({ type: "agent_status", taskId, status: "thinking", message: `Fixing build errors (attempt ${context.retryCount}/${MAX_RETRIES})...` }));
|
|
222
|
+
try {
|
|
223
|
+
const result = await runOneShotAgent(cwd, fixPrompt, "project", { provider: "google", model: "gemini-2.5-flash", lassoKey: resolvedLassoKey, serverUrl: "https://api.lasso.byorello.space" }, controller.signal, (status, message, detail) => {
|
|
224
|
+
if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
|
|
225
|
+
socket.send(JSON.stringify({ type: "agent_status", taskId, status, message, detail }));
|
|
226
|
+
}
|
|
227
|
+
}, (prompt) => {
|
|
228
|
+
if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
|
|
229
|
+
socket.send(JSON.stringify({ type: "agent_prompt", taskId, prompt }));
|
|
230
|
+
}
|
|
231
|
+
}, undefined, "https://api.lasso.byorello.space", resolvedLassoKey || agentConfig?.apiKey || collabConfig?.apiKey, taskId, undefined);
|
|
232
|
+
if (result.ok && result.changes && result.changes.length > 0) {
|
|
233
|
+
// Apply the fix
|
|
234
|
+
const snapshots = [];
|
|
235
|
+
const planned = new Map();
|
|
236
|
+
for (const change of result.changes) {
|
|
237
|
+
const isNew = !change.oldString;
|
|
238
|
+
const filePath = resolveProposedFile(cwd, change.filePath, true);
|
|
239
|
+
const exists = node_fs_1.default.existsSync(filePath);
|
|
240
|
+
const content = exists ? node_fs_1.default.readFileSync(filePath, "utf8") : "";
|
|
241
|
+
const prepared = (!exists || isNew)
|
|
242
|
+
? { start: 0, end: content.length, oldString: content, newString: change.newString }
|
|
243
|
+
: prepareChange(content, change);
|
|
244
|
+
const fileChanges = planned.get(filePath) || [];
|
|
245
|
+
if (fileChanges.some((item) => prepared.start < item.end && item.start < prepared.end)) {
|
|
246
|
+
throw new Error(`Could not safely apply ${change.filePath}. Proposed changes overlap.`);
|
|
247
|
+
}
|
|
248
|
+
fileChanges.push({ filePath, content, exists, ...prepared });
|
|
249
|
+
planned.set(filePath, fileChanges);
|
|
250
|
+
}
|
|
251
|
+
taskSnapshots.set(taskId, snapshots);
|
|
252
|
+
for (const [filePath, changes] of planned) {
|
|
253
|
+
const exists = changes[0].exists;
|
|
254
|
+
const content = changes[0].content;
|
|
255
|
+
snapshots.push({ filePath, content: exists ? content : null });
|
|
256
|
+
const nextContent = exists
|
|
257
|
+
? [...changes]
|
|
258
|
+
.sort((a, b) => b.start - a.start)
|
|
259
|
+
.reduce((value, change) => value.slice(0, change.start) + change.newString + value.slice(change.end), content)
|
|
260
|
+
: changes[0].newString;
|
|
261
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(filePath), { recursive: true });
|
|
262
|
+
node_fs_1.default.writeFileSync(filePath, nextContent);
|
|
263
|
+
}
|
|
264
|
+
// Validate the fix
|
|
265
|
+
const validation = await validateBuild(cwd);
|
|
266
|
+
if (validation.passed) {
|
|
267
|
+
// Build fixed!
|
|
268
|
+
buildErrorContext.delete(taskId);
|
|
269
|
+
socket.send(JSON.stringify({
|
|
270
|
+
type: "applied",
|
|
271
|
+
taskId,
|
|
272
|
+
message: `Build errors fixed after ${context.retryCount} attempt${context.retryCount > 1 ? "s" : ""}. ${result.changes.length} file${result.changes.length === 1 ? "" : "s"} updated.`,
|
|
273
|
+
validation
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
// Still failing - trigger another retry
|
|
278
|
+
socket.send(JSON.stringify({
|
|
279
|
+
type: "agent_status",
|
|
280
|
+
taskId,
|
|
281
|
+
status: "working",
|
|
282
|
+
message: `⚠️ Build still failing. Retrying...`,
|
|
283
|
+
detail: `Build command: ${validation.command}\nErrors:\n${validation.errors.slice(0, 5).join("\n")}${validation.errors.length > 5 ? `\n... and ${validation.errors.length - 5} more errors` : ""}`
|
|
284
|
+
}));
|
|
285
|
+
// Update context with new errors
|
|
286
|
+
buildErrorContext.set(taskId, {
|
|
287
|
+
errors: validation.errors,
|
|
288
|
+
command: validation.command,
|
|
289
|
+
originalChanges: originalChanges,
|
|
290
|
+
retryCount: context.retryCount
|
|
291
|
+
});
|
|
292
|
+
await triggerBuildFix(cwd, taskId, validation.errors, validation.command, originalChanges, resolvedLassoKey, agentConfig, collabConfig, socket, taskControllers, taskSnapshots, buildErrorContext);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
// Agent failed to produce a fix
|
|
297
|
+
socket.send(JSON.stringify({ type: "agent_status", taskId, status: "error", message: result.error || "Failed to fix build errors" }));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
socket.send(JSON.stringify({ type: "agent_status", taskId, status: "error", message: error instanceof Error ? error.message : "Build fix encountered an error" }));
|
|
302
|
+
}
|
|
303
|
+
finally {
|
|
304
|
+
if (taskControllers.get(taskId) === controller)
|
|
305
|
+
taskControllers.delete(taskId);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
150
308
|
async function validateBuild(cwd) {
|
|
151
309
|
const pkgPath = node_path_1.default.join(cwd, "package.json");
|
|
152
310
|
if (!node_fs_1.default.existsSync(pkgPath)) {
|
|
@@ -257,6 +415,7 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
|
|
|
257
415
|
const editRequests = new Map();
|
|
258
416
|
const editConfigs = new Map();
|
|
259
417
|
const reviewRefreshAttempts = new Map();
|
|
418
|
+
const buildErrorContext = new Map();
|
|
260
419
|
const envRoots = [cwd, node_path_1.default.join(cwd, "web"), node_path_1.default.join(cwd, "server")];
|
|
261
420
|
const fileEnv = envRoots.reduce((values, root) => ({
|
|
262
421
|
...values,
|
|
@@ -996,6 +1155,27 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
|
|
|
996
1155
|
}
|
|
997
1156
|
// Run build validation after applying changes
|
|
998
1157
|
const validation = await validateBuild(cwd);
|
|
1158
|
+
if (!validation.passed) {
|
|
1159
|
+
// Build failed - automatically trigger retry with build error context
|
|
1160
|
+
const errorContext = validation.errors.slice(0, 10).join("\n");
|
|
1161
|
+
socket.send(JSON.stringify({
|
|
1162
|
+
type: "agent_status",
|
|
1163
|
+
taskId: msg.taskId,
|
|
1164
|
+
status: "working",
|
|
1165
|
+
message: `⚠️ Build validation failed. Attempting to fix build errors automatically...`,
|
|
1166
|
+
detail: `Build command: ${validation.command}\nErrors:\n${errorContext}${validation.errors.length > 10 ? `\n... and ${validation.errors.length - 10} more errors` : ""}`
|
|
1167
|
+
}));
|
|
1168
|
+
// Store context for retry
|
|
1169
|
+
buildErrorContext.set(msg.taskId, {
|
|
1170
|
+
errors: validation.errors,
|
|
1171
|
+
command: validation.command,
|
|
1172
|
+
originalChanges: msg.changes,
|
|
1173
|
+
retryCount: 0
|
|
1174
|
+
});
|
|
1175
|
+
// Trigger automatic build fix
|
|
1176
|
+
await triggerBuildFix(cwd, msg.taskId, validation.errors, validation.command, msg.changes, resolvedLassoKey, agentConfig, collabConfig, socket, taskControllers, taskSnapshots, buildErrorContext);
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
999
1179
|
socket.send(JSON.stringify({
|
|
1000
1180
|
type: "applied",
|
|
1001
1181
|
taskId: msg.taskId,
|
package/dist/overlay.js
CHANGED
|
@@ -21640,7 +21640,7 @@
|
|
|
21640
21640
|
${errors.slice(0, 5).join("\n")}${errors.length > 5 ? `
|
|
21641
21641
|
...and ${errors.length - 5} more errors` : ""}
|
|
21642
21642
|
|
|
21643
|
-
|
|
21643
|
+
The agent is automatically attempting to fix the build errors...`;
|
|
21644
21644
|
} else if (passed) {
|
|
21645
21645
|
result = `${message.message}
|
|
21646
21646
|
|
package/package.json
CHANGED