@lasso-ai/cli 1.0.19 → 1.0.21

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.
@@ -134,6 +134,9 @@ export type ServerBridgeMessage = {
134
134
  type: "git_result";
135
135
  message?: string;
136
136
  error?: string;
137
+ } | {
138
+ type: "git_progress";
139
+ message: string;
137
140
  } | {
138
141
  type: "git_commit_message";
139
142
  message?: string;
@@ -277,8 +277,8 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
277
277
  taskControllers.get(request.taskId)?.abort();
278
278
  const controller = new AbortController();
279
279
  taskControllers.set(request.taskId, controller);
280
- if (statusMessage && socket.readyState === socket.OPEN) {
281
- socket.send(JSON.stringify({ type: "agent_status", taskId: request.taskId, status: "working", message: statusMessage }));
280
+ if (socket.readyState === socket.OPEN) {
281
+ socket.send(JSON.stringify({ type: "agent_status", taskId: request.taskId, status: "working", message: statusMessage || "Starting agent…" }));
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,6 +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
340
  void (0, agent_1.answerQuestion)(cwd, { question: msg.question, context: msg.context, element: msg.element, messages: msg.messages }, selectedConfig, controller.signal, (message, detail) => {
340
341
  if (!controller.signal.aborted && socket.readyState === socket.OPEN)
341
342
  socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: "working", message, detail }));
@@ -396,10 +397,10 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
396
397
  const controller = new AbortController();
397
398
  activeAgentController?.abort();
398
399
  activeAgentController = controller;
399
- socket.send(JSON.stringify({ type: "agent_status", status: "thinking", message: "Generating a commit message…" }));
400
+ socket.send(JSON.stringify({ type: "git_progress", message: "Generating a commit message…" }));
400
401
  void getGitState(cwd).then((git) => (0, agent_1.generateCommitMessage)(cwd, git.status || [], selectedConfig, controller.signal, (message, detail) => {
401
402
  if (!controller.signal.aborted && socket.readyState === socket.OPEN)
402
- socket.send(JSON.stringify({ type: "agent_status", status: "working", message, detail }));
403
+ socket.send(JSON.stringify({ type: "git_progress", message: detail ? `${message} · ${detail}` : message }));
403
404
  })).then((message) => {
404
405
  if (!controller.signal.aborted)
405
406
  socket.send(JSON.stringify({ type: "git_commit_message", message }));
@@ -518,7 +519,7 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
518
519
  runEditReview(editRequest, editConfig, "The source changed. Refreshing the review against the current file…");
519
520
  }
520
521
  else {
521
- socket.send(JSON.stringify({ type: "agent_status", status: "error", message }));
522
+ socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: "error", message }));
522
523
  }
523
524
  }
524
525
  }
package/dist/overlay.js CHANGED
@@ -7880,6 +7880,7 @@
7880
7880
  pendingChanges = [];
7881
7881
  agentTasks = [];
7882
7882
  activeTaskId = null;
7883
+ promptTaskId = null;
7883
7884
  runtimeErrors = [];
7884
7885
  screenshotPromise = Promise.resolve({});
7885
7886
  // Agent State
@@ -7904,7 +7905,7 @@
7904
7905
  myUser = null;
7905
7906
  presenceUsers = /* @__PURE__ */ new Map();
7906
7907
  lockMap = /* @__PURE__ */ new Map();
7907
- heldLockElement = "";
7908
+ heldLockElements = /* @__PURE__ */ new Set();
7908
7909
  spotlightUserId = null;
7909
7910
  // Comments
7910
7911
  commentThreads = /* @__PURE__ */ new Map();
@@ -10933,6 +10934,7 @@
10933
10934
  .lasso-task-detail-status { color: var(--lo-primary); font-size: 11px; font-weight: 600; }
10934
10935
  .lasso-task-prompt { margin: 0 0 10px; font-size: 13px; line-height: 1.45; }
10935
10936
  .lasso-task-message { margin: 0 0 12px; line-height: 1.45; }
10937
+ .lasso-task-activity { display: flex; flex-direction: column; gap: 4px; max-height: 130px; margin: 0 0 12px; padding: 8px; overflow: auto; border-radius: var(--lo-radius-md); background: var(--lo-surface-2); color: var(--lo-text-3); font-size: 10.5px; line-height: 1.35; }
10936
10938
  .lasso-task-changes { margin-bottom: 10px; color: var(--lo-text-2); font-size: 11px; }
