@lasso-ai/cli 1.0.27 → 1.0.29
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 +63 -21
- package/dist/cli/bridge.js +2 -2
- package/dist/overlay.js +20 -12
- package/package.json +1 -1
package/dist/cli/agent.js
CHANGED
|
@@ -202,9 +202,36 @@ function extractLocalAgentText(raw, provider) {
|
|
|
202
202
|
}
|
|
203
203
|
return texts.at(-1) || raw;
|
|
204
204
|
}
|
|
205
|
+
function collectTextFields(value, fields = new Set(["text", "output_text", "result", "content", "message"]), depth = 0) {
|
|
206
|
+
if (depth > 8 || value == null)
|
|
207
|
+
return [];
|
|
208
|
+
if (typeof value === "string")
|
|
209
|
+
return [value];
|
|
210
|
+
if (Array.isArray(value))
|
|
211
|
+
return value.flatMap((entry) => collectTextFields(entry, fields, depth + 1));
|
|
212
|
+
if (typeof value !== "object")
|
|
213
|
+
return [];
|
|
214
|
+
const output = [];
|
|
215
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
216
|
+
if (fields.has(key) && typeof entry === "string")
|
|
217
|
+
output.push(entry);
|
|
218
|
+
if (typeof entry === "object" && entry !== null)
|
|
219
|
+
output.push(...collectTextFields(entry, fields, depth + 1));
|
|
220
|
+
}
|
|
221
|
+
return output;
|
|
222
|
+
}
|
|
205
223
|
function extractLocalAgentProposal(raw, provider) {
|
|
206
224
|
const outputs = [];
|
|
207
|
-
|
|
225
|
+
const lines = raw.split(/\r?\n/).filter((line) => line.trim());
|
|
226
|
+
const streamObjects = jsonObjectCandidates(raw).flatMap((candidate) => {
|
|
227
|
+
try {
|
|
228
|
+
return [JSON.parse(candidate)];
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
for (const line of lines) {
|
|
208
235
|
try {
|
|
209
236
|
const event = JSON.parse(line);
|
|
210
237
|
const item = event.item;
|
|
@@ -212,7 +239,7 @@ function extractLocalAgentProposal(raw, provider) {
|
|
|
212
239
|
const values = provider === "claude-code"
|
|
213
240
|
? [event.result, ...(event.message?.content || []).map((entry) => entry.text)]
|
|
214
241
|
: provider === "opencode"
|
|
215
|
-
? [part?.type === "text" ? part.text : undefined
|
|
242
|
+
? [...collectTextFields(event), part?.type === "text" ? part.text : undefined]
|
|
216
243
|
: [event.text, event.output_text, item?.text, item?.output_text, item?.message];
|
|
217
244
|
for (const value of values)
|
|
218
245
|
if (typeof value === "string" && value.trim())
|
|
@@ -223,6 +250,15 @@ function extractLocalAgentProposal(raw, provider) {
|
|
|
223
250
|
outputs.push(line);
|
|
224
251
|
}
|
|
225
252
|
}
|
|
253
|
+
// OpenCode can emit multiple JSON records on one physical line. The
|
|
254
|
+
// line-based pass above cannot see those records, so inspect each balanced
|
|
255
|
+
// JSON object as well and collect text nested in its final response event.
|
|
256
|
+
for (const event of streamObjects) {
|
|
257
|
+
outputs.push(...collectTextFields(event));
|
|
258
|
+
const part = event.part;
|
|
259
|
+
if (part?.type === "text" && typeof part.text === "string")
|
|
260
|
+
outputs.push(part.text);
|
|
261
|
+
}
|
|
226
262
|
// Tool events are interleaved with the final answer. Try text events in
|
|
227
263
|
// reverse order, then the complete stream as a final fallback.
|
|
228
264
|
for (const output of [...outputs.reverse(), raw]) {
|
|
@@ -329,6 +365,21 @@ function progressFromLine(raw, provider) {
|
|
|
329
365
|
}
|
|
330
366
|
return null;
|
|
331
367
|
}
|
|
368
|
+
function progressEventsFromRaw(raw, provider) {
|
|
369
|
+
const records = (() => {
|
|
370
|
+
try {
|
|
371
|
+
JSON.parse(raw);
|
|
372
|
+
return [raw];
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
return jsonObjectCandidates(raw);
|
|
376
|
+
}
|
|
377
|
+
})();
|
|
378
|
+
return records.flatMap((record) => {
|
|
379
|
+
const progress = progressFromLine(record, provider);
|
|
380
|
+
return progress ? [progress] : [];
|
|
381
|
+
});
|
|
382
|
+
}
|
|
332
383
|
function localAgentError(command, stderr, exitCode) {
|
|
333
384
|
const output = stderr.trim();
|
|
334
385
|
if (command === "codex" && output.includes("missing field `base_instructions`")) {
|
|
@@ -367,7 +418,6 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress) {
|
|
|
367
418
|
let timedOut = false;
|
|
368
419
|
const timeout = setTimeout(() => {
|
|
369
420
|
timedOut = true;
|
|
370
|
-
onProgress?.(`${label} did not respond; stopping this task…`);
|
|
371
421
|
child.kill("SIGTERM");
|
|
372
422
|
setTimeout(() => child.kill("SIGKILL"), 3000).unref();
|
|
373
423
|
}, 5 * 60 * 1000);
|
|
@@ -390,11 +440,10 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress) {
|
|
|
390
440
|
else
|
|
391
441
|
pendingStderr = remainder;
|
|
392
442
|
for (const line of lines) {
|
|
393
|
-
const progress
|
|
394
|
-
if (progress)
|
|
443
|
+
for (const progress of progressEventsFromRaw(line, provider)) {
|
|
395
444
|
onProgress?.(progress.message, progress.detail);
|
|
396
|
-
|
|
397
|
-
|
|
445
|
+
}
|
|
446
|
+
if (stream === "stderr" && line.trim()) {
|
|
398
447
|
if (provider === "opencode" && /quota exceeded|authentication failed|invalid api key|unauthorized|forbidden/i.test(line)) {
|
|
399
448
|
onProgress?.("OpenCode · provider rejected the request; stopping this task…");
|
|
400
449
|
child.kill("SIGTERM");
|
|
@@ -404,12 +453,6 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress) {
|
|
|
404
453
|
};
|
|
405
454
|
child.stdout.on("data", (chunk) => consume(chunk, "stdout"));
|
|
406
455
|
child.stderr.on("data", (chunk) => consume(chunk, "stderr"));
|
|
407
|
-
child.once("spawn", () => onProgress?.(`${label} process started${child.pid ? ` · PID ${child.pid}` : ""}`));
|
|
408
|
-
const heartbeat = setInterval(() => {
|
|
409
|
-
if (!signal?.aborted)
|
|
410
|
-
onProgress?.(`${label} is still running${child.pid ? ` · PID ${child.pid}` : ""}…`);
|
|
411
|
-
}, 15000);
|
|
412
|
-
heartbeat.unref();
|
|
413
456
|
const abort = () => child.kill("SIGTERM");
|
|
414
457
|
if (signal?.aborted)
|
|
415
458
|
abort();
|
|
@@ -420,7 +463,6 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress) {
|
|
|
420
463
|
return;
|
|
421
464
|
settled = true;
|
|
422
465
|
clearTimeout(timeout);
|
|
423
|
-
clearInterval(heartbeat);
|
|
424
466
|
signal?.removeEventListener("abort", abort);
|
|
425
467
|
reject(error);
|
|
426
468
|
});
|
|
@@ -429,7 +471,6 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress) {
|
|
|
429
471
|
return;
|
|
430
472
|
settled = true;
|
|
431
473
|
clearTimeout(timeout);
|
|
432
|
-
clearInterval(heartbeat);
|
|
433
474
|
signal?.removeEventListener("abort", abort);
|
|
434
475
|
if (timedOut) {
|
|
435
476
|
reject(new Error(`${label} did not finish within 5 minutes. Check its login or approval prompt, then retry.`));
|
|
@@ -439,14 +480,17 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress) {
|
|
|
439
480
|
const stderrTail = pendingStderr.trim();
|
|
440
481
|
if (stdoutTail) {
|
|
441
482
|
stdout += pendingStdout;
|
|
442
|
-
const progress
|
|
443
|
-
if (progress)
|
|
483
|
+
for (const progress of progressEventsFromRaw(stdoutTail, provider)) {
|
|
444
484
|
onProgress?.(progress.message, progress.detail);
|
|
485
|
+
}
|
|
445
486
|
}
|
|
446
487
|
if (stderrTail) {
|
|
447
|
-
const progress
|
|
448
|
-
if (progress)
|
|
488
|
+
for (const progress of progressEventsFromRaw(stderrTail, provider)) {
|
|
449
489
|
onProgress?.(progress.message, progress.detail);
|
|
490
|
+
}
|
|
491
|
+
if (provider === "opencode" && /quota exceeded|authentication failed|invalid api key|unauthorized|forbidden/i.test(stderrTail)) {
|
|
492
|
+
onProgress?.("OpenCode · provider rejected the request; stopping this task…");
|
|
493
|
+
}
|
|
450
494
|
}
|
|
451
495
|
resolve({ stdout, stderr, exitCode: code ?? 1 });
|
|
452
496
|
});
|
|
@@ -464,9 +508,7 @@ async function proposeWithLocalAgent(cwd, instruction, context, config, signal,
|
|
|
464
508
|
return extractLocalAgentProposal(result.stdout, config.provider);
|
|
465
509
|
}
|
|
466
510
|
async function proposeChanges(cwd, input, config, signal, onProgress) {
|
|
467
|
-
onProgress?.("Reading source context…");
|
|
468
511
|
const context = await contextFor(cwd, input.element);
|
|
469
|
-
onProgress?.("Starting the coding agent…");
|
|
470
512
|
const visualContext = input.context ? { ...input.context, screenshots: undefined } : undefined;
|
|
471
513
|
const history = input.messages?.map((message) => `${message.role}: ${message.content}`).join("\n") || input.instruction;
|
|
472
514
|
const priorChanges = input.changesHistory?.length ? JSON.stringify(input.changesHistory, null, 2) : "None";
|
package/dist/cli/bridge.js
CHANGED
|
@@ -278,7 +278,7 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
278
278
|
const controller = new AbortController();
|
|
279
279
|
taskControllers.set(request.taskId, controller);
|
|
280
280
|
if (socket.readyState === socket.OPEN) {
|
|
281
|
-
socket.send(JSON.stringify({ type: "agent_status", taskId: request.taskId, status: "working", message: statusMessage || "
|
|
281
|
+
socket.send(JSON.stringify({ type: "agent_status", taskId: request.taskId, status: "working", message: statusMessage || "Working…" }));
|
|
282
282
|
}
|
|
283
283
|
void (0, agent_1.proposeChanges)(cwd, request, config, controller.signal, (message, detail) => {
|
|
284
284
|
if (!controller.signal.aborted && socket.readyState === socket.OPEN) {
|
|
@@ -336,7 +336,7 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
336
336
|
const selectedConfig = localProvider
|
|
337
337
|
? { provider: cliProvider, model: msg.model }
|
|
338
338
|
: { ...agentConfig, provider: (msg.provider || agentConfig.provider), model: msg.model };
|
|
339
|
-
socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: "working", message: "
|
|
339
|
+
socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: "working", message: "Working…" }));
|
|
340
340
|
void (0, agent_1.answerQuestion)(cwd, { question: msg.question, context: msg.context, element: msg.element, messages: msg.messages }, selectedConfig, controller.signal, (message, detail) => {
|
|
341
341
|
if (!controller.signal.aborted && socket.readyState === socket.OPEN)
|
|
342
342
|
socket.send(JSON.stringify({ type: "agent_status", taskId: msg.taskId, status: "working", message, detail }));
|
package/dist/overlay.js
CHANGED
|
@@ -12151,6 +12151,15 @@
|
|
|
12151
12151
|
return Boolean(other) && change.filePath === other.filePath && change.oldString === other.oldString && change.newString === other.newString;
|
|
12152
12152
|
});
|
|
12153
12153
|
}
|
|
12154
|
+
function normalizedInstruction(value2) {
|
|
12155
|
+
return value2.trim().replace(/\s+/g, " ").toLowerCase();
|
|
12156
|
+
}
|
|
12157
|
+
function findReusableAgentTask(instruction, element) {
|
|
12158
|
+
const normalized = normalizedInstruction(instruction);
|
|
12159
|
+
return state.agentTasks.find(
|
|
12160
|
+
(task) => task.status !== "complete" && task.status !== "error" && task.status !== "stopped" && normalizedInstruction(task.instruction) === normalized && task.element === (element || void 0)
|
|
12161
|
+
);
|
|
12162
|
+
}
|
|
12154
12163
|
function onAgentTaskSelected(listener) {
|
|
12155
12164
|
selectionListeners.add(listener);
|
|
12156
12165
|
return () => selectionListeners.delete(listener);
|
|
@@ -18504,6 +18513,9 @@
|
|
|
18504
18513
|
cli: terminal_default
|
|
18505
18514
|
};
|
|
18506
18515
|
function providerIcon(provider, active = false) {
|
|
18516
|
+
if (provider === "cli") {
|
|
18517
|
+
return `<span class="${active ? "lasso-model-active-icon" : "lasso-model-item-icon"} provider-cli" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" focusable="false"><circle cx="12" cy="12" r="9" fill="currentColor" opacity=".16"/><circle cx="12" cy="9" r="3" fill="currentColor"/><path d="M6.5 18c.9-2.4 2.7-3.6 5.5-3.6s4.6 1.2 5.5 3.6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></span>`;
|
|
18518
|
+
}
|
|
18507
18519
|
const icon = providerIcons[provider];
|
|
18508
18520
|
return `<span class="${active ? "lasso-model-active-icon" : "lasso-model-item-icon"} provider-${provider}" aria-hidden="true"><svg viewBox="0 0 ${icon.width} ${icon.height}" xmlns="http://www.w3.org/2000/svg" focusable="false">${icon.body}</svg></span>`;
|
|
18509
18521
|
}
|
|
@@ -19031,7 +19043,7 @@
|
|
|
19031
19043
|
appendAgentLog(logMessage);
|
|
19032
19044
|
if (taskId2) recordTaskActivity(taskId2, logMessage);
|
|
19033
19045
|
const running = status === "thinking" || status === "working";
|
|
19034
|
-
agentStatusElement.hidden = status === "review";
|
|
19046
|
+
agentStatusElement.hidden = status === "review" || status === "complete";
|
|
19035
19047
|
agentStatusElement.dataset.status = status;
|
|
19036
19048
|
agentStatusMessage.textContent = message;
|
|
19037
19049
|
const badgeEl = agentStatusElement.querySelector(".lasso-agent-status-badge");
|
|
@@ -19062,18 +19074,9 @@
|
|
|
19062
19074
|
}
|
|
19063
19075
|
function resetAgentState() {
|
|
19064
19076
|
state.agentRunning = false;
|
|
19065
|
-
const hasLog = agentLogLines.length > 0;
|
|
19066
19077
|
if (agentStatusElement) {
|
|
19067
|
-
agentStatusElement.hidden =
|
|
19068
|
-
agentStatusElement.dataset.status =
|
|
19069
|
-
}
|
|
19070
|
-
if (hasLog && agentStatusMessage) agentStatusMessage.textContent = "Agent complete";
|
|
19071
|
-
if (hasLog) {
|
|
19072
|
-
const badgeEl = agentStatusElement?.querySelector(".lasso-agent-status-badge");
|
|
19073
|
-
if (badgeEl) {
|
|
19074
|
-
badgeEl.textContent = "done";
|
|
19075
|
-
badgeEl.className = "lasso-agent-status-badge complete";
|
|
19076
|
-
}
|
|
19078
|
+
agentStatusElement.hidden = true;
|
|
19079
|
+
agentStatusElement.dataset.status = "idle";
|
|
19077
19080
|
}
|
|
19078
19081
|
if (sendButton) {
|
|
19079
19082
|
sendButton.classList.remove("loading");
|
|
@@ -19258,6 +19261,11 @@
|
|
|
19258
19261
|
const selectionId = state.selectionId;
|
|
19259
19262
|
const model = state.selectedModel;
|
|
19260
19263
|
const screenshotPromise = state.screenshotPromise;
|
|
19264
|
+
const reusableTask = findReusableAgentTask(instruction, selectedElement);
|
|
19265
|
+
if (reusableTask) {
|
|
19266
|
+
selectAgentTask(reusableTask.id);
|
|
19267
|
+
return;
|
|
19268
|
+
}
|
|
19261
19269
|
appendChat("user", instruction);
|
|
19262
19270
|
const messages = state.chatHistory.map((message) => ({ ...message }));
|
|
19263
19271
|
const changesHistory = state.changesHistory.map((entry) => ({ ...entry, changes: entry.changes.map((change) => ({ ...change })) }));
|
package/package.json
CHANGED