@lasso-ai/cli 1.1.2 → 1.1.4
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 +20 -10
- package/dist/cli/bridge.js +32 -10
- package/dist/overlay.js +2544 -2506
- 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
|
@@ -36,8 +36,9 @@ async function detectLocalAgents() {
|
|
|
36
36
|
}
|
|
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
|
+
const layoutFiles = /^(layout|_layout|_app|app|_document|document|template|_template)\.(tsx?|jsx?)$/i;
|
|
39
40
|
const sourceFileCache = new Map();
|
|
40
|
-
async function sourceFiles(directory, budget = { remaining:
|
|
41
|
+
async function sourceFiles(directory, budget = { remaining: 25 }) {
|
|
41
42
|
if (budget.remaining <= 0)
|
|
42
43
|
return [];
|
|
43
44
|
const cached = sourceFileCache.get(directory);
|
|
@@ -48,6 +49,8 @@ async function sourceFiles(directory, budget = { remaining: 40 }) {
|
|
|
48
49
|
}
|
|
49
50
|
const entries = await promises_1.default.readdir(directory, { withFileTypes: true });
|
|
50
51
|
const files = [];
|
|
52
|
+
const layoutFilesList = [];
|
|
53
|
+
const otherFiles = [];
|
|
51
54
|
for (const entry of entries) {
|
|
52
55
|
if (ignored.has(entry.name) || entry.name.startsWith("."))
|
|
53
56
|
continue;
|
|
@@ -59,11 +62,18 @@ async function sourceFiles(directory, budget = { remaining: 40 }) {
|
|
|
59
62
|
files.push(...nested);
|
|
60
63
|
}
|
|
61
64
|
else if (sourceExtensions.test(entry.name)) {
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
if (layoutFiles.test(entry.name)) {
|
|
66
|
+
layoutFilesList.push(fullPath);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
otherFiles.push(fullPath);
|
|
70
|
+
}
|
|
64
71
|
}
|
|
65
72
|
}
|
|
66
|
-
|
|
73
|
+
// Prioritize layout files first, then other files
|
|
74
|
+
files.push(...layoutFilesList, ...otherFiles);
|
|
75
|
+
budget.remaining -= files.length;
|
|
76
|
+
sourceFileCache.set(directory, { expiresAt: Date.now() + 30000, files });
|
|
67
77
|
return files;
|
|
68
78
|
}
|
|
69
79
|
async function contextFor(cwd, element) {
|
|
@@ -95,7 +105,7 @@ async function contextFor(cwd, element) {
|
|
|
95
105
|
try {
|
|
96
106
|
const content = await promises_1.default.readFile(file, "utf8");
|
|
97
107
|
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,
|
|
108
|
+
return `FILE: ${node_path_1.default.relative(cwd, file)}\n${content.slice(0, 6000)}`;
|
|
99
109
|
}
|
|
100
110
|
}
|
|
101
111
|
catch {
|
|
@@ -104,7 +114,7 @@ async function contextFor(cwd, element) {
|
|
|
104
114
|
return null;
|
|
105
115
|
}));
|
|
106
116
|
for (const result of results) {
|
|
107
|
-
if (result && snippets.length <
|
|
117
|
+
if (result && snippets.length < 4)
|
|
108
118
|
snippets.push(result);
|
|
109
119
|
}
|
|
110
120
|
return snippets.join("\n\n---\n\n");
|
|
@@ -560,9 +570,9 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress, taskI
|
|
|
560
570
|
else if (stream === "stderr" && line.trim()) {
|
|
561
571
|
const hint = agentFailureHint(line);
|
|
562
572
|
if (hint)
|
|
563
|
-
onProgress?.(`${label} · ${hint}
|
|
573
|
+
onProgress?.(`${label} · ${hint}`, undefined, "error");
|
|
564
574
|
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…");
|
|
575
|
+
onProgress?.("OpenCode · provider rejected the request; stopping this task…", undefined, "error");
|
|
566
576
|
child.kill("SIGTERM");
|
|
567
577
|
}
|
|
568
578
|
}
|
|
@@ -611,9 +621,9 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress, taskI
|
|
|
611
621
|
}
|
|
612
622
|
const hint = agentFailureHint(stderrTail);
|
|
613
623
|
if (hint)
|
|
614
|
-
onProgress?.(`${label} · ${hint}
|
|
624
|
+
onProgress?.(`${label} · ${hint}`, undefined, "error");
|
|
615
625
|
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…");
|
|
626
|
+
onProgress?.("OpenCode · provider rejected the request; stopping this task…", undefined, "error");
|
|
617
627
|
}
|
|
618
628
|
}
|
|
619
629
|
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 }));
|