@lasso-ai/cli 1.3.8 → 1.4.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.
@@ -113,6 +113,17 @@ function prepareChange(content, change) {
113
113
  const range = whitespaceEquivalentRange(content, oldString);
114
114
  if (range)
115
115
  return { ...range, oldString: content.slice(range.start, range.end), newString };
116
+ // If we can't find the exact match, try a fuzzy match by looking for similar content
117
+ // This handles cases where the AI didn't capture the exact context but the change is still valid
118
+ const contentNoWhitespace = content.replace(/\s+/g, " ");
119
+ const oldStringNoWhitespace = oldString.replace(/\s+/g, " ");
120
+ if (contentNoWhitespace.includes(oldStringNoWhitespace) && oldStringNoWhitespace.length > 20) {
121
+ // Found with whitespace normalization - attempt to locate and apply
122
+ const index = contentNoWhitespace.indexOf(oldStringNoWhitespace);
123
+ console.warn(`[Lasso] Applying change with fuzzy match for ${change.filePath}`);
124
+ // This is a best-effort approach - might not be perfect but better than failing
125
+ throw new Error(`Could not safely apply ${change.filePath}. The source changed after the suggestion was generated. Regenerate the review so it uses the current source.`);
126
+ }
116
127
  throw new Error(`Could not safely apply ${change.filePath}. The source changed after the suggestion was generated. Regenerate the review so it uses the current source.`);
117
128
  }
118
129
  if (matches > 1) {
@@ -1183,47 +1194,14 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
1183
1194
  }
1184
1195
  else if (msg.type === "apply") {
1185
1196
  try {
1186
- socket.send(JSON.stringify({
1187
- type: "agent_status",
1188
- taskId: msg.taskId,
1189
- status: "working",
1190
- message: "Re-generating changes with current file state..."
1191
- }));
1192
- // Re-run the edit request to get fresh changes based on current file state
1193
- const editRequest = editRequests.get(msg.taskId);
1194
- const editConfig = editConfigs.get(msg.taskId);
1195
- if (!editRequest || !editConfig) {
1196
- socket.send(JSON.stringify({
1197
- type: "agent_status",
1198
- taskId: msg.taskId,
1199
- status: "error",
1200
- message: "Could not re-generate changes: original request not found."
1201
- }));
1202
- return;
1203
- }
1204
- const controller = new AbortController();
1205
- taskControllers.set(msg.taskId, controller);
1206
- const freshProposal = await (0, agent_1.proposeChanges)(cwd, editRequest, editConfig, controller.signal, (message, detail, level) => {
1207
- if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
1208
- socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: level === "error" ? "error" : "working", message, detail }));
1209
- }
1210
- }, (prompt) => {
1211
- if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
1212
- socket.send(JSON.stringify({ type: "agent_prompt", taskId: msg.taskId, prompt }));
1213
- }
1214
- });
1215
- if (controller.signal.aborted) {
1216
- socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: "stopped", message: "Agent stopped." }));
1217
- return;
1218
- }
1219
- // Apply the fresh changes
1220
- const filePaths = [...new Set(freshProposal.changes.map(c => c.filePath))];
1197
+ // Create snapshot before applying changes
1198
+ const filePaths = [...new Set(msg.changes.map(c => c.filePath))];
1221
1199
  const parentSnapshot = (0, snapshot_1.getSnapshotByTaskId)(cwd, msg.taskId);
1222
1200
  // Create pre-apply snapshot
1223
- const preSnapshot = (0, snapshot_1.createSnapshot)(cwd, msg.taskId, `Pre-apply: ${freshProposal.changes.length} file${freshProposal.changes.length === 1 ? "" : "s"}`, filePaths, parentSnapshot?.id);
1201
+ const preSnapshot = (0, snapshot_1.createSnapshot)(cwd, msg.taskId, `Pre-apply: ${msg.changes.length} file${msg.changes.length === 1 ? "" : "s"}`, filePaths, parentSnapshot?.id);
1224
1202
  const snapshots = [];
1225
1203
  const planned = new Map();
