@lasso-ai/cli 2.1.14 → 2.1.16

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/overlay.js CHANGED
@@ -7815,16 +7815,7 @@
7815
7815
  });
7816
7816
 
7817
7817
  // src/overlay/state.ts
7818
- var COLLAB_COLORS = [
7819
- "#6ea0ff",
7820
- "#81c995",
7821
- "#fdd663",
7822
- "#f28b82",
7823
- "#c084fc",
7824
- "#22d3ee",
7825
- "#fb923c",
7826
- "#a78bfa"
7827
- ];
7818
+ var COLLAB_COLORS = ["#6ea0ff", "#81c995", "#fdd663", "#f28b82", "#c084fc", "#22d3ee", "#fb923c", "#a78bfa"];
7828
7819
  function userNameColor(userId) {
7829
7820
  if (!userId) return COLLAB_COLORS[0];
7830
7821
  let hash = 0;
@@ -11531,7 +11522,6 @@
11531
11522
  align-items: center;
11532
11523
  justify-content: space-between;
11533
11524
  padding: 6px 8px 8px;
11534
- border-bottom: 1px solid var(--lo-border-subtle);
11535
11525
  font-size: 11px;
11536
11526
  font-weight: 600;
11537
11527
  color: var(--lo-text-3);
@@ -11562,6 +11552,48 @@
11562
11552
  width: 100%;
11563
11553
  }
11564
11554
 
11555
+ .lasso-command-history-load {
11556
+ display: flex;
11557
+ align-items: center;
11558
+ justify-content: space-between;
11559
+ gap: 8px;
11560
+ min-width: 0;
11561
+ flex: 1;
11562
+ padding: 0;
11563
+ border: 0;
11564
+ background: transparent;
11565
+ color: inherit;
11566
+ cursor: pointer;
11567
+ text-align: left;
11568
+ }
11569
+
11570
+ .lasso-command-history-delete {
11571
+ display: inline-flex;
11572
+ align-items: center;
11573
+ justify-content: center;
11574
+ flex: 0 0 auto;
11575
+ width: 24px;
11576
+ height: 24px;
11577
+ padding: 0;
11578
+ border: 0;
11579
+ border-radius: 5px;
11580
+ background: transparent;
11581
+ color: var(--lo-text-3);
11582
+ cursor: pointer;
11583
+ opacity: 0;
11584
+ transition: opacity 120ms ease, color 120ms ease, background 120ms ease;
11585
+ }
11586
+
11587
+ .lasso-command-history-item:hover .lasso-command-history-delete,
11588
+ .lasso-command-history-delete:focus-visible {
11589
+ opacity: 1;
11590
+ }
11591
+
11592
+ .lasso-command-history-delete:hover {
11593
+ color: #fca5a5;
11594
+ background: rgba(239, 68, 68, 0.14);
11595
+ }
11596
+
11565
11597
  .lasso-command-history-item:hover {
11566
11598
  background: var(--lo-surface-hover);
11567
11599
  }
@@ -13593,16 +13625,7 @@
13593
13625
  this.commentPins.className = "lasso-comment-pins";
13594
13626
  this.dragLayer = document.createElement("div");
13595
13627
  this.dragLayer.className = "lasso-drag-layer";
13596
- this.shadow.append(
13597
- this.remoteLayer,
13598
- this.spotlightBox,
13599
- this.hoverBox,
13600
- this.selectedBox,
13601
- this.label,
13602
- this.activityStrip,
13603
- this.commentPins,
13604
- this.dragLayer
13605
- );
13628
+ this.shadow.append(this.remoteLayer, this.spotlightBox, this.hoverBox, this.selectedBox, this.label, this.activityStrip, this.commentPins, this.dragLayer);
13606
13629
  }
