@lasso-ai/cli 1.0.27 → 1.0.29

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/agent.js CHANGED
@@ -202,9 +202,36 @@ function extractLocalAgentText(raw, provider) {
202
202
  }
203
203
  return texts.at(-1) || raw;
204
204
  }
205
+ function collectTextFields(value, fields = new Set(["text", "output_text", "result", "content", "message"]), depth = 0) {
206
+ if (depth > 8 || value == null)
207
+ return [];
208
+ if (typeof value === "string")
209
+ return [value];
210
+ if (Array.isArray(value))
211
+ return value.flatMap((entry) => collectTextFields(entry, fields, depth + 1));
212
+ if (typeof value !== "object")
213
+ return [];
214
+ const output = [];
215
+ for (const [key, entry] of Object.entries(value)) {
216
+ if (fields.has(key) && typeof entry === "string")
217
+ output.push(entry);
218
+ if (typeof entry === "object" && entry !== null)
219
+ output.push(...collectTextFields(entry, fields, depth + 1));
220
+ }
221
+ return output;
222
+ }
205
223
  function extractLocalAgentProposal(raw, provider) {
206
224
  const outputs = [];
207
- for (const line of raw.split(/\r?\n/)) {
225
+ const lines = raw.split(/\r?\n/).filter((line) => line.trim());
226
+ const streamObjects = jsonObjectCandidates(raw).flatMap((candidate) => {
227
+ try {
228
+ return [JSON.parse(candidate)];
229
+ }
230
+ catch {
231
+ return [];
232
+ }
233
+ });
234
+ for (const line of lines) {
208
235
  try {
209
236
  const event = JSON.parse(line);
210
237
  const item = event.item;
@@ -212,7 +239,7 @@ function extractLocalAgentProposal(raw, provider) {
212
239
  const values = provider === "claude-code"
213
240
  ? [event.result, ...(event.message?.content || []).map((entry) => entry.text)]
214
241
  : provider === "opencode"
215
- ? [part?.type === "text" ? part.text : undefined, event.text, event.output_text]
242
+ ? [...collectTextFields(event), part?.type === "text" ? part.text : undefined]
216
243
  : [event.text, event.output_text, item?.text, item?.output_text, item?.message];
217
244
  for (const value of values)
218
245
  if (typeof value === "string" && value.trim())
@@ -223,6 +250,15 @@ function extractLocalAgentProposal(raw, provider) {
223
250
  outputs.push(line);
224
251
  }
225
252
  }
253
+ // OpenCode can emit multiple JSON records on one physical line. The
254
+ // line-based pass above cannot see those records, so inspect each balanced
255
+ // JSON object as well and collect text nested in its final response event.
256
+ for (const event of streamObjects) {
257
+ outputs.push(...collectTextFields(event));
258
+ const part = event.part;
259
+ if (part?.type === "text" && typeof part.text === "string")
260
+ outputs.push(part.text);
261
+ }
226
262
  // Tool events are interleaved with the final answer. Try text events in
227
263
  // reverse order, then the complete stream as a final fallback.
228
264
  for (const output of [...outputs.reverse(), raw]) {
@@ -329,6 +365,21 @@ function progressFromLine(raw, provider) {
329
365
  }
330
366
  return null;
331
367
  }
368
+ function progressEventsFromRaw(raw, provider) {
369
+ const records = (() => {
370
+ try {
371
+ JSON.parse(raw);
372
+ return [raw];
373
+ }
374
+ catch {
375
+ return jsonObjectCandidates(raw);
376
+ }
377
+ })();
378
+ return records.flatMap((record) => {
379
+ const progress = progressFromLine(record, provider);
380
+ return progress ? [progress] : [];
381
+ });
382
+ }
332
383
  function localAgentError(command, stderr, exitCode) {
333
384
  const output = stderr.trim();
334
385
  if (command === "codex" && output.includes("missing field `base_instructions`")) {
@@ -367,7 +418,6 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress) {
367
418
  let timedOut = false;
368
419
  const timeout = setTimeout(() => {
369
420
  timedOut = true;
370
- onProgress?.(`${label} did not respond; stopping this task…`);
371
421
  child.kill("SIGTERM");
372
422
  setTimeout(() => child.kill("SIGKILL"), 3000).unref();
373
423
  }, 5 * 60 * 1000);
@@ -390,11 +440,10 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress) {
390
440
  else
391
441
  pendingStderr = remainder;
392
442
  for (const line of lines) {
393
- const progress = progressFromLine(line, provider);
394
- if (progress)
443
+ for (const progress of progressEventsFromRaw(line, provider)) {
395
444
  onProgress?.(progress.message, progress.detail);
396
- else if (stream === "stderr" && line.trim()) {
397
- onProgress?.(`${label} · ${snippet(line, 110)}`);
445
+ }
446
+ if (stream === "stderr" && line.trim()) {
398
447
  if (provider === "opencode" && /quota exceeded|authentication failed|invalid api key|unauthorized|forbidden/i.test(line)) {
399
448
  onProgress?.("OpenCode · provider rejected the request; stopping this task…");
400
449
  child.kill("SIGTERM");
@@ -404,12 +453,6 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress) {
404
453
  };
405
454
  child.stdout.on("data", (chunk) => consume(chunk, "stdout"));
406
455
  child.stderr.on("data", (chunk) => consume(chunk, "stderr"));
407
- child.once("spawn", () => onProgress?.(`${label} process started${child.pid ? ` · PID ${child.pid}` : ""}`));
408
- const heartbeat = setInterval(() => {
409
- if (!signal?.aborted)
410
- onProgress?.(`${label} is still running${child.pid ? ` · PID ${child.pid}` : ""}…`);
411
- }, 15000);
412
- heartbeat.unref();
413
456
  const abort = () => child.kill("SIGTERM");
414
457
  if (signal?.aborted)
415
458
  abort();
@@ -420,7 +463,6 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress) {
420
463
  return;
421
464
  settled = true;
422
465
  clearTimeout(timeout);
423
- clearInterval(heartbeat);
424
466
  signal?.removeEventListener("abort", abort);
425
467
  reject(error);
426
468
  });
@@ -429,7 +471,6 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress) {
429
471
  return;
430
472
  settled = true;
431
473
  clearTimeout(timeout);
432
- clearInterval(heartbeat);
433
474
  signal?.removeEventListener("abort", abort);
434
475
  if (timedOut) {
435
476
  reject(new Error(`${label} did not finish within 5 minutes. Check its login or approval prompt, then retry.`));
@@ -439,14 +480,17 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress) {
439
480
  const stderrTail = pendingStderr.trim();
440
481
  if (stdoutTail) {
441
482
  stdout += pendingStdout;
442
- const progress = progressFromLine(stdoutTail, provider);
443
- if (progress)
483
+ for (const progress of progressEventsFromRaw(stdoutTail, provider)) {
444
484
  onProgress?.(progress.message, progress.detail);
485
+ }
445
486
  }
446
487
  if (stderrTail) {
447
- const progress = progressFromLine(stderrTail, provider);
448
- if (progress)
488
+ for (const progress of progressEventsFromRaw(stderrTail, provider)) {
449
489
  onProgress?.(progress.message, progress.detail);
490
+ }
491
+ if (provider === "opencode" && /quota exceeded|authentication failed|invalid api key|unauthorized|forbidden/i.test(stderrTail)) {
492
+ onProgress?.("OpenCode · provider rejected the request; stopping this task…");
493
+ }
450
494
  }
451
495
  resolve({ stdout, stderr, exitCode: code ?? 1 });
452
496
  });
@@ -464,9 +508,7 @@ async function proposeWithLocalAgent(cwd, instruction, context, config, signal,
464
508
  return extractLocalAgentProposal(result.stdout, config.provider);
465
509
  }
466
510
  async function proposeChanges(cwd, input, config, signal, onProgress) {
467
- onProgress?.("Reading source context…");
468
511
  const context = await contextFor(cwd, input.element);
469
- onProgress?.("Starting the coding agent…");
470
512
  const visualContext = input.context ? { ...input.context, screenshots: undefined } : undefined;
471
513
  const history = input.messages?.map((message) => `${message.role}: ${message.content}`).join("\n") || input.instruction;
472
514
  const priorChanges = input.changesHistory?.length ? JSON.stringify(input.changesHistory, null, 2) : "None";
@@ -278,7 +278,7 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
278
278
  const controller = new AbortController();
279
279
  taskControllers.set(request.taskId, controller);
280
280
  if (socket.readyState === socket.OPEN) {
281
- socket.send(JSON.stringify({ type: "agent_status", taskId: request.taskId, status: "working", message: statusMessage || "Starting agent…" }));
281
+ socket.send(JSON.stringify({ type: "agent_status", taskId: request.taskId, status: "working", message: statusMessage || "Working…" }));
282
282
  }
283
283
  void (0, agent_1.proposeChanges)(cwd, request, config, controller.signal, (message, detail) => {
284
284
  if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
@@ -336,7 +336,7 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
336
336
  const selectedConfig = localProvider
337
337
  ? { provider: cliProvider, model: msg.model }
338
338
  : { ...agentConfig, provider: (msg.provider || agentConfig.provider), model: msg.model };
339
- socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: "working", message: "Starting agent…" }));
339
+ socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: "working", message: "Working…" }));
340
340
  void (0, agent_1.answerQuestion)(cwd, { question: msg.question, context: msg.context, element: msg.element, messages: msg.messages }, selectedConfig, controller.signal, (message, detail) => {
341
341
  if (!controller.signal.aborted && socket.readyState === socket.OPEN)
342
342
  socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: "working", message, detail }));
package/dist/overlay.js CHANGED
@@ -12151,6 +12151,15 @@
12151
12151
  return Boolean(other) && change.filePath === other.filePath && change.oldString === other.oldString && change.newString === other.newString;
12152
12152
  });
