@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.
@@ -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
- You may need to manually fix these errors or undo the change.`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lasso-ai/cli",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "Select any part of your running app, describe a change, and let AI edit the real source code.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/cli/index.js",