13607
13630
  isLassoElement(el) {
13608
13631
  if (!el) return false;
@@ -17133,9 +17156,7 @@
17133
17156
  if (!avatarsEl || !countEl) return;
17134
17157
  avatarsEl.innerHTML = "";
17135
17158
  const offline = Boolean(state.collabProjectId && !state.collabJoined);
17136
- const others = [...state.presenceUsers.values()].filter(
17137
- (user) => user.socketId !== state.collabSocket?.id
17138
- );
17159
+ const others = [...state.presenceUsers.values()].filter((user) => user.socketId !== state.collabSocket?.id);
17139
17160
  for (const user of others.slice(0, 6)) {
17140
17161
  const av = document.createElement("button");
17141
17162
  av.type = "button";
@@ -17322,13 +17343,7 @@
17322
17343
  }
17323
17344
  function getSupportedMimeType() {
17324
17345
  if (typeof MediaRecorder === "undefined") return "audio/webm";
17325
- const types = [
17326
- "audio/webm;codecs=opus",
17327
- "audio/webm",
17328
- "audio/mp4",
17329
- "audio/ogg;codecs=opus",
17330
- "audio/wav"
17331
- ];
17346
+ const types = ["audio/webm;codecs=opus", "audio/webm", "audio/mp4", "audio/ogg;codecs=opus", "audio/wav"];
17332
17347
  for (const t of types) {
17333
17348
  if (MediaRecorder.isTypeSupported(t)) return t;
17334
17349
  }
@@ -17350,53 +17365,48 @@
17350
17365
  const base64 = await blobToBase64(blob);
17351
17366
  if (state.bridgeSocket?.readyState === WebSocket.OPEN) {
17352
17367
  try {
17353
- const result = await new Promise(
17354
- (resolve, reject) => {
17355
- const requestId = `transcribe_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
17356
- const timeout = window.setTimeout(() => {
17357
- cleanup();
17358
- reject(new Error("Transcription timed out via local bridge"));
17359
- }, 25e3);
17360
- const handler = (event) => {
17361
- try {
17362
- const msg = JSON.parse(event.data);
17363
- if (msg.type === "transcribe_result" && msg.requestId === requestId) {
17364
- cleanup();
17365
- if (msg.success) {
17366
- resolve({ text: msg.text || "", provider: msg.provider || "gradium" });
17367
- } else {
17368
- reject(new Error(msg.error || "Transcription failed"));
17369
- }
17368
+ const result = await new Promise((resolve, reject) => {
17369
+ const requestId = `transcribe_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
17370
+ const timeout = window.setTimeout(() => {
17371
+ cleanup();
17372
+ reject(new Error("Transcription timed out via local bridge"));
17373
+ }, 25e3);
17374
+ const handler = (event) => {
17375
+ try {
17376
+ const msg = JSON.parse(event.data);
17377
+ if (msg.type === "transcribe_result" && msg.requestId === requestId) {
17378
+ cleanup();
17379
+ if (msg.success) {
17380
+ resolve({ text: msg.text || "", provider: msg.provider || "gradium" });
17381
+ } else {
17382
+ reject(new Error(msg.error || "Transcription failed"));
17370
17383
  }
17371
- } catch {
17372
17384
  }
17373
- };
17374
- const cleanup = () => {
17375
- window.clearTimeout(timeout);
17376
- state.bridgeSocket?.removeEventListener("message", handler);
17377
- };
17378
- state.bridgeSocket?.addEventListener("message", handler);
17379
- state.bridgeSocket?.send(
17380
- JSON.stringify({
17381
- type: "transcribe",
17382
- requestId,
17383
- audio: base64,
17384
- mimeType,
17385
- language: "en"
17386
- })
17387
- );
17388
- }
17389
- );
17385
+ } catch {
17386
+ }
17387
+ };
17388
+ const cleanup = () => {
17389
+ window.clearTimeout(timeout);
17390
+ state.bridgeSocket?.removeEventListener("message", handler);
17391
+ };
17392
+ state.bridgeSocket?.addEventListener("message", handler);
17393
+ state.bridgeSocket?.send(
17394
+ JSON.stringify({
17395
+ type: "transcribe",
17396
+ requestId,
17397
+ audio: base64,
17398
+ mimeType,
17399
+ language: "en"
17400
+ })
17401
+ );
17402
+ });
17390
17403
  return result;
17391
17404
  } catch (bridgeErr) {
17392
17405
  console.warn("[transcribe] Bridge transcription failed, falling back to direct server fetch:", bridgeErr);
17393
17406
  }
17394
17407
  }
17395
17408
  const isLassoPage = typeof window !== "undefined" && window.location.hostname.endsWith(".lasso");
17396
- const candidateUrls = isLassoPage ? ["https://api.lasso.byorello.space/api/v1/transcribe"] : [
17397
- "https://api.lasso.byorello.space/api/v1/transcribe",
17398
- "/api/v1/transcribe"
17399
- ];
17409
+ const candidateUrls = isLassoPage ? ["https://api.lasso.byorello.space/api/v1/transcribe"] : ["https://api.lasso.byorello.space/api/v1/transcribe", "/api/v1/transcribe"];
17400
17410
  let lastError = new Error("Failed to contact transcription service");