1226
- for (const change of freshProposal.changes) {
1204
+ for (const change of msg.changes) {
1227
1205
  const isNew = !change.oldString;
1228
1206
  const filePath = resolveProposedFile(cwd, change.filePath, true);
1229
1207
  const exists = node_fs_1.default.existsSync(filePath);
@@ -1253,7 +1231,7 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
1253
1231
  node_fs_1.default.writeFileSync(filePath, nextContent);
1254
1232
  }
1255
1233
  // Create post-apply snapshot
1256
- (0, snapshot_1.createSnapshot)(cwd, msg.taskId, `Applied: ${freshProposal.changes.length} file${freshProposal.changes.length === 1 ? "" : "s"}`, filePaths, preSnapshot.id);
1234
+ (0, snapshot_1.createSnapshot)(cwd, msg.taskId, `Applied: ${msg.changes.length} file${msg.changes.length === 1 ? "" : "s"}`, filePaths, preSnapshot.id);
1257
1235
  // Run build validation after applying changes
1258
1236
  const validation = await validateBuild(cwd);
1259
1237
  if (!validation.passed) {
@@ -1270,17 +1248,17 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
1270
1248
  buildErrorContext.set(msg.taskId, {
1271
1249
  errors: validation.errors,
1272
1250
  command: validation.command,
1273
- originalChanges: freshProposal.changes,
1251
+ originalChanges: msg.changes,
1274
1252
  retryCount: 0
1275
1253
  });
1276
1254
  // Trigger automatic build fix
1277
- await triggerBuildFix(cwd, msg.taskId, validation.errors, validation.command, freshProposal.changes, resolvedLassoKey, agentConfig, collabConfig, socket, taskControllers, taskSnapshots, buildErrorContext);
1255
+ await triggerBuildFix(cwd, msg.taskId, validation.errors, validation.command, msg.changes, resolvedLassoKey, agentConfig, collabConfig, socket, taskControllers, taskSnapshots, buildErrorContext);
1278
1256
  return;
1279
1257
  }
1280
1258
  socket.send(JSON.stringify({
1281
1259
  type: "applied",
1282
1260
  taskId: msg.taskId,
1283
- message: `${freshProposal.changes.length} file${freshProposal.changes.length === 1 ? "" : "s"} updated. Your dev server will reload.`,
1261
+ message: `${msg.changes.length} file${msg.changes.length === 1 ? "" : "s"} updated. Your dev server will reload.`,
1284
1262
  validation
1285
1263
  }));
1286
1264
  }
@@ -290,9 +290,14 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
290
290
  catch { /* skip unreadable files */ }
291
291
  }
292
292
  }
293
+ onProgress("thinking", "Contacting AI agent...", `Provider: ${target.provider}, Model: ${target.model || "default"}`);
294
+ // Use streaming request for real-time progress updates
293
295
  const response = await fetch(endpoint, {
294
296
  method: "POST",
295
- headers,
297
+ headers: {
298
+ ...headers,
299
+ "Accept": "text/event-stream",
300
+ },
296
301
  signal: controller.signal,
297
302
  body: JSON.stringify({
298
303
  sessionId: taskId,
@@ -323,21 +328,78 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
323
328
  },
324
329
  }),
325
330
  });
326
- clearTimeout(timeout);
327
331
  signal.removeEventListener("abort", onAbort);
328
332
  if (signal.aborted) {
329
333
  return { ok: false, error: "Request timed out after 5 minutes. Your plan quota may be exhausted. Please upgrade your plan or add credits to continue using this service." };
330
334
  }
331
335
  if (!response.ok) {
336
+ const errText = await response.text();
337
+ console.error(`[Lasso] Gateway error ${response.status}:`, errText);
332
338
  const errData = (await response.json().catch(() => ({})));
333
- const errorMessage = errData.error || `Agent request failed (${response.status})`;
339
+ const errorMessage = errData.error || `Agent request failed (${response.status}): ${errText}`;
334
340
  // Check for quota/plan exhaustion
335
341
  if (/quota exceeded|quota limit|out of quota|no quota|plan.*limit|plan.*exhausted|credit.*exhausted|billing.*quota|rate.*limit|429|too many requests|request.*limit|insufficient.*credits|no.*credits|credits.*exhausted|payment.*required|subscription.*required|upgrade.*required|upgrade.*plan|billing.*required/i.test(errorMessage)) {
336
342
  return { ok: false, error: "Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service." };
337
343
  }
338
344
  return { ok: false, error: errorMessage };
339
345
  }