10937
10939
  .lasso-task-apply { padding: 7px 11px; border: 0; border-radius: var(--lo-radius-full); background: var(--lo-primary); color: #111214; font: inherit; font-size: 11px; font-weight: 600; cursor: pointer; }
10938
10940
 
@@ -12125,6 +12127,7 @@
12125
12127
  var list = null;
12126
12128
  var detail = null;
12127
12129
  var badge = null;
12130
+ var selectionListeners = /* @__PURE__ */ new Set();
12128
12131
  var statusLabel = {
12129
12132
  queued: "Queued",
12130
12133
  thinking: "Thinking",
@@ -12137,9 +12140,36 @@
12137
12140
  function taskId() {
12138
12141
  return `task-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
12139
12142
  }
12140
- function createAgentTask(instruction) {
12143
+ function sameChangeList(left, right) {
12144
+ return left.length === right.length && left.every((change, index) => {
12145
+ const other = right[index];
12146
+ return Boolean(other) && change.filePath === other.filePath && change.oldString === other.oldString && change.newString === other.newString;
12147
+ });
12148
+ }
12149
+ function onAgentTaskSelected(listener) {
12150
+ selectionListeners.add(listener);
12151
+ return () => selectionListeners.delete(listener);
12152
+ }
12153
+ function notifyTaskSelection(task) {
12154
+ for (const listener of selectionListeners) listener(task);
12155
+ }
12156
+ function createAgentTask(instruction, messages = state.chatHistory, changesHistory = state.changesHistory, element = state.selected, selectionId = state.selectionId) {
12141
12157
  const now = (/* @__PURE__ */ new Date()).toISOString();
12142
- const task = { id: taskId(), instruction, status: "queued", message: "Queued", createdAt: now, updatedAt: now };
12158
+ const task = {
12159
+ id: taskId(),
12160
+ instruction,
12161
+ status: "queued",
12162
+ message: "Queued",
12163
+ pendingChanges: [],
12164
+ messages: messages.map((message) => ({ ...message })),
12165
+ changesHistory: changesHistory.map((entry) => ({ ...entry, changes: entry.changes.map((change) => ({ ...change })) })),
12166
+ activity: ["Queued"],
12167
+ lastInstruction: instruction,
12168
+ selectionId,
12169
+ element: element || void 0,
12170
+ createdAt: now,
12171
+ updatedAt: now
12172
+ };
12143
12173
  state.agentTasks.unshift(task);
12144
12174
  state.activeTaskId = task.id;
12145
12175
  renderTasks();
@@ -12155,9 +12185,41 @@
12155
12185
  Object.assign(task, patch, { updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
12156
12186
  renderTasks();
12157
12187
  }
12188
+ function recordTaskActivity(id, text) {
12189
+ const task = getAgentTask(id);
12190
+ const value2 = text.trim();
12191
+ if (!task || !value2 || task.activity[task.activity.length - 1] === value2) return;
12192
+ updateAgentTask(id, { activity: [...task.activity, value2].slice(-200) });
12193
+ }
12194
+ function recordTaskChangeHistory(id, summary, changes) {
12195
+ const task = getAgentTask(id);
12196
+ if (!task) return;
12197
+ const previous = task.changesHistory[task.changesHistory.length - 1];
12198
+ if (previous?.summary === summary && sameChangeList(previous.changes, changes)) return;
12199
+ updateAgentTask(id, {
12200
+ changesHistory: [...task.changesHistory, { summary, changes: changes.map((change) => ({ ...change })), createdAt: (/* @__PURE__ */ new Date()).toISOString() }]
12201
+ });
12202
+ }
12203
+ function appendTaskMessage(id, role, text) {
12204
+ const task = getAgentTask(id);
12205
+ const content = text.trim();
12206
+ if (!task || !content) return;
12207
+ const previous = task.messages[task.messages.length - 1];
12208
+ if (previous?.role === role && previous.content === content) return;
12209
+ const message = {
12210
+ role,
12211
+ content,
12212
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
12213
+ contextId: task.selectionId || void 0
12214
+ };
12215
+ updateAgentTask(task.id, { messages: [...task.messages, message] });
12216
+ return message;
12217
+ }
12158
12218
  function selectAgentTask(id) {
12159
- if (!getAgentTask(id)) return;
12160
- state.activeTaskId = id;
12219
+ const task = getAgentTask(id);
12220
+ state.activeTaskId = task?.id || null;
12221
+ state.promptTaskId = task?.id || null;
12222
+ notifyTaskSelection(task || null);
12161
12223
  renderTasks();
12162
12224
  }
12163
12225
  function toggleTaskPanel(force) {
@@ -12194,9 +12256,11 @@
12194
12256
  }
12195
12257
  function renderTasks() {
12196
12258
  if (!list || !detail || !badge) return;
12197
- const running = state.agentTasks.filter((task) => task.status === "thinking" || task.status === "working").length;
12259
+ const activeCount = state.agentTasks.filter((task) => task.status !== "complete" && task.status !== "error" && task.status !== "stopped").length;
12198
12260
  badge.hidden = state.agentTasks.length === 0;
12199
- badge.textContent = String(running || state.agentTasks.length);
12261
+ badge.textContent = String(activeCount || state.agentTasks.length);
12262
+ const navBadge = getDOM().shadow.querySelector(".lasso-agent-tasks-badge");
12263
+ if (navBadge) navBadge.hidden = activeCount === 0;
12200
12264
  list.replaceChildren();
12201
12265
  if (!state.agentTasks.length) {
12202
12266
  list.innerHTML = `<div class="lasso-task-empty">Prompt an element to start a task.</div>`;
@@ -12221,10 +12285,7 @@
12221
12285
  const heading = document.createElement("div");
12222
12286
  heading.className = "lasso-task-detail-heading";
12223
12287
  heading.innerHTML = `<span class="lasso-task-detail-status ${selected.status}">${statusLabel[selected.status]}</span><button type="button" class="lasso-task-back">All tasks</button>`;
12224
- heading.querySelector("button").addEventListener("click", () => {
12225
- state.activeTaskId = null;
12226
- renderTasks();
12227
- });
12288
+ heading.querySelector("button").addEventListener("click", () => selectAgentTask(null));
12228
12289
  const prompt = document.createElement("p");
12229
12290
  prompt.className = "lasso-task-prompt";
12230
12291
  prompt.textContent = selected.instruction;
@@ -12232,10 +12293,21 @@
12232
12293
  activity.className = "lasso-task-message";
12233
12294
  activity.textContent = selected.response || selected.message;
12234
12295
  detail.append(heading, prompt, activity);
12235
- if (selected.changes?.length) {
12296
+ if (selected.activity.length) {
12297
+ const activityList = document.createElement("div");
12298
+ activityList.className = "lasso-task-activity";
12299
+ for (const entry of selected.activity.slice(-8)) {
12300
+ const line = document.createElement("div");
12301
+ line.textContent = entry;
12302
+ activityList.appendChild(line);
12303
+ }
12304
+ detail.appendChild(activityList);
12305
+ }
12306
+ const pendingChanges = selected.pendingChanges.length ? selected.pendingChanges : selected.changes || [];
12307
+ if (pendingChanges.length) {
12236
12308
  const changes = document.createElement("div");
12237
12309
  changes.className = "lasso-task-changes";
12238
- changes.textContent = `${selected.changes.length} proposed file change${selected.changes.length === 1 ? "" : "s"}`;
12310
+ changes.textContent = `${pendingChanges.length} proposed file change${pendingChanges.length === 1 ? "" : "s"}`;
12239
12311
  detail.appendChild(changes);
12240
12312
  if (selected.status === "review") {
12241
12313
  const apply = document.createElement("button");
@@ -12244,8 +12316,8 @@
12244
12316
  apply.textContent = "Apply changes";
12245
12317
  apply.addEventListener("click", () => {
12246
12318
  if (state.bridgeSocket?.readyState !== WebSocket.OPEN) return;
12247
- state.bridgeSocket.send(JSON.stringify({ type: "apply", taskId: selected.id, changes: selected.changes }));
12248
- updateAgentTask(selected.id, { message: "Applying changes\u2026" });
12319
+ state.bridgeSocket.send(JSON.stringify({ type: "apply", taskId: selected.id, changes: pendingChanges }));
12320
+ updateAgentTask(selected.id, { status: "working", message: "Applying changes\u2026", activity: [...selected.activity, "Applying changes\u2026"] });
12249
12321
  });
12250
12322
  detail.appendChild(apply);
12251
12323
  }
@@ -18236,10 +18308,10 @@
18236
18308
  state: "online",
18237
18309
  selection: currentSelectionPayload()
18238
18310
  });
18239
- if (state.heldLockElement) {
18311
+ for (const elementId of state.heldLockElements) {
18240
18312
  collabEmit("lock:extend", {
18241
18313
  sessionId: state.collabProjectId,
18242
- elementId: state.heldLockElement
18314
+ elementId
18243
18315
  });
18244
18316
  }
18245
18317
  }, 3e4);
@@ -18292,7 +18364,7 @@
18292
18364
  }
18293
18365
  const key = elementKey(state.selected);
18294
18366
  const lock = state.lockMap.get(key);
18295
- const mine = state.heldLockElement === key;
18367
+ const mine = state.heldLockElements.has(key);
18296
18368
  if (lock) {
18297
18369
  dom.heldLockChip.hidden = false;
18298
18370
  dom.heldLockChip.classList.toggle("held", mine);
@@ -18327,7 +18399,7 @@
18327
18399
  },
18328
18400
  (res) => {
18329
18401
  if (res.ok && res.lock) {
18330
- state.heldLockElement = key;
18402
+ state.heldLockElements.add(key);
18331
18403
  state.lockMap.set(key, res.lock);
18332
18404
  updateLockChip();
18333
18405
  resolve(true);
@@ -18346,14 +18418,16 @@
18346
18418
  );
18347
18419
  });
18348
18420
  }
18349
- function releaseHeldLock() {
18350
- if (!state.heldLockElement) return;
18351
- const elementId = state.heldLockElement;
18352
- state.heldLockElement = "";
18353
- updateLockChip();
18354
- if (state.collabSocket?.connected && state.collabJoined) {
18355
- collabEmit("lock:release", { sessionId: state.collabProjectId, elementId });
18421
+ function releaseHeldLock(elementId) {
18422
+ const elementIds = elementId ? [elementId] : Array.from(state.heldLockElements);
18423
+ for (const id of elementIds) {
18424
+ if (!state.heldLockElements.delete(id)) continue;
18425
+ state.lockMap.delete(id);
18426
+ if (state.collabSocket?.connected && state.collabJoined) {
18427
+ collabEmit("lock:release", { sessionId: state.collabProjectId, elementId: id });
18428
+ }
18356
18429
  }
18430
+ updateLockChip();
18357
18431
  }
18358
18432
 
18359
18433
  // src/overlay/icons/lasso.ts
@@ -18522,6 +18596,10 @@
18522
18596
  `;
18523
18597
  dom.shadow.appendChild(rev);
18524
18598
  reviewPanel = rev;
18599
+ onAgentTaskSelected((task) => {
18600
+ if (task) restoreTaskPrompt(task);
18601
+ else resetPromptSession();
18602
+ });
18525
18603
  const promptTop = el.querySelector(".lasso-prompt-top");
18526
18604
  let dragState = null;
18527
18605
  promptTop.addEventListener("pointerdown", (event) => {
@@ -18583,10 +18661,19 @@
18583
18661
  stopButton.addEventListener("click", (event) => {
18584
18662
  event.preventDefault();
18585
18663
  event.stopPropagation();
18664
+ const taskId2 = state.promptTaskId || void 0;
18665
+ const task = taskId2 ? getAgentTask(taskId2) : void 0;
18586
18666
  if (state.bridgeSocket?.readyState === WebSocket.OPEN) {
18587
- state.bridgeSocket.send(JSON.stringify({ type: "stop", taskId: state.activeTaskId || void 0 }));
18667
+ state.bridgeSocket.send(JSON.stringify({ type: "stop", taskId: taskId2 }));
18668
+ }
18669
+ if (taskId2) {
18670
+ appendChat("assistant", "Agent stopped.", taskId2);
18671
+ if (task) {
18672
+ updateAgentTask(task.id, { status: "stopped", message: "Agent stopped." });
18673
+ recordTaskActivity(task.id, "Agent stopped.");
18674
+ }
18588
18675
  }
18589
- appendChat("assistant", "Agent stopped.");
18676
+ if (task?.element) releaseHeldLock(elementKey(task.element));
18590
18677
  resetAgentState();
18591
18678
  });
18592
18679
  const voiceBtn = el.querySelector(".lasso-prompt-voice");
@@ -18643,20 +18730,22 @@
18643
18730
  });
18644
18731
  rev.querySelector(".lasso-review-close").addEventListener("click", closeReview);
18645
18732
  rev.querySelector(".lasso-review-undo").addEventListener("click", () => {
18733
+ const taskId2 = state.promptTaskId || void 0;
18646
18734
  if (state.pendingChanges.length && rev.querySelector(".lasso-review-apply").hidden) {
18647
18735
  if (state.bridgeSocket?.readyState === WebSocket.OPEN) {
18648
- state.bridgeSocket.send(JSON.stringify({ type: "undo", taskId: state.activeTaskId || void 0 }));
18736
+ state.bridgeSocket.send(JSON.stringify({ type: "undo", taskId: taskId2 }));
18649
18737
  }
18650
18738
  return;
18651
18739
  }
18652
18740
  closeReview();
18653
- appendChat("assistant", "Kept as a proposal. Nothing was changed.");
18741
+ appendChat("assistant", "Kept as a proposal. Nothing was changed.", taskId2);
18654
18742
  });
18655
18743
  rev.querySelector(".lasso-review-apply").addEventListener("click", () => {
18656
- if (!state.bridgeSocket || state.bridgeSocket.readyState !== WebSocket.OPEN || !state.pendingChanges.length) return;
18744
+ const taskId2 = state.promptTaskId;
18745
+ if (!taskId2 || !state.bridgeSocket || state.bridgeSocket.readyState !== WebSocket.OPEN || !state.pendingChanges.length) return;
18657
18746
  requestAgentNotificationPermission();
18658
- state.bridgeSocket.send(JSON.stringify({ type: "apply", taskId: state.activeTaskId || "", changes: state.pendingChanges }));
18659
- appendChat("assistant", "Applying the reviewed change\u2026");
18747
+ state.bridgeSocket.send(JSON.stringify({ type: "apply", taskId: taskId2, changes: state.pendingChanges }));
18748
+ appendChat("assistant", "Applying the reviewed change\u2026", taskId2);
18660
18749
  });
18661
18750
  return { prompt: el, review: rev };
18662
18751
  }
@@ -18825,29 +18914,50 @@
18825
18914
  text.textContent = value2;
18826
18915
  line.append(prefix, text);
18827
18916
  agentLogElement.append(line);
18828
- if (agentLogExpanded) agentLogElement.scrollTop = agentLogElement.scrollHeight;
18917
+ agentLogElement.scrollTop = agentLogElement.scrollHeight;
18829
18918
  }
18830
18919
  if (agentLogToggle) agentLogToggle.hidden = false;
18831
18920
  }
