@lasso-ai/cli 2.1.8 → 2.1.10
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/bridge.js +38 -8
- package/dist/overlay.js +130 -10
- package/dist/runtime/workspace-guard.js +13 -3
- package/package.json +1 -1
package/dist/cli/bridge.js
CHANGED
|
@@ -23,13 +23,34 @@ const local_runtime_1 = require("./local-runtime");
|
|
|
23
23
|
exports.DEFAULT_BRIDGE_PORT = 3056;
|
|
24
24
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
25
25
|
function isConversationalPrompt(prompt) {
|
|
26
|
-
const
|
|
27
|
-
|
|
26
|
+
const p = prompt.trim().toLowerCase().replace(/[!?.,;:]+$/, "");
|
|
27
|
+
// 1. Direct greetings and conversational niceties
|
|
28
|
+
const greetings = [
|
|
29
|
+
"hi", "hello", "hey", "heya", "howdy", "sup", "yo",
|
|
30
|
+
"good morning", "good afternoon", "good evening",
|
|
31
|
+
"thanks", "thank you", "thx", "ty", "cool", "awesome", "great", "nice",
|
|
32
|
+
"who are you", "what are you", "what can you do", "help", "help me",
|
|
33
|
+
"what is lasso", "how do you work", "how does this work",
|
|
34
|
+
];
|
|
35
|
+
if (greetings.includes(p))
|
|
28
36
|
return true;
|
|
29
|
-
|
|
37
|
+
// Short greetings
|
|
38
|
+
if (/^(hi|hello|hey|howdy|yo)\b/i.test(p) && p.split(/\s+/).length <= 4) {
|
|
39
|
+
const buildKeywords = /\b(build|create|add|make|fix|update|modify|change|refactor|implement|delete|remove|style|install|wire|code)\b/i;
|
|
40
|
+
if (!buildKeywords.test(p))
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
// 2. Action verbs indicating deliberate code changes
|
|
44
|
+
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;
|
|
45
|
+
// 3. Question / informational prefixes
|
|
46
|
+
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;
|
|
47
|
+
if (infoPrefixes.test(p) && !mutationVerbs.test(p)) {
|
|
30
48
|
return true;
|
|
31
|
-
|
|
49
|
+
}
|
|
50
|
+
// 4. Questions about the project / app / page without mutation verbs
|
|
51
|
+
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)) {
|
|
32
52
|
return true;
|
|
53
|
+
}
|
|
33
54
|
return false;
|
|
34
55
|
}
|
|
35
56
|
function cleanConversationReply(value) {
|
|
@@ -473,11 +494,18 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
|
|
|
473
494
|
return { filePath: change.filePath, content };
|
|
474
495
|
}));
|
|
475
496
|
(0, snapshot_1.createSnapshot)(cwd, taskId, instruction, changes.map((change) => change.filePath), undefined, session.id);
|
|
497
|
+
// Extract the assistant's reply from the session conversation so the user
|
|
498
|
+
// sees both the explanation and the review panel — not just "Review changes".
|
|
499
|
+
const sessionConvo = finalSession.conversation || [];
|
|
500
|
+
const assistantReply = [...sessionConvo]
|
|
501
|
+
.reverse()
|
|
502
|
+
.find((m) => m?.role === "assistant" && typeof m.content === "string")
|
|
503
|
+
?.content?.trim();
|
|
476
504
|
socket.send(JSON.stringify({
|
|
477
505
|
type: "agent_status",
|
|
478
506
|
taskId,
|
|
479
507
|
status: "review",
|
|
480
|
-
message: "Review changes",
|
|
508
|
+
message: assistantReply || "Review changes",
|
|
481
509
|
changes,
|
|
482
510
|
}));
|
|
483
511
|
}
|
|
@@ -495,7 +523,7 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
|
|
|
495
523
|
}
|
|
496
524
|
}
|
|
497
525
|
// Handler for edit/ask - uses runtime
|
|
498
|
-
async function handleHostedConversation(taskId, prompt, model, provider, messages, socket, signal) {
|
|
526
|
+
async function handleHostedConversation(taskId, prompt, model, provider, messages, socket, signal, conversationId) {
|
|
499
527
|
const requestController = new AbortController();
|
|
500
528
|
const abortRequest = () => requestController.abort();
|
|
501
529
|
signal.addEventListener("abort", abortRequest, { once: true });
|
|
@@ -521,6 +549,8 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
|
|
|
521
549
|
prompt,
|
|
522
550
|
model: { id: model, provider },
|
|
523
551
|
messages: Array.isArray(messages) ? messages : undefined,
|
|
552
|
+
conversationId: conversationId || undefined,
|
|
553
|
+
projectId: projectId || undefined,
|
|
524
554
|
}),
|
|
525
555
|
signal: requestController.signal,
|
|
526
556
|
});
|
|
@@ -578,7 +608,7 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
|
|
|
578
608
|
taskControllers.set(taskId, controller);
|
|
579
609
|
const isLocalAgent = ["claude_code", "opencode", "codex_cli", "cursor"].includes(provider || "");
|
|
580
610
|
if ((message.type === "ask" || isConversationalPrompt(instruction)) && !isLocalAgent) {
|
|
581
|
-
await handleHostedConversation(taskId, instruction, model, provider, messages, socket, controller.signal);
|
|
611
|
+
await handleHostedConversation(taskId, instruction, model, provider, messages, socket, controller.signal, message.conversationId);
|
|
582
612
|
return;
|
|
583
613
|
}
|
|
584
614
|
// Do not echo the user's prompt into the thinking feed. The prompt is
|
|
@@ -907,7 +937,7 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
|
|
|
907
937
|
try {
|
|
908
938
|
const isLocalAgent = ["claude_code", "opencode", "codex_cli", "cursor"].includes(provider || "");
|
|
909
939
|
if (isConversationalPrompt(prompt) && !isLocalAgent) {
|
|
910
|
-
await handleHostedConversation(taskId, prompt, model, provider, message.messages, socket, controller.signal);
|
|
940
|
+
await handleHostedConversation(taskId, prompt, model, provider, message.messages, socket, controller.signal, message.conversationId);
|
|
911
941
|
return;
|
|
912
942
|
}
|
|
913
943
|
const backendModelId = (0, backends_1.modelIdForBackend)(model || "", provider || "");
|
package/dist/overlay.js
CHANGED
|
@@ -19632,12 +19632,15 @@
|
|
|
19632
19632
|
];
|
|
19633
19633
|
if (greetings.includes(p)) return true;
|
|
19634
19634
|
if (/^(hi|hello|hey|howdy|yo)\b/i.test(p) && p.split(/\s+/).length <= 4) {
|
|
19635
|
-
const buildKeywords =
|
|
19635
|
+
const buildKeywords = /\b(build|create|add|make|fix|update|modify|change|refactor|implement|delete|remove|style|install|wire|code)\b/i;
|
|
19636
19636
|
if (!buildKeywords.test(p)) return true;
|
|
19637
19637
|
}
|
|
19638
|
-
const
|
|
19639
|
-
const
|
|
19640
|
-
if (
|
|
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)) {
|
|
19641
19644
|
return true;
|
|
19642
19645
|
}
|
|
19643
19646
|
return false;
|
|
@@ -19676,6 +19679,63 @@
|
|
|
19676
19679
|
return null;
|
|
19677
19680
|
}
|
|
19678
19681
|
}
|
|
19682
|
+
function getStorageProjectKey() {
|
|
19683
|
+
const proj = state.collab?.projectId || state.collabProjectId || "default";
|
|
19684
|
+
return `lasso:sessions:${proj}`;
|
|
19685
|
+
}
|
|
19686
|
+
function saveLocalSession() {
|
|
19687
|
+
try {
|
|
19688
|
+
const projKey = getStorageProjectKey();
|
|
19689
|
+
if (!activeConversationId) {
|
|
19690
|
+
activeConversationId = `local-session-${Date.now()}`;
|
|
19691
|
+
}
|
|
19692
|
+
const sessionData = {
|
|
19693
|
+
id: activeConversationId,
|
|
19694
|
+
messages: commandMessages,
|
|
19695
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19696
|
+
title: commandMessages.find((m) => m.role === "user")?.content?.slice(0, 80) || "Untitled Session"
|
|
19697
|
+
};
|
|
19698
|
+
localStorage.setItem(`lasso:session:${activeConversationId}`, JSON.stringify(sessionData));
|
|
19699
|
+
const listRaw = localStorage.getItem(projKey);
|
|
19700
|
+
let list2 = listRaw ? JSON.parse(listRaw) : [];
|
|
19701
|
+
list2 = list2.filter((item) => item.id !== activeConversationId);
|
|
19702
|
+
list2.unshift({
|
|
19703
|
+
id: activeConversationId,
|
|
19704
|
+
title: sessionData.title,
|
|
19705
|
+
updatedAt: sessionData.updatedAt
|
|
19706
|
+
});
|
|
19707
|
+
localStorage.setItem(projKey, JSON.stringify(list2.slice(0, 30)));
|
|
19708
|
+
} catch (err) {
|
|
19709
|
+
}
|
|
19710
|
+
}
|
|
19711
|
+
function loadLocalSession(convId) {
|
|
19712
|
+
try {
|
|
19713
|
+
const raw = localStorage.getItem(`lasso:session:${convId}`);
|
|
19714
|
+
if (!raw) return false;
|
|
19715
|
+
const session = JSON.parse(raw);
|
|
19716
|
+
if (session && Array.isArray(session.messages)) {
|
|
19717
|
+
commandMessages = session.messages;
|
|
19718
|
+
activeConversationId = convId;
|
|
19719
|
+
renderMessages();
|
|
19720
|
+
scrollToBottom();
|
|
19721
|
+
return true;
|
|
19722
|
+
}
|
|
19723
|
+
} catch {
|
|
19724
|
+
}
|
|
19725
|
+
return false;
|
|
19726
|
+
}
|
|
19727
|
+
function restoreLatestSession() {
|
|
19728
|
+
try {
|
|
19729
|
+
const projKey = getStorageProjectKey();
|
|
19730
|
+
const listRaw = localStorage.getItem(projKey);
|
|
19731
|
+
if (!listRaw) return;
|
|
19732
|
+
const list2 = JSON.parse(listRaw);
|
|
19733
|
+
if (list2 && list2.length > 0 && !activeConversationId) {
|
|
19734
|
+
loadLocalSession(list2[0].id);
|
|
19735
|
+
}
|
|
19736
|
+
} catch {
|
|
19737
|
+
}
|
|
19738
|
+
}
|
|
19679
19739
|
async function createServerConversation(scope2 = "private") {
|
|
19680
19740
|
const projectId = state.collab?.projectId || state.collabProjectId;
|
|
19681
19741
|
if (!projectId) return null;
|
|
@@ -19687,10 +19747,29 @@
|
|
|
19687
19747
|
}
|
|
19688
19748
|
async function loadConversationList() {
|
|
19689
19749
|
const projectId = state.collab?.projectId || state.collabProjectId;
|
|
19750
|
+
try {
|
|
19751
|
+
const projKey = getStorageProjectKey();
|
|
19752
|
+
const listRaw = localStorage.getItem(projKey);
|
|
19753
|
+
if (listRaw) {
|
|
19754
|
+
const localList = JSON.parse(listRaw);
|
|
19755
|
+
conversationList = localList.map((item) => ({
|
|
19756
|
+
id: item.id,
|
|
19757
|
+
title: item.title,
|
|
19758
|
+
scope: "local",
|
|
19759
|
+
updatedAt: item.updatedAt
|
|
19760
|
+
}));
|
|
19761
|
+
}
|
|
19762
|
+
} catch {
|
|
19763
|
+
}
|
|
19690
19764
|
if (!projectId) return;
|
|
19691
19765
|
const data6 = await apiFetch(`/agent/conversations?projectId=${encodeURIComponent(projectId)}`);
|
|
19692
|
-
if (data6?.conversations) {
|
|
19693
|
-
|
|
19766
|
+
if (data6?.conversations && Array.isArray(data6.conversations)) {
|
|
19767
|
+
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
|
+
];
|
|
19772
|
+
conversationList = merged;
|
|
19694
19773
|
}
|
|
19695
19774
|
}
|
|
19696
19775
|
function formatDateAgo(dateStr) {
|
|
@@ -19742,6 +19821,9 @@
|
|
|
19742
19821
|
activeConversationId = convId;
|
|
19743
19822
|
if (commandHistoryPopover) commandHistoryPopover.hidden = true;
|
|
19744
19823
|
commandHistoryBtn?.classList.remove("active");
|
|
19824
|
+
if (loadLocalSession(convId)) {
|
|
19825
|
+
return;
|
|
19826
|
+
}
|
|
19745
19827
|
const data6 = await apiFetch(`/agent/conversations/${convId}`);
|
|
19746
19828
|
if (data6?.conversation?.messages && Array.isArray(data6.conversation.messages)) {
|
|
19747
19829
|
commandMessages = data6.conversation.messages.map((m, idx) => ({
|
|
@@ -19759,6 +19841,7 @@
|
|
|
19759
19841
|
changesApplied: m.changesApplied,
|
|
19760
19842
|
error: m.error
|
|
19761
19843
|
}));
|
|
19844
|
+
saveLocalSession();
|
|
19762
19845
|
} else {
|
|
19763
19846
|
commandMessages = [];
|
|
19764
19847
|
}
|
|
@@ -20255,6 +20338,12 @@
|
|
|
20255
20338
|
}
|
|
20256
20339
|
return;
|
|
20257
20340
|
}
|
|
20341
|
+
const continueBtn = target.closest(".lasso-command-continue-btn");
|
|
20342
|
+
if (continueBtn) {
|
|
20343
|
+
const msgId = continueBtn.closest(".lasso-command-msg")?.dataset.msgId;
|
|
20344
|
+
continueWithThoughts(msgId);
|
|
20345
|
+
return;
|
|
20346
|
+
}
|
|
20258
20347
|
const retryBtn = target.closest(".lasso-command-retry-btn");
|
|
20259
20348
|
if (retryBtn) {
|
|
20260
20349
|
const msgId = retryBtn.closest(".lasso-command-msg")?.dataset.msgId;
|
|
@@ -20286,6 +20375,9 @@
|
|
|
20286
20375
|
updateEmptyStateUserName();
|
|
20287
20376
|
renderSuggestions();
|
|
20288
20377
|
populateModelMenu();
|
|
20378
|
+
if (commandMessages.length === 0) {
|
|
20379
|
+
restoreLatestSession();
|
|
20380
|
+
}
|
|
20289
20381
|
renderMessages();
|
|
20290
20382
|
loadConversationList();
|
|
20291
20383
|
}
|
|
@@ -20356,6 +20448,24 @@
|
|
|
20356
20448
|
commandMessages.splice(failedIndex, 1);
|
|
20357
20449
|
void runPrompt(promptToRetry, true);
|
|
20358
20450
|
}
|
|
20451
|
+
function continueWithThoughts(msgId) {
|
|
20452
|
+
if (activeTaskId) return;
|
|
20453
|
+
const targetMsg = msgId ? commandMessages.find((m) => m.id === msgId) : [...commandMessages].reverse().find((m) => m.role === "assistant");
|
|
20454
|
+
if (!targetMsg) return;
|
|
20455
|
+
const targetIndex = commandMessages.indexOf(targetMsg);
|
|
20456
|
+
const precedingUserMsg = [...commandMessages].slice(0, targetIndex).reverse().find((m) => m.role === "user");
|
|
20457
|
+
const originalPrompt = precedingUserMsg?.content || "";
|
|
20458
|
+
const thoughtSummary = (targetMsg.thinking?.steps || []).map((s) => `- ${s.title}${s.detail ? `: ${s.detail}` : ""}`).join("\n");
|
|
20459
|
+
const continuationPrompt = thoughtSummary ? `Continue executing the previous task: "${originalPrompt}".
|
|
20460
|
+
|
|
20461
|
+
Here is your prior analysis and plan:
|
|
20462
|
+
${thoughtSummary}
|
|
20463
|
+
|
|
20464
|
+
Please proceed directly with the implementation and code changes from this point.` : originalPrompt;
|
|
20465
|
+
targetMsg.error = void 0;
|
|
20466
|
+
saveLocalSession();
|
|
20467
|
+
void runPrompt(continuationPrompt);
|
|
20468
|
+
}
|
|
20359
20469
|
async function handleSubmit() {
|
|
20360
20470
|
if (!commandInput) return;
|
|
20361
20471
|
if (activeTaskId) {
|
|
@@ -20587,6 +20697,7 @@
|
|
|
20587
20697
|
});
|
|
20588
20698
|
}
|
|
20589
20699
|
}
|
|
20700
|
+
saveLocalSession();
|
|
20590
20701
|
renderMessages();
|
|
20591
20702
|
scrollToBottom();
|
|
20592
20703
|
} else if (message.status === "error" || message.status === "stopped") {
|
|
@@ -20607,6 +20718,7 @@
|
|
|
20607
20718
|
sessionId: state.collabProjectId,
|
|
20608
20719
|
status: "idle"
|
|
20609
20720
|
});
|
|
20721
|
+
saveLocalSession();
|
|
20610
20722
|
renderMessages();
|
|
20611
20723
|
scrollToBottom();
|
|
20612
20724
|
}
|
|
@@ -20685,10 +20797,18 @@
|
|
|
20685
20797
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
|
20686
20798
|
${escapeHtml4(msg.error)}
|
|
20687
20799
|
</div>
|
|
20688
|
-
<
|
|
20689
|
-
|
|
20690
|
-
|
|
20691
|
-
|
|
20800
|
+
<div class="lasso-command-error-actions" style="display: flex; gap: 8px; margin-top: 6px; flex-wrap: wrap;">
|
|
20801
|
+
${msg.thinking && msg.thinking.steps.length > 0 ? `
|
|
20802
|
+
<button class="lasso-command-continue-btn" type="button" title="Continue execution using the thoughts and analysis generated so far" style="display: inline-flex; align-items: center; gap: 5px; padding: 5px 10px; font-size: 12px; font-weight: 500; border-radius: 6px; background: rgba(59, 130, 246, 0.15); border: 1px solid rgba(59, 130, 246, 0.3); color: #93c5fd; cursor: pointer;">
|
|
20803
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
|
20804
|
+
Continue with thoughts so far
|
|
20805
|
+
</button>
|
|
20806
|
+
` : ""}
|
|
20807
|
+
<button class="lasso-command-retry-btn" type="button" title="Retry from start" style="display: inline-flex; align-items: center; gap: 5px; padding: 5px 10px; font-size: 12px; font-weight: 500; border-radius: 6px; background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.15); color: #e2e8f0; cursor: pointer;">
|
|
20808
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
|
|
20809
|
+
Retry
|
|
20810
|
+
</button>
|
|
20811
|
+
</div>
|
|
20692
20812
|
` : ""}
|
|
20693
20813
|
</div>
|
|
20694
20814
|
`;
|
|
@@ -49,6 +49,15 @@ const DEFAULT_IGNORE_DIRS = new Set([
|
|
|
49
49
|
".gradle",
|
|
50
50
|
".terraform",
|
|
51
51
|
]);
|
|
52
|
+
const DEFAULT_IGNORE_FILES = new Set([
|
|
53
|
+
"next-env.d.ts",
|
|
54
|
+
"tsconfig.tsbuildinfo",
|
|
55
|
+
".DS_Store",
|
|
56
|
+
"Thumbs.db",
|
|
57
|
+
]);
|
|
58
|
+
function shouldIgnoreFile(name) {
|
|
59
|
+
return DEFAULT_IGNORE_FILES.has(name) || name.endsWith(".tsbuildinfo");
|
|
60
|
+
}
|
|
52
61
|
function hashBuffer(buf) {
|
|
53
62
|
return node_crypto_1.default.createHash("sha256").update(buf).digest("hex");
|
|
54
63
|
}
|
|
@@ -110,8 +119,9 @@ class WorkspaceGuard {
|
|
|
110
119
|
walk(absPath, relPath, depth + 1);
|
|
111
120
|
continue;
|
|
112
121
|
}
|
|
113
|
-
if (!entry.isFile()) {
|
|
114
|
-
|
|
122
|
+
if (!entry.isFile() || shouldIgnoreFile(entry.name)) {
|
|
123
|
+
if (!entry.isFile())
|
|
124
|
+
guard.skipped.push(relPath);
|
|
115
125
|
continue;
|
|
116
126
|
}
|
|
117
127
|
try {
|
|
@@ -278,7 +288,7 @@ class WorkspaceGuard {
|
|
|
278
288
|
walk(absPath, relPath, depth + 1);
|
|
279
289
|
continue;
|
|
280
290
|
}
|
|
281
|
-
if (!entry.isFile())
|
|
291
|
+
if (!entry.isFile() || shouldIgnoreFile(entry.name))
|
|
282
292
|
continue;
|
|
283
293
|
if (this.ignoreFn && this.ignoreFn(relPath, false))
|
|
284
294
|
continue;
|
package/package.json
CHANGED