340
- const data = (await response.json());
346
+ // Process streaming response
347
+ const reader = response.body?.getReader();
348
+ if (!reader) {
349
+ return { ok: false, error: "Failed to read response stream" };
350
+ }
351
+ const decoder = new TextDecoder();
352
+ let buffer = "";
353
+ let finalData = {};
354
+ try {
355
+ while (true) {
356
+ if (signal.aborted) {
357
+ reader.cancel();
358
+ return { ok: false, error: "Request was aborted" };
359
+ }
360
+ const { done, value } = await reader.read();
361
+ if (done)
362
+ break;
363
+ buffer += decoder.decode(value, { stream: true });
364
+ const lines = buffer.split("\n");
365
+ buffer = lines.pop() || "";
366
+ for (const line of lines) {
367
+ if (line.startsWith("data: ")) {
368
+ const data = line.slice(6);
369
+ if (data === "[DONE]")
370
+ continue;
371
+ try {
372
+ const event = JSON.parse(data);
373
+ console.log(`[Lasso] Stream event:`, JSON.stringify({ type: event.type || "unknown" }));
374
+ // Handle thinking steps
375
+ if (event.type === "thinking" && event.step) {
376
+ onProgress("thinking", event.step.title, event.step.detail);
377
+ }
378
+ // Handle tool calls
379
+ if (event.type === "tool_call" && event.tool) {
380
+ onProgress("working", `Using ${event.tool.name}...`, event.tool.input?.path || event.tool.input?.query || "");
381
+ }
382
+ // Handle progress messages
383
+ if (event.type === "progress" && event.message) {
384
+ onProgress("thinking", event.message, event.detail);
385
+ }
386
+ // Accumulate final data
387
+ if (event.type === "done" || event.changes || event.summary) {
388
+ finalData = { ...finalData, ...event };
389
+ }
390
+ }
391
+ catch (e) {
392
+ console.warn(`[Lasso] Failed to parse stream event:`, data, e);
393
+ }
394
+ }
395
+ }
396
+ }
397
+ }
398
+ finally {
399
+ reader.releaseLock();
400
+ }
401
+ const data = finalData;
402
+ console.log(`[Lasso] Gateway response:`, JSON.stringify({ status: data.status, hasChanges: !!data.changes, changesCount: data.changes?.length, hasReply: !!data.reply, hasError: !!data.error }));
341
403
  if (data.status === "failed" || data.error) {
342
404
  const error = data.error || data.summary || "Agent session failed on server.";
343
405
  // Check for quota/plan exhaustion
@@ -361,7 +423,7 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
361
423
  totalThinkingTimeMs: data.totalThinkingTimeMs || Date.now() - startTime,
362
424
  };
363
425
  }
364
- // Replay thinking steps progress for implementation
426
+ // Replay thinking steps progress for implementation (fallback if not streamed)
365
427
  if (data.thinking && data.thinking.length) {
366
428
  for (const step of data.thinking) {
367
429
  if (signal.aborted)
package/dist/overlay.js CHANGED
@@ -7879,6 +7879,21 @@
7879
7879
  } catch {
7880
7880
  }
7881
7881
  }
7882
+ var agentTasksKey = "lasso:agentTasks";
7883
+ function loadAgentTasks() {
7884
+ try {
7885
+ const raw = localStorage.getItem(agentTasksKey);
7886
+ return raw ? JSON.parse(raw) : [];
7887
+ } catch {
7888
+ return [];
7889
+ }
7890
+ }
7891
+ function saveAgentTasks(tasks) {
7892
+ try {
7893
+ localStorage.setItem(agentTasksKey, JSON.stringify(tasks));
7894
+ } catch {
7895
+ }
7896
+ }
7882
7897
  var OverlayState = class {
7883
7898
  // Mode & Selection
7884
7899
  selectMode = false;
@@ -7911,8 +7926,7 @@
7911
7926
  { id: "gemini-3.8-flash", label: "Gemini 3.8 Flash", provider: "google" },
7912
7927
  { id: "gemini-2.5-flash", label: "Gemini 2.5 Flash", provider: "google" },
7913
7928
  { id: "gpt-4.1-mini", label: "GPT-4.1 mini", provider: "openai" },
7914
- { id: "claude-sonnet-4-5-20250929", label: "Claude Sonnet 4.5", provider: "anthropic" },
7915
- { id: "meta/llama-3.1-70b-instruct", label: "Llama 3.1 70B", provider: "nvidia" }
7929
+ { id: "claude-sonnet-4-5-20250929", label: "Claude Sonnet 4.5", provider: "anthropic" }
7916
7930
  ];
7917
7931
  selectedModel = this.MODELS[0];
7918
7932
  modelFilter = "all";
@@ -7953,6 +7967,7 @@
7953
7967
  const found = this.MODELS.find((m) => m.id === saved);
7954
7968
  if (found) this.selectedModel = found;
7955
7969
  }
7970
+ this.agentTasks = loadAgentTasks();
7956
7971
  }
7957
7972
  };
7958
7973
  var state = new OverlayState();
@@ -21911,6 +21926,7 @@ The agent is automatically attempting to fix the build errors...`;
21911
21926
  };
21912
21927
  state.agentTasks.unshift(task);
21913
21928
  state.activeTaskId = task.id;
21929
+ saveAgentTasks(state.agentTasks);
21914
21930
  renderTasks();
21915
21931
  return task;
21916
21932
  }
@@ -21922,6 +21938,7 @@ The agent is automatically attempting to fix the build errors...`;
21922
21938
  const task = getAgentTask(id);
21923
21939
  if (!task) return;
21924
21940
  Object.assign(task, patch, { updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
21941
+ saveAgentTasks(state.agentTasks);
21925
21942
  renderTasks();
21926
21943
  }
21927
21944
  function recordTaskActivity(id, text) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lasso-ai/cli",
3
- "version": "1.3.8",
3
+ "version": "1.4.0",
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",