@lasso-ai/cli 2.1.12 → 2.1.14

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.
@@ -22,6 +22,19 @@ const backends_1 = require("./backends");
22
22
  const local_runtime_1 = require("./local-runtime");
23
23
  exports.DEFAULT_BRIDGE_PORT = 3056;
24
24
  const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
25
+ /** Map overlay chat history to the runtime conversation shape. */
26
+ function toRuntimeConversation(messages) {
27
+ if (!Array.isArray(messages) || messages.length === 0)
28
+ return undefined;
29
+ const turns = messages
30
+ .filter((m) => typeof m?.content === "string" && m.content.trim())
31
+ .map((m) => ({
32
+ role: (m.role === "user" ? "user" : m.role === "assistant" ? "assistant" : "system"),
33
+ content: m.content,
34
+ timestamp: m.createdAt ? Date.parse(m.createdAt) || Date.now() : Date.now(),
35
+ }));
36
+ return turns.length > 0 ? turns : undefined;
37
+ }
25
38
  function isConversationalPrompt(prompt) {
26
39
  const p = prompt.trim().toLowerCase().replace(/[!?.,;:]+$/, "");
27
40
  // 1. Direct greetings and conversational niceties
@@ -626,6 +639,7 @@ async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort
626
639
  const backendModelId = (0, backends_1.modelIdForBackend)(model || "", provider || "");
627
640
  const sessionInput = {
628
641
  userPrompt: instruction,
642
+ conversation: toRuntimeConversation(messages),
629
643
  project: {
630
644
  projectId,
631
645
  framework,
@@ -17,6 +17,24 @@ exports.LocalRuntime = void 0;
17
17
  exports.getLocalRuntime = getLocalRuntime;
18
18
  const node_crypto_1 = __importDefault(require("node:crypto"));
19
19
  const index_js_1 = require("../runtime/index.js");
20
+ /**
21
+ * Seed a session's conversation from the caller's history plus the current
22
+ * user prompt. Skipping the history makes every follow-up a blank-slate run
23
+ * ("try again" becomes a literal search instead of a retry).
24
+ */
25
+ function seedConversation(input, timestamp) {
26
+ const history = (input.conversation || []).map((msg) => ({
27
+ id: node_crypto_1.default.randomUUID(),
28
+ role: msg.role,
29
+ content: msg.content,
30
+ timestamp: msg.timestamp || timestamp,
31
+ }));
32
+ const last = history[history.length - 1];
33
+ if (!last || last.role !== "user" || last.content !== input.userPrompt) {
34
+ history.push({ id: node_crypto_1.default.randomUUID(), role: "user", content: input.userPrompt, timestamp });
35
+ }
36
+ return history;
37
+ }
20
38
  class LocalRuntime {
21
39
  sessions = new Map();
22
40
  abortControllers = new Map();
@@ -37,9 +55,7 @@ class LocalRuntime {
37
55
  iteration: 0,
38
56
  userPrompt: input.userPrompt,
39
57
  mode: input.mode || "task",
40
- conversation: [
41
- { id: node_crypto_1.default.randomUUID(), role: "user", content: input.userPrompt, timestamp: now.getTime() },
42
- ],
58
+ conversation: seedConversation(input, now.getTime()),
43
59
  observations: [],
44
60
  actions: [],
45
61
  completedActions: [],
@@ -117,7 +133,9 @@ class LocalRuntime {
117
133
  content: msg.content,
118
134
  timestamp: msg.timestamp,
119
135
  })),
120
- previousObservations: [],
136
+ // Carry prior observations so the agent knows what was already
137
+ // searched/read instead of re-exploring the project every turn.
138
+ previousObservations: record.observations,
121
139
  previousErrors,
122
140
  currentPhase: "execute",
123
141
  constraints: { safetyLevel: "standard" },
@@ -82,6 +82,11 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
82
82
  onProgress("thinking", "Creating runtime session...", `Model: ${config.model || "claude-3-5-sonnet-20241022"}`);
83
83
  const sessionInput = {
84
84
  userPrompt: prompt,
85
+ conversation: Array.isArray(messages) && messages.length > 0
86
+ ? messages
87
+ .filter((m) => typeof m?.content === "string" && m.content.trim())
88
+ .map((m) => ({ role: m.role, content: m.content }))
89
+ : undefined,
85
90
  project: {
86
91
  projectId: "", // TODO: Get from workspace config
87
92
  framework,
@@ -45,6 +45,16 @@ export interface RuntimeSessionInput {
45
45
  screenshot?: string;
46
46
  };
47
47
  conversationUid?: string;
48
+ /**
49
+ * Prior turns of the same conversation. Follow-up prompts must carry
50
+ * history here — a session created with only userPrompt gives the agent
51
+ * no idea what was asked or done before.
52
+ */
53
+ conversation?: Array<{
54
+ role: "user" | "assistant" | "system";
55
+ content: string;
56
+ timestamp?: number;
57
+ }>;
48
58
  backendConfig?: {
49
59
  type: "hosted_model" | "local_agent";
50
60
  provider: "claude_code" | "opencode" | "codex_cli" | "cursor";
@@ -88,9 +88,13 @@ class RuntimeClient {
88
88
  }
89
89
  const executor = new index_js_1.ActionExecutor(projectPath, opts?.browserContextProvider);
90
90
  const verifier = new index_js_1.VerificationEngine(projectPath);
91
+ // Idempotent: called on agent_completed AND on stream failure, so the
92
+ // diff is always cached before the safety-net restore wipes the edits.
93
+ let finalized = false;
91
94
  const finalizeGuard = async () => {
92
- if (!guard)
95
+ if (!guard || finalized)
93
96
  return;
97
+ finalized = true;
94
98
  try {
95
99
  const changes = guard.diff();
96
100
  const applyable = changes.filter((c) => !c.binary);
@@ -174,10 +178,11 @@ class RuntimeClient {
174
178
  }
175
179
  finally {
176
180
  reader.releaseLock();
177
- // Safety net: if the stream died mid-run, still restore the workspace.
178
- if (guard) {
179
- guard.restore().catch(() => { });
180
- }
181
+ // Stream died before agent_completed (network cut, server crash, model
182
+ // error surfaced as an error event): cache the diff first so the user's
183
+ // changes are recoverable, then restore the workspace. On the normal
184
+ // path finalizeGuard already ran and this is a no-op.
185
+ await finalizeGuard();
181
186
  }
182
187
  }
183
188
  /** Execute a tool_call request locally against the real project. */
@@ -311,6 +311,20 @@ class LocalAgentAdapter {
311
311
  lines.push("2. All file changes will be captured as a diff, reviewed by the user, and only applied if approved.");
312
312
  lines.push("3. Do not commit, push, or perform operations outside this project directory.");
313
313
  lines.push("");
314
+ // Retry/continuation replies ("try again", "continue", …) must resume
315
+ // the previous request instead of being treated as a literal task —
316
+ // otherwise the agent greps for words like "again".
317
+ const normalizedPrompt = context.userPrompt.trim().replace(/[.!]+$/, "").toLowerCase();
318
+ const isContinuation = /^(continue|proceed|go on|go ahead|keep going|yes|yep|yup|sure|ok|okay|alright|do it|carry on|resume|next|try again|again|retry|re-try|do it again|run again|run it again|one more time|same again)$/.test(normalizedPrompt) ||
319
+ /^(try|do|run|go)\s+(it\s+|that\s+)?again$|^one more time$|^retry(\s+it)?$/i.test(normalizedPrompt);
320
+ if (isContinuation) {
321
+ const priorUserMessages = context.conversation.filter((m) => m.role === "user" && m.content.trim() && m.content.trim() !== context.userPrompt.trim());
322
+ const prior = priorUserMessages[priorUserMessages.length - 1];
323
+ if (prior) {
324
+ lines.push(`The user is continuing/retrying. Resume this previous request: ${prior.content}`);
325
+ lines.push("");
326
+ }
327
+ }
314
328
  lines.push(`User request: ${context.userPrompt}`);
315
329
  lines.push("");
316
330
  lines.push("Project context:");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lasso-ai/cli",
3
- "version": "2.1.12",
3
+ "version": "2.1.14",
4
4
  "description": "Select any part of your running app, describe a change, and let AI edit the real source code.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/cli/index.js",