@lasso-ai/cli 1.0.15 → 1.0.17
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.js +1 -1
- package/dist/cli/bridge.js +64 -23
- package/package.json +1 -1
package/dist/cli/agent.js
CHANGED
|
@@ -314,7 +314,7 @@ function localCommand(provider, model, prompt) {
|
|
|
314
314
|
return { command: "codex", args: ["exec", "--json", "--sandbox", "read-only", "--skip-git-repo-check", ...(selectedModel ? ["--model", selectedModel] : []), prompt || ""] };
|
|
315
315
|
}
|
|
316
316
|
async function proposeWithLocalAgent(cwd, instruction, context, config, signal, onProgress) {
|
|
317
|
-
const outputContract = `Return ONLY valid JSON in this exact shape: {"summary":"short explanation","changes":[{"filePath":"relative/path","oldString":"exact existing text","newString":"replacement text"}]}.
|
|
317
|
+
const outputContract = `Return ONLY valid JSON in this exact shape: {"summary":"short explanation","changes":[{"filePath":"relative/path","oldString":"exact existing text","newString":"replacement text"}]}. Treat the supplied source context as read-only. Before returning, verify every oldString against that context. Use project-relative paths only. Do not edit files, run write commands, commit, or produce markdown fences.`;
|
|
318
318
|
const prompt = `${instruction}\n\n${outputContract}\n\nLasso has already assembled this source context:\n${context || "No matching source context was found."}`;
|
|
319
319
|
const local = localCommand(config.provider, config.model, prompt);
|
|
320
320
|
const command = local.command;
|
package/dist/cli/bridge.js
CHANGED
|
@@ -114,6 +114,18 @@ function prepareChange(content, change) {
|
|
|
114
114
|
const start = content.indexOf(oldString);
|
|
115
115
|
return { start, end: start + oldString.length, oldString, newString };
|
|
116
116
|
}
|
|
117
|
+
function proposalMatchesCurrentSource(cwd, changes) {
|
|
118
|
+
try {
|
|
119
|
+
for (const change of changes) {
|
|
120
|
+
const filePath = resolveProposedFile(cwd, change.filePath);
|
|
121
|
+
prepareChange(node_fs_1.default.readFileSync(filePath, "utf8"), change);
|
|
122
|
+
}
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
117
129
|
function readEnvFile(cwd, filename) {
|
|
118
130
|
try {
|
|
119
131
|
return node_fs_1.default.readFileSync(node_path_1.default.join(cwd, filename), "utf8").split(/\r?\n/).reduce((values, line) => {
|
|
@@ -136,6 +148,9 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
136
148
|
const wss = new ws_1.WebSocketServer({ server: bridgeServer });
|
|
137
149
|
let overlaySocket = null;
|
|
138
150
|
let lastSnapshot = [];
|
|
151
|
+
let lastEditRequest = null;
|
|
152
|
+
let lastEditConfig = null;
|
|
153
|
+
let reviewRefreshAttempts = 0;
|
|
139
154
|
const envRoots = [cwd, node_path_1.default.join(cwd, "web"), node_path_1.default.join(cwd, "server")];
|
|
140
155
|
const fileEnv = envRoots.reduce((values, root) => ({
|
|
141
156
|
...values,
|
|
@@ -254,6 +269,43 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
254
269
|
if (socket.readyState === socket.OPEN)
|
|
255
270
|
socket.send(JSON.stringify({ type: "git_state", git }));
|
|
256
271
|
});
|
|
272
|
+
const runEditReview = (request, config, statusMessage) => {
|
|
273
|
+
activeAgentController?.abort();
|
|
274
|
+
const controller = new AbortController();
|
|
275
|
+
activeAgentController = controller;
|
|
276
|
+
if (statusMessage && socket.readyState === socket.OPEN) {
|
|
277
|
+
socket.send(JSON.stringify({ type: "agent_status", status: "working", message: statusMessage }));
|
|
278
|
+
}
|
|
279
|
+
void (0, agent_1.proposeChanges)(cwd, request, config, controller.signal, (message, detail) => {
|
|
280
|
+
if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
|
|
281
|
+
socket.send(JSON.stringify({ type: "agent_status", status: "working", message, detail }));
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
.then((proposal) => {
|
|
285
|
+
if (controller.signal.aborted || socket.readyState !== socket.OPEN)
|
|
286
|
+
return;
|
|
287
|
+
if (!proposalMatchesCurrentSource(cwd, proposal.changes)) {
|
|
288
|
+
if (reviewRefreshAttempts < 1) {
|
|
289
|
+
reviewRefreshAttempts += 1;
|
|
290
|
+
runEditReview(request, config, "The source changed while the proposal was being prepared. Refreshing the review…");
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
socket.send(JSON.stringify({ type: "agent_status", status: "error", message: "The source is still changing. Stop the dev-server edit or try the request again." }));
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
socket.send(JSON.stringify({ type: "agent_status", status: "review", message: proposal.summary, changes: proposal.changes }));
|
|
298
|
+
})
|
|
299
|
+
.catch((error) => {
|
|
300
|
+
if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
|
|
301
|
+
socket.send(JSON.stringify({ type: "agent_status", status: "error", message: error instanceof Error ? error.message : "The agent could not prepare a change." }));
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
.finally(() => {
|
|
305
|
+
if (activeAgentController === controller)
|
|
306
|
+
activeAgentController = null;
|
|
307
|
+
});
|
|
308
|
+
};
|
|
257
309
|
socket.on("message", async (raw) => {
|
|
258
310
|
const msg = JSON.parse(raw.toString());
|
|
259
311
|
if (msg.type === "edit" || msg.type === "ask") {
|
|
@@ -304,31 +356,13 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
304
356
|
socket.send(JSON.stringify({ type: "agent_status", status: "error", message: "Add a supported agent key: GOOGLE_GENERATIVE_AI_API_KEY, OPENAI_API_KEY, or ANTHROPIC_API_KEY." }));
|
|
305
357
|
return;
|
|
306
358
|
}
|
|
307
|
-
activeAgentController?.abort();
|
|
308
|
-
const controller = new AbortController();
|
|
309
|
-
activeAgentController = controller;
|
|
310
359
|
const selectedConfig = localProvider
|
|
311
360
|
? { provider: cliProvider, model: msg.model }
|
|
312
361
|
: { ...agentConfig, provider: (msg.provider || agentConfig.provider), model: msg.model };
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
})
|
|
318
|
-
.then((proposal) => {
|
|
319
|
-
if (!controller.signal.aborted)
|
|
320
|
-
socket.send(JSON.stringify({ type: "agent_status", status: "review", message: proposal.summary, changes: proposal.changes }));
|
|
321
|
-
})
|
|
322
|
-
.catch((error) => {
|
|
323
|
-
if (controller.signal.aborted)
|
|
324
|
-
return;
|
|
325
|
-
const message = error instanceof Error ? error.message : "The agent could not prepare a change.";
|
|
326
|
-
socket.send(JSON.stringify({ type: "agent_status", status: "error", message }));
|
|
327
|
-
})
|
|
328
|
-
.finally(() => {
|
|
329
|
-
if (activeAgentController === controller)
|
|
330
|
-
activeAgentController = null;
|
|
331
|
-
});
|
|
362
|
+
lastEditRequest = msg;
|
|
363
|
+
lastEditConfig = selectedConfig;
|
|
364
|
+
reviewRefreshAttempts = 0;
|
|
365
|
+
runEditReview(msg, selectedConfig);
|
|
332
366
|
}
|
|
333
367
|
else if (msg.type === "stop") {
|
|
334
368
|
activeAgentController?.abort();
|
|
@@ -463,7 +497,14 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
463
497
|
for (const snapshot of lastSnapshot)
|
|
464
498
|
node_fs_1.default.writeFileSync(snapshot.filePath, snapshot.content);
|
|
465
499
|
lastSnapshot = [];
|
|
466
|
-
|
|
500
|
+
const message = error instanceof Error ? error.message : "The change could not be applied.";
|
|
501
|
+
const sourceChanged = message.includes("The source changed after the suggestion was generated");
|
|
502
|
+
if (sourceChanged && lastEditRequest && lastEditConfig && reviewRefreshAttempts < 1) {
|
|
503
|
+
runEditReview(lastEditRequest, lastEditConfig, "The source changed. Refreshing the review against the current file…");
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
socket.send(JSON.stringify({ type: "agent_status", status: "error", message }));
|
|
507
|
+
}
|
|
467
508
|
}
|
|
468
509
|
}
|
|
469
510
|
else if (msg.type === "undo") {
|
package/package.json
CHANGED