@lasso-ai/cli 1.5.0 → 2.0.0

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.
Files changed (52) hide show
  1. package/dist/cli/agent.d.ts +1 -0
  2. package/dist/cli/backends.d.ts +53 -0
  3. package/dist/cli/backends.js +267 -0
  4. package/dist/cli/bridge.d.ts +8 -5
  5. package/dist/cli/bridge.js +357 -151
  6. package/dist/cli/host/install.js +12 -1
  7. package/dist/cli/local-runtime.d.ts +28 -0
  8. package/dist/cli/local-runtime.js +294 -0
  9. package/dist/cli/oneshot.js +28 -10
  10. package/dist/cli/runtime-client.d.ts +91 -10
  11. package/dist/cli/runtime-client.js +38 -3
  12. package/dist/overlay.js +44 -28
  13. package/dist/runtime/__tests__/adapter-normalization.test.d.ts +1 -0
  14. package/dist/runtime/__tests__/adapter-normalization.test.js +392 -0
  15. package/dist/runtime/__tests__/state-machine.test.d.ts +1 -0
  16. package/dist/runtime/__tests__/state-machine.test.js +107 -0
  17. package/dist/runtime/__tests__/verification-engine.test.d.ts +1 -0
  18. package/dist/runtime/__tests__/verification-engine.test.js +91 -0
  19. package/dist/runtime/__tests__/workspace-guard.test.d.ts +1 -0
  20. package/dist/runtime/__tests__/workspace-guard.test.js +104 -0
  21. package/dist/runtime/adapters/claude-code-adapter.d.ts +46 -0
  22. package/dist/runtime/adapters/claude-code-adapter.js +230 -0
  23. package/dist/runtime/adapters/codex-cli-adapter.d.ts +44 -0
  24. package/dist/runtime/adapters/codex-cli-adapter.js +202 -0
  25. package/dist/runtime/adapters/cursor-adapter.d.ts +47 -0
  26. package/dist/runtime/adapters/cursor-adapter.js +214 -0
  27. package/dist/runtime/adapters/factory.d.ts +19 -0
  28. package/dist/runtime/adapters/factory.js +48 -0
  29. package/dist/runtime/adapters/local-agent-adapter.d.ts +143 -0
  30. package/dist/runtime/adapters/local-agent-adapter.js +408 -0
  31. package/dist/runtime/adapters/opencode-adapter.d.ts +43 -0
  32. package/dist/runtime/adapters/opencode-adapter.js +210 -0
  33. package/dist/runtime/agent-runtime.d.ts +286 -0
  34. package/dist/runtime/agent-runtime.js +8 -0
  35. package/dist/runtime/agent-state-machine.d.ts +67 -0
  36. package/dist/runtime/agent-state-machine.js +234 -0
  37. package/dist/runtime/backend-interface.d.ts +272 -0
  38. package/dist/runtime/backend-interface.js +38 -0
  39. package/dist/runtime/event-mapper.d.ts +11 -0
  40. package/dist/runtime/event-mapper.js +40 -0
  41. package/dist/runtime/index.d.ts +20 -0
  42. package/dist/runtime/index.js +43 -0
  43. package/dist/runtime/local-session-runner.d.ts +60 -0
  44. package/dist/runtime/local-session-runner.js +185 -0
  45. package/dist/runtime/review.d.ts +24 -0
  46. package/dist/runtime/review.js +30 -0
  47. package/dist/runtime/verification-engine.d.ts +71 -0
  48. package/dist/runtime/verification-engine.js +374 -0
  49. package/dist/runtime/workspace-guard.d.ts +70 -0
  50. package/dist/runtime/workspace-guard.js +321 -0
  51. package/docs/local-coding-agents.md +183 -0
  52. package/package.json +22 -3
@@ -27,7 +27,18 @@ const AGENT_LABEL = "com.lasso.host";
27
27
  const LAUNCH_AGENT_FILE = node_path_1.default.join(node_os_1.default.homedir(), "Library", "LaunchAgents", `${AGENT_LABEL}.plist`);
