@lasso-ai/cli 2.1.15 → 2.1.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/bridge.d.ts +27 -0
- package/dist/cli/bridge.js +59 -5
- package/dist/cli/runtime-client.d.ts +8 -0
- package/dist/cli/runtime-client.js +34 -1
- package/dist/overlay.js +137 -14
- package/dist/runtime/agent-runtime.d.ts +3 -1
- package/dist/runtime/verification-engine.d.ts +10 -0
- package/dist/runtime/verification-engine.js +83 -35
- package/package.json +1 -1
package/dist/cli/bridge.d.ts
CHANGED
|
@@ -24,6 +24,11 @@ export type BridgeMessage = {
|
|
|
24
24
|
content: string;
|
|
25
25
|
createdAt?: string;
|
|
26
26
|
}>;
|
|
27
|
+
conversation?: Array<{
|
|
28
|
+
role: "user" | "assistant" | "system";
|
|
29
|
+
content: string;
|
|
30
|
+
createdAt?: string;
|
|
31
|
+
}>;
|
|
27
32
|
changesHistory?: Array<{
|
|
28
33
|
summary: string;
|
|
29
34
|
changes: any[];
|
|
@@ -59,6 +64,11 @@ export type BridgeMessage = {
|
|
|
59
64
|
content: string;
|
|
60
65
|
createdAt?: string;
|
|
61
66
|
}>;
|
|
67
|
+
conversation?: Array<{
|
|
68
|
+
role: "user" | "assistant" | "system";
|
|
69
|
+
content: string;
|
|
70
|
+
createdAt?: string;
|
|
71
|
+
}>;
|
|
62
72
|
context?: {
|
|
63
73
|
selectionId?: string;
|
|
64
74
|
position?: Record<string, number>;
|
|
@@ -96,6 +106,11 @@ export type BridgeMessage = {
|
|
|
96
106
|
type: "agent_prompt_response";
|
|
97
107
|
taskId: string;
|
|
98
108
|
response: string;
|
|
109
|
+
} | {
|
|
110
|
+
type: "steer";
|
|
111
|
+
taskId: string;
|
|
112
|
+
queueId: string;
|
|
113
|
+
prompt: string;
|
|
99
114
|
} | {
|
|
100
115
|
type: "list_page_folders";
|
|
101
116
|
path?: string;
|
|
@@ -144,6 +159,12 @@ export type BridgeMessage = {
|
|
|
144
159
|
role: "user" | "assistant";
|
|
145
160
|
content: string;
|
|
146
161
|
}>;
|
|
162
|
+
conversation?: Array<{
|
|
163
|
+
role: "user" | "assistant" | "system";
|
|
164
|
+
content: string;
|
|
165
|
+
timestamp?: number;
|
|
166
|
+
}>;
|
|
167
|
+
conversationId?: string;
|
|
147
168
|
taskId?: string;
|
|
148
169
|
} | {
|
|
149
170
|
type: "page_context_response";
|
|
@@ -221,6 +242,12 @@ export type ServerBridgeMessage = {
|
|
|
221
242
|
kind: "permission" | "input" | "multiselect";
|
|
222
243
|
options?: string[];
|
|
223
244
|
};
|
|
245
|
+
} | {
|
|
246
|
+
type: "steer_result";
|
|
247
|
+
taskId: string;
|
|
248
|
+
queueId: string;
|
|
249
|
+
success: boolean;
|
|
250
|
+
error?: string;
|
|
224
251
|
} | {
|
|
225
252
|
type: "page_folders";
|
|
226
253
|
path: string;
|
package/dist/cli/bridge.js
CHANGED
|
@@ -31,7 +31,7 @@ function toRuntimeConversation(messages) {
|
|
|
31
31
|
.map((m) => ({
|
|
32
32
|
role: (m.role === "user" ? "user" : m.role === "assistant" ? "assistant" : "system"),
|
|
33
33
|
content: m.content,
|
|
34
|
-
timestamp: m.createdAt ? Date.parse(m.createdAt) || Date.now() : Date.now(),
|
|
34
|
+
timestamp: typeof m.timestamp === "number" ? m.timestamp : m.createdAt ? Date.parse(m.createdAt) || Date.now() : Date.now(),
|
|
35
35
|
}));
|
|
36
36
|
return turns.length > 0 ? turns : undefined;
|
|
37
37
|
}
|
|
@@ -392,11 +392,21 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
|
|
|
392
392
|
}));
|
|
393
393
|
break;
|
|
394
394
|
}
|
|
395
|
-
case "agent_completed":
|
|
396
|
-
|
|
395
|
+
case "agent_completed": {
|
|
396
|
+
const data = event.data || {};
|
|
397
|
+
const reply = typeof data.reply === "string" ? data.reply.trim() : "";
|
|
398
|
+
if (reply && data.conversational === true) {
|
|
399
|
+
socket.send(JSON.stringify({
|
|
400
|
+
type: "assistant_message",
|
|
401
|
+
taskId,
|
|
402
|
+
message: reply,
|
|
403
|
+
}));
|
|
397
404
|
break;
|
|
398
|
-
|
|
405
|
+
}
|
|
406
|
+
if (review)
|
|
407
|
+
void handleReviewReady(session, socket, taskId, instruction);
|
|
399
408
|
break;
|
|
409
|
+
}
|
|
400
410
|
case "error":
|
|
401
411
|
socket.send(JSON.stringify({
|
|
402
412
|
type: "agent_status",
|
|
@@ -900,6 +910,40 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
|
|
|
900
910
|
message: "Agent stopped",
|
|
901
911
|
}));
|
|
902
912
|
}
|
|
913
|
+
/** Forward an overlay answer to the runtime's pending user question. */
|
|
914
|
+
async function handleAgentPromptResponse(message, socket) {
|
|
915
|
+
const session = activeSessions.get(message.taskId);
|
|
916
|
+
const facade = taskFacades.get(message.taskId);
|
|
917
|
+
if (!session || !facade) {
|
|
918
|
+
throw new Error("No active agent session is waiting for a response.");
|
|
919
|
+
}
|
|
920
|
+
if (!facade.respondToPrompt) {
|
|
921
|
+
throw new Error("This agent backend does not support interactive prompts.");
|
|
922
|
+
}
|
|
923
|
+
await facade.respondToPrompt(session.id, message.response);
|
|
924
|
+
socket.send(JSON.stringify({
|
|
925
|
+
type: "agent_status",
|
|
926
|
+
taskId: message.taskId,
|
|
927
|
+
status: "working",
|
|
928
|
+
message: "Continuing with your response…",
|
|
929
|
+
}));
|
|
930
|
+
}
|
|
931
|
+
/** Queue a prompt for the active runtime's next model turn. */
|
|
932
|
+
async function handleSteer(message, socket) {
|
|
933
|
+
const session = activeSessions.get(message.taskId);
|
|
934
|
+
const facade = taskFacades.get(message.taskId);
|
|
935
|
+
if (!session || !facade?.steerSession) {
|
|
936
|
+
socket.send(JSON.stringify({ type: "steer_result", taskId: message.taskId, queueId: message.queueId, success: false, error: "This agent backend does not support live steering." }));
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
try {
|
|
940
|
+
await facade.steerSession(session.id, message.prompt);
|
|
941
|
+
socket.send(JSON.stringify({ type: "steer_result", taskId: message.taskId, queueId: message.queueId, success: true }));
|
|
942
|
+
}
|
|
943
|
+
catch (error) {
|
|
944
|
+
socket.send(JSON.stringify({ type: "steer_result", taskId: message.taskId, queueId: message.queueId, success: false, error: error instanceof Error ? error.message : "Failed to steer the agent." }));
|
|
945
|
+
}
|
|
946
|
+
}
|
|
903
947
|
// Handler for switch_model
|
|
904
948
|
async function handleSwitchModel(message, socket) {
|
|
905
949
|
const { taskId, provider, modelId } = message;
|
|
@@ -945,7 +989,7 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
|
|
|
945
989
|
}
|
|
946
990
|
// Handler for oneshot
|
|
947
991
|
async function handleOneshot(message, socket, cwd) {
|
|
948
|
-
const { taskId, prompt, model, provider } = message;
|
|
992
|
+
const { taskId, prompt, model, provider, messages, conversation, conversationId } = message;
|
|
949
993
|
const instruction = prompt;
|
|
950
994
|
taskControllers.get(taskId)?.abort();
|
|
951
995
|
const controller = new AbortController();
|
|
@@ -957,6 +1001,10 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
|
|
|
957
1001
|
const backendModelId = (0, backends_1.modelIdForBackend)(model || "", provider || "");
|
|
958
1002
|
const sessionInput = {
|
|
959
1003
|
userPrompt: prompt,
|
|
1004
|
+
// The sidebar is the source of truth for history. `conversation` is
|
|
1005
|
+
// preferred; `messages` remains a compatibility alias for older overlays.
|
|
1006
|
+
conversation: toRuntimeConversation(conversation || messages),
|
|
1007
|
+
conversationUid: conversationId || undefined,
|
|
960
1008
|
project: {
|
|
961
1009
|
projectId,
|
|
962
1010
|
framework,
|
|
@@ -1138,6 +1186,12 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
|
|
|
1138
1186
|
case "stop":
|
|
1139
1187
|
await handleStop(msg, socket);
|
|
1140
1188
|
break;
|
|
1189
|
+
case "agent_prompt_response":
|
|
1190
|
+
await handleAgentPromptResponse(msg, socket);
|
|
1191
|
+
break;
|
|
1192
|
+
case "steer":
|
|
1193
|
+
await handleSteer(msg, socket);
|
|
1194
|
+
break;
|
|
1141
1195
|
case "switch_model":
|
|
1142
1196
|
await handleSwitchModel(msg, socket);
|
|
1143
1197
|
break;
|
|
@@ -157,6 +157,7 @@ export interface RuntimeFacade {
|
|
|
157
157
|
runSession(sessionId: string, projectPath: string, onEvent: RuntimeEventHandler, signal?: AbortSignal, opts?: {
|
|
158
158
|
providerApiKey?: string;
|
|
159
159
|
browserContextProvider?: BrowserContextProvider;
|
|
160
|
+
devServerUrl?: string;
|
|
160
161
|
}): Promise<void>;
|
|
161
162
|
getSession(sessionId: string): Promise<RuntimeSession>;
|
|
162
163
|
verifySession(sessionId: string, projectPath: string): Promise<SessionVerificationResult>;
|
|
@@ -166,6 +167,8 @@ export interface RuntimeFacade {
|
|
|
166
167
|
name?: string;
|
|
167
168
|
}, apiKey?: string, backendConfig?: RuntimeSessionInput["backendConfig"]): Promise<void>;
|
|
168
169
|
cancelSession(sessionId: string): Promise<void>;
|
|
170
|
+
respondToPrompt?(sessionId: string, response: string): Promise<void>;
|
|
171
|
+
steerSession?(sessionId: string, prompt: string): Promise<void>;
|
|
169
172
|
}
|
|
170
173
|
export type RuntimeEvent = {
|
|
171
174
|
type: "agent_started";
|
|
@@ -315,6 +318,7 @@ export declare class RuntimeClient {
|
|
|
315
318
|
runSession(sessionId: string, projectPath: string, onEvent: RuntimeEventHandler, signal?: AbortSignal, opts?: {
|
|
316
319
|
providerApiKey?: string;
|
|
317
320
|
browserContextProvider?: BrowserContextProvider;
|
|
321
|
+
devServerUrl?: string;
|
|
318
322
|
}): Promise<void>;
|
|
319
323
|
/** Parse and dispatch one complete SSE frame. */
|
|
320
324
|
private processSseFrame;
|
|
@@ -326,6 +330,10 @@ export declare class RuntimeClient {
|
|
|
326
330
|
* Cancel a running session
|
|
327
331
|
*/
|
|
328
332
|
cancelSession(sessionId: string): Promise<void>;
|
|
333
|
+
/** Resume a hosted runtime that is waiting for an answer from the user. */
|
|
334
|
+
respondToPrompt(sessionId: string, response: string): Promise<void>;
|
|
335
|
+
/** Queue a prompt for the next turn of an active hosted runtime. */
|
|
336
|
+
steerSession(sessionId: string, prompt: string): Promise<void>;
|
|
329
337
|
/**
|
|
330
338
|
* Switch model/backend for a session
|
|
331
339
|
*/
|
|
@@ -83,7 +83,10 @@ class RuntimeClient {
|
|
|
83
83
|
// pre-run workspace capture could not be established.
|
|
84
84
|
const guard = await index_js_1.WorkspaceGuard.capture(projectPath);
|
|
85
85
|
const executor = new index_js_1.ActionExecutor(projectPath, opts?.browserContextProvider);
|
|
86
|
-
const verifier = new index_js_1.VerificationEngine(projectPath
|
|
86
|
+
const verifier = new index_js_1.VerificationEngine(projectPath, {
|
|
87
|
+
devServerUrl: opts?.devServerUrl || process.env.LASSO_DEV_SERVER_URL,
|
|
88
|
+
browserContextProvider: opts?.browserContextProvider,
|
|
89
|
+
});
|
|
87
90
|
// Idempotent: called on agent_completed AND on stream failure, so the
|
|
88
91
|
// diff is always cached before the safety-net restore wipes the edits.
|
|
89
92
|
let finalized = false;
|
|
@@ -268,6 +271,36 @@ class RuntimeClient {
|
|
|
268
271
|
throw new Error(`Failed to cancel session: ${response.statusText}`);
|
|
269
272
|
}
|
|
270
273
|
}
|
|
274
|
+
/** Resume a hosted runtime that is waiting for an answer from the user. */
|
|
275
|
+
async respondToPrompt(sessionId, response) {
|
|
276
|
+
const result = await fetch(`${this.serverUrl}/api/v1/agent-runtime/sessions/${sessionId}/user-response`, {
|
|
277
|
+
method: "POST",
|
|
278
|
+
headers: {
|
|
279
|
+
"Content-Type": "application/json",
|
|
280
|
+
...(this.apiKey && { Authorization: `Bearer ${this.apiKey}` }),
|
|
281
|
+
},
|
|
282
|
+
body: JSON.stringify({ response }),
|
|
283
|
+
});
|
|
284
|
+
if (!result.ok) {
|
|
285
|
+
const body = (await result.json().catch(() => ({})));
|
|
286
|
+
throw new Error(body.error || `Failed to submit agent response: ${result.statusText}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/** Queue a prompt for the next turn of an active hosted runtime. */
|
|
290
|
+
async steerSession(sessionId, prompt) {
|
|
291
|
+
const result = await fetch(`${this.serverUrl}/api/v1/agent-runtime/sessions/${sessionId}/steer`, {
|
|
292
|
+
method: "POST",
|
|
293
|
+
headers: {
|
|
294
|
+
"Content-Type": "application/json",
|
|
295
|
+
...(this.apiKey && { Authorization: `Bearer ${this.apiKey}` }),
|
|
296
|
+
},
|
|
297
|
+
body: JSON.stringify({ prompt }),
|
|
298
|
+
});
|
|
299
|
+
if (!result.ok) {
|
|
300
|
+
const body = (await result.json().catch(() => ({})));
|
|
301
|
+
throw new Error(body.error || `Failed to queue steering prompt: ${result.statusText}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
271
304
|
/**
|
|
272
305
|
* Switch model/backend for a session
|
|
273
306
|
*/
|
package/dist/overlay.js
CHANGED
|
@@ -11522,7 +11522,6 @@
|
|
|
11522
11522
|
align-items: center;
|
|
11523
11523
|
justify-content: space-between;
|
|
11524
11524
|
padding: 6px 8px 8px;
|
|
11525
|
-
border-bottom: 1px solid var(--lo-border-subtle);
|
|
11526
11525
|
font-size: 11px;
|
|
11527
11526
|
font-weight: 600;
|
|
11528
11527
|
color: var(--lo-text-3);
|
|
@@ -11553,6 +11552,48 @@
|
|
|
11553
11552
|
width: 100%;
|
|
11554
11553
|
}
|
|
11555
11554
|
|
|
11555
|
+
.lasso-command-history-load {
|
|
11556
|
+
display: flex;
|
|
11557
|
+
align-items: center;
|
|
11558
|
+
justify-content: space-between;
|
|
11559
|
+
gap: 8px;
|
|
11560
|
+
min-width: 0;
|
|
11561
|
+
flex: 1;
|
|
11562
|
+
padding: 0;
|
|
11563
|
+
border: 0;
|
|
11564
|
+
background: transparent;
|
|
11565
|
+
color: inherit;
|
|
11566
|
+
cursor: pointer;
|
|
11567
|
+
text-align: left;
|
|
11568
|
+
}
|
|
11569
|
+
|
|
11570
|
+
.lasso-command-history-delete {
|
|
11571
|
+
display: inline-flex;
|
|
11572
|
+
align-items: center;
|
|
11573
|
+
justify-content: center;
|
|
11574
|
+
flex: 0 0 auto;
|
|
11575
|
+
width: 24px;
|
|
11576
|
+
height: 24px;
|
|
11577
|
+
padding: 0;
|
|
11578
|
+
border: 0;
|
|
11579
|
+
border-radius: 5px;
|
|
11580
|
+
background: transparent;
|
|
11581
|
+
color: var(--lo-text-3);
|
|
11582
|
+
cursor: pointer;
|
|
11583
|
+
opacity: 0;
|
|
11584
|
+
transition: opacity 120ms ease, color 120ms ease, background 120ms ease;
|
|
11585
|
+
}
|
|
11586
|
+
|
|
11587
|
+
.lasso-command-history-item:hover .lasso-command-history-delete,
|
|
11588
|
+
.lasso-command-history-delete:focus-visible {
|
|
11589
|
+
opacity: 1;
|
|
11590
|
+
}
|
|
11591
|
+
|
|
11592
|
+
.lasso-command-history-delete:hover {
|
|
11593
|
+
color: #fca5a5;
|
|
11594
|
+
background: rgba(239, 68, 68, 0.14);
|
|
11595
|
+
}
|
|
11596
|
+
|
|
11556
11597
|
.lasso-command-history-item:hover {
|
|
11557
11598
|
background: var(--lo-surface-hover);
|
|
11558
11599
|
}
|
|
@@ -19700,17 +19741,44 @@
|
|
|
19700
19741
|
</div>
|
|
19701
19742
|
${conversationList.map(
|
|
19702
19743
|
(conv) => `
|
|
19703
|
-
<
|
|
19704
|
-
<
|
|
19705
|
-
|
|
19706
|
-
<
|
|
19707
|
-
|
|
19708
|
-
|
|
19709
|
-
|
|
19744
|
+
<div class="lasso-command-history-item ${conv.id === activeConversationId ? "active" : ""}" data-conv-id="${escapeHtml4(conv.id)}">
|
|
19745
|
+
<button class="lasso-command-history-load" type="button" title="Open session">
|
|
19746
|
+
<span class="lasso-command-history-title" title="${escapeHtml4(conv.title || "Untitled Session")}">${escapeHtml4(conv.title || "Untitled Session")}</span>
|
|
19747
|
+
<div class="lasso-command-history-meta">
|
|
19748
|
+
<span class="lasso-command-history-scope ${conv.scope || "private"}">${conv.scope || "private"}</span>
|
|
19749
|
+
<span>${formatDateAgo(conv.updatedAt)}</span>
|
|
19750
|
+
</div>
|
|
19751
|
+
</button>
|
|
19752
|
+
<button class="lasso-command-history-delete" type="button" title="Delete session" aria-label="Delete session">
|
|
19753
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v5M14 11v5"/></svg>
|
|
19754
|
+
</button>
|
|
19755
|
+
</div>
|
|
19710
19756
|
`
|
|
19711
19757
|
).join("")}
|
|
19712
19758
|
`;
|
|
19713
19759
|
}
|
|
19760
|
+
async function deleteConversation(convId) {
|
|
19761
|
+
const conversation = conversationList.find((item) => item.id === convId);
|
|
19762
|
+
if (!conversation) return;
|
|
19763
|
+
if (!convId.startsWith("local-session-")) {
|
|
19764
|
+
const deleted = await apiFetch(`/agent/conversations/${encodeURIComponent(convId)}`, { method: "DELETE" });
|
|
19765
|
+
if (!deleted?.success) return;
|
|
19766
|
+
}
|
|
19767
|
+
try {
|
|
19768
|
+
localStorage.removeItem(`lasso:session:${convId}`);
|
|
19769
|
+
const key = getStorageProjectKey();
|
|
19770
|
+
const list2 = JSON.parse(localStorage.getItem(key) || "[]");
|
|
19771
|
+
localStorage.setItem(key, JSON.stringify(list2.filter((item) => item.id !== convId)));
|
|
19772
|
+
} catch {
|
|
19773
|
+
}
|
|
19774
|
+
conversationList = conversationList.filter((item) => item.id !== convId);
|
|
19775
|
+
if (activeConversationId === convId) {
|
|
19776
|
+
activeConversationId = null;
|
|
19777
|
+
commandMessages = [];
|
|
19778
|
+
renderMessages();
|
|
19779
|
+
}
|
|
19780
|
+
renderHistoryPopover();
|
|
19781
|
+
}
|
|
19714
19782
|
async function switchConversation(convId) {
|
|
19715
19783
|
if (activeThinkingInterval) {
|
|
19716
19784
|
clearInterval(activeThinkingInterval);
|
|
@@ -20038,9 +20106,16 @@
|
|
|
20038
20106
|
});
|
|
20039
20107
|
commandHistoryPopover.addEventListener("click", async (event) => {
|
|
20040
20108
|
event.stopPropagation();
|
|
20041
|
-
const
|
|
20109
|
+
const target = event.target;
|
|
20110
|
+
const item = target.closest(".lasso-command-history-item");
|
|
20042
20111
|
if (!item?.dataset.convId) return;
|
|
20043
|
-
|
|
20112
|
+
if (target.closest(".lasso-command-history-delete")) {
|
|
20113
|
+
await deleteConversation(item.dataset.convId);
|
|
20114
|
+
return;
|
|
20115
|
+
}
|
|
20116
|
+
if (target.closest(".lasso-command-history-load")) {
|
|
20117
|
+
await switchConversation(item.dataset.convId);
|
|
20118
|
+
}
|
|
20044
20119
|
});
|
|
20045
20120
|
const setCommandModelMenuOpen = (open) => {
|
|
20046
20121
|
if (!commandModelMenu || !commandModelBtn) return;
|
|
@@ -20194,6 +20269,19 @@
|
|
|
20194
20269
|
}
|
|
20195
20270
|
return;
|
|
20196
20271
|
}
|
|
20272
|
+
const steerBtn = target.closest(".lasso-command-steer-btn");
|
|
20273
|
+
if (steerBtn) {
|
|
20274
|
+
const msgId = steerBtn.closest(".lasso-command-msg")?.dataset.msgId;
|
|
20275
|
+
const msg = commandMessages.find((m) => m.id === msgId);
|
|
20276
|
+
if (msg?.queued && !msg.steering && activeTaskId && state.bridgeSocket?.readyState === WebSocket.OPEN) {
|
|
20277
|
+
steerBtn.disabled = true;
|
|
20278
|
+
steerBtn.textContent = "Steering\u2026";
|
|
20279
|
+
msg.steering = true;
|
|
20280
|
+
state.bridgeSocket.send(JSON.stringify({ type: "steer", taskId: activeTaskId, queueId: msg.id, prompt: msg.content }));
|
|
20281
|
+
renderMessages();
|
|
20282
|
+
}
|
|
20283
|
+
return;
|
|
20284
|
+
}
|
|
20197
20285
|
const fileCheck = target.closest(".lasso-command-file-check");
|
|
20198
20286
|
if (fileCheck) {
|
|
20199
20287
|
const msgId = fileCheck.closest(".lasso-command-msg")?.dataset.msgId;
|
|
@@ -20405,6 +20493,21 @@ Please proceed directly with the implementation and code changes from this point
|
|
|
20405
20493
|
async function handleSubmit() {
|
|
20406
20494
|
if (!commandInput) return;
|
|
20407
20495
|
if (activeTaskId) {
|
|
20496
|
+
const queuedPrompt = commandInput.value.trim();
|
|
20497
|
+
if (queuedPrompt) {
|
|
20498
|
+
commandMessages.push({
|
|
20499
|
+
id: `queued-${Date.now()}`,
|
|
20500
|
+
role: "user",
|
|
20501
|
+
content: queuedPrompt,
|
|
20502
|
+
timestamp: Date.now(),
|
|
20503
|
+
queued: true
|
|
20504
|
+
});
|
|
20505
|
+
commandInput.value = "";
|
|
20506
|
+
autoResizeTextarea(commandInput);
|
|
20507
|
+
renderMessages();
|
|
20508
|
+
scrollToBottom();
|
|
20509
|
+
return;
|
|
20510
|
+
}
|
|
20408
20511
|
if (state.bridgeSocket?.readyState === WebSocket.OPEN) {
|
|
20409
20512
|
state.bridgeSocket.send(
|
|
20410
20513
|
JSON.stringify({
|
|
@@ -20488,9 +20591,7 @@ Please proceed directly with the implementation and code changes from this point
|
|
|
20488
20591
|
}
|
|
20489
20592
|
}, 1e3);
|
|
20490
20593
|
if (!activeConversationId) {
|
|
20491
|
-
createServerConversation()
|
|
20492
|
-
if (id) activeConversationId = id;
|
|
20493
|
-
});
|
|
20594
|
+
activeConversationId = await createServerConversation();
|
|
20494
20595
|
}
|
|
20495
20596
|
collabEmit("collab:action", {
|
|
20496
20597
|
sessionId: state.collabProjectId,
|
|
@@ -20510,7 +20611,7 @@ Please proceed directly with the implementation and code changes from this point
|
|
|
20510
20611
|
// The current prompt is sent in its dedicated `prompt` field. Keep it
|
|
20511
20612
|
// out of history so the model sees it once, not once as history and
|
|
20512
20613
|
// once as the active request.
|
|
20513
|
-
|
|
20614
|
+
conversation: commandMessages.map((message, index) => ({ message, index })).filter(({ message, index }) => Boolean(message.content) && !message.error && index !== commandMessages.reduce((last, item, itemIndex) => item.role === "user" && item.content === prompt ? itemIndex : last, -1)).map(({ message }) => ({ role: message.role, content: message.content, timestamp: message.timestamp }))
|
|
20514
20615
|
})
|
|
20515
20616
|
);
|
|
20516
20617
|
} else {
|
|
@@ -20663,6 +20764,18 @@ Please proceed directly with the implementation and code changes from this point
|
|
|
20663
20764
|
renderMessages();
|
|
20664
20765
|
}
|
|
20665
20766
|
}
|
|
20767
|
+
function handleCommandBarSteerResult(message) {
|
|
20768
|
+
const queued = commandMessages.find((msg) => msg.id === message.queueId);
|
|
20769
|
+
if (!queued) return;
|
|
20770
|
+
queued.steering = false;
|
|
20771
|
+
if (message.success) {
|
|
20772
|
+
queued.queued = false;
|
|
20773
|
+
queued.steered = true;
|
|
20774
|
+
} else {
|
|
20775
|
+
queued.error = message.error || "The agent could not accept this steering prompt.";
|
|
20776
|
+
}
|
|
20777
|
+
renderMessages();
|
|
20778
|
+
}
|
|
20666
20779
|
function handleCommandBarPrompt(message) {
|
|
20667
20780
|
const taskId2 = message.taskId;
|
|
20668
20781
|
let targetMsg = commandMessages.find((m) => m.id === taskId2);
|
|
@@ -20716,6 +20829,7 @@ Please proceed directly with the implementation and code changes from this point
|
|
|
20716
20829
|
html += `
|
|
20717
20830
|
<div class="lasso-command-msg user" data-msg-id="${msg.id}">
|
|
20718
20831
|
<div class="lasso-command-msg-bubble">${escapeHtml4(msg.content)}</div>
|
|
20832
|
+
${msg.queued ? `<button class="lasso-command-steer-btn" type="button"${msg.steering ? " disabled" : ""} style="margin-top: 6px; padding: 4px 9px; border: 1px solid rgba(96, 165, 250, 0.45); border-radius: 6px; background: rgba(59, 130, 246, 0.14); color: #bfdbfe; cursor: pointer;">${msg.steering ? "Steering\u2026" : "Steer"}</button>` : msg.steered ? `<span class="lasso-command-steered-label" style="display: inline-block; margin-top: 6px; color: #93c5fd; font-size: 12px;">Steered into next turn</span>` : ""}
|
|
20719
20833
|
</div>
|
|
20720
20834
|
`;
|
|
20721
20835
|
} else {
|
|
@@ -21821,6 +21935,15 @@ Please proceed directly with the implementation and code changes from this point
|
|
|
21821
21935
|
window.dispatchEvent(new CustomEvent("lasso-page-folder-created", { detail: { path: message.path || "", error: message.error } }));
|
|
21822
21936
|
return;
|
|
21823
21937
|
}
|
|
21938
|
+
if (message.type === "steer_result" && message.taskId && message.queueId) {
|
|
21939
|
+
handleCommandBarSteerResult({
|
|
21940
|
+
taskId: message.taskId,
|
|
21941
|
+
queueId: message.queueId,
|
|
21942
|
+
success: Boolean(message.success),
|
|
21943
|
+
error: message.error
|
|
21944
|
+
});
|
|
21945
|
+
return;
|
|
21946
|
+
}
|
|
21824
21947
|
if (message.type === "agent_status" && message.status && message.message) {
|
|
21825
21948
|
const taskId2 = message.taskId;
|
|
21826
21949
|
if (isCommandBarTaskId(taskId2)) {
|
|
@@ -195,10 +195,12 @@ export interface LassoModelResponse {
|
|
|
195
195
|
raw?: string;
|
|
196
196
|
};
|
|
197
197
|
decision: {
|
|
198
|
-
type: "inspect" | "plan" | "execute" | "verify" | "recover" | "ask_user";
|
|
198
|
+
type: "respond" | "inspect" | "plan" | "execute" | "verify" | "recover" | "ask_user";
|
|
199
199
|
goal: string;
|
|
200
200
|
};
|
|
201
201
|
actions: AgentAction[];
|
|
202
|
+
/** Direct conversational answer when no project work is requested. */
|
|
203
|
+
reply?: string;
|
|
202
204
|
expectedOutcome?: string;
|
|
203
205
|
}
|
|
204
206
|
export type AgentEventType = "agent_started" | "agent_reasoning" | "agent_completed" | "agent_failed" | "agent_cancelled" | "phase_changed" | "tool_started" | "tool_completed" | "tool_call" | "observation" | "plan_created" | "file_changed" | "verification_started" | "verification_completed" | "error" | "model_changed" | "waiting_for_user";
|
|
@@ -10,6 +10,14 @@ export interface VerificationConfig {
|
|
|
10
10
|
buildTimeout: number;
|
|
11
11
|
devServerTimeout: number;
|
|
12
12
|
browserTimeout: number;
|
|
13
|
+
/** URL of the project's already-running dev server, if available. */
|
|
14
|
+
devServerUrl?: string;
|
|
15
|
+
/** Optional browser bridge used for DOM/runtime verification. */
|
|
16
|
+
browserContextProvider?: (action: {
|
|
17
|
+
id: string;
|
|
18
|
+
type: "browser_check";
|
|
19
|
+
data?: Record<string, unknown>;
|
|
20
|
+
}) => Promise<unknown>;
|
|
13
21
|
}
|
|
14
22
|
export interface VerificationResult {
|
|
15
23
|
type: "typecheck" | "build" | "dev_server" | "browser";
|
|
@@ -68,4 +76,6 @@ export declare class VerificationEngine {
|
|
|
68
76
|
private hasTypeScriptConfig;
|
|
69
77
|
/** package.json with a non-empty build script. */
|
|
70
78
|
private hasBuildScript;
|
|
79
|
+
/** Fetch a health URL with a portable timeout for supported Node versions. */
|
|
80
|
+
private fetchWithTimeout;
|
|
71
81
|
}
|
|
@@ -19,6 +19,8 @@ class VerificationEngine {
|
|
|
19
19
|
buildTimeout: config?.buildTimeout || 120000,
|
|
20
20
|
devServerTimeout: config?.devServerTimeout || 30000,
|
|
21
21
|
browserTimeout: config?.browserTimeout || 60000,
|
|
22
|
+
devServerUrl: config?.devServerUrl,
|
|
23
|
+
browserContextProvider: config?.browserContextProvider,
|
|
22
24
|
};
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
@@ -43,7 +45,6 @@ class VerificationEngine {
|
|
|
43
45
|
// Dev server health
|
|
44
46
|
const devServerResult = await this.verifyDevServer(phase);
|
|
45
47
|
results.push(devServerResult);
|
|
46
|
-
// Browser check (placeholder)
|
|
47
48
|
const browserResult = await this.verifyBrowser(phase);
|
|
48
49
|
results.push(browserResult);
|
|
49
50
|
return results;
|
|
@@ -283,23 +284,48 @@ class VerificationEngine {
|
|
|
283
284
|
const startTime = Date.now();
|
|
284
285
|
const observations = [];
|
|
285
286
|
const errors = [];
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
287
|
+
const url = this.config.devServerUrl;
|
|
288
|
+
if (!url) {
|
|
289
|
+
observations.push({
|
|
290
|
+
id: this.generateId(),
|
|
291
|
+
type: "build",
|
|
292
|
+
summary: "Dev server health check skipped (no devServerUrl configured)",
|
|
293
|
+
data: { type: "dev_server", success: true, skipped: true },
|
|
294
|
+
timestamp: Date.now(),
|
|
295
|
+
phase,
|
|
296
|
+
});
|
|
297
|
+
return { type: "dev_server", success: true, skipped: true, observations, errors, duration: Date.now() - startTime };
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
const response = await this.fetchWithTimeout(url, this.config.devServerTimeout);
|
|
301
|
+
const success = response.status >= 200 && response.status < 400;
|
|
302
|
+
const summary = success ? `Dev server responded with HTTP ${response.status}` : `Dev server returned HTTP ${response.status}`;
|
|
303
|
+
observations.push({
|
|
304
|
+
id: this.generateId(),
|
|
305
|
+
type: "build",
|
|
306
|
+
summary,
|
|
307
|
+
data: { type: "dev_server", success, url, status: response.status },
|
|
308
|
+
timestamp: Date.now(),
|
|
309
|
+
phase,
|
|
310
|
+
});
|
|
311
|
+
if (!success) {
|
|
312
|
+
errors.push({
|
|
313
|
+
id: this.generateId(),
|
|
314
|
+
type: "verification",
|
|
315
|
+
message: summary,
|
|
316
|
+
phase,
|
|
317
|
+
timestamp: Date.now(),
|
|
318
|
+
recoverable: true,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return { type: "dev_server", success, command: `GET ${url}`, error: success ? undefined : summary, observations, errors, duration: Date.now() - startTime };
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
const message = `Dev server health check failed: ${error?.message || String(error)}`;
|
|
325
|
+
observations.push({ id: this.generateId(), type: "build", summary: message, data: { type: "dev_server", success: false, url }, timestamp: Date.now(), phase });
|
|
326
|
+
errors.push({ id: this.generateId(), type: "verification", message, phase, timestamp: Date.now(), recoverable: true });
|
|
327
|
+
return { type: "dev_server", success: false, command: `GET ${url}`, error: message, observations, errors, duration: Date.now() - startTime };
|
|
328
|
+
}
|
|
303
329
|
}
|
|
304
330
|
/**
|
|
305
331
|
* Verify browser/runtime state
|
|
@@ -308,23 +334,34 @@ class VerificationEngine {
|
|
|
308
334
|
const startTime = Date.now();
|
|
309
335
|
const observations = [];
|
|
310
336
|
const errors = [];
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
337
|
+
const provider = this.config.browserContextProvider;
|
|
338
|
+
if (!provider) {
|
|
339
|
+
observations.push({
|
|
340
|
+
id: this.generateId(),
|
|
341
|
+
type: "browser",
|
|
342
|
+
summary: "Browser verification skipped (no browser bridge configured)",
|
|
343
|
+
data: { type: "browser", success: true, skipped: true },
|
|
344
|
+
timestamp: Date.now(),
|
|
345
|
+
phase,
|
|
346
|
+
});
|
|
347
|
+
return { type: "browser", success: true, skipped: true, observations, errors, duration: Date.now() - startTime };
|
|
348
|
+
}
|
|
349
|
+
try {
|
|
350
|
+
const result = await Promise.race([provider({ id: this.generateId(), type: "browser_check", data: { phase } }), new Promise((_, reject) => setTimeout(() => reject(new Error("Browser verification timed out")), this.config.browserTimeout))]);
|
|
351
|
+
const details = result && typeof result === "object" ? result : {};
|
|
352
|
+
const success = details.success !== false;
|
|
353
|
+
const summary = typeof details.summary === "string" ? details.summary : success ? "Browser check passed" : "Browser check failed";
|
|
354
|
+
observations.push({ id: this.generateId(), type: "browser", summary, data: { type: "browser", success, result }, timestamp: Date.now(), phase });
|
|
355
|
+
if (!success)
|
|
356
|
+
errors.push({ id: this.generateId(), type: "verification", message: summary, phase, timestamp: Date.now(), recoverable: true });
|
|
357
|
+
return { type: "browser", success, error: success ? undefined : summary, observations, errors, duration: Date.now() - startTime };
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
const message = `Browser verification failed: ${error?.message || String(error)}`;
|
|
361
|
+
observations.push({ id: this.generateId(), type: "browser", summary: message, data: { type: "browser", success: false }, timestamp: Date.now(), phase });
|
|
362
|
+
errors.push({ id: this.generateId(), type: "verification", message, phase, timestamp: Date.now(), recoverable: true });
|
|
363
|
+
return { type: "browser", success: false, error: message, observations, errors, duration: Date.now() - startTime };
|
|
364
|
+
}
|
|
328
365
|
}
|
|
329
366
|
/**
|
|
330
367
|
* Check if verification passed overall
|
|
@@ -370,5 +407,16 @@ class VerificationEngine {
|
|
|
370
407
|
return false;
|
|
371
408
|
}
|
|
372
409
|
}
|
|
410
|
+
/** Fetch a health URL with a portable timeout for supported Node versions. */
|
|
411
|
+
async fetchWithTimeout(url, timeoutMs) {
|
|
412
|
+
const controller = new AbortController();
|
|
413
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
414
|
+
try {
|
|
415
|
+
return await fetch(url, { signal: controller.signal, redirect: "manual" });
|
|
416
|
+
}
|
|
417
|
+
finally {
|
|
418
|
+
clearTimeout(timer);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
373
421
|
}
|
|
374
422
|
exports.VerificationEngine = VerificationEngine;
|
package/package.json
CHANGED