12153
12153
  }
12154
+ function normalizedInstruction(value2) {
12155
+ return value2.trim().replace(/\s+/g, " ").toLowerCase();
12156
+ }
12157
+ function findReusableAgentTask(instruction, element) {
12158
+ const normalized = normalizedInstruction(instruction);
12159
+ return state.agentTasks.find(
12160
+ (task) => task.status !== "complete" && task.status !== "error" && task.status !== "stopped" && normalizedInstruction(task.instruction) === normalized && task.element === (element || void 0)
12161
+ );
12162
+ }
12154
12163
  function onAgentTaskSelected(listener) {
12155
12164
  selectionListeners.add(listener);
12156
12165
  return () => selectionListeners.delete(listener);
@@ -18504,6 +18513,9 @@
18504
18513
  cli: terminal_default
18505
18514
  };
18506
18515
  function providerIcon(provider, active = false) {
18516
+ if (provider === "cli") {
18517
+ return `<span class="${active ? "lasso-model-active-icon" : "lasso-model-item-icon"} provider-cli" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" focusable="false"><circle cx="12" cy="12" r="9" fill="currentColor" opacity=".16"/><circle cx="12" cy="9" r="3" fill="currentColor"/><path d="M6.5 18c.9-2.4 2.7-3.6 5.5-3.6s4.6 1.2 5.5 3.6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></span>`;
18518
+ }
18507
18519
  const icon = providerIcons[provider];
18508
18520
  return `<span class="${active ? "lasso-model-active-icon" : "lasso-model-item-icon"} provider-${provider}" aria-hidden="true"><svg viewBox="0 0 ${icon.width} ${icon.height}" xmlns="http://www.w3.org/2000/svg" focusable="false">${icon.body}</svg></span>`;
18509
18521
  }