28
28
  function cliEntry() {
29
29
  const entry = process.argv[1];
30
- return entry && entry.endsWith(".js") ? entry : null;
30
+ if (!entry)
31
+ return null;
32
+ // Resolve symlinks (e.g. pnpm's global bin is extensionless) so the
33
+ // spawned child gets a real .js path.
34
+ let resolved = entry;
35
+ try {
36
+ resolved = node_fs_1.default.realpathSync(entry);
37
+ }
38
+ catch {
39
+ // keep the original if realpath fails
40
+ }
41
+ return resolved.endsWith(".js") ? resolved : null;
31
42
  }
32
43
  function spawnDaemon(env) {
33
44
  const port = (0, paths_1.hostProxyPort)(env);
@@ -0,0 +1,28 @@
1
+ /**
2
+ * LocalRuntime — local coding agent execution on the user's machine.
3
+ *
4
+ * Implements the same session surface the CLI bridge uses for hosted
5
+ * sessions (RuntimeClient), but runs Claude Code / OpenCode / Codex CLI /
6
+ * Cursor Agent in THIS process: prompts, project context, and source code
7
+ * never travel through Lasso's server. Session state is in-memory (per CLI
8
+ * process) and the review-first workflow comes from the shared runtime core
9
+ * (LocalSessionRunner + WorkspaceGuard + VerificationEngine).
10
+ */
11
+ import type { RuntimeSession, RuntimeSessionInput, RuntimeEventHandler, SessionVerificationResult, RuntimeFacade } from "./runtime-client.js";
12
+ export declare class LocalRuntime implements RuntimeFacade {
13
+ private sessions;
14
+ private abortControllers;
15
+ createSession(input: RuntimeSessionInput): Promise<RuntimeSession>;
16
+ getSession(sessionId: string): Promise<RuntimeSession>;
17
+ runSession(sessionId: string, projectPath: string, onEvent: RuntimeEventHandler, signal?: AbortSignal): Promise<void>;
18
+ verifySession(sessionId: string, projectPath: string): Promise<SessionVerificationResult>;
19
+ switchModel(sessionId: string, model: {
20
+ provider: string;
21
+ id: string;
22
+ name?: string;
23
+ }, _apiKey?: string, backendConfig?: RuntimeSessionInput["backendConfig"]): Promise<void>;
24
+ cancelSession(sessionId: string): Promise<void>;
25
+ deleteSession(sessionId: string): Promise<void>;
26
+ private toSession;
27
+ }
28
+ export declare function getLocalRuntime(): LocalRuntime;
@@ -0,0 +1,294 @@
1
+ "use strict";
2
+ /**
3
+ * LocalRuntime — local coding agent execution on the user's machine.
4
+ *
5
+ * Implements the same session surface the CLI bridge uses for hosted
6
+ * sessions (RuntimeClient), but runs Claude Code / OpenCode / Codex CLI /
7
+ * Cursor Agent in THIS process: prompts, project context, and source code
8
+ * never travel through Lasso's server. Session state is in-memory (per CLI
9
+ * process) and the review-first workflow comes from the shared runtime core
10
+ * (LocalSessionRunner + WorkspaceGuard + VerificationEngine).
11
+ */
12
+ var __importDefault = (this && this.__importDefault) || function (mod) {
13
+ return (mod && mod.__esModule) ? mod : { "default": mod };
14
+ };
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.LocalRuntime = void 0;
17
+ exports.getLocalRuntime = getLocalRuntime;
18
+ const node_crypto_1 = __importDefault(require("node:crypto"));
19
+ const index_js_1 = require("../runtime/index.js");
20
+ class LocalRuntime {
21
+ sessions = new Map();
22
+ abortControllers = new Map();
23
+ async createSession(input) {
24
+ const backendConfig = input.backendConfig;
25
+ if (!(0, index_js_1.isLocalAgentConfig)(backendConfig)) {
26
+ throw new Error("LocalRuntime only accepts local_agent backend configs");
27
+ }
28
+ if (!backendConfig.projectPath) {
29
+ throw new Error("LocalRuntime requires backendConfig.projectPath");
30
+ }
31
+ const id = node_crypto_1.default.randomUUID();
32
+ const now = new Date();
33
+ const record = {
34
+ id,
35
+ phase: "execute",
36
+ status: "running",
37
+ iteration: 0,
38
+ userPrompt: input.userPrompt,
39
+ conversation: [
40
+ { id: node_crypto_1.default.randomUUID(), role: "user", content: input.userPrompt, timestamp: now.getTime() },
41
+ ],
42
+ observations: [],
43
+ actions: [],
44
+ completedActions: [],
45
+ errors: [],
46
+ selection: input.selection,
47
+ project: input.project,
48
+ model: input.model,
49
+ createdAt: now,
50
+ updatedAt: now,
51
+ metadata: { backendConfig },
52
+ backendConfig: { ...backendConfig, type: "local_agent" },
53
+ projectPath: backendConfig.projectPath,
54
+ input,
55
+ running: false,
56
+ };
57
+ this.sessions.set(id, record);
58
+ return this.toSession(record);
59
+ }
60
+ async getSession(sessionId) {
61
+ const record = this.sessions.get(sessionId);
62
+ if (!record)
63
+ throw new Error("Session not found");
64
+ return this.toSession(record);
65
+ }
66
+ async runSession(sessionId, projectPath, onEvent, signal) {
67
+ const record = this.sessions.get(sessionId);
68
+ if (!record)
69
+ throw new Error("Session not found");
70
+ if (record.running)
71
+ throw new Error("Session is already running");
72
+ record.running = true;
73
+ record.status = "running";
74
+ record.phase = "execute";
75
+ record.updatedAt = new Date();
76
+ const controller = new AbortController();
77
+ this.abortControllers.set(sessionId, controller);
78
+ const onOuterAbort = () => controller.abort();
79
+ signal?.addEventListener("abort", onOuterAbort, { once: true });
80
+ const emit = (event) => {
81
+ void onEvent(event);
82
+ };
83
+ try {
84
+ // Fresh capture per run — corrective re-runs capture the post-apply state.
85
+ const guard = await index_js_1.WorkspaceGuard.capture(projectPath);
86
+ record.backend =
87
+ record.backend ||
88
+ (await (0, index_js_1.createLocalAgentBackend)({ ...record.backendConfig, type: "local_agent", projectPath }));
89
+ const previousErrors = (record.errors || []);
90
+ const externalSessionId = typeof record.metadata.backendSessionId === "string" ? record.metadata.backendSessionId : undefined;
91
+ const runner = new index_js_1.LocalSessionRunner({
92
+ sessionId: record.id,
93
+ backend: record.backend,
94
+ guard,
95
+ emit,
96
+ phase: "execute",
97
+ });
98
+ const outcome = await runner.run({
99
+ taskId: record.id,
100
+ userPrompt: record.userPrompt,
101
+ project: {
102
+ projectId: record.project.projectId || "local",
103
+ framework: record.project.framework,
104
+ hasTypeScript: Boolean(record.project.hasTypeScript),
105
+ hasTailwind: false,
106
+ hasReact: Boolean(record.project.hasReact),
107
+ hasNext: Boolean(record.project.hasNext),
108
+ hasVite: Boolean(record.project.hasVite),
109
+ files: record.project.files || [],
110
+ },
111
+ selection: record.selection,
112
+ conversation: record.conversation.map((msg) => ({
113
+ role: msg.role,
114
+ content: msg.content,
115
+ timestamp: msg.timestamp,
116
+ })),
117
+ previousObservations: [],
118
+ previousErrors,
119
+ currentPhase: "execute",
120
+ constraints: { safetyLevel: "standard" },
121
+ sessionRef: externalSessionId ? { id: externalSessionId } : undefined,
122
+ }, controller.signal);
123
+ // Persist review payload + normalized actions for the review flow.
124
+ record.actions = outcome.actions;
125
+ record.observations.push(...outcome.observations.map((obs) => ({
126
+ id: obs.id,
127
+ type: String(obs.type),
128
+ summary: obs.summary,
129
+ data: obs.data,
130
+ timestamp: obs.timestamp,
131
+ phase: String(obs.phase),
132
+ })));
133
+ record.errors = outcome.errors.map((err) => ({
134
+ id: err.id,
135
+ type: String(err.type),
136
+ message: err.message,
137
+ phase: String(err.phase),
138
+ timestamp: err.timestamp,
139
+ recoverable: err.recoverable,
140
+ }));
141
+ if (outcome.conversation.length > 0) {
142
+ record.conversation.push(...outcome.conversation.map((msg) => ({
143
+ id: node_crypto_1.default.randomUUID(),
144
+ role: msg.role,
145
+ content: msg.content,
146
+ timestamp: msg.timestamp,
147
+ })));
148
+ }
149
+ record.metadata = {
150
+ ...record.metadata,
151
+ ...outcome.metadata,
152
+ pendingChanges: outcome.changes,
153
+ binaryChanges: outcome.binaryPaths,
154
+ };
155
+ record.status =
156
+ outcome.status === "waiting" ? "waiting" : outcome.status === "completed" ? "completed" : "failed";
157
+ record.phase = "complete";
158
+ record.updatedAt = new Date();
159
+ }
160
+ catch (error) {
161
+ record.status = "failed";
162
+ record.errors = [
163
+ ...record.errors,
164
+ {
165
+ id: node_crypto_1.default.randomUUID(),
166
+ type: "execution",
167
+ message: error instanceof Error ? error.message : "Local agent run failed",
168
+ phase: "execute",
169
+ timestamp: Date.now(),
170
+ recoverable: false,
171
+ },
172
+ ];
173
+ record.updatedAt = new Date();
174
+ emit({
175
+ type: "error",
176
+ sessionId: record.id,
177
+ timestamp: Date.now(),
178
+ data: { error: error instanceof Error ? error.message : "Local agent run failed" },
179
+ });
180
+ }
181
+ finally {
182
+ signal?.removeEventListener("abort", onOuterAbort);
183
+ this.abortControllers.delete(sessionId);
184
+ record.running = false;
185
+ }
186
+ }
187
+ async verifySession(sessionId, projectPath) {
188
+ const record = this.sessions.get(sessionId);
189
+ if (!record)
190
+ throw new Error("Session not found");
191
+ const engine = new index_js_1.VerificationEngine(projectPath);
192
+ const results = await engine.verifyAll("verify");
193
+ const observations = engine.getObservations(results);
194
+ const errors = engine.getErrors(results);
195
+ const passed = engine.allPassed(results);
196
+ // Keep verification errors so a corrective re-run receives them as context.
197
+ if (!passed) {
198
+ record.errors = [
199
+ ...record.errors,
200
+ ...errors.map((err) => ({
201
+ id: err.id,
202
+ type: String(err.type),
203
+ message: err.message,
204
+ phase: String(err.phase),
205
+ timestamp: err.timestamp,
206
+ recoverable: err.recoverable,
207
+ })),
208
+ ];
209
+ }
210
+ const command = results.map((r) => r.command).find((c) => typeof c === "string");
211
+ return { passed, errors, observations, command, requiresFix: !passed };
212
+ }
213
+ async switchModel(sessionId, model, _apiKey, backendConfig) {
214
+ const record = this.sessions.get(sessionId);
215
+ if (!record)
216
+ throw new Error("Session not found");
217
+ if (!(0, index_js_1.isLocalAgentConfig)(backendConfig)) {
218
+ throw new Error("LocalRuntime.switchModel requires a local_agent backendConfig");
219
+ }
220
+ const nextBackend = await (0, index_js_1.createLocalAgentBackend)({ ...backendConfig, type: "local_agent" });
221
+ if (record.backend) {
222
+ await record.backend.switchBackend(nextBackend, {
223
+ taskId: record.id,
224
+ userPrompt: record.userPrompt,
225
+ project: {
226
+ projectId: record.project.projectId || "local",
227
+ hasTypeScript: Boolean(record.project.hasTypeScript),
228
+ hasTailwind: false,
229
+ hasReact: Boolean(record.project.hasReact),
230
+ hasNext: Boolean(record.project.hasNext),
231
+ hasVite: Boolean(record.project.hasVite),
232
+ files: record.project.files || [],
233
+ },
234
+ conversation: [],
235
+ previousObservations: [],
236
+ previousErrors: [],
237
+ currentPhase: "execute",
238
+ constraints: {},
239
+ sessionRef: typeof record.metadata.backendSessionId === "string"
240
+ ? { id: record.metadata.backendSessionId }
241
+ : undefined,
242
+ });
243
+ }
244
+ record.backend = nextBackend;
245
+ record.backendConfig = { ...backendConfig, type: "local_agent" };
246
+ record.model = { provider: model.provider, id: model.id, name: model.name };
247
+ record.metadata = {
248
+ ...record.metadata,
249
+ backendConfig: record.backendConfig,
250
+ backendModel: model.id,
251
+ };
252
+ // Switching agents drops the previous agent's external session: context
253
+ // cannot be transferred across CLI agents.
254
+ delete record.metadata.backendSessionId;
255
+ record.updatedAt = new Date();
256
+ }
257
+ async cancelSession(sessionId) {
258
+ const controller = this.abortControllers.get(sessionId);
259
+ if (controller)
260
+ controller.abort();
261
+ }
262
+ async deleteSession(sessionId) {
263
+ this.sessions.delete(sessionId);
264
+ this.abortControllers.delete(sessionId);
265
+ }
266
+ toSession(record) {
267
+ return {
268
+ id: record.id,
269
+ phase: record.phase,
270
+ status: record.status,
271
+ iteration: record.iteration,
272
+ userPrompt: record.userPrompt,
273
+ conversation: record.conversation,
274
+ observations: record.observations,
275
+ actions: record.actions,
276
+ completedActions: record.completedActions,
277
+ errors: record.errors,
278
+ selection: record.selection,
279
+ project: record.project,
280
+ model: record.model,
281
+ createdAt: record.createdAt,
282
+ updatedAt: record.updatedAt,
283
+ metadata: record.metadata,
284
+ };
285
+ }
286
+ }
287
+ exports.LocalRuntime = LocalRuntime;
288
+ /** Process-wide local runtime (sessions live for the CLI bridge lifetime). */
289
+ let sharedLocalRuntime;
290
+ function getLocalRuntime() {
291
+ if (!sharedLocalRuntime)
292
+ sharedLocalRuntime = new LocalRuntime();
293
+ return sharedLocalRuntime;
294
+ }
@@ -138,7 +138,7 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
138
138
  onProgress("review", "Verifying changes...");
139
139
  break;
140
140
  case "verification_completed":
141
- const passed = event.data.passed;
141
+ const passed = event.data.success ?? event.data.passed;
142
142
  onProgress(passed ? "working" : "error", passed ? "Verification passed" : "Verification failed");
143
143
  break;
144
144
  case "waiting_for_user":
@@ -173,27 +173,45 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
173
173
  function extractChangesFromSession(session) {
174
174
  if (!session)
175
175
  return [];
176
+ const pending = session.metadata?.pendingChanges;
177
+ if (Array.isArray(pending) && pending.length > 0) {
178
+ return pending.map((change) => ({
179
+ filePath: change.filePath ?? change.path,
180
+ oldString: change.oldString ?? "",
181
+ newString: change.newString ?? "",
182
+ operation: change.operation ||
183
+ (change.oldString === "" && change.newString === "" ? "deleted" : change.oldString === "" ? "created" : "modified"),
184
+ }));
185
+ }
176
186
  const changes = [];
177
187
  for (const action of session.actions || []) {
178
- if (action.type === "edit_file" && action.data) {
188
+ if (!action.data)
189
+ continue;
190
+ const filePath = action.data.filePath ?? action.data.path;
191
+ if (!filePath)
192
+ continue;
193
+ if (action.type === "edit_file") {
179
194
  changes.push({
180
- filePath: action.data.filePath,
181
- oldString: action.data.oldString,
182
- newString: action.data.newString,
195
+ filePath,
196
+ oldString: action.data.oldString ?? "",
197
+ newString: action.data.newString ?? "",
198
+ operation: "modified",
183
199
  });
184
200
  }
185
- else if (action.type === "create_file" && action.data) {
201
+ else if (action.type === "create_file") {
186
202
  changes.push({
187
- filePath: action.data.filePath,
203
+ filePath,
188
204
  oldString: "",
189
205
  newString: action.data.content || "",
206
+ operation: "created",
190
207
  });
191
208
  }
192
- else if (action.type === "delete_file" && action.data) {
209
+ else if (action.type === "delete_file") {
193
210
  changes.push({
194
- filePath: action.data.filePath,
195
- oldString: "(deleted)",
211
+ filePath,
212
+ oldString: action.data.oldContent ?? "(deleted)",
196
213
  newString: "",
214
+ operation: "deleted",
197
215
  });
198
216
  }
199
217
  }
@@ -4,6 +4,7 @@
4
4
  * Client for the new Lasso Agentic Runtime
5
5
  * Uses SSE for real-time progress events
6
6
  */
7
+ export type RuntimeProvider = "anthropic" | "openai" | "google" | "nvidia" | "ollama" | "claude_code" | "opencode" | "codex_cli" | "cursor";
7
8
  export interface RuntimeSessionInput {
8
9
  userPrompt: string;
9
10
  project: {
@@ -17,7 +18,7 @@ export interface RuntimeSessionInput {
17
18
  currentRoute?: string;
18
19
  };
19
20
  model: {
20
- provider: "anthropic" | "openai" | "google" | "nvidia" | "ollama";
21
+ provider: RuntimeProvider;
21
22
  id: string;
22
23
  name?: string;
23
24
  };
@@ -38,6 +39,12 @@ export interface RuntimeSessionInput {
38
39
  screenshot?: string;
39
40
  };
40
41
  conversationUid?: string;
42
+ backendConfig?: {
43
+ type: "hosted_model" | "local_agent";
44
+ provider: "claude_code" | "opencode" | "codex_cli" | "cursor";
45
+ modelId?: string;
46
+ projectPath?: string;
47
+ };
41
48
  }
42
49
  export interface RuntimeSession {
43
50
  id: string;
@@ -97,6 +104,44 @@ export interface RuntimeSession {
97
104
  conversationUid?: string;
98
105
  createdAt: Date;
99
106
  updatedAt: Date;
107
+ /** Runtime-owned metadata: pendingChanges, backendConfig, backendSessionId, … */
108
+ metadata?: Record<string, unknown>;
109
+ }
110
+ /** Result of post-apply verification (typecheck/build via VerificationEngine). */
111
+ export interface SessionVerificationResult {
112
+ passed: boolean;
113
+ errors: Array<{
114
+ id: string;
115
+ type: string;
116
+ message: string;
117
+ phase: string;
118
+ timestamp: number;
119
+ recoverable: boolean;
120
+ }>;
121
+ observations: Array<{
122
+ id: string;
123
+ type: string;
124
+ summary: string;
125
+ timestamp: number;
126
+ }>;
127
+ command?: string;
128
+ requiresFix: boolean;
129
+ }
130
+ /**
131
+ * The session surface shared by RuntimeClient (hosted, via Lasso server) and
132
+ * LocalRuntime (local coding agents, executed in this process).
133
+ */
134
+ export interface RuntimeFacade {
135
+ createSession(input: RuntimeSessionInput): Promise<RuntimeSession>;
136
+ runSession(sessionId: string, projectPath: string, onEvent: RuntimeEventHandler, signal?: AbortSignal): Promise<void>;
137
+ getSession(sessionId: string): Promise<RuntimeSession>;
138
+ verifySession(sessionId: string, projectPath: string): Promise<SessionVerificationResult>;
139
+ switchModel(sessionId: string, model: {
140
+ provider: string;
141
+ id: string;
142
+ name?: string;
143
+ }, apiKey?: string, backendConfig?: RuntimeSessionInput["backendConfig"]): Promise<void>;
144
+ cancelSession(sessionId: string): Promise<void>;
100
145
  }
101
146
  export type RuntimeEvent = {
102
147
  type: "agent_started";
@@ -166,12 +211,7 @@ export type RuntimeEvent = {
166
211
  } | {
167
212
  type: "error";
168
213
  sessionId: string;
169
- data: {
170
- errorType: string;
171
- message: string;
172
- phase: string;
173
- recoverable: boolean;
174
- };
214
+ data: Record<string, unknown>;
175
215
  timestamp: number;
176
216
  } | {
177
217
  type: "model_changed";
@@ -184,7 +224,22 @@ export type RuntimeEvent = {
184
224
  } | {
185
225
  type: "agent_completed";
186
226
  sessionId: string;
187
- data: {};
227
+ data: Record<string, unknown>;
228
+ timestamp: number;
229
+ } | {
230
+ type: "agent_cancelled";
231
+ sessionId: string;
232
+ data: Record<string, unknown>;
233
+ timestamp: number;
234
+ } | {
235
+ type: "agent_failed";
236
+ sessionId: string;
237
+ data: Record<string, unknown>;
238
+ timestamp: number;
239
+ } | {
240
+ type: "file_changed";
241
+ sessionId: string;
242
+ data: Record<string, unknown>;
188
243
  timestamp: number;
189
244
  } | {
190
245
  type: "started";
@@ -215,13 +270,39 @@ export declare class RuntimeClient {
215
270
  */
216
271
  cancelSession(sessionId: string): Promise<void>;
217
272
  /**
218
- * Switch model for a session
273
+ * Switch model/backend for a session
219
274
  */
220
275
  switchModel(sessionId: string, model: {
221
276
  provider: string;
222
277
  id: string;
223
278
  name?: string;
224
- }, apiKey?: string): Promise<void>;
279
+ }, apiKey?: string, backendConfig?: RuntimeSessionInput["backendConfig"]): Promise<void>;
280
+ /**
281
+ * Verify the workspace for a session (after reviewed changes were applied).
282
+ *
283
+ * Returns whether verification passed; on failure the session keeps the
284
+ * verification errors so a corrective re-run can fix them.
285
+ */
286
+ verifySession(sessionId: string, projectPath: string): Promise<SessionVerificationResult>;
287
+ /**
288
+ * List available backends from the server (hosted providers + local agents).
289
+ */
290
+ listBackends(projectPath?: string): Promise<{
291
+ backends: Array<{
292
+ provider: string;
293
+ type: string;
294
+ available: boolean;
295
+ authenticated: boolean;
296
+ installed?: boolean;
297
+ version?: string;
298
+ error?: string;
299
+ fixInstructions?: string;
300
+ models?: Array<{
301
+ id: string;
302
+ label: string;
303
+ }>;
304
+ }>;
305
+ }>;
225
306
  /**
226
307
  * Delete a session
227
308
  */
@@ -110,21 +110,56 @@ class RuntimeClient {
110
110
  }
111
111
  }
112
112
  /**
113
- * Switch model for a session
113
+ * Switch model/backend for a session
114
114
  */
115
- async switchModel(sessionId, model, apiKey) {
115
+ async switchModel(sessionId, model, apiKey, backendConfig) {
116
116
  const response = await fetch(`${this.serverUrl}/api/v1/agent-runtime/sessions/${sessionId}/model`, {
117
117
  method: "POST",
118
118
  headers: {
119
119
  "Content-Type": "application/json",
120
120
  ...(this.apiKey && { "Authorization": `Bearer ${this.apiKey}` }),
121
121
  },
122
- body: JSON.stringify({ ...model, apiKey }),
122
+ body: JSON.stringify({ ...model, apiKey, backendConfig }),
123
123
  });
124
124
  if (!response.ok) {
125
125
  throw new Error(`Failed to switch model: ${response.statusText}`);
126
126
  }
127
127
  }
128
+ /**
129
+ * Verify the workspace for a session (after reviewed changes were applied).
130
+ *
131
+ * Returns whether verification passed; on failure the session keeps the
132
+ * verification errors so a corrective re-run can fix them.
133
+ */
134
+ async verifySession(sessionId, projectPath) {
135
+ const response = await fetch(`${this.serverUrl}/api/v1/agent-runtime/sessions/${sessionId}/verify`, {
136
+ method: "POST",
137
+ headers: {
138
+ "Content-Type": "application/json",
139
+ ...(this.apiKey && { "Authorization": `Bearer ${this.apiKey}` }),
140
+ },
141
+ body: JSON.stringify({ projectPath }),
142
+ });
143
+ if (!response.ok) {
144
+ throw new Error(`Failed to verify session: ${response.statusText}`);
145
+ }
146
+ return (await response.json());
147
+ }
148
+ /**
149
+ * List available backends from the server (hosted providers + local agents).
150
+ */
151
+ async listBackends(projectPath) {
152
+ const url = `${this.serverUrl}/api/v1/agent-runtime/backends${projectPath ? `?projectPath=${encodeURIComponent(projectPath)}` : ""}`;
153
+ const response = await fetch(url, {
154
+ headers: {
155
+ ...(this.apiKey && { "Authorization": `Bearer ${this.apiKey}` }),
156
+ },
157
+ });
158
+ if (!response.ok) {
159
+ throw new Error(`Failed to list backends: ${response.statusText}`);
160
+ }
161
+ return (await response.json());
162
+ }
128
163
  /**
129
164
  * Delete a session
130
165
  */