@lasso-ai/cli 1.1.2 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/agent.d.ts +2 -1
- package/dist/cli/agent.js +8 -8
- package/dist/cli/bridge.js +32 -10
- package/dist/overlay.js +2513 -2488
- package/package.json +1 -1
package/dist/cli/agent.d.ts
CHANGED
|
@@ -45,7 +45,8 @@ export type AgentConfig = {
|
|
|
45
45
|
baseUrl?: string;
|
|
46
46
|
};
|
|
47
47
|
export type LocalAgent = "claude-code" | "codex" | "opencode";
|
|
48
|
-
export type
|
|
48
|
+
export type AgentProgressLevel = "working" | "error";
|
|
49
|
+
export type AgentProgress = (message: string, detail?: string, level?: AgentProgressLevel) => void;
|
|
49
50
|
export type AgentPrompt = {
|
|
50
51
|
message: string;
|
|
51
52
|
kind: "permission" | "input";
|
package/dist/cli/agent.js
CHANGED
|
@@ -37,7 +37,7 @@ async function detectLocalAgents() {
|
|
|
37
37
|
const ignored = new Set(["node_modules", ".git", ".next", "dist", "build", ".turbo"]);
|
|
38
38
|
const sourceExtensions = /\.(tsx?|jsx?|vue|svelte|css|scss|html)$/i;
|
|
39
39
|
const sourceFileCache = new Map();
|
|
40
|
-
async function sourceFiles(directory, budget = { remaining:
|
|
40
|
+
async function sourceFiles(directory, budget = { remaining: 25 }) {
|
|
41
41
|
if (budget.remaining <= 0)
|
|
42
42
|
return [];
|
|
43
43
|
const cached = sourceFileCache.get(directory);
|
|
@@ -63,7 +63,7 @@ async function sourceFiles(directory, budget = { remaining: 40 }) {
|
|
|
63
63
|
budget.remaining -= 1;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
sourceFileCache.set(directory, { expiresAt: Date.now() +
|
|
66
|
+
sourceFileCache.set(directory, { expiresAt: Date.now() + 30000, files });
|
|
67
67
|
return files;
|
|
68
68
|
}
|
|
69
69
|
async function contextFor(cwd, element) {
|
|
@@ -95,7 +95,7 @@ async function contextFor(cwd, element) {
|
|
|
95
95
|
try {
|
|
96
96
|
const content = await promises_1.default.readFile(file, "utf8");
|
|
97
97
|
if (!needle || (sourceFile && file.endsWith(sourceFile)) || content.includes(needle) || content.includes(element.label)) {
|
|
98
|
-
return `FILE: ${node_path_1.default.relative(cwd, file)}\n${content.slice(0,
|
|
98
|
+
return `FILE: ${node_path_1.default.relative(cwd, file)}\n${content.slice(0, 6000)}`;
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
catch {
|
|
@@ -104,7 +104,7 @@ async function contextFor(cwd, element) {
|
|
|
104
104
|
return null;
|
|
105
105
|
}));
|
|
106
106
|
for (const result of results) {
|
|
107
|
-
if (result && snippets.length <
|
|
107
|
+
if (result && snippets.length < 4)
|
|
108
108
|
snippets.push(result);
|
|
109
109
|
}
|
|
110
110
|
return snippets.join("\n\n---\n\n");
|
|
@@ -560,9 +560,9 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress, taskI
|
|
|
560
560
|
else if (stream === "stderr" && line.trim()) {
|
|
561
561
|
const hint = agentFailureHint(line);
|
|
562
562
|
if (hint)
|
|
563
|
-
onProgress?.(`${label} · ${hint}
|
|
563
|
+
onProgress?.(`${label} · ${hint}`, undefined, "error");
|
|
564
564
|
if (provider === "opencode" && /quota exceeded|authentication failed|invalid api key|unauthorized|forbidden/i.test(line)) {
|
|
565
|
-
onProgress?.("OpenCode · provider rejected the request; stopping this task…");
|
|
565
|
+
onProgress?.("OpenCode · provider rejected the request; stopping this task…", undefined, "error");
|
|
566
566
|
child.kill("SIGTERM");
|
|
567
567
|
}
|
|
568
568
|
}
|
|
@@ -611,9 +611,9 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress, taskI
|
|
|
611
611
|
}
|
|
612
612
|
const hint = agentFailureHint(stderrTail);
|
|
613
613
|
if (hint)
|
|
614
|
-
onProgress?.(`${label} · ${hint}
|
|
614
|
+
onProgress?.(`${label} · ${hint}`, undefined, "error");
|
|
615
615
|
if (provider === "opencode" && /quota exceeded|authentication failed|invalid api key|unauthorized|forbidden/i.test(stderrTail)) {
|
|
616
|
-
onProgress?.("OpenCode · provider rejected the request; stopping this task…");
|
|
616
|
+
onProgress?.("OpenCode · provider rejected the request; stopping this task…", undefined, "error");
|
|
617
617
|
}
|
|
618
618
|
}
|
|
619
619
|
resolve({ stdout, stderr, exitCode: code ?? 1 });
|
package/dist/cli/bridge.js
CHANGED
|
@@ -118,7 +118,20 @@ function proposalMatchesCurrentSource(cwd, changes) {
|
|
|
118
118
|
try {
|
|
119
119
|
for (const change of changes) {
|
|
120
120
|
const filePath = resolveProposedFile(cwd, change.filePath);
|
|
121
|
-
|
|
121
|
+
const content = node_fs_1.default.readFileSync(filePath, "utf8");
|
|
122
|
+
// Try exact match first
|
|
123
|
+
try {
|
|
124
|
+
prepareChange(content, change);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// If exact match fails, try with normalized whitespace
|
|
128
|
+
const normalizedContent = content.replace(/\s+/g, " ").trim();
|
|
129
|
+
const normalizedOldString = change.oldString.replace(/\s+/g, " ").trim();
|
|
130
|
+
const normalizedNewString = change.newString.replace(/\s+/g, " ").trim();
|
|
131
|
+
if (!normalizedContent.includes(normalizedOldString)) {
|
|
132
|
+
throw new Error("Source content does not match proposed change (even with normalized whitespace)");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
122
135
|
}
|
|
123
136
|
return true;
|
|
124
137
|
}
|
|
@@ -308,9 +321,9 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
308
321
|
if (socket.readyState === socket.OPEN) {
|
|
309
322
|
socket.send(JSON.stringify({ type: "agent_status", taskId: request.taskId, status: "working", message: statusMessage || "Working…" }));
|
|
310
323
|
}
|
|
311
|
-
void (0, agent_1.proposeChanges)(cwd, request, config, controller.signal, (message, detail) => {
|
|
324
|
+
void (0, agent_1.proposeChanges)(cwd, request, config, controller.signal, (message, detail, level) => {
|
|
312
325
|
if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
|
|
313
|
-
socket.send(JSON.stringify({ type: "agent_status", taskId: request.taskId, status: "working", message, detail }));
|
|
326
|
+
socket.send(JSON.stringify({ type: "agent_status", taskId: request.taskId, status: level === "error" ? "error" : "working", message, detail }));
|
|
314
327
|
}
|
|
315
328
|
}, (prompt) => {
|
|
316
329
|
if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
|
|
@@ -322,21 +335,30 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
322
335
|
return;
|
|
323
336
|
if (!proposalMatchesCurrentSource(cwd, proposal.changes)) {
|
|
324
337
|
const refreshAttempts = reviewRefreshAttempts.get(request.taskId) || 0;
|
|
325
|
-
|
|
338
|
+
const MAX_REFRESH_ATTEMPTS = 3;
|
|
339
|
+
if (refreshAttempts < MAX_REFRESH_ATTEMPTS) {
|
|
326
340
|
reviewRefreshAttempts.set(request.taskId, refreshAttempts + 1);
|
|
327
|
-
|
|
341
|
+
setTimeout(() => {
|
|
342
|
+
if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
|
|
343
|
+
runEditReview(request, config, `The source changed while the proposal was being prepared. Refreshing the review… (${refreshAttempts + 1}/${MAX_REFRESH_ATTEMPTS})`);
|
|
344
|
+
}
|
|
345
|
+
}, 500 * (refreshAttempts + 1));
|
|
328
346
|
}
|
|
329
347
|
else {
|
|
330
|
-
socket.send(JSON.stringify({ type: "agent_status", taskId: request.taskId, status: "error", message: "The source is still changing. Stop the dev-server edit or try the request again." }));
|
|
348
|
+
socket.send(JSON.stringify({ type: "agent_status", taskId: request.taskId, status: "error", message: "The source is still changing after multiple refresh attempts. Stop the dev-server edit or try the request again." }));
|
|
331
349
|
}
|
|
332
350
|
return;
|
|
333
351
|
}
|
|
334
352
|
socket.send(JSON.stringify({ type: "agent_status", taskId: request.taskId, status: "review", message: proposal.summary, changes: proposal.changes }));
|
|
335
353
|
})
|
|
336
354
|
.catch((error) => {
|
|
337
|
-
if (
|
|
338
|
-
|
|
355
|
+
if (socket.readyState !== socket.OPEN)
|
|
356
|
+
return;
|
|
357
|
+
if (controller.signal.aborted) {
|
|
358
|
+
socket.send(JSON.stringify({ type: "agent_status", taskId: request.taskId, status: "stopped", message: "Agent stopped." }));
|
|
359
|
+
return;
|
|
339
360
|
}
|
|
361
|
+
socket.send(JSON.stringify({ type: "agent_status", taskId: request.taskId, status: "error", message: error instanceof Error ? error.message : "The agent could not prepare a change." }));
|
|
340
362
|
})
|
|
341
363
|
.finally(() => {
|
|
342
364
|
if (taskControllers.get(request.taskId) === controller)
|
|
@@ -432,9 +454,9 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
432
454
|
? { provider: cliProvider, model: msg.model }
|
|
433
455
|
: { ...agentConfig, provider: (msg.provider || agentConfig.provider), model: msg.model };
|
|
434
456
|
socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: "working", message: "Working…" }));
|
|
435
|
-
void (0, agent_1.answerQuestion)(cwd, { taskId: msg.taskId, question: msg.question, context: msg.context, element: msg.element, messages: msg.messages }, selectedConfig, controller.signal, (message, detail) => {
|
|
457
|
+
void (0, agent_1.answerQuestion)(cwd, { taskId: msg.taskId, question: msg.question, context: msg.context, element: msg.element, messages: msg.messages }, selectedConfig, controller.signal, (message, detail, level) => {
|
|
436
458
|
if (!controller.signal.aborted && socket.readyState === socket.OPEN)
|
|
437
|
-
socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: "working", message, detail }));
|
|
459
|
+
socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: level === "error" ? "error" : "working", message, detail }));
|
|
438
460
|
}, (prompt) => {
|
|
439
461
|
if (!controller.signal.aborted && socket.readyState === socket.OPEN)
|
|
440
462
|
socket.send(JSON.stringify({ type: "agent_prompt", taskId: msg.taskId, prompt }));
|