@@ -19031,7 +19043,7 @@
19031
19043
  appendAgentLog(logMessage);
19032
19044
  if (taskId2) recordTaskActivity(taskId2, logMessage);
19033
19045
  const running = status === "thinking" || status === "working";
19034
- agentStatusElement.hidden = status === "review";
19046
+ agentStatusElement.hidden = status === "review" || status === "complete";
19035
19047
  agentStatusElement.dataset.status = status;
19036
19048
  agentStatusMessage.textContent = message;
19037
19049
  const badgeEl = agentStatusElement.querySelector(".lasso-agent-status-badge");
@@ -19062,18 +19074,9 @@
19062
19074
  }
19063
19075
  function resetAgentState() {
19064
19076
  state.agentRunning = false;
19065
- const hasLog = agentLogLines.length > 0;
19066
19077
  if (agentStatusElement) {
19067
- agentStatusElement.hidden = !hasLog;
19068
- agentStatusElement.dataset.status = hasLog ? "complete" : "idle";
19069
- }
19070
- if (hasLog && agentStatusMessage) agentStatusMessage.textContent = "Agent complete";
19071
- if (hasLog) {
19072
- const badgeEl = agentStatusElement?.querySelector(".lasso-agent-status-badge");
19073
- if (badgeEl) {
19074
- badgeEl.textContent = "done";
19075
- badgeEl.className = "lasso-agent-status-badge complete";
19076
- }
19078
+ agentStatusElement.hidden = true;
19079
+ agentStatusElement.dataset.status = "idle";
19077
19080
  }
19078
19081
  if (sendButton) {
19079
19082
  sendButton.classList.remove("loading");
@@ -19258,6 +19261,11 @@
19258
19261
  const selectionId = state.selectionId;
19259
19262
  const model = state.selectedModel;
19260
19263
  const screenshotPromise = state.screenshotPromise;
19264
+ const reusableTask = findReusableAgentTask(instruction, selectedElement);
19265
+ if (reusableTask) {
19266
+ selectAgentTask(reusableTask.id);
19267
+ return;
19268
+ }
19261
19269
  appendChat("user", instruction);
19262
19270
  const messages = state.chatHistory.map((message) => ({ ...message }));
19263
19271
  const changesHistory = state.changesHistory.map((entry) => ({ ...entry, changes: entry.changes.map((change) => ({ ...change })) }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lasso-ai/cli",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
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",