@lasso-ai/cli 1.0.14 → 1.0.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/cli/agent.js +52 -9
- package/dist/cli/bridge.js +37 -1
- package/package.json +1 -1
package/dist/cli/agent.js
CHANGED
|
@@ -91,17 +91,60 @@ async function contextFor(cwd, element) {
|
|
|
91
91
|
}
|
|
92
92
|
return snippets.join("\n\n---\n\n");
|
|
93
93
|
}
|
|
94
|
+
function jsonObjectCandidates(text) {
|
|
95
|
+
const candidates = [];
|
|
96
|
+
for (let start = 0; start < text.length; start += 1) {
|
|
97
|
+
if (text[start] !== "{")
|
|
98
|
+
continue;
|
|
99
|
+
let depth = 0;
|
|
100
|
+
let inString = false;
|
|
101
|
+
let escaped = false;
|
|
102
|
+
for (let index = start; index < text.length; index += 1) {
|
|
103
|
+
const character = text[index];
|
|
104
|
+
if (inString) {
|
|
105
|
+
if (escaped)
|
|
106
|
+
escaped = false;
|
|
107
|
+
else if (character === "\\")
|
|
108
|
+
escaped = true;
|
|
109
|
+
else if (character === '"')
|
|
110
|
+
inString = false;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (character === '"') {
|
|
114
|
+
inString = true;
|
|
115
|
+
}
|
|
116
|
+
else if (character === "{") {
|
|
117
|
+
depth += 1;
|
|
118
|
+
}
|
|
119
|
+
else if (character === "}" && --depth === 0) {
|
|
120
|
+
candidates.push(text.slice(start, index + 1));
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return candidates;
|
|
126
|
+
}
|
|
94
127
|
function jsonFrom(text) {
|
|
95
|
-
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1]
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
128
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1];
|
|
129
|
+
const candidates = [...(fenced ? [fenced] : []), ...jsonObjectCandidates(text)];
|
|
130
|
+
for (const candidate of candidates) {
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(candidate.trim());
|
|
133
|
+
if (!Array.isArray(parsed.changes))
|
|
134
|
+
continue;
|
|
135
|
+
for (const change of parsed.changes) {
|
|
136
|
+
if (!change.filePath || typeof change.oldString !== "string" || typeof change.newString !== "string") {
|
|
137
|
+
throw new Error("The agent returned an invalid file change.");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return { summary: parsed.summary || "The proposed source changes are ready for review.", changes: parsed.changes };
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (error instanceof Error && error.message === "The agent returned an invalid file change.")
|
|
144
|
+
throw error;
|
|
102
145
|
}
|
|
103
146
|
}
|
|
104
|
-
|
|
147
|
+
throw new Error("The agent returned no valid reviewable changes. Progress output may have been mixed with the final JSON.");
|
|
105
148
|
}
|
|
106
149
|
function extractLocalAgentText(raw, provider) {
|
|
107
150
|
if (provider === "claude-code") {
|
|
@@ -271,7 +314,7 @@ function localCommand(provider, model, prompt) {
|
|
|
271
314
|
return { command: "codex", args: ["exec", "--json", "--sandbox", "read-only", "--skip-git-repo-check", ...(selectedModel ? ["--model", selectedModel] : []), prompt || ""] };
|
|
272
315
|
}
|
|
273
316
|
async function proposeWithLocalAgent(cwd, instruction, context, config, signal, onProgress) {
|
|
274
|
-
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.`;
|
|
275
318
|
const prompt = `${instruction}\n\n${outputContract}\n\nLasso has already assembled this source context:\n${context || "No matching source context was found."}`;
|
|
276
319
|
const local = localCommand(config.provider, config.model, prompt);
|
|
277
320
|
const command = local.command;
|
package/dist/cli/bridge.js
CHANGED
|
@@ -136,6 +136,9 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
136
136
|
const wss = new ws_1.WebSocketServer({ server: bridgeServer });
|
|
137
137
|
let overlaySocket = null;
|
|
138
138
|
let lastSnapshot = [];
|
|
139
|
+
let lastEditRequest = null;
|
|
140
|
+
let lastEditConfig = null;
|
|
141
|
+
let reviewRefreshAttempts = 0;
|
|
139
142
|
const envRoots = [cwd, node_path_1.default.join(cwd, "web"), node_path_1.default.join(cwd, "server")];
|
|
140
143
|
const fileEnv = envRoots.reduce((values, root) => ({
|
|
141
144
|
...values,
|
|
@@ -310,6 +313,9 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
310
313
|
const selectedConfig = localProvider
|
|
311
314
|
? { provider: cliProvider, model: msg.model }
|
|
312
315
|
: { ...agentConfig, provider: (msg.provider || agentConfig.provider), model: msg.model };
|
|
316
|
+
lastEditRequest = msg;
|
|
317
|
+
lastEditConfig = selectedConfig;
|
|
318
|
+
reviewRefreshAttempts = 0;
|
|
313
319
|
void (0, agent_1.proposeChanges)(cwd, msg, selectedConfig, controller.signal, (message, detail) => {
|
|
314
320
|
if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
|
|
315
321
|
socket.send(JSON.stringify({ type: "agent_status", status: "working", message, detail }));
|
|
@@ -463,7 +469,37 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
463
469
|
for (const snapshot of lastSnapshot)
|
|
464
470
|
node_fs_1.default.writeFileSync(snapshot.filePath, snapshot.content);
|
|
465
471
|
lastSnapshot = [];
|
|
466
|
-
|
|
472
|
+
const message = error instanceof Error ? error.message : "The change could not be applied.";
|
|
473
|
+
const sourceChanged = message.includes("The source changed after the suggestion was generated");
|
|
474
|
+
if (sourceChanged && lastEditRequest && lastEditConfig && reviewRefreshAttempts < 1) {
|
|
475
|
+
reviewRefreshAttempts += 1;
|
|
476
|
+
socket.send(JSON.stringify({ type: "agent_status", status: "working", message: "The source changed. Refreshing the review against the current file…" }));
|
|
477
|
+
activeAgentController?.abort();
|
|
478
|
+
const controller = new AbortController();
|
|
479
|
+
activeAgentController = controller;
|
|
480
|
+
void (0, agent_1.proposeChanges)(cwd, lastEditRequest, lastEditConfig, controller.signal, (progress, detail) => {
|
|
481
|
+
if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
|
|
482
|
+
socket.send(JSON.stringify({ type: "agent_status", status: "working", message: progress, detail }));
|
|
483
|
+
}
|
|
484
|
+
})
|
|
485
|
+
.then((proposal) => {
|
|
486
|
+
if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
|
|
487
|
+
socket.send(JSON.stringify({ type: "agent_status", status: "review", message: `Review refreshed: ${proposal.summary}`, changes: proposal.changes }));
|
|
488
|
+
}
|
|
489
|
+
})
|
|
490
|
+
.catch((refreshError) => {
|
|
491
|
+
if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
|
|
492
|
+
socket.send(JSON.stringify({ type: "agent_status", status: "error", message: refreshError instanceof Error ? refreshError.message : "The refreshed review could not be prepared." }));
|
|
493
|
+
}
|
|
494
|
+
})
|
|
495
|
+
.finally(() => {
|
|
496
|
+
if (activeAgentController === controller)
|
|
497
|
+
activeAgentController = null;
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
socket.send(JSON.stringify({ type: "agent_status", status: "error", message }));
|
|
502
|
+
}
|
|
467
503
|
}
|
|
468
504
|
}
|
|
469
505
|
else if (msg.type === "undo") {
|
package/package.json
CHANGED