17401
17411
  for (const url2 of candidateUrls) {
17402
17412
  try {
@@ -17445,12 +17455,7 @@
17445
17455
  const legacyGetUserMedia = nav && (nav.getUserMedia || nav.webkitGetUserMedia || nav.mozGetUserMedia || nav.msGetUserMedia);
17446
17456
  if (legacyGetUserMedia) {
17447
17457
  return new Promise((resolve, reject) => {
17448
- legacyGetUserMedia.call(
17449
- nav,
17450
- { audio: true },
17451
- resolve,
17452
- (err) => reject(new Error(err?.message || "Microphone access was denied."))
17453
- );
17458
+ legacyGetUserMedia.call(nav, { audio: true }, resolve, (err) => reject(new Error(err?.message || "Microphone access was denied.")));
17454
17459
  });
17455
17460
  }
17456
17461
  if (typeof window !== "undefined") {
@@ -17494,10 +17499,7 @@
17494
17499
  activeStream = stream;
17495
17500
  currentMimeType = getSupportedMimeType() || "audio/webm";
17496
17501
  if (typeof MediaRecorder !== "undefined") {
17497
- const recorder = new MediaRecorder(
17498
- stream,
17499
- currentMimeType ? { mimeType: currentMimeType } : void 0
17500
- );
17502
+ const recorder = new MediaRecorder(stream, currentMimeType ? { mimeType: currentMimeType } : void 0);
17501
17503
  activeRecorder = recorder;
17502
17504
  recordedChunks = [];
17503
17505
  recorder.ondataavailable = (event) => {
@@ -18091,9 +18093,7 @@
18091
18093
  presenceUsers.push({ label: user.name, type: "user", color: user.color || "#6ea0ff" });
18092
18094
  }
18093
18095
  }
18094
- const filteredStatic = (!query ? STATIC_TAGS : STATIC_TAGS.filter((t) => t.startsWith(query))).map(
18095
- (t) => ({ label: t, type: "tag", color: "#94a3b8" })
18096
- );
18096
+ const filteredStatic = (!query ? STATIC_TAGS : STATIC_TAGS.filter((t) => t.startsWith(query))).map((t) => ({ label: t, type: "tag", color: "#94a3b8" }));
18097
18097
  const suggestions = [...presenceUsers, ...filteredStatic].slice(0, 6);
18098
18098
  if (suggestions.length === 0) {
18099
18099
  tagMenu.hidden = true;
@@ -19038,7 +19038,8 @@
19038
19038
  function saveNote(text) {
19039
19039
  if (collabEmit("note:save", { body: text }, (response) => {
19040
19040
  if (!response.ok) saveNoteLocally(text);
19041
- })) return;
19041
+ }))
19042
+ return;
19042
19043
  saveNoteLocally(text);
19043
19044
  }
19044
19045
  function saveNoteLocally(text) {
@@ -19049,33 +19050,21 @@
19049
19050
  }
19050
19051
  function renderMarkdown(raw) {
19051
19052
  let t = raw.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
19052
- t = t.replace(
19053
- /```([\s\S]*?)```/g,
19054
- (_, code) => `<pre class="lasso-np-pre"><code>${code.trim()}</code></pre>`
19055
- );
19053
+ t = t.replace(/```([\s\S]*?)```/g, (_, code) => `<pre class="lasso-np-pre"><code>${code.trim()}</code></pre>`);
19056
19054
  t = t.replace(/^### (.+)$/gm, '<h3 class="lasso-np-h">$1</h3>');
19057
19055
  t = t.replace(/^## (.+)$/gm, '<h2 class="lasso-np-h">$1</h2>');
19058
19056
  t = t.replace(/^# (.+)$/gm, '<h1 class="lasso-np-h">$1</h1>');
19059
19057
  t = t.replace(/^---$/gm, '<hr class="lasso-np-hr" />');
19060
19058
  t = t.replace(/^&gt; (.+)$/gm, '<blockquote class="lasso-np-blockquote">$1</blockquote>');
19061
- t = t.replace(
19062
- /^- \[x\] (.+)$/gm,
19063
- '<div class="lasso-np-check done"><span class="lasso-np-check-box">\u2713</span><del>$1</del></div>'
19064
- );
19065
- t = t.replace(
19066
- /^- \[ \] (.+)$/gm,
19067
- '<div class="lasso-np-check"><span class="lasso-np-check-box">\u25CB</span>$1</div>'
19068
- );
19059
+ t = t.replace(/^- \[x\] (.+)$/gm, '<div class="lasso-np-check done"><span class="lasso-np-check-box">\u2713</span><del>$1</del></div>');
19060
+ t = t.replace(/^- \[ \] (.+)$/gm, '<div class="lasso-np-check"><span class="lasso-np-check-box">\u25CB</span>$1</div>');
19069
19061
  t = t.replace(/^[-*] (.+)$/gm, '<li class="lasso-np-li">$1</li>');
19070
19062
  t = t.replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>");
19071
19063
  t = t.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
19072
19064
  t = t.replace(/\*(.+?)\*/g, "<em>$1</em>");
19073
19065
  t = t.replace(/~~(.+?)~~/g, "<del>$1</del>");
19074
19066
  t = t.replace(/`(.+?)`/g, '<code class="lasso-np-code">$1</code>');
19075
- t = t.replace(
19076
- /\[(.+?)\]\((.+?)\)/g,
19077
- '<a href="$2" target="_blank" rel="noopener" class="lasso-np-link">$1</a>'
19078
- );
19067
+ t = t.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2" target="_blank" rel="noopener" class="lasso-np-link">$1</a>');
19079
19068
  t = t.replace(/\n\n/g, '</p><p class="lasso-np-p">');
19080
19069
  t = t.replace(/\n/g, "<br>");
19081
19070
  return `<p class="lasso-np-p">${t}</p>`;
@@ -19600,51 +19589,6 @@
19600
19589
  var activeStartTime = 0;
19601
19590
  var activeConversationId = null;
19602
19591
  var conversationList = [];
19603
- function isConversationalPrompt(prompt) {
19604
- const p = prompt.trim().toLowerCase().replace(/[!?.,;:]+$/, "");
19605
- const greetings = [
19606
- "hi",
19607
- "hello",
19608
- "hey",
19609
- "heya",
19610
- "howdy",
19611
- "sup",
19612
- "yo",
19613
- "good morning",
19614
- "good afternoon",
19615
- "good evening",
19616
- "thanks",
19617
- "thank you",
19618
- "thx",
19619
- "ty",
19620
- "cool",
19621
- "awesome",
19622
- "great",
19623
- "nice",
19624
- "who are you",
19625
- "what are you",
19626
- "what can you do",
19627
- "help",
19628
- "help me",
19629
- "what is lasso",
19630
- "how do you work",
19631
- "how does this work"
19632
- ];
19633
- if (greetings.includes(p)) return true;
19634
- if (/^(hi|hello|hey|howdy|yo)\b/i.test(p) && p.split(/\s+/).length <= 4) {
19635
- const buildKeywords = /\b(build|create|add|make|fix|update|modify|change|refactor|implement|delete|remove|style|install|wire|code)\b/i;
19636
- if (!buildKeywords.test(p)) return true;
19637
- }
19638
- const mutationVerbs = /\b(build|create|add|make|fix|update|modify|change|refactor|implement|delete|remove|style|install|wire|code|replace|set|switch|swap|rewrite|restyle|rebuild|apply)\b/i;
19639
- const infoPrefixes = /^(what|how|why|when|where|who|which|can you explain|explain|describe|tell me|check out|check|look at|summarize|give me an overview|overview|inspect)\b/i;
19640
- if (infoPrefixes.test(p) && !mutationVerbs.test(p)) {
19641
- return true;
19642
- }
19643
- if (/\b(about|doing|does this|is this|mean|purpose|work|repo|project|codebase|app|application|page|overview|summary)\b/i.test(p) && !mutationVerbs.test(p)) {
19644
- return true;
19645
- }
19646
- return false;
19647
- }
19648
19592
  function isRetryIntent(prompt) {
19649
19593
  const p = prompt.trim().toLowerCase().replace(/[!?.]+$/, "");
19650
19594
  return /^(try\s+again|retry|re-?try|do\s+it\s+again|run\s+again|attempt\s+again|go\s+again|one\s+more\s+time|again)$/.test(p);
@@ -19765,10 +19709,7 @@
19765
19709
  const data6 = await apiFetch(`/agent/conversations?projectId=${encodeURIComponent(projectId)}`);
19766
19710
  if (data6?.conversations && Array.isArray(data6.conversations)) {
19767
19711
  const serverIds = new Set(data6.conversations.map((c) => c.id));
19768
- const merged = [
19769
- ...data6.conversations,
19770
- ...conversationList.filter((local) => !serverIds.has(local.id))
19771
- ];
19712
+ const merged = [...data6.conversations, ...conversationList.filter((local) => !serverIds.has(local.id))];
19772
19713
  conversationList = merged;
19773
19714
  }
19774
19715
  }
@@ -19800,17 +19741,44 @@
19800
19741
  </div>
19801
19742
  ${conversationList.map(
19802
19743
  (conv) => `
19803
- <button class="lasso-command-history-item ${conv.id === activeConversationId ? "active" : ""}" type="button" data-conv-id="${conv.id}">
19804
- <span class="lasso-command-history-title" title="${escapeHtml4(conv.title || "Untitled Session")}">${escapeHtml4(conv.title || "Untitled Session")}</span>
19805
- <div class="lasso-command-history-meta">
19806
- <span class="lasso-command-history-scope ${conv.scope || "private"}">${conv.scope || "private"}</span>
19807
- <span>${formatDateAgo(conv.updatedAt)}</span>
19808
- </div>
19809
- </button>
19744
+ <div class="lasso-command-history-item ${conv.id === activeConversationId ? "active" : ""}" data-conv-id="${escapeHtml4(conv.id)}">
19745
+ <button class="lasso-command-history-load" type="button" title="Open session">
19746
+ <span class="lasso-command-history-title" title="${escapeHtml4(conv.title || "Untitled Session")}">${escapeHtml4(conv.title || "Untitled Session")}</span>
19747
+ <div class="lasso-command-history-meta">
19748
+ <span class="lasso-command-history-scope ${conv.scope || "private"}">${conv.scope || "private"}</span>
19749
+ <span>${formatDateAgo(conv.updatedAt)}</span>
19750
+ </div>
19751
+ </button>
19752
+ <button class="lasso-command-history-delete" type="button" title="Delete session" aria-label="Delete session">
19753
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v5M14 11v5"/></svg>
19754
+ </button>
19755
+ </div>
19810
19756
  `
19811
19757
  ).join("")}
19812
19758
  `;
19813
19759
  }
19760
+ async function deleteConversation(convId) {
19761
+ const conversation = conversationList.find((item) => item.id === convId);
19762
+ if (!conversation) return;
19763
+ if (!convId.startsWith("local-session-")) {
19764
+ const deleted = await apiFetch(`/agent/conversations/${encodeURIComponent(convId)}`, { method: "DELETE" });
19765
+ if (!deleted?.success) return;
19766
+ }
19767
+ try {
19768
+ localStorage.removeItem(`lasso:session:${convId}`);
19769
+ const key = getStorageProjectKey();
19770
+ const list2 = JSON.parse(localStorage.getItem(key) || "[]");
19771
+ localStorage.setItem(key, JSON.stringify(list2.filter((item) => item.id !== convId)));
19772
+ } catch {
19773
+ }
19774
+ conversationList = conversationList.filter((item) => item.id !== convId);
19775
+ if (activeConversationId === convId) {
19776
+ activeConversationId = null;
19777
+ commandMessages = [];
19778
+ renderMessages();
19779
+ }
19780
+ renderHistoryPopover();
19781
+ }
19814
19782
  async function switchConversation(convId) {
19815
19783
  if (activeThinkingInterval) {
19816
19784
  clearInterval(activeThinkingInterval);
@@ -20138,9 +20106,16 @@
20138
20106
  });
20139
20107
  commandHistoryPopover.addEventListener("click", async (event) => {
20140
20108
  event.stopPropagation();
20141
- const item = event.target.closest(".lasso-command-history-item");
20109
+ const target = event.target;
20110
+ const item = target.closest(".lasso-command-history-item");
20142
20111
  if (!item?.dataset.convId) return;
20143
- await switchConversation(item.dataset.convId);
20112
+ if (target.closest(".lasso-command-history-delete")) {
20113
+ await deleteConversation(item.dataset.convId);
20114
+ return;
20115
+ }
20116
+ if (target.closest(".lasso-command-history-load")) {
20117
+ await switchConversation(item.dataset.convId);
20118
+ }
20144
20119
  });
20145
20120
  const setCommandModelMenuOpen = (open) => {
20146
20121
  if (!commandModelMenu || !commandModelBtn) return;
@@ -20294,6 +20269,19 @@
20294
20269
  }
20295
20270
  return;
20296
20271
  }
20272
+ const steerBtn = target.closest(".lasso-command-steer-btn");
20273
+ if (steerBtn) {
20274
+ const msgId = steerBtn.closest(".lasso-command-msg")?.dataset.msgId;
20275
+ const msg = commandMessages.find((m) => m.id === msgId);
20276
+ if (msg?.queued && !msg.steering && activeTaskId && state.bridgeSocket?.readyState === WebSocket.OPEN) {
20277
+ steerBtn.disabled = true;
20278
+ steerBtn.textContent = "Steering\u2026";
20279
+ msg.steering = true;
20280
+ state.bridgeSocket.send(JSON.stringify({ type: "steer", taskId: activeTaskId, queueId: msg.id, prompt: msg.content }));
20281
+ renderMessages();
20282
+ }
20283
+ return;
20284
+ }
20297
20285
  const fileCheck = target.closest(".lasso-command-file-check");
20298
20286
  if (fileCheck) {
20299
20287
  const msgId = fileCheck.closest(".lasso-command-msg")?.dataset.msgId;
@@ -20505,6 +20493,21 @@ Please proceed directly with the implementation and code changes from this point
20505
20493
  async function handleSubmit() {
20506
20494
  if (!commandInput) return;
20507
20495
  if (activeTaskId) {
20496
+ const queuedPrompt = commandInput.value.trim();
20497
+ if (queuedPrompt) {
20498
+ commandMessages.push({
20499
+ id: `queued-${Date.now()}`,
20500
+ role: "user",
20501
+ content: queuedPrompt,
20502
+ timestamp: Date.now(),
20503
+ queued: true
20504
+ });
20505
+ commandInput.value = "";
20506
+ autoResizeTextarea(commandInput);
20507
+ renderMessages();
20508
+ scrollToBottom();
20509
+ return;
20510
+ }
20508
20511
  if (state.bridgeSocket?.readyState === WebSocket.OPEN) {
20509
20512
  state.bridgeSocket.send(
20510
20513
  JSON.stringify({
@@ -20561,8 +20564,7 @@ Please proceed directly with the implementation and code changes from this point
20561
20564
  const taskId2 = `oneshot-${Date.now()}`;
20562
20565
  activeTaskId = taskId2;
20563
20566
  activeStartTime = Date.now();
20564
- const isChat = isConversationalPrompt(prompt);
20565
- const initialStepTitle = isChat ? "Preparing response\u2026" : "Preparing task\u2026";
20567
+ const initialStepTitle = "Preparing task\u2026";
20566
20568
  commandMessages.push({
20567
20569
  id: taskId2,
20568
20570
  role: "assistant",
@@ -20593,13 +20595,11 @@ Please proceed directly with the implementation and code changes from this point
20593
20595
  if (id) activeConversationId = id;
20594
20596
  });
20595
20597
  }
20596
- if (!isChat) {
20597
- collabEmit("collab:action", {
20598
- sessionId: state.collabProjectId,
20599
- status: "building",
20600
- summary: prompt.slice(0, 120)
20601
- });
20602
- }
20598
+ collabEmit("collab:action", {
20599
+ sessionId: state.collabProjectId,
20600
+ status: "building",
20601
+ summary: prompt.slice(0, 120)
20602
+ });
20603
20603
  if (state.bridgeSocket?.readyState === WebSocket.OPEN) {
20604
20604
  state.bridgeSocket.send(
20605
20605
  JSON.stringify({
@@ -20613,10 +20613,7 @@ Please proceed directly with the implementation and code changes from this point
20613
20613
  // The current prompt is sent in its dedicated `prompt` field. Keep it
20614
20614
  // out of history so the model sees it once, not once as history and
20615
20615
  // once as the active request.
20616
- messages: commandMessages.map((message, index) => ({ message, index })).filter(({ message, index }) => Boolean(message.content) && !message.error && index !== commandMessages.reduce(
20617
- (last, item, itemIndex) => item.role === "user" && item.content === prompt ? itemIndex : last,
20618
- -1
20619
- )).map(({ message }) => message).map((m) => ({ role: m.role, content: m.content }))
20616
+ messages: commandMessages.map((message, index) => ({ message, index })).filter(({ message, index }) => Boolean(message.content) && !message.error && index !== commandMessages.reduce((last, item, itemIndex) => item.role === "user" && item.content === prompt ? itemIndex : last, -1)).map(({ message }) => message).map((m) => ({ role: m.role, content: m.content }))
20620
20617
  })
20621
20618
  );
20622
20619
  } else {
@@ -20769,6 +20766,18 @@ Please proceed directly with the implementation and code changes from this point
20769
20766
  renderMessages();
20770
20767
  }
20771
20768
  }
20769
+ function handleCommandBarSteerResult(message) {
20770
+ const queued = commandMessages.find((msg) => msg.id === message.queueId);
20771
+ if (!queued) return;
20772
+ queued.steering = false;
20773
+ if (message.success) {
20774
+ queued.queued = false;
20775
+ queued.steered = true;
20776
+ } else {
20777
+ queued.error = message.error || "The agent could not accept this steering prompt.";
20778
+ }
20779
+ renderMessages();
20780
+ }
20772
20781
  function handleCommandBarPrompt(message) {
20773
20782
  const taskId2 = message.taskId;
20774
20783
  let targetMsg = commandMessages.find((m) => m.id === taskId2);
@@ -20822,6 +20831,7 @@ Please proceed directly with the implementation and code changes from this point
20822
20831
  html += `
20823
20832
  <div class="lasso-command-msg user" data-msg-id="${msg.id}">
20824
20833
  <div class="lasso-command-msg-bubble">${escapeHtml4(msg.content)}</div>
20834
+ ${msg.queued ? `<button class="lasso-command-steer-btn" type="button"${msg.steering ? " disabled" : ""} style="margin-top: 6px; padding: 4px 9px; border: 1px solid rgba(96, 165, 250, 0.45); border-radius: 6px; background: rgba(59, 130, 246, 0.14); color: #bfdbfe; cursor: pointer;">${msg.steering ? "Steering\u2026" : "Steer"}</button>` : msg.steered ? `<span class="lasso-command-steered-label" style="display: inline-block; margin-top: 6px; color: #93c5fd; font-size: 12px;">Steered into next turn</span>` : ""}
20825
20835
  </div>
20826
20836
  `;
20827
20837
  } else {
@@ -20926,13 +20936,15 @@ Please proceed directly with the implementation and code changes from this point
20926
20936
  const activeFile = msg.activeDiffFile || changes[0]?.filePath;
20927
20937
  const scopeHtml = msg.todo && msg.todo.length ? `
20928
20938
  <div class="lasso-command-scope-list">
20929
- ${msg.todo.map((item) => `
20939
+ ${msg.todo.map(
20940
+ (item) => `
20930
20941
  <div class="lasso-command-scope-item">
20931
20942
  <span class="lasso-command-scope-action ${item.action}">${item.action === "create" ? "+" : item.action === "delete" ? "\u2212" : "~"}</span>
20932
20943
  <span class="lasso-command-scope-file">${escapeHtml4(item.file)}</span>
20933
20944
  <span class="lasso-command-scope-reason">${escapeHtml4(item.reason)}</span>
20934
20945
  </div>
20935
- `).join("")}
20946
+ `
20947
+ ).join("")}
20936
20948
  </div>
20937
20949
  ` : "";
20938
20950
  const approvedCount = changes.length - rejected.size;
@@ -21162,97 +21174,72 @@ Please proceed directly with the implementation and code changes from this point
21162
21174
  if (payload.sessionId !== state.collabProjectId || !payload.itemId) return;
21163
21175
  removeClipboardItem(payload.itemId);
21164
21176
  });
21165
- state.collabSocket.on(
21166
- "collab:action",
21167
- (payload) => {
21168
- if (payload.sessionId !== state.collabProjectId) return;
21169
- const name = payload.user?.name || "A teammate";
21170
- const status = payload.status || "";
21171
- if (status === "building" || status === "build") {
21172
- if (payload.user?.id && payload.user.id !== state.myUser?.id) {
21173
- state.buildModeUser = { id: payload.user.id, name };
21174
- showActivity(`\u23F8\uFE0F ${name} is in Build Mode. Editing is paused.`, "#fbbc04");
21175
- return;
21176
- }
21177
- } else if (status === "idle" || status === "done" || status === "complete") {
21178
- if (state.buildModeUser && (!payload.user?.id || state.buildModeUser.id === payload.user.id)) {
21179
- state.buildModeUser = null;
21180
- showActivity(`${name} finished building. Editing resumed.`, "#81c995");
21181
- return;
21182
- }
21177
+ state.collabSocket.on("collab:action", (payload) => {
21178
+ if (payload.sessionId !== state.collabProjectId) return;
21179
+ const name = payload.user?.name || "A teammate";
21180
+ const status = payload.status || "";
21181
+ if (status === "building" || status === "build") {
21182
+ if (payload.user?.id && payload.user.id !== state.myUser?.id) {
21183
+ state.buildModeUser = { id: payload.user.id, name };
21184
+ showActivity(`\u23F8\uFE0F ${name} is in Build Mode. Editing is paused.`, "#fbbc04");
21185
+ return;
21186
+ }
21187
+ } else if (status === "idle" || status === "done" || status === "complete") {
21188
+ if (state.buildModeUser && (!payload.user?.id || state.buildModeUser.id === payload.user.id)) {
21189
+ state.buildModeUser = null;
21190
+ showActivity(`${name} finished building. Editing resumed.`, "#81c995");
21191
+ return;
21183
21192
  }
21184
- const text = payload.summary ? `${name} is ${status === "idle" ? "done" : status.replace(/e?$/, "ing")} \u2014 ${payload.summary.slice(0, 90)}` : `${name} ${status === "idle" ? "finished working" : `is ${status}`}`;
21185
- showActivity(text, userNameColor(payload.user?.id || ""));
21186
- }
21187
- );
21188
- state.collabSocket.on(
21189
- "collab:spotlight",
21190
- (payload) => {
21191
- if (payload.sessionId !== state.collabProjectId || !payload.elementId) return;
21192
- const el = state.elementRegistry.get(payload.elementId) || null;
21193
- const by = payload.by || { id: "", name: "Teammate", photo: "" };
21194
- spotlightFor(by, el, payload.label || "");
21195
21193
  }
21196
- );
21197
- state.collabSocket.on(
21198
- "voice:offer",
21199
- (payload) => handleOffer(payload)
21200
- );
21201
- state.collabSocket.on(
21202
- "voice:answer",
21203
- (payload) => handleAnswer(payload)
21204
- );
21205
- state.collabSocket.on(
21206
- "voice:candidate",
21207
- (payload) => handleCandidate(payload)
21208
- );
21209
- state.collabSocket.on(
21210
- "voice:peer_left",
21211
- (payload) => closePeer(payload.socketId || "")
21212
- );
21194
+ const text = payload.summary ? `${name} is ${status === "idle" ? "done" : status.replace(/e?$/, "ing")} \u2014 ${payload.summary.slice(0, 90)}` : `${name} ${status === "idle" ? "finished working" : `is ${status}`}`;
21195
+ showActivity(text, userNameColor(payload.user?.id || ""));
21196
+ });
21197
+ state.collabSocket.on("collab:spotlight", (payload) => {
21198
+ if (payload.sessionId !== state.collabProjectId || !payload.elementId) return;
21199
+ const el = state.elementRegistry.get(payload.elementId) || null;
21200
+ const by = payload.by || { id: "", name: "Teammate", photo: "" };
21201
+ spotlightFor(by, el, payload.label || "");
21202
+ });
21203
+ state.collabSocket.on("voice:offer", (payload) => handleOffer(payload));
21204
+ state.collabSocket.on("voice:answer", (payload) => handleAnswer(payload));
21205
+ state.collabSocket.on("voice:candidate", (payload) => handleCandidate(payload));
21206
+ state.collabSocket.on("voice:peer_left", (payload) => closePeer(payload.socketId || ""));
21213
21207
  }
21214
21208
  function joinCollabSession() {
21215
21209
  if (!state.collabSocket?.connected || !state.collabProjectId) return;
21216
21210
  const selection = currentSelectionPayload();
21217
- collabEmit(
21218
- "session:join",
21219
- { sessionId: state.collabProjectId, selection },
21220
- (payload) => {
21221
- const countEl = getDOM().shadow.querySelector(".lasso-presence-count");
21222
- if (!payload.ok) {
21223
- console.warn("[Lasso Collab] Session join failed for", state.collabProjectId, ":", payload.error);
21224
- state.collabJoined = false;
21225
- if (countEl) countEl.textContent = state.collabProjectId ? "\xB7 closed" : "";
21226
- showActivity(
21227
- payload.error || "Could not join realtime session. Log in to your Lasso workspace and start Lasso to collaborate.",
21228
- "#fdd663"
21229
- );
21230
- return;
21231
- }
21232
- console.log("[Lasso Collab] Joined realtime session:", state.collabProjectId, "as", payload.me);
21233
- state.collabJoined = true;
21234
- if (countEl) countEl.textContent = "";
21235
- state.myUser = payload.me || null;
21236
- if (state.myUser?.name) {
21237
- try {
21238
- localStorage.setItem("lasso:user:name", state.myUser.name);
21239
- } catch {
21240
- }
21241
- updateEmptyStateUserName();
21242
- }
21243
- const snapshot = payload.snapshot || {};
21244
- applyPresence(snapshot.presence || []);
21245
- indexLocks(snapshot.locks || []);
21246
- for (const c of snapshot.comments || []) {
21247
- upsertComment(c);
21211
+ collabEmit("session:join", { sessionId: state.collabProjectId, selection }, (payload) => {
21212
+ const countEl = getDOM().shadow.querySelector(".lasso-presence-count");
21213
+ if (!payload.ok) {
21214
+ console.warn("[Lasso Collab] Session join failed for", state.collabProjectId, ":", payload.error);
21215
+ state.collabJoined = false;
21216
+ if (countEl) countEl.textContent = state.collabProjectId ? "\xB7 closed" : "";
21217
+ showActivity(payload.error || "Could not join realtime session. Log in to your Lasso workspace and start Lasso to collaborate.", "#fdd663");
21218
+ return;
21219
+ }
21220
+ console.log("[Lasso Collab] Joined realtime session:", state.collabProjectId, "as", payload.me);
21221
+ state.collabJoined = true;
21222
+ if (countEl) countEl.textContent = "";
21223
+ state.myUser = payload.me || null;
21224
+ if (state.myUser?.name) {
21225
+ try {
21226
+ localStorage.setItem("lasso:user:name", state.myUser.name);
21227
+ } catch {
21248
21228
  }
21249
- updateLockChip();
21250
- replaceTodos(snapshot.todos || []);
21251
- renderComments();
21252
- updateCommentsBadge();
21253
- startCollabHeartbeat();
21229
+ updateEmptyStateUserName();
21254
21230
  }
21255
- );
21231
+ const snapshot = payload.snapshot || {};
21232
+ applyPresence(snapshot.presence || []);
21233
+ indexLocks(snapshot.locks || []);
21234
+ for (const c of snapshot.comments || []) {
21235
+ upsertComment(c);
21236
+ }
21237
+ updateLockChip();
21238
+ replaceTodos(snapshot.todos || []);
21239
+ renderComments();
21240
+ updateCommentsBadge();
21241
+ startCollabHeartbeat();
21242
+ });
21256
21243
  }
21257
21244
  function startCollabHeartbeat() {
21258
21245
  if (state.collabHeartbeat) return;
@@ -21282,16 +21269,20 @@ Please proceed directly with the implementation and code changes from this point
21282
21269
  function initCursorTracking() {
21283
21270
  let lastEmit = 0;
21284
21271
  const THROTTLE_MS = 50;
21285
- document.addEventListener("mousemove", (e) => {
21286
- if (!state.collabSocket?.connected || !state.collabJoined || !state.collabProjectId) return;
21287
- const now = Date.now();
21288
- if (now - lastEmit < THROTTLE_MS) return;
21289
- lastEmit = now;
21290
- collabEmit("presence:update", {
21291
- sessionId: state.collabProjectId,
21292
- cursor: { x: e.clientX, y: e.clientY }
21293
- });
21294
- }, { passive: true });
21272
+ document.addEventListener(
21273
+ "mousemove",
21274
+ (e) => {
21275
+ if (!state.collabSocket?.connected || !state.collabJoined || !state.collabProjectId) return;
21276
+ const now = Date.now();
21277
+ if (now - lastEmit < THROTTLE_MS) return;
21278
+ lastEmit = now;
21279
+ collabEmit("presence:update", {
21280
+ sessionId: state.collabProjectId,
21281
+ cursor: { x: e.clientX, y: e.clientY }
21282
+ });
21283
+ },
21284
+ { passive: true }
21285
+ );
21295
21286
  }
21296
21287
  function initCollabPagehide() {
21297
21288
  window.addEventListener("pagehide", () => {
@@ -21707,9 +21698,7 @@ Please proceed directly with the implementation and code changes from this point
21707
21698
  }
21708
21699
  }
21709
21700
  const sections = [];
21710
- const sectionEls = document.querySelectorAll(
21711
- "header, nav, main, section, article, footer, [role='banner'], [role='main'], [role='navigation'], [id*='hero'], [class*='hero'], [id*='feature'], [class*='feature'], [id*='pricing'], [class*='pricing'], [id*='testimonial'], [class*='testimonial']"
21712
- );
21701
+ const sectionEls = document.querySelectorAll("header, nav, main, section, article, footer, [role='banner'], [role='main'], [role='navigation'], [id*='hero'], [class*='hero'], [id*='feature'], [class*='feature'], [id*='pricing'], [class*='pricing'], [id*='testimonial'], [class*='testimonial']");
21713
21702
  for (const el of sectionEls) {
21714
21703
  if (el.closest("#lasso-root")) continue;
21715
21704
  const tag = el.tagName.toLowerCase();
@@ -21811,9 +21800,7 @@ Please proceed directly with the implementation and code changes from this point
21811
21800
  if (!clean || state.runtimeErrors.includes(clean)) return;
21812
21801
  state.runtimeErrors = [...state.runtimeErrors.slice(-4), clean];
21813
21802
  if (notifyBridge && state.bridgeSocket?.readyState === WebSocket.OPEN) {
21814
- state.bridgeSocket.send(
21815
- JSON.stringify({ type: "runtime_error", selectionId: state.selectionId, details: clean })
21816
- );
21803
+ state.bridgeSocket.send(JSON.stringify({ type: "runtime_error", selectionId: state.selectionId, details: clean }));
21817
21804
  }
21818
21805
  }
21819
21806
  function formatConsoleArg(arg) {
@@ -21830,9 +21817,7 @@ Please proceed directly with the implementation and code changes from this point
21830
21817
  if (errorListenersInstalled) return;
21831
21818
  errorListenersInstalled = true;
21832
21819
  window.addEventListener("error", (event) => {
21833
- reportRuntimeError(
21834
- `${event.message || "Runtime error"}${event.filename ? ` \xB7 ${event.filename}:${event.lineno}` : ""}`
21835
- );
21820
+ reportRuntimeError(`${event.message || "Runtime error"}${event.filename ? ` \xB7 ${event.filename}:${event.lineno}` : ""}`);
21836
21821
  });
21837
21822
  window.addEventListener("unhandledrejection", (event) => {
21838
21823
  const reason = event.reason instanceof Error ? event.reason.message : String(event.reason || "Unhandled promise rejection");
@@ -21952,6 +21937,15 @@ Please proceed directly with the implementation and code changes from this point
21952
21937
  window.dispatchEvent(new CustomEvent("lasso-page-folder-created", { detail: { path: message.path || "", error: message.error } }));
21953
21938
  return;
21954
21939
  }
21940
+ if (message.type === "steer_result" && message.taskId && message.queueId) {
21941
+ handleCommandBarSteerResult({
21942
+ taskId: message.taskId,
21943
+ queueId: message.queueId,
21944
+ success: Boolean(message.success),
21945
+ error: message.error
21946
+ });
21947
+ return;
21948
+ }
21955
21949
  if (message.type === "agent_status" && message.status && message.message) {
21956
21950
  const taskId2 = message.taskId;
21957
21951
  if (isCommandBarTaskId(taskId2)) {
@@ -22104,9 +22098,7 @@ The agent is automatically attempting to fix the build errors...`;
22104
22098
  }
22105
22099
  function findReusableAgentTask(instruction, element) {
22106
22100
  const normalized = normalizedInstruction(instruction);
22107
- return state.agentTasks.find(
22108
- (task) => normalizedInstruction(task.instruction) === normalized && task.element === (element || void 0)
22109
- );
22101
+ return state.agentTasks.find((task) => normalizedInstruction(task.instruction) === normalized && task.element === (element || void 0));
22110
22102
  }
22111
22103
  function getComponentConversation(selectionId) {
22112
22104
  const task = state.agentTasks.find((candidate) => candidate.selectionId === selectionId);
@@ -22825,9 +22817,7 @@ The agent is automatically attempting to fix the build errors...`;
22825
22817
  submitBtn.className = "primary";
22826
22818
  submitBtn.addEventListener("click", (e) => {
22827
22819
  e.preventDefault();
22828
- const checked = Array.from(
22829
- optionsContainer?.querySelectorAll(".lasso-agent-prompt-checkbox:checked") || []
22830
- ).map((cb) => cb.value);
22820
+ const checked = Array.from(optionsContainer?.querySelectorAll(".lasso-agent-prompt-checkbox:checked") || []).map((cb) => cb.value);
22831
22821
  respondToAgentPrompt(checked.length ? checked.join(", ") : "None");
22832
22822
  });
22833
22823
  actions.append(skipBtn, submitBtn);
@@ -22976,10 +22966,7 @@ The agent is automatically attempting to fix the build errors...`;
22976
22966
  ignoreElements: (node) => node.id === "lasso-root" || Boolean(node.closest?.("#lasso-root"))
22977
22967
  };
22978
22968
  try {
22979
- const [fullCanvas, elementCanvas] = await Promise.all([
22980
- (0, import_html2canvas2.default)(document.body, options),
22981
- (0, import_html2canvas2.default)(el, options)
22982
- ]);
22969
+ const [fullCanvas, elementCanvas] = await Promise.all([(0, import_html2canvas2.default)(document.body, options), (0, import_html2canvas2.default)(el, options)]);
22983
22970
  return {
22984
22971
  full: fullCanvas.toDataURL("image/jpeg", 0.72),
22985
22972
  element: elementCanvas.toDataURL("image/jpeg", 0.82)
@@ -23394,10 +23381,7 @@ The agent is automatically attempting to fix the build errors...`;
23394
23381
  requestAgentNotificationPermission();
23395
23382
  const bridgeSocket = state.bridgeSocket;
23396
23383
  if (!bridgeSocket || bridgeSocket.readyState !== WebSocket.OPEN) {
23397
- setAgentStatus(
23398
- "error",
23399
- "The Lasso agent bridge is not connected. Start Lasso with your dev server and try again."
23400
- );
23384
+ setAgentStatus("error", "The Lasso agent bridge is not connected. Start Lasso with your dev server and try again.");
23401
23385
  return;
23402
23386
  }
23403
23387
  const ownershipGranted = await acquireOwnership(selectedElement);
@@ -23441,9 +23425,7 @@ The agent is automatically attempting to fix the build errors...`;
23441
23425
  const changesHistory = state.changesHistory.map((entry) => ({ ...entry, changes: entry.changes.map((change) => ({ ...change })) }));
23442
23426
  const rect = selectedElement.getBoundingClientRect();
23443
23427
  const computed = getComputedStyle(selectedElement);
23444
- const attributes = Object.fromEntries(
23445
- Array.from(selectedElement.attributes).map((attr) => [attr.name, attr.value])
23446
- );
23428
+ const attributes = Object.fromEntries(Array.from(selectedElement.attributes).map((attr) => [attr.name, attr.value]));
23447
23429
  const needsVisualContext = /\b(look|visual|appearance|color|colour|background|image|icon|spacing|layout|position|align|responsive|style|restyle|font|size)\b/i.test(instruction);
23448
23430
  const screenshots = needsVisualContext ? await captureElementScreenshot(selectedElement) : await screenshotPromise;
23449
23431
  if (bridgeSocket.readyState !== WebSocket.OPEN) {