@lasso-ai/cli 2.0.1 → 2.1.1
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/runtime-client.d.ts +35 -6
- package/dist/cli/runtime-client.js +175 -17
- package/dist/runtime/action-executor.d.ts +76 -0
- package/dist/runtime/action-executor.js +420 -0
- package/dist/runtime/agent-runtime.d.ts +17 -1
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +4 -1
- package/dist/runtime/tool-protocol.d.ts +40 -0
- package/dist/runtime/tool-protocol.js +13 -0
- package/package.json +1 -1
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Lasso Runtime Client
|
|
3
3
|
*
|
|
4
|
-
* Client for the
|
|
5
|
-
*
|
|
4
|
+
* Client for the hosted-model agentic runtime. The server orchestrates the
|
|
5
|
+
* agent loop and calls the model, but never touches the local filesystem:
|
|
6
|
+
* workspace tools arrive as `tool_call` events and are executed HERE, in the
|
|
7
|
+
* CLI, against the real project directory. The CLI also owns the
|
|
8
|
+
* WorkspaceGuard (snapshot → diff → rollback → review → approval).
|
|
6
9
|
*/
|
|
10
|
+
import { type ToolCallRequest } from "../runtime/index.js";
|
|
7
11
|
export type RuntimeProvider = "anthropic" | "openai" | "google" | "nvidia" | "ollama" | "claude_code" | "opencode" | "codex_cli" | "cursor";
|
|
8
12
|
export interface RuntimeSessionInput {
|
|
9
13
|
userPrompt: string;
|
|
@@ -178,6 +182,11 @@ export type RuntimeEvent = {
|
|
|
178
182
|
success: boolean;
|
|
179
183
|
};
|
|
180
184
|
timestamp: number;
|
|
185
|
+
} | {
|
|
186
|
+
type: "tool_call";
|
|
187
|
+
sessionId: string;
|
|
188
|
+
data: ToolCallRequest;
|
|
189
|
+
timestamp: number;
|
|
181
190
|
} | {
|
|
182
191
|
type: "observation";
|
|
183
192
|
sessionId: string;
|
|
@@ -252,19 +261,38 @@ export type RuntimeEventHandler = (event: RuntimeEvent) => void | Promise<void>;
|
|
|
252
261
|
export declare class RuntimeClient {
|
|
253
262
|
private serverUrl;
|
|
254
263
|
private apiKey?;
|
|
264
|
+
/**
|
|
265
|
+
* Review payloads computed locally from the WorkspaceGuard diff, keyed by
|
|
266
|
+
* sessionId. Merged into getSession() so the bridge's review flow sees the
|
|
267
|
+
* authoritative pendingChanges without the server ever touching the FS.
|
|
268
|
+
*/
|
|
269
|
+
private localReviewCache;
|
|
255
270
|
constructor(serverUrl: string, apiKey?: string);
|
|
256
271
|
/**
|
|
257
272
|
* Create a new runtime session
|
|
258
273
|
*/
|
|
259
274
|
createSession(input: RuntimeSessionInput): Promise<RuntimeSession>;
|
|
260
275
|
/**
|
|
261
|
-
* Get a session by ID
|
|
276
|
+
* Get a session by ID. Locally computed review changes (WorkspaceGuard
|
|
277
|
+
* diff from the last run) are merged into metadata.pendingChanges.
|
|
262
278
|
*/
|
|
263
279
|
getSession(sessionId: string): Promise<RuntimeSession>;
|
|
264
280
|
/**
|
|
265
|
-
* Run a session with SSE event streaming
|
|
281
|
+
* Run a session with SSE event streaming.
|
|
282
|
+
*
|
|
283
|
+
* Hosted-model flow (server orchestrates, CLI executes):
|
|
284
|
+
* 1. Capture the WorkspaceGuard BEFORE the run (CLI owns the snapshot).
|
|
285
|
+
* 2. Stream events from the server. `tool_call` events are executed
|
|
286
|
+
* locally (ActionExecutor / VerificationEngine) and the result is
|
|
287
|
+
* posted back to the server so the model loop can continue.
|
|
288
|
+
* 3. On agent_completed: diff the workspace, roll it back, and cache the
|
|
289
|
+
* changes for the review panel. Only user approval re-applies them.
|
|
266
290
|
*/
|
|
267
291
|
runSession(sessionId: string, projectPath: string, onEvent: RuntimeEventHandler, signal?: AbortSignal): Promise<void>;
|
|
292
|
+
/** Execute a tool_call request locally against the real project. */
|
|
293
|
+
private executeToolCallLocally;
|
|
294
|
+
/** Post a tool result back to the server (resolves its pending request). */
|
|
295
|
+
private postToolResult;
|
|
268
296
|
/**
|
|
269
297
|
* Cancel a running session
|
|
270
298
|
*/
|
|
@@ -280,8 +308,9 @@ export declare class RuntimeClient {
|
|
|
280
308
|
/**
|
|
281
309
|
* Verify the workspace for a session (after reviewed changes were applied).
|
|
282
310
|
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
311
|
+
* Hosted sessions verify LOCALLY (the server cannot run typecheck/build
|
|
312
|
+
* against a filesystem it does not have). The results are also persisted
|
|
313
|
+
* on the server session so a corrective re-run has them in context.
|
|
285
314
|
*/
|
|
286
315
|
verifySession(sessionId: string, projectPath: string): Promise<SessionVerificationResult>;
|
|
287
316
|
/**
|
|
@@ -2,14 +2,28 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Lasso Runtime Client
|
|
4
4
|
*
|
|
5
|
-
* Client for the
|
|
6
|
-
*
|
|
5
|
+
* Client for the hosted-model agentic runtime. The server orchestrates the
|
|
6
|
+
* agent loop and calls the model, but never touches the local filesystem:
|
|
7
|
+
* workspace tools arrive as `tool_call` events and are executed HERE, in the
|
|
8
|
+
* CLI, against the real project directory. The CLI also owns the
|
|
9
|
+
* WorkspaceGuard (snapshot → diff → rollback → review → approval).
|
|
7
10
|
*/
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
8
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
15
|
exports.RuntimeClient = void 0;
|
|
16
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
17
|
+
const index_js_1 = require("../runtime/index.js");
|
|
10
18
|
class RuntimeClient {
|
|
11
19
|
serverUrl;
|
|
12
20
|
apiKey;
|
|
21
|
+
/**
|
|
22
|
+
* Review payloads computed locally from the WorkspaceGuard diff, keyed by
|
|
23
|
+
* sessionId. Merged into getSession() so the bridge's review flow sees the
|
|
24
|
+
* authoritative pendingChanges without the server ever touching the FS.
|
|
25
|
+
*/
|
|
26
|
+
localReviewCache = new Map();
|
|
13
27
|
constructor(serverUrl, apiKey) {
|
|
14
28
|
this.serverUrl = serverUrl;
|
|
15
29
|
this.apiKey = apiKey;
|
|
@@ -33,7 +47,8 @@ class RuntimeClient {
|
|
|
33
47
|
return data;
|
|
34
48
|
}
|
|
35
49
|
/**
|
|
36
|
-
* Get a session by ID
|
|
50
|
+
* Get a session by ID. Locally computed review changes (WorkspaceGuard
|
|
51
|
+
* diff from the last run) are merged into metadata.pendingChanges.
|
|
37
52
|
*/
|
|
38
53
|
async getSession(sessionId) {
|
|
39
54
|
const response = await fetch(`${this.serverUrl}/api/v1/agent-runtime/sessions/${sessionId}`, {
|
|
@@ -45,12 +60,67 @@ class RuntimeClient {
|
|
|
45
60
|
throw new Error(`Failed to get session: ${response.statusText}`);
|
|
46
61
|
}
|
|
47
62
|
const data = await response.json();
|
|
63
|
+
const local = this.localReviewCache.get(sessionId);
|
|
64
|
+
if (local && local.length > 0) {
|
|
65
|
+
data.metadata = { ...(data.metadata || {}), pendingChanges: local };
|
|
66
|
+
}
|
|
48
67
|
return data;
|
|
49
68
|
}
|
|
50
69
|
/**
|
|
51
|
-
* Run a session with SSE event streaming
|
|
70
|
+
* Run a session with SSE event streaming.
|
|
71
|
+
*
|
|
72
|
+
* Hosted-model flow (server orchestrates, CLI executes):
|
|
73
|
+
* 1. Capture the WorkspaceGuard BEFORE the run (CLI owns the snapshot).
|
|
74
|
+
* 2. Stream events from the server. `tool_call` events are executed
|
|
75
|
+
* locally (ActionExecutor / VerificationEngine) and the result is
|
|
76
|
+
* posted back to the server so the model loop can continue.
|
|
77
|
+
* 3. On agent_completed: diff the workspace, roll it back, and cache the
|
|
78
|
+
* changes for the review panel. Only user approval re-applies them.
|
|
52
79
|
*/
|
|
53
80
|
async runSession(sessionId, projectPath, onEvent, signal) {
|
|
81
|
+
// CLI-side snapshot: nothing the agent does is left on disk before review.
|
|
82
|
+
let guard = null;
|
|
83
|
+
try {
|
|
84
|
+
guard = await index_js_1.WorkspaceGuard.capture(projectPath);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
console.error("WorkspaceGuard capture failed:", error);
|
|
88
|
+
}
|
|
89
|
+
const executor = new index_js_1.ActionExecutor(projectPath);
|
|
90
|
+
const verifier = new index_js_1.VerificationEngine(projectPath);
|
|
91
|
+
const finalizeGuard = async () => {
|
|
92
|
+
if (!guard)
|
|
93
|
+
return;
|
|
94
|
+
try {
|
|
95
|
+
const changes = guard.diff();
|
|
96
|
+
const applyable = changes.filter((c) => !c.binary);
|
|
97
|
+
const pending = applyable.map((change) => ({
|
|
98
|
+
filePath: change.filePath,
|
|
99
|
+
oldString: change.operation === "created" ? "" : change.oldContent ?? "",
|
|
100
|
+
newString: change.operation === "deleted" ? "" : change.newContent ?? "",
|
|
101
|
+
operation: change.operation,
|
|
102
|
+
}));
|
|
103
|
+
this.localReviewCache.set(sessionId, pending);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error("WorkspaceGuard diff failed:", error);
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
// Always restore BEFORE the review flow runs — it captures originals
|
|
110
|
+
// from disk for the undo snapshot, so the workspace must be back to
|
|
111
|
+
// its pre-run state by the time agent_completed is delivered.
|
|
112
|
+
try {
|
|
113
|
+
await guard.restore();
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
console.error("WorkspaceGuard restore failed:", error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const handleToolCall = async (request) => {
|
|
121
|
+
const result = await this.executeToolCallLocally(request, executor, verifier);
|
|
122
|
+
await this.postToolResult(sessionId, result);
|
|
123
|
+
};
|
|
54
124
|
const response = await fetch(`${this.serverUrl}/api/v1/agent-runtime/sessions/${sessionId}/run`, {
|
|
55
125
|
method: "POST",
|
|
56
126
|
headers: {
|
|
@@ -83,6 +153,15 @@ class RuntimeClient {
|
|
|
83
153
|
continue;
|
|
84
154
|
try {
|
|
85
155
|
const event = JSON.parse(data);
|
|
156
|
+
if (event.type === "tool_call") {
|
|
157
|
+
// Execute locally and answer the server — never forwarded raw.
|
|
158
|
+
await handleToolCall(event.data);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (event.type === "agent_completed") {
|
|
162
|
+
// Diff + rollback BEFORE the review flow reads the session.
|
|
163
|
+
await finalizeGuard();
|
|
164
|
+
}
|
|
86
165
|
await onEvent(event);
|
|
87
166
|
}
|
|
88
167
|
catch (error) {
|
|
@@ -93,6 +172,71 @@ class RuntimeClient {
|
|
|
93
172
|
}
|
|
94
173
|
finally {
|
|
95
174
|
reader.releaseLock();
|
|
175
|
+
// Safety net: if the stream died mid-run, still restore the workspace.
|
|
176
|
+
if (guard) {
|
|
177
|
+
guard.restore().catch(() => { });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/** Execute a tool_call request locally against the real project. */
|
|
182
|
+
async executeToolCallLocally(request, executor, verifier) {
|
|
183
|
+
try {
|
|
184
|
+
if (request.kind === "verify") {
|
|
185
|
+
const results = await verifier.verifyAll(request.verify?.phase || "verify");
|
|
186
|
+
return {
|
|
187
|
+
callId: request.callId,
|
|
188
|
+
success: verifier.allPassed(results),
|
|
189
|
+
observations: verifier.getObservations(results),
|
|
190
|
+
errors: verifier.getErrors(results),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (!request.action) {
|
|
194
|
+
return { callId: request.callId, success: false, error: "tool_call missing action" };
|
|
195
|
+
}
|
|
196
|
+
const result = await executor.execute(request.action);
|
|
197
|
+
return {
|
|
198
|
+
callId: request.callId,
|
|
199
|
+
success: result.success,
|
|
200
|
+
output: result.output,
|
|
201
|
+
error: result.error,
|
|
202
|
+
observations: result.observation
|
|
203
|
+
? [{
|
|
204
|
+
id: node_crypto_1.default.randomUUID(),
|
|
205
|
+
type: result.observation.type,
|
|
206
|
+
summary: result.observation.summary,
|
|
207
|
+
data: result.observation.data,
|
|
208
|
+
timestamp: Date.now(),
|
|
209
|
+
phase: "execute",
|
|
210
|
+
actionId: request.action.id,
|
|
211
|
+
}]
|
|
212
|
+
: undefined,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
return {
|
|
217
|
+
callId: request.callId,
|
|
218
|
+
success: false,
|
|
219
|
+
error: error instanceof Error ? error.message : String(error),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/** Post a tool result back to the server (resolves its pending request). */
|
|
224
|
+
async postToolResult(sessionId, result) {
|
|
225
|
+
try {
|
|
226
|
+
const response = await fetch(`${this.serverUrl}/api/v1/agent-runtime/sessions/${sessionId}/tool-results`, {
|
|
227
|
+
method: "POST",
|
|
228
|
+
headers: {
|
|
229
|
+
"Content-Type": "application/json",
|
|
230
|
+
...(this.apiKey && { "Authorization": `Bearer ${this.apiKey}` }),
|
|
231
|
+
},
|
|
232
|
+
body: JSON.stringify(result),
|
|
233
|
+
});
|
|
234
|
+
if (!response.ok) {
|
|
235
|
+
console.error(`Failed to post tool result: ${response.statusText}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
console.error("Failed to post tool result:", error);
|
|
96
240
|
}
|
|
97
241
|
}
|
|
98
242
|
/**
|
|
@@ -128,22 +272,36 @@ class RuntimeClient {
|
|
|
128
272
|
/**
|
|
129
273
|
* Verify the workspace for a session (after reviewed changes were applied).
|
|
130
274
|
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
275
|
+
* Hosted sessions verify LOCALLY (the server cannot run typecheck/build
|
|
276
|
+
* against a filesystem it does not have). The results are also persisted
|
|
277
|
+
* on the server session so a corrective re-run has them in context.
|
|
133
278
|
*/
|
|
134
279
|
async verifySession(sessionId, projectPath) {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
280
|
+
const engine = new index_js_1.VerificationEngine(projectPath);
|
|
281
|
+
const results = await engine.verifyAll("verify");
|
|
282
|
+
const passed = engine.allPassed(results);
|
|
283
|
+
const errors = engine.getErrors(results);
|
|
284
|
+
const observations = engine.getObservations(results);
|
|
285
|
+
// Persist on the server session (best-effort — corrective context).
|
|
286
|
+
try {
|
|
287
|
+
await fetch(`${this.serverUrl}/api/v1/agent-runtime/sessions/${sessionId}/verify`, {
|
|
288
|
+
method: "POST",
|
|
289
|
+
headers: {
|
|
290
|
+
"Content-Type": "application/json",
|
|
291
|
+
...(this.apiKey && { "Authorization": `Bearer ${this.apiKey}` }),
|
|
292
|
+
},
|
|
293
|
+
body: JSON.stringify({ projectPath, localResults: { passed, errors, observations } }),
|
|
294
|
+
});
|
|
145
295
|
}
|
|
146
|
-
|
|
296
|
+
catch {
|
|
297
|
+
// Non-fatal: the local result is still returned to the caller.
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
passed,
|
|
301
|
+
errors,
|
|
302
|
+
observations,
|
|
303
|
+
requiresFix: !passed,
|
|
304
|
+
};
|
|
147
305
|
}
|
|
148
306
|
/**
|
|
149
307
|
* List available backends from the server (hosted providers + local agents).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { AgentAction } from "./agent-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Action Executor - Executes validated model actions on the LOCAL machine.
|
|
4
|
+
*
|
|
5
|
+
* Used by the CLI to fulfil `tool_call` requests from the hosted-model
|
|
6
|
+
* orchestrator. Every path is validated to stay inside the project directory
|
|
7
|
+
* (workspace boundary enforcement happens client-side, never on the server).
|
|
8
|
+
*/
|
|
9
|
+
export interface ActionExecutionResult {
|
|
10
|
+
success: boolean;
|
|
11
|
+
actionId: string;
|
|
12
|
+
output?: any;
|
|
13
|
+
error?: string;
|
|
14
|
+
observation?: {
|
|
15
|
+
type: string;
|
|
16
|
+
summary: string;
|
|
17
|
+
data?: any;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export declare class ActionExecutor {
|
|
21
|
+
private projectPath;
|
|
22
|
+
constructor(projectPath: string);
|
|
23
|
+
/**
|
|
24
|
+
* Execute a single action
|
|
25
|
+
*/
|
|
26
|
+
execute(action: AgentAction): Promise<ActionExecutionResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Execute multiple actions (stop on first failure)
|
|
29
|
+
*/
|
|
30
|
+
executeBatch(actions: AgentAction[]): Promise<ActionExecutionResult[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Read file action
|
|
33
|
+
*/
|
|
34
|
+
private executeReadFile;
|
|
35
|
+
/**
|
|
36
|
+
* Edit file action
|
|
37
|
+
*/
|
|
38
|
+
private executeEditFile;
|
|
39
|
+
/**
|
|
40
|
+
* Create file action
|
|
41
|
+
*/
|
|
42
|
+
private executeCreateFile;
|
|
43
|
+
/**
|
|
44
|
+
* Delete file action
|
|
45
|
+
*/
|
|
46
|
+
private executeDeleteFile;
|
|
47
|
+
/**
|
|
48
|
+
* Run command action
|
|
49
|
+
*/
|
|
50
|
+
private executeRunCommand;
|
|
51
|
+
/**
|
|
52
|
+
* Search code action
|
|
53
|
+
*/
|
|
54
|
+
private executeSearchCode;
|
|
55
|
+
/**
|
|
56
|
+
* Inspect project action
|
|
57
|
+
*/
|
|
58
|
+
private executeInspectProject;
|
|
59
|
+
/**
|
|
60
|
+
* Browser check action (placeholder for now)
|
|
61
|
+
*/
|
|
62
|
+
private executeBrowserCheck;
|
|
63
|
+
/**
|
|
64
|
+
* Resolve a path relative to the project and enforce the workspace
|
|
65
|
+
* boundary: every resolved path must stay inside projectPath.
|
|
66
|
+
*/
|
|
67
|
+
private resolvePath;
|
|
68
|
+
/**
|
|
69
|
+
* List all project files recursively
|
|
70
|
+
*/
|
|
71
|
+
private listProjectFiles;
|
|
72
|
+
/**
|
|
73
|
+
* Read package.json if it exists
|
|
74
|
+
*/
|
|
75
|
+
private readPackageJson;
|
|
76
|
+
}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ActionExecutor = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_child_process_1 = require("node:child_process");
|
|
10
|
+
const node_util_1 = require("node:util");
|
|
11
|
+
const exec = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
12
|
+
class ActionExecutor {
|
|
13
|
+
projectPath;
|
|
14
|
+
constructor(projectPath) {
|
|
15
|
+
this.projectPath = projectPath;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Execute a single action
|
|
19
|
+
*/
|
|
20
|
+
async execute(action) {
|
|
21
|
+
const { id, type } = action;
|
|
22
|
+
try {
|
|
23
|
+
switch (type) {
|
|
24
|
+
case "read_file":
|
|
25
|
+
return await this.executeReadFile(action);
|
|
26
|
+
case "edit_file":
|
|
27
|
+
return await this.executeEditFile(action);
|
|
28
|
+
case "create_file":
|
|
29
|
+
return await this.executeCreateFile(action);
|
|
30
|
+
case "delete_file":
|
|
31
|
+
return await this.executeDeleteFile(action);
|
|
32
|
+
case "run_command":
|
|
33
|
+
return await this.executeRunCommand(action);
|
|
34
|
+
case "search_code":
|
|
35
|
+
return await this.executeSearchCode(action);
|
|
36
|
+
case "inspect_project":
|
|
37
|
+
return await this.executeInspectProject(action);
|
|
38
|
+
case "browser_check":
|
|
39
|
+
return await this.executeBrowserCheck(action);
|
|
40
|
+
default:
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
actionId: id,
|
|
44
|
+
error: `Unknown action type: ${type}`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return {
|
|
50
|
+
success: false,
|
|
51
|
+
actionId: id,
|
|
52
|
+
error: error instanceof Error ? error.message : String(error),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Execute multiple actions (stop on first failure)
|
|
58
|
+
*/
|
|
59
|
+
async executeBatch(actions) {
|
|
60
|
+
const results = [];
|
|
61
|
+
for (const action of actions) {
|
|
62
|
+
const result = await this.execute(action);
|
|
63
|
+
results.push(result);
|
|
64
|
+
// Stop if an action fails
|
|
65
|
+
if (!result.success) {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return results;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Read file action
|
|
73
|
+
*/
|
|
74
|
+
async executeReadFile(action) {
|
|
75
|
+
const { id, data } = action;
|
|
76
|
+
const filePath = data.path;
|
|
77
|
+
const fullPath = this.resolvePath(filePath);
|
|
78
|
+
if (!node_fs_1.default.existsSync(fullPath)) {
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
actionId: id,
|
|
82
|
+
error: `File not found: ${filePath}`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const content = node_fs_1.default.readFileSync(fullPath, "utf-8");
|
|
86
|
+
return {
|
|
87
|
+
success: true,
|
|
88
|
+
actionId: id,
|
|
89
|
+
output: { content, path: filePath },
|
|
90
|
+
observation: {
|
|
91
|
+
type: "file",
|
|
92
|
+
summary: `Read file: ${filePath}`,
|
|
93
|
+
data: { path: filePath, size: content.length },
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Edit file action
|
|
99
|
+
*/
|
|
100
|
+
async executeEditFile(action) {
|
|
101
|
+
const { id, data } = action;
|
|
102
|
+
const filePath = data.path;
|
|
103
|
+
const oldString = data.oldString;
|
|
104
|
+
const newString = data.newString;
|
|
105
|
+
const fullPath = this.resolvePath(filePath);
|
|
106
|
+
if (!node_fs_1.default.existsSync(fullPath)) {
|
|
107
|
+
return {
|
|
108
|
+
success: false,
|
|
109
|
+
actionId: id,
|
|
110
|
+
error: `File not found: ${filePath}`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const content = node_fs_1.default.readFileSync(fullPath, "utf-8");
|
|
114
|
+
if (oldString && !content.includes(oldString)) {
|
|
115
|
+
return {
|
|
116
|
+
success: false,
|
|
117
|
+
actionId: id,
|
|
118
|
+
error: `oldString not found in file: ${filePath}`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const newContent = oldString ? content.replace(oldString, newString) : newString;
|
|
122
|
+
// No local backup: the WorkspaceGuard snapshot taken before the run is
|
|
123
|
+
// the authoritative rollback mechanism (and .lasso-backup files would
|
|
124
|
+
// pollute the review diff).
|
|
125
|
+
node_fs_1.default.writeFileSync(fullPath, newContent);
|
|
126
|
+
return {
|
|
127
|
+
success: true,
|
|
128
|
+
actionId: id,
|
|
129
|
+
output: { path: filePath },
|
|
130
|
+
observation: {
|
|
131
|
+
type: "file",
|
|
132
|
+
summary: `Edited file: ${filePath}`,
|
|
133
|
+
data: { path: filePath },
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Create file action
|
|
139
|
+
*/
|
|
140
|
+
async executeCreateFile(action) {
|
|
141
|
+
const { id, data } = action;
|
|
142
|
+
const filePath = data.path;
|
|
143
|
+
const content = data.content;
|
|
144
|
+
const fullPath = this.resolvePath(filePath);
|
|
145
|
+
if (node_fs_1.default.existsSync(fullPath)) {
|
|
146
|
+
return {
|
|
147
|
+
success: false,
|
|
148
|
+
actionId: id,
|
|
149
|
+
error: `File already exists: ${filePath}`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
// Ensure directory exists
|
|
153
|
+
const dir = node_path_1.default.dirname(fullPath);
|
|
154
|
+
if (!node_fs_1.default.existsSync(dir)) {
|
|
155
|
+
node_fs_1.default.mkdirSync(dir, { recursive: true });
|
|
156
|
+
}
|
|
157
|
+
node_fs_1.default.writeFileSync(fullPath, content || "");
|
|
158
|
+
return {
|
|
159
|
+
success: true,
|
|
160
|
+
actionId: id,
|
|
161
|
+
output: { path: filePath },
|
|
162
|
+
observation: {
|
|
163
|
+
type: "file",
|
|
164
|
+
summary: `Created file: ${filePath}`,
|
|
165
|
+
data: { path: filePath },
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Delete file action
|
|
171
|
+
*/
|
|
172
|
+
async executeDeleteFile(action) {
|
|
173
|
+
const { id, data } = action;
|
|
174
|
+
const filePath = data.path;
|
|
175
|
+
const fullPath = this.resolvePath(filePath);
|
|
176
|
+
if (!node_fs_1.default.existsSync(fullPath)) {
|
|
177
|
+
return {
|
|
178
|
+
success: false,
|
|
179
|
+
actionId: id,
|
|
180
|
+
error: `File not found: ${filePath}`,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
// No local backup: the WorkspaceGuard snapshot is the authoritative
|
|
184
|
+
// rollback mechanism.
|
|
185
|
+
node_fs_1.default.unlinkSync(fullPath);
|
|
186
|
+
return {
|
|
187
|
+
success: true,
|
|
188
|
+
actionId: id,
|
|
189
|
+
output: { path: filePath },
|
|
190
|
+
observation: {
|
|
191
|
+
type: "file",
|
|
192
|
+
summary: `Deleted file: ${filePath}`,
|
|
193
|
+
data: { path: filePath },
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Run command action
|
|
199
|
+
*/
|
|
200
|
+
async executeRunCommand(action) {
|
|
201
|
+
const { id, data } = action;
|
|
202
|
+
const command = data.command;
|
|
203
|
+
const cwd = data.cwd;
|
|
204
|
+
const workingDir = cwd ? this.resolvePath(cwd) : this.projectPath;
|
|
205
|
+
if (!node_fs_1.default.existsSync(workingDir)) {
|
|
206
|
+
return {
|
|
207
|
+
success: false,
|
|
208
|
+
actionId: id,
|
|
209
|
+
error: `Working directory not found: ${workingDir}`,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
// Safety check: prevent dangerous commands
|
|
213
|
+
const dangerousPatterns = [
|
|
214
|
+
/\brm\s+-rf\b/,
|
|
215
|
+
/\bdd\s+if=/,
|
|
216
|
+
/>\s*\/dev\/null/,
|
|
217
|
+
/>\s*\/dev\/sd/,
|
|
218
|
+
];
|
|
219
|
+
for (const pattern of dangerousPatterns) {
|
|
220
|
+
if (pattern.test(command)) {
|
|
221
|
+
return {
|
|
222
|
+
success: false,
|
|
223
|
+
actionId: id,
|
|
224
|
+
error: `Command rejected for safety: ${command}`,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
const { stdout, stderr } = await exec(command, {
|
|
230
|
+
cwd: workingDir,
|
|
231
|
+
timeout: 120000, // 2 minute timeout
|
|
232
|
+
});
|
|
233
|
+
return {
|
|
234
|
+
success: true,
|
|
235
|
+
actionId: id,
|
|
236
|
+
output: { stdout, stderr },
|
|
237
|
+
observation: {
|
|
238
|
+
type: "command",
|
|
239
|
+
summary: `Ran command: ${command}`,
|
|
240
|
+
data: { command, exitCode: 0 },
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
return {
|
|
246
|
+
success: false,
|
|
247
|
+
actionId: id,
|
|
248
|
+
error: error.message,
|
|
249
|
+
output: { stdout: error.stdout, stderr: error.stderr },
|
|
250
|
+
observation: {
|
|
251
|
+
type: "command",
|
|
252
|
+
summary: `Command failed: ${command}`,
|
|
253
|
+
data: { command, exitCode: error.code || 1 },
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Search code action
|
|
260
|
+
*/
|
|
261
|
+
async executeSearchCode(action) {
|
|
262
|
+
const { id, data } = action;
|
|
263
|
+
const pattern = data.pattern;
|
|
264
|
+
const filePattern = data.filePattern;
|
|
265
|
+
const maxResults = data.maxResults || 50;
|
|
266
|
+
// Use grep to search
|
|
267
|
+
const grepPattern = filePattern ? `--include=${filePattern}` : "";
|
|
268
|
+
const command = `grep -r -n "${pattern}" ${grepPattern} . 2>/dev/null | head -n ${maxResults}`;
|
|
269
|
+
try {
|
|
270
|
+
const { stdout } = await exec(command, {
|
|
271
|
+
cwd: this.projectPath,
|
|
272
|
+
timeout: 30000, // 30 second timeout
|
|
273
|
+
});
|
|
274
|
+
const results = stdout
|
|
275
|
+
.trim()
|
|
276
|
+
.split("\n")
|
|
277
|
+
.filter(Boolean)
|
|
278
|
+
.map((line) => {
|
|
279
|
+
const [filePath, lineNum, ...content] = line.split(":");
|
|
280
|
+
return {
|
|
281
|
+
file: filePath,
|
|
282
|
+
line: parseInt(lineNum, 10),
|
|
283
|
+
content: content.join(":").trim(),
|
|
284
|
+
};
|
|
285
|
+
});
|
|
286
|
+
return {
|
|
287
|
+
success: true,
|
|
288
|
+
actionId: id,
|
|
289
|
+
output: { results },
|
|
290
|
+
observation: {
|
|
291
|
+
type: "search",
|
|
292
|
+
summary: `Found ${results.length} matches for: ${pattern}`,
|
|
293
|
+
data: { pattern, count: results.length },
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
// grep returns non-zero if no matches found
|
|
299
|
+
if (error.code === 1 && !error.stdout) {
|
|
300
|
+
return {
|
|
301
|
+
success: true,
|
|
302
|
+
actionId: id,
|
|
303
|
+
output: { results: [] },
|
|
304
|
+
observation: {
|
|
305
|
+
type: "search",
|
|
306
|
+
summary: `No matches found for: ${pattern}`,
|
|
307
|
+
data: { pattern, count: 0 },
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
success: false,
|
|
313
|
+
actionId: id,
|
|
314
|
+
error: error.message,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Inspect project action
|
|
320
|
+
*/
|
|
321
|
+
async executeInspectProject(action) {
|
|
322
|
+
const { id } = action;
|
|
323
|
+
const files = this.listProjectFiles(this.projectPath);
|
|
324
|
+
const packageJson = this.readPackageJson();
|
|
325
|
+
return {
|
|
326
|
+
success: true,
|
|
327
|
+
actionId: id,
|
|
328
|
+
output: { files, packageJson },
|
|
329
|
+
observation: {
|
|
330
|
+
type: "file",
|
|
331
|
+
summary: `Inspected project: ${files.length} files`,
|
|
332
|
+
data: { fileCount: files.length },
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Browser check action (placeholder for now)
|
|
338
|
+
*/
|
|
339
|
+
async executeBrowserCheck(action) {
|
|
340
|
+
const { id, data } = action;
|
|
341
|
+
const url = data.url;
|
|
342
|
+
const check = data.check;
|
|
343
|
+
// TODO: Implement actual browser checking via Puppeteer or similar
|
|
344
|
+
return {
|
|
345
|
+
success: true,
|
|
346
|
+
actionId: id,
|
|
347
|
+
output: { url, check, result: "pending" },
|
|
348
|
+
observation: {
|
|
349
|
+
type: "browser",
|
|
350
|
+
summary: `Browser check pending: ${url}`,
|
|
351
|
+
data: { url, check },
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Resolve a path relative to the project and enforce the workspace
|
|
357
|
+
* boundary: every resolved path must stay inside projectPath.
|
|
358
|
+
*/
|
|
359
|
+
resolvePath(filePath) {
|
|
360
|
+
const resolved = node_path_1.default.isAbsolute(filePath)
|
|
361
|
+
? node_path_1.default.resolve(filePath)
|
|
362
|
+
: node_path_1.default.resolve(this.projectPath, filePath);
|
|
363
|
+
const root = node_path_1.default.resolve(this.projectPath);
|
|
364
|
+
if (resolved !== root && !resolved.startsWith(root + node_path_1.default.sep)) {
|
|
365
|
+
throw new Error(`Path escapes workspace: ${filePath}`);
|
|
366
|
+
}
|
|
367
|
+
return resolved;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* List all project files recursively
|
|
371
|
+
*/
|
|
372
|
+
listProjectFiles(dir, maxDepth = 10) {
|
|
373
|
+
const files = [];
|
|
374
|
+
let depth = 0;
|
|
375
|
+
const traverse = (currentDir) => {
|
|
376
|
+
if (depth > maxDepth)
|
|
377
|
+
return;
|
|
378
|
+
const entries = node_fs_1.default.readdirSync(currentDir, { withFileTypes: true });
|
|
379
|
+
for (const entry of entries) {
|
|
380
|
+
const fullPath = node_path_1.default.join(currentDir, entry.name);
|
|
381
|
+
// Skip common ignored directories
|
|
382
|
+
if (entry.name === "node_modules" ||
|
|
383
|
+
entry.name === ".git" ||
|
|
384
|
+
entry.name === "dist" ||
|
|
385
|
+
entry.name === "build" ||
|
|
386
|
+
entry.name === ".next" ||
|
|
387
|
+
entry.name === ".lasso") {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (entry.isDirectory()) {
|
|
391
|
+
depth++;
|
|
392
|
+
traverse(fullPath);
|
|
393
|
+
depth--;
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
files.push(node_path_1.default.relative(this.projectPath, fullPath));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
traverse(dir);
|
|
401
|
+
return files;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Read package.json if it exists
|
|
405
|
+
*/
|
|
406
|
+
readPackageJson() {
|
|
407
|
+
const packageJsonPath = node_path_1.default.join(this.projectPath, "package.json");
|
|
408
|
+
if (!node_fs_1.default.existsSync(packageJsonPath)) {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
const content = node_fs_1.default.readFileSync(packageJsonPath, "utf-8");
|
|
413
|
+
return JSON.parse(content);
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
exports.ActionExecutor = ActionExecutor;
|
|
@@ -199,7 +199,7 @@ export interface LassoModelResponse {
|
|
|
199
199
|
actions: AgentAction[];
|
|
200
200
|
expectedOutcome?: string;
|
|
201
201
|
}
|
|
202
|
-
export type AgentEventType = "agent_started" | "agent_reasoning" | "agent_completed" | "agent_failed" | "agent_cancelled" | "phase_changed" | "tool_started" | "tool_completed" | "observation" | "plan_created" | "file_changed" | "verification_started" | "verification_completed" | "error" | "model_changed" | "waiting_for_user";
|
|
202
|
+
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";
|
|
203
203
|
export interface AgentEvent {
|
|
204
204
|
type: AgentEventType;
|
|
205
205
|
sessionId: string;
|
|
@@ -237,6 +237,22 @@ export interface ToolCompletedEvent extends AgentEvent {
|
|
|
237
237
|
duration?: number;
|
|
238
238
|
};
|
|
239
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Emitted by the server when the hosted model requests a workspace tool.
|
|
242
|
+
* The local CLI executes the tool and posts the result back to
|
|
243
|
+
* `POST /sessions/:sessionId/tool-results` (see tool-protocol.ts).
|
|
244
|
+
*/
|
|
245
|
+
export interface ToolCallEvent extends AgentEvent {
|
|
246
|
+
type: "tool_call";
|
|
247
|
+
data: {
|
|
248
|
+
callId: string;
|
|
249
|
+
kind: "action" | "verify";
|
|
250
|
+
action?: AgentAction;
|
|
251
|
+
verify?: {
|
|
252
|
+
phase?: string;
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
}
|
|
240
256
|
export interface ObservationEvent extends AgentEvent {
|
|
241
257
|
type: "observation";
|
|
242
258
|
data: {
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export * from "./agent-state-machine";
|
|
|
11
11
|
export * from "./event-mapper";
|
|
12
12
|
export * from "./review";
|
|
13
13
|
export * from "./local-session-runner";
|
|
14
|
+
export * from "./tool-protocol";
|
|
15
|
+
export { ActionExecutor, ActionExecutionResult } from "./action-executor";
|
|
14
16
|
export * from "./adapters/factory";
|
|
15
17
|
export { LocalAgentAdapter, EventSink, newRunState } from "./adapters/local-agent-adapter";
|
|
16
18
|
export type { LocalAgentConfig, CommandSpec, AgentRunState } from "./adapters/local-agent-adapter";
|
package/dist/runtime/index.js
CHANGED
|
@@ -19,7 +19,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
19
19
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
20
20
|
};
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.CursorAdapter = exports.CodexCLIAdapter = exports.OpenCodeAdapter = exports.ClaudeCodeAdapter = exports.newRunState = exports.EventSink = exports.LocalAgentAdapter = void 0;
|
|
22
|
+
exports.CursorAdapter = exports.CodexCLIAdapter = exports.OpenCodeAdapter = exports.ClaudeCodeAdapter = exports.newRunState = exports.EventSink = exports.LocalAgentAdapter = exports.ActionExecutor = void 0;
|
|
23
23
|
__exportStar(require("./agent-runtime"), exports);
|
|
24
24
|
__exportStar(require("./backend-interface"), exports);
|
|
25
25
|
__exportStar(require("./workspace-guard"), exports);
|
|
@@ -28,6 +28,9 @@ __exportStar(require("./agent-state-machine"), exports);
|
|
|
28
28
|
__exportStar(require("./event-mapper"), exports);
|
|
29
29
|
__exportStar(require("./review"), exports);
|
|
30
30
|
__exportStar(require("./local-session-runner"), exports);
|
|
31
|
+
__exportStar(require("./tool-protocol"), exports);
|
|
32
|
+
var action_executor_1 = require("./action-executor");
|
|
33
|
+
Object.defineProperty(exports, "ActionExecutor", { enumerable: true, get: function () { return action_executor_1.ActionExecutor; } });
|
|
31
34
|
__exportStar(require("./adapters/factory"), exports);
|
|
32
35
|
var local_agent_adapter_1 = require("./adapters/local-agent-adapter");
|
|
33
36
|
Object.defineProperty(exports, "LocalAgentAdapter", { enumerable: true, get: function () { return local_agent_adapter_1.LocalAgentAdapter; } });
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-call protocol for hosted-model agent runs.
|
|
3
|
+
*
|
|
4
|
+
* The server orchestrates the agent loop and calls the hosted model, but it
|
|
5
|
+
* never touches the user's filesystem. When the model requests a workspace
|
|
6
|
+
* action (read/edit/run/verify/...), the server emits a `tool_call` event over
|
|
7
|
+
* the runtime event channel (SSE). The local CLI executes the tool against
|
|
8
|
+
* the real project directory and posts the result back to
|
|
9
|
+
* `POST /sessions/:sessionId/tool-results`, where the orchestrator feeds it
|
|
10
|
+
* back into the model loop.
|
|
11
|
+
*/
|
|
12
|
+
import type { AgentAction, AgentError, Observation } from "./agent-runtime";
|
|
13
|
+
/** A tool execution request from the server to the local CLI. */
|
|
14
|
+
export interface ToolCallRequest {
|
|
15
|
+
/** Unique id correlating the request with its result. */
|
|
16
|
+
callId: string;
|
|
17
|
+
sessionId: string;
|
|
18
|
+
/** `action` = workspace tool; `verify` = run the verification engine. */
|
|
19
|
+
kind: "action" | "verify";
|
|
20
|
+
/** Present when kind === "action". */
|
|
21
|
+
action?: AgentAction;
|
|
22
|
+
/** Present when kind === "verify". */
|
|
23
|
+
verify?: {
|
|
24
|
+
phase?: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** The CLI's response to a tool_call, posted back to the server. */
|
|
28
|
+
export interface ToolCallResultPayload {
|
|
29
|
+
callId: string;
|
|
30
|
+
success: boolean;
|
|
31
|
+
output?: unknown;
|
|
32
|
+
error?: string;
|
|
33
|
+
/** Observations to feed back into the model's context. */
|
|
34
|
+
observations?: Observation[];
|
|
35
|
+
/** Errors to record on the session (e.g. verification failures). */
|
|
36
|
+
errors?: AgentError[];
|
|
37
|
+
}
|
|
38
|
+
/** Default per-tool timeouts (ms). run_command is long-running by nature. */
|
|
39
|
+
export declare const TOOL_CALL_TIMEOUT_MS: Record<string, number>;
|
|
40
|
+
export declare function toolCallTimeoutMs(tool: string): number;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TOOL_CALL_TIMEOUT_MS = void 0;
|
|
4
|
+
exports.toolCallTimeoutMs = toolCallTimeoutMs;
|
|
5
|
+
/** Default per-tool timeouts (ms). run_command is long-running by nature. */
|
|
6
|
+
exports.TOOL_CALL_TIMEOUT_MS = {
|
|
7
|
+
run_command: 180_000,
|
|
8
|
+
verify: 300_000,
|
|
9
|
+
default: 120_000,
|
|
10
|
+
};
|
|
11
|
+
function toolCallTimeoutMs(tool) {
|
|
12
|
+
return exports.TOOL_CALL_TIMEOUT_MS[tool] ?? exports.TOOL_CALL_TIMEOUT_MS.default;
|
|
13
|
+
}
|
package/package.json
CHANGED