18832
- function appendChat(role, text) {
18921
+ function loadAgentLog(lines) {
18922
+ clearAgentLog();
18923
+ for (const line of lines) appendAgentLog(line);
18924
+ }
18925
+ function renderChatThread() {
18833
18926
  const thread = promptEl?.querySelector(".lasso-chat-thread");
18834
- if (!thread || !text.trim()) return;
18835
- const previous = thread.lastElementChild;
18836
- if (previous?.classList.contains(role) && previous.dataset.rawText === text) return;
18837
- state.chatHistory.push({
18838
- role,
18839
- content: text,
18840
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
18841
- contextId: state.selectionId || void 0
18842
- });
18843
- const item = document.createElement("div");
18844
- item.className = `lasso-chat-message ${role}`;
18845
- item.dataset.rawText = text;
18846
- renderChatText(item, text);
18847
- thread.appendChild(item);
18848
- while (thread.children.length > 6) thread.firstElementChild?.remove();
18927
+ if (!thread) return;
18928
+ thread.replaceChildren();
18929
+ for (const message of state.chatHistory.slice(-6)) {
18930
+ const item = document.createElement("div");
18931
+ item.className = `lasso-chat-message ${message.role}`;
18932
+ item.dataset.rawText = message.content;
18933
+ renderChatText(item, message.content);
18934
+ thread.appendChild(item);
18935
+ }
18849
18936
  thread.scrollTop = thread.scrollHeight;
18850
18937
  }
18938
+ function appendChat(role, text, taskId2 = state.promptTaskId) {
18939
+ const content = text.trim();
18940
+ if (!content) return;
18941
+ const task = taskId2 ? getAgentTask(taskId2) : void 0;
18942
+ if (taskId2 && !task) return;
18943
+ if (task) {
18944
+ const previous = task.messages[task.messages.length - 1];
18945
+ if (previous?.role === role && previous.content === content) return;
18946
+ const message = appendTaskMessage(task.id, role, content);
18947
+ if (!message || state.promptTaskId !== task.id) return;
18948
+ state.chatHistory = [...task.messages];
18949
+ } else {
18950
+ const previous = state.chatHistory[state.chatHistory.length - 1];
18951
+ if (previous?.role === role && previous.content === content) return;
18952
+ state.chatHistory.push({
18953
+ role,
18954
+ content,
18955
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
18956
+ contextId: state.selectionId || void 0
18957
+ });
18958
+ }
18959
+ renderChatThread();
18960
+ }
18851
18961
  function renderChatText(container, text) {
18852
18962
  const codePattern = /(`+)([\s\S]*?)\1/g;
18853
18963
  let lastIndex = 0;
@@ -18865,33 +18975,35 @@
18865
18975
  container.append(document.createTextNode(text.slice(lastIndex)));
18866
18976
  }
18867
18977
  }
18868
- function setAgentStatus(status, message, detail2) {
18978
+ function setAgentStatus(status, message, detail2, taskId2 = state.promptTaskId, appendMessage = true) {
18869
18979
  if (!agentStatusElement || !agentStatusMessage || !sendButton) return;
18870
18980
  const logMessage = detail2?.trim() || message.trim();
18871
18981
  const lastLog = agentLogLines[agentLogLines.length - 1];
18872
- if (agentStatusElement.dataset.status === status && agentStatusMessage.textContent === message && (status === "thinking" || status === "working") && (!logMessage || lastLog === logMessage)) return;
18982
+ if (agentStatusElement.dataset.status === status && agentStatusMessage.textContent === message && (!logMessage || lastLog === logMessage)) return;
18873
18983
  appendAgentLog(logMessage);
18984
+ if (taskId2) recordTaskActivity(taskId2, logMessage);
18985
+ const running = status === "thinking" || status === "working";
18874
18986
  agentStatusElement.hidden = status === "review";
18875
18987
  agentStatusElement.dataset.status = status;
18876
18988
  agentStatusMessage.textContent = message;
18877
18989
  const badgeEl = agentStatusElement.querySelector(".lasso-agent-status-badge");
18878
18990
  if (badgeEl) {
18879
- badgeEl.textContent = status === "thinking" ? "thinking" : status === "working" ? "executing" : status;
18991
+ badgeEl.textContent = status === "thinking" ? "thinking" : status === "working" ? "executing" : status === "complete" ? "done" : status;
18880
18992
  badgeEl.className = `lasso-agent-status-badge ${status}`;
18881
18993
  }
18882
- state.agentRunning = status === "thinking" || status === "working";
18883
- sendButton.classList.toggle("loading", state.agentRunning);
18884
- if (stopButton) stopButton.hidden = !state.agentRunning;
18994
+ state.agentRunning = running;
18995
+ sendButton.classList.toggle("loading", running);
18996
+ if (stopButton) stopButton.hidden = !running;
18885
18997
  if (promptInput) promptInput.disabled = false;
18886
18998
  const label = sendButton.querySelector("span");
18887
18999
  if (label) {
18888
- label.textContent = status === "review" ? "Review" : status === "error" ? "Retry" : state.agentRunning ? "" : "Send";
19000
+ label.textContent = status === "review" ? "Review" : status === "error" ? "Retry" : running ? "" : "Send";
18889
19001
  }
18890
- sendButton.setAttribute("aria-label", state.agentRunning ? "Agent is working" : status === "error" ? "Retry request" : "Send request");
18891
- sendButton.dataset.state = status === "error" ? "retry" : state.agentRunning ? "working" : status;
19002
+ sendButton.setAttribute("aria-label", running ? "Agent is working" : status === "error" ? "Retry request" : "Send request");
19003
+ sendButton.dataset.state = status === "error" ? "retry" : running ? "working" : status;
18892
19004
  syncSendButtonState();
18893
- if (status === "review" || status === "error" || status === "stopped") {
18894
- appendChat(status === "error" ? "error" : "assistant", message);
19005
+ if (appendMessage && (status === "review" || status === "error" || status === "stopped")) {
19006
+ appendChat(status === "error" ? "error" : "assistant", message, taskId2);
18895
19007
  }
18896
19008
  }
18897
19009
  function syncSendButtonState() {
@@ -18926,6 +19038,64 @@
18926
19038
  if (stopButton) stopButton.hidden = true;
18927
19039
  if (promptInput) promptInput.disabled = false;
18928
19040
  }
19041
+ function hideReview() {
19042
+ if (reviewPanel) reviewPanel.hidden = true;
19043
+ }
19044
+ function resetPromptSession() {
19045
+ state.promptTaskId = null;
19046
+ state.chatHistory = [];
19047
+ state.changesHistory = [];
19048
+ state.pendingChanges = [];
19049
+ state.lastInstruction = "";
19050
+ state.selectionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
19051
+ state.screenshotPromise = Promise.resolve({});
19052
+ hideReview();
19053
+ clearAgentLog();
19054
+ resetAgentState();
19055
+ renderChatThread();
19056
+ if (promptInput) {
19057
+ promptInput.value = "";
19058
+ syncSendButtonState();
19059
+ }
19060
+ }
19061
+ function restoreTaskPrompt(task) {
19062
+ state.activeTaskId = task.id;
19063
+ state.promptTaskId = task.id;
19064
+ state.chatHistory = [...task.messages];
19065
+ state.changesHistory = task.changesHistory.map((entry) => ({ ...entry, changes: entry.changes.map((change) => ({ ...change })) }));
19066
+ state.pendingChanges = [...task.pendingChanges];
19067
+ state.lastInstruction = task.lastInstruction;
19068
+ state.selectionId = task.selectionId;
19069
+ state.screenshotPromise = Promise.resolve({});
19070
+ hideReview();
19071
+ clearAgentLog();
19072
+ loadAgentLog(task.activity);
19073
+ renderChatThread();
19074
+ const dom = getDOM();
19075
+ if (task.element?.isConnected) {
19076
+ state.selected = task.element;
19077
+ state.elementRegistry.set(elementKey(task.element), task.element);
19078
+ updateSelectedVisual();
19079
+ updateLockChip();
19080
+ if (promptElement) promptElement.textContent = getElementLabel(task.element);
19081
+ positionPrompt(task.element);
19082
+ } else {
19083
+ state.selected = null;
19084
+ dom.selectedBox.style.display = "none";
19085
+ dom.label.style.display = "none";
19086
+ if (promptElement) promptElement.textContent = "Agent task";
19087
+ if (promptEl) {
19088
+ promptEl.style.left = `${Math.max(12, (window.innerWidth - 360) / 2)}px`;
19089
+ promptEl.style.top = `${Math.max(12, (window.innerHeight - 200) / 2)}px`;
19090
+ }
19091
+ updateLockChip();
19092
+ }
19093
+ if (promptEl) promptEl.classList.add("visible");
19094
+ setSelectMode(false);
19095
+ if (task.status === "review" && state.pendingChanges.length) showReview(state.pendingChanges, task.message);
19096
+ setAgentStatus(task.status, task.message, task.detail, task.id, false);
19097
+ requestAnimationFrame(() => promptInput?.focus());
19098
+ }
18929
19099
  function showReview(changes, summary) {
18930
19100
  if (!reviewPanel) return;
18931
19101
  const subtitle = reviewPanel.querySelector(".lasso-review-subtitle");
@@ -18959,7 +19129,7 @@
18959
19129
  reviewPanel.style.top = `${top}px`;
18960
19130
  }
18961
19131
  function closeReview() {
18962
- if (reviewPanel) reviewPanel.hidden = true;
19132
+ hideReview();
18963
19133
  resetAgentState();
18964
19134
  }
18965
19135
  function positionPrompt(el) {
@@ -18990,11 +19160,10 @@
18990
19160
  promptEl.classList.remove("visible");
18991
19161
  if (promptInput) promptInput.value = "";
18992
19162
  sendPresenceUpdate({ selection: null });
18993
- if (state.heldLockElement) releaseHeldLock();
18994
- updateLockChip();
18995
19163
  renderRemoteBoxes();
18996
19164
  renderComments();
18997
19165
  state.selected = null;
19166
+ updateLockChip();
18998
19167
  getDOM().selectedBox.style.display = "none";
18999
19168
  getDOM().label.style.display = "none";
19000
19169
  if (reenter) {
@@ -19004,18 +19173,19 @@
19004
19173
  async function handleSend(event) {
19005
19174
  event.preventDefault();
19006
19175
  event.stopPropagation();
19007
- if (!state.selected || !promptInput || !sendButton) return;
19176
+ const selectedElement = state.selected;
19177
+ if (!selectedElement || !promptInput || !sendButton) return;
19008
19178
  const instruction = promptInput.value.trim() || (sendButton.dataset.state === "retry" ? state.lastInstruction : "");
19009
19179
  if (!instruction) {
19010
19180
  promptInput.focus();
19011
19181
  return;
19012
19182
  }
19013
- clearAgentLog();
19014
19183
  requestAgentNotificationPermission();
19015
19184
  const isQuestion = /^(hi|hello|hey|thanks|thank you|what|why|how|when|where|who|which|is|are|does|do|can|could|would|should|tell me|explain|describe)\b/i.test(instruction) || /\?$/.test(instruction);
19016
19185
  const isExplicitEdit = /\b(change|edit|update|make|add|remove|delete|fix|replace|turn|convert|style|restyle|move|rename|implement|build|create|increase|decrease|hide|show|align|resize|set|enable|disable)\b/i.test(instruction);
19017
19186
  const wantsAnswer = isQuestion && !/\b(can|could|would|please)\s+you\s+(change|edit|update|add|fix|make)\b/i.test(instruction) || !isExplicitEdit && !isQuestion;
19018
- if (!state.bridgeSocket || state.bridgeSocket.readyState !== WebSocket.OPEN) {
19187
+ const bridgeSocket = state.bridgeSocket;
19188
+ if (!bridgeSocket || bridgeSocket.readyState !== WebSocket.OPEN) {
19019
19189
  setAgentStatus(
19020
19190
  "error",
19021
19191
  "The Lasso agent bridge is not connected. Start Lasso with your dev server and try again."
@@ -19023,41 +19193,54 @@
19023
19193
  return;
19024
19194
  }
19025
19195
  if (!wantsAnswer) {
19026
- const ownershipGranted = await acquireOwnership(state.selected);
19196
+ const ownershipGranted = await acquireOwnership(selectedElement);
19027
19197
  if (!ownershipGranted) return;
19028
19198
  }
19029
19199
  if (!wantsAnswer && state.collabSocket?.connected && state.collabJoined) {
19030
19200
  collabEmit("collab:action", {
19031
19201
  sessionId: state.collabProjectId,
19032
19202
  status: "preparing",
19033
- elementId: elementKey(state.selected),
19203
+ elementId: elementKey(selectedElement),
19034
19204
  summary: `Edits for ${instruction.slice(0, 80)}`
19035
19205
  });
19036
19206
  }
19207
+ const selectionId = state.selectionId;
19208
+ const model = state.selectedModel;
19209
+ const screenshotPromise = state.screenshotPromise;
19037
19210
  appendChat("user", instruction);
19038
- const task = createAgentTask(instruction);
19039
- setAgentStatus("thinking", "Thinking\u2026");
19211
+ const messages = state.chatHistory.map((message) => ({ ...message }));
19212
+ const changesHistory = state.changesHistory.map((entry) => ({ ...entry, changes: entry.changes.map((change) => ({ ...change })) }));
19213
+ const task = createAgentTask(instruction, messages, changesHistory, selectedElement, selectionId);
19214
+ updateAgentTask(task.id, { status: "thinking", message: "Thinking\u2026", activity: [...task.activity, "Thinking\u2026"] });
19215
+ resetPromptSession();
19216
+ state.promptTaskId = task.id;
19040
19217
  state.lastInstruction = instruction;
19041
- promptInput.value = "";
19042
- const rect = state.selected.getBoundingClientRect();
19043
- const computed = getComputedStyle(state.selected);
19218
+ const rect = selectedElement.getBoundingClientRect();
19219
+ const computed = getComputedStyle(selectedElement);
19044
19220
  const attributes = Object.fromEntries(
19045
- Array.from(state.selected.attributes).map((attr) => [attr.name, attr.value])
19221
+ Array.from(selectedElement.attributes).map((attr) => [attr.name, attr.value])
19046
19222
  );
19047
19223
  const needsVisualContext = /\b(look|visual|appearance|color|colour|background|image|icon|spacing|layout|position|align|responsive|style|restyle|font|size)\b/i.test(instruction);
19048
- const screenshots = needsVisualContext && state.selected ? await captureElementScreenshot(state.selected) : await state.screenshotPromise;
19049
- state.bridgeSocket.send(
19224
+ const screenshots = needsVisualContext ? await captureElementScreenshot(selectedElement) : await screenshotPromise;
19225
+ if (bridgeSocket.readyState !== WebSocket.OPEN) {
19226
+ const message = "The Lasso agent bridge disconnected before the task could start.";
19227
+ updateAgentTask(task.id, { status: "error", message });
19228
+ appendChat("error", message, task.id);
19229
+ if (!wantsAnswer) releaseHeldLock(elementKey(selectedElement));
19230
+ return;
19231
+ }
19232
+ bridgeSocket.send(
19050
19233
  JSON.stringify({
19051
19234
  type: wantsAnswer ? "ask" : "edit",
19052
19235
  taskId: task.id,
19053
19236
  question: wantsAnswer ? instruction : void 0,
19054
19237
  instruction,
19055
- messages: state.chatHistory,
19056
- changesHistory: state.changesHistory,
19057
- model: state.selectedModel.id,
19058
- provider: state.selectedModel.provider,
19238
+ messages,
19239
+ changesHistory,
19240
+ model: model.id,
19241
+ provider: model.provider,
19059
19242
  context: {
19060
- selectionId: state.selectionId,
19243
+ selectionId,
19061
19244
  position: {
19062
19245
  top: rect.top,
19063
19246
  left: rect.left,
@@ -19088,18 +19271,18 @@
19088
19271
  screenshots
19089
19272
  },
19090
19273
  element: {
19091
- tag: state.selected.tagName.toLowerCase(),
19092
- group: getElementGroup(state.selected),
19093
- label: getElementLabel(state.selected),
19094
- html: state.selected.outerHTML.slice(0, 6e3),
19095
- sourceHint: state.selected.getAttribute("data-source") || state.selected.getAttribute("data-lasso-source") || getSourceHint(state.selected)
19274
+ tag: selectedElement.tagName.toLowerCase(),
19275
+ group: getElementGroup(selectedElement),
19276
+ label: getElementLabel(selectedElement),
19277
+ html: selectedElement.outerHTML.slice(0, 6e3),
19278
+ sourceHint: selectedElement.getAttribute("data-source") || selectedElement.getAttribute("data-lasso-source") || getSourceHint(selectedElement)
19096
19279
  }
19097
19280
  })
19098
19281
  );
19099
19282
  }
19100
19283
  function openPromptForSelected(selected) {
19101
19284
  if (!promptEl || !promptElement) return;
19102
- clearAgentLog();
19285
+ resetPromptSession();
19103
19286
  promptElement.textContent = getElementLabel(selected);
19104
19287
  positionPrompt(selected);
19105
19288
  promptEl.classList.add("visible");
@@ -19838,7 +20021,7 @@
19838
20021
  state.hovered = el;
19839
20022
  updateHoverVisual(state.hovered);
19840
20023
  }
19841
- function updateSelectedVisual2() {
20024
+ function updateSelectedVisual() {
19842
20025
  const dom = getDOM();
19843
20026
  if (!state.selected) {
19844
20027
  dom.selectedBox.style.display = "none";
@@ -20559,47 +20742,85 @@
20559
20742
  if (message.type === "git_commit_message") {
20560
20743
  setGeneratedCommitMessage(message.message || "", message.error);
20561
20744
  }
20745
+ if (message.type === "git_progress" && message.message) {
20746
+ setGitMessage(message.message);
20747
+ }
20562
20748
  if (message.type === "agent_status" && message.status && message.message) {
20749
+ const taskId2 = message.taskId;
20750
+ const task = taskId2 ? getAgentTask(taskId2) : void 0;
20563
20751
  const taskStatus = message.status === "error" ? "error" : message.status === "stopped" ? "stopped" : message.status;
20564
- updateAgentTask(message.taskId, { status: taskStatus, message: message.message, detail: message.detail, changes: message.changes });
20565
- if (message.taskId && message.taskId !== state.activeTaskId) return;
20566
- setAgentStatus(message.status, message.message, message.detail);
20752
+ if (task) {
20753
+ const patch = {
20754
+ status: taskStatus,
20755
+ message: message.message,
20756
+ detail: message.detail
20757
+ };
20758
+ if (message.status === "review" && message.changes) {
20759
+ patch.changes = message.changes;
20760
+ patch.pendingChanges = message.changes;
20761
+ recordTaskChangeHistory(task.id, message.message, message.changes);
20762
+ }
20763
+ updateAgentTask(task.id, patch);
20764
+ recordTaskActivity(task.id, message.detail || message.message);
20765
+ }
20766
+ if (message.status === "review") {
20767
+ appendChat("assistant", message.message, taskId2);
20768
+ if (message.changes?.length) void notifyAgent("Review requested", message.message);
20769
+ } else if (message.status === "error" || message.status === "stopped") {
20770
+ appendChat(message.status === "error" ? "error" : "assistant", message.message, taskId2);
20771
+ if (task?.element) releaseHeldLock(elementKey(task.element));
20772
+ void notifyAgent(message.status === "error" ? "Agent error" : "Agent stopped", message.message);
20773
+ }
20774
+ if (taskId2 && taskId2 !== state.promptTaskId) return;
20775
+ setAgentStatus(message.status, message.message, message.detail, taskId2, false);
20567
20776
  setDragCardStatus(message.status, message.message);
20568
20777
  if (message.status === "review" && message.changes?.length) {
20569
- void notifyAgent("Review requested", message.message);
20570
- state.pendingChanges = message.changes;
20571
- state.changesHistory.push({
20572
- summary: message.message,
20573
- changes: message.changes,
20574
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
20575
- });
20778
+ if (task) state.changesHistory = task.changesHistory.map((entry) => ({ ...entry, changes: entry.changes.map((change) => ({ ...change })) }));
20779
+ state.pendingChanges = [...message.changes];
20576
20780
  showReview(message.changes, message.message);
20577
20781
  }
20578
20782
  }
20579
20783
  if (message.type === "assistant_message" && message.message) {
20580
- updateAgentTask(message.taskId, { status: "complete", message: "Complete", response: message.message });
20581
- if (message.taskId && message.taskId !== state.activeTaskId) return;
20784
+ const taskId2 = message.taskId;
20785
+ const task = taskId2 ? getAgentTask(taskId2) : void 0;
20786
+ if (task) {
20787
+ updateAgentTask(task.id, { status: "complete", message: "Complete", response: message.message });
20788
+ recordTaskActivity(task.id, "Agent complete");
20789
+ }
20790
+ appendChat("assistant", message.message, taskId2);
20791
+ if (taskId2 && taskId2 !== state.promptTaskId) {
20792
+ void notifyAgent("Agent complete", message.message);
20793
+ return;
20794
+ }
20582
20795
  void notifyAgent("Agent complete", message.message);
20583
- appendChat("assistant", message.message);
20584
20796
  resetAgentState();
20585
20797
  }
20586
20798
  if (message.type === "applied" || message.type === "undone") {
20587
- updateAgentTask(message.taskId, { status: "complete", message: message.message || "Done." });
20588
- if (message.taskId && message.taskId !== state.activeTaskId) return;
20589
- void notifyAgent(message.type === "applied" ? "Changes applied" : "Change undone", message.message || "Done.");
20590
- appendChat("assistant", message.message || "Done.");
20591
- releaseHeldLock();
20799
+ const taskId2 = message.taskId;
20800
+ const task = taskId2 ? getAgentTask(taskId2) : void 0;
20801
+ if (task) {
20802
+ updateAgentTask(task.id, { status: "complete", message: message.message || "Done.", changes: [], pendingChanges: [] });
20803
+ recordTaskActivity(task.id, message.message || "Done.");
20804
+ }
20805
+ const isPromptTask = !taskId2 || taskId2 === state.promptTaskId;
20806
+ const result = message.message || "Done.";
20807
+ void notifyAgent(message.type === "applied" ? "Changes applied" : "Change undone", result);
20808
+ appendChat("assistant", result, taskId2);
20809
+ if (task?.element) releaseHeldLock(elementKey(task.element));
20810
+ else if (isPromptTask && state.selected) releaseHeldLock(elementKey(state.selected));
20592
20811
  if (state.collabSocket?.connected && state.collabJoined) {
20593
20812
  collabEmit("collab:action", {
20594
20813
  sessionId: state.collabProjectId,
20595
20814
  status: "idle",
20596
- elementId: state.selected ? elementKey(state.selected) : void 0,
20597
- summary: message.message || (message.type === "undone" ? "Undid the last change" : "Applied the change")
20815
+ elementId: task?.element ? elementKey(task.element) : state.selected ? elementKey(state.selected) : void 0,
20816
+ summary: result || (message.type === "undone" ? "Undid the last change" : "Applied the change")
20598
20817
  });
20599
20818
  }
20600
- closeReview();
20601
- resetDrag(false);
20602
- state.pendingChanges = [];
20819
+ if (isPromptTask) {
20820
+ closeReview();
20821
+ resetDrag(false);
20822
+ state.pendingChanges = [];
20823
+ }
20603
20824
  }
20604
20825
  } catch {
20605
20826
  console.warn("[lasso] Invalid bridge message");
@@ -20641,7 +20862,7 @@
20641
20862
  renderCommentPins();
20642
20863
  }
20643
20864
  if (state.selected) {
20644
- updateSelectedVisual2();
20865
+ updateSelectedVisual();
20645
20866
  if (!state.promptDragged) {
20646
20867
  positionPrompt(state.selected);
20647
20868
  }
@@ -20683,13 +20904,8 @@
20683
20904
  updateLockChip();
20684
20905
  sendPresenceUpdate({});
20685
20906
  state.promptDragged = false;
20686
- state.lastInstruction = "";
20687
- state.selectionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
20688
- state.chatHistory = [];
20689
- state.changesHistory = [];
20690
- state.screenshotPromise = Promise.resolve({});
20691
20907
  dom.hoverBox.style.display = "none";
20692
- updateSelectedVisual2();
20908
+ updateSelectedVisual();
20693
20909
  openPromptForSelected(target);
20694
20910
  },
20695
20911
  true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lasso-ai/cli",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
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",