Skip to content

Commit 23c74d2

Browse files
committed
🤖 Add workdir to LocalRuntime for symmetry with SSHRuntime
Both LocalRuntime and SSHRuntime now require a workdir parameter: - LocalRuntime(workdir: string) - SSHRuntime({ host, workdir, ... }) Benefits: - Symmetric interface - both runtimes bound to a workspace directory - Less error-prone - no need to pass cwd to every exec() call - Default cwd fallback - exec() uses workdir if cwd not specified - Better abstraction - Runtime represents execution environment for a workspace Updated: - RuntimeConfig type to require workdir for local - All call sites in src/ to provide workdir - All tests to provide workdir
1 parent 03381f6 commit 23c74d2

File tree

12 files changed

+45
-29
lines changed

12 files changed

+45
-29
lines changed

src/runtime/LocalRuntime.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ import { NON_INTERACTIVE_ENV_VARS } from "../constants/env";
1212
* directly on the host machine using Node.js APIs.
1313
*/
1414
export class LocalRuntime implements Runtime {
15+
private readonly workdir: string;
16+
17+
constructor(workdir: string) {
18+
this.workdir = workdir;
19+
}
20+
1521
exec(command: string, options: ExecOptions): ExecStream {
1622
const startTime = performance.now();
1723

@@ -23,7 +29,7 @@ export class LocalRuntime implements Runtime {
2329
: ["-c", command];
2430

2531
const childProcess = spawn(spawnCommand, spawnArgs, {
26-
cwd: options.cwd,
32+
cwd: options.cwd ?? this.workdir,
2733
env: {
2834
...process.env,
2935
...(options.env ?? {}),

src/runtime/runtimeFactory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { RuntimeConfig } from "@/types/runtime";
99
export function createRuntime(config: RuntimeConfig): Runtime {
1010
switch (config.type) {
1111
case "local":
12-
return new LocalRuntime();
12+
return new LocalRuntime(config.workdir);
1313

1414
case "ssh":
1515
return new SSHRuntime({

src/services/aiService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ export class AIService extends EventEmitter {
420420
const [providerName] = modelString.split(":");
421421

422422
// Get tool names early for mode transition sentinel (stub config, no workspace context needed)
423-
const earlyRuntime = createRuntime({ type: "local" });
423+
const earlyRuntime = createRuntime({ type: "local", workdir: process.cwd() });
424424
const earlyAllTools = await getToolsForModel(modelString, {
425425
cwd: process.cwd(),
426426
runtime: earlyRuntime,
@@ -521,7 +521,7 @@ export class AIService extends EventEmitter {
521521
const tempDir = this.streamManager.createTempDirForStream(streamToken);
522522

523523
// Create runtime from workspace metadata config (defaults to local)
524-
const runtime = createRuntime(metadata.runtimeConfig ?? { type: "local" });
524+
const runtime = createRuntime(metadata.runtimeConfig ?? { type: "local", workdir: workspacePath });
525525

526526
// Get model-specific tools with workspace path configuration and secrets
527527
const allTools = await getToolsForModel(modelString, {

src/services/ipcMain.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,7 @@ export class IpcMain {
841841
// All IPC bash calls are from UI (background operations) - use truncate to avoid temp file spam
842842
const bashTool = createBashTool({
843843
cwd: namedPath,
844-
runtime: createRuntime({ type: "local" }),
844+
runtime: createRuntime({ type: "local", workdir: namedPath }),
845845
secrets: secretsToRecord(projectSecrets),
846846
niceness: options?.niceness,
847847
tempDir: tempDir.path,

src/services/tools/bash.test.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function createTestBashTool(options?: { niceness?: number }) {
2121
const tempDir = new TestTempDir("test-bash");
2222
const tool = createBashTool({
2323
cwd: process.cwd(),
24-
runtime: createRuntime({ type: "local" }),
24+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
2525
tempDir: tempDir.path,
2626
...options,
2727
});
@@ -163,7 +163,7 @@ describe("bash tool", () => {
163163
const tempDir = new TestTempDir("test-bash-truncate");
164164
const tool = createBashTool({
165165
cwd: process.cwd(),
166-
runtime: createRuntime({ type: "local" }),
166+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
167167
tempDir: tempDir.path,
168168
overflow_policy: "truncate",
169169
});
@@ -202,7 +202,7 @@ describe("bash tool", () => {
202202
const tempDir = new TestTempDir("test-bash-overlong-line");
203203
const tool = createBashTool({
204204
cwd: process.cwd(),
205-
runtime: createRuntime({ type: "local" }),
205+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
206206
tempDir: tempDir.path,
207207
overflow_policy: "truncate",
208208
});
@@ -234,7 +234,7 @@ describe("bash tool", () => {
234234
const tempDir = new TestTempDir("test-bash-boundary");
235235
const tool = createBashTool({
236236
cwd: process.cwd(),
237-
runtime: createRuntime({ type: "local" }),
237+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
238238
tempDir: tempDir.path,
239239
overflow_policy: "truncate",
240240
});
@@ -270,7 +270,7 @@ describe("bash tool", () => {
270270
const tempDir = new TestTempDir("test-bash-default");
271271
const tool = createBashTool({
272272
cwd: process.cwd(),
273-
runtime: createRuntime({ type: "local" }),
273+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
274274
tempDir: tempDir.path,
275275
// overflow_policy not specified - should default to tmpfile
276276
});
@@ -302,7 +302,7 @@ describe("bash tool", () => {
302302
const tempDir = new TestTempDir("test-bash-100kb");
303303
const tool = createBashTool({
304304
cwd: process.cwd(),
305-
runtime: createRuntime({ type: "local" }),
305+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
306306
tempDir: tempDir.path,
307307
});
308308

@@ -354,7 +354,7 @@ describe("bash tool", () => {
354354
const tempDir = new TestTempDir("test-bash-100kb-limit");
355355
const tool = createBashTool({
356356
cwd: process.cwd(),
357-
runtime: createRuntime({ type: "local" }),
357+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
358358
tempDir: tempDir.path,
359359
});
360360

@@ -397,7 +397,7 @@ describe("bash tool", () => {
397397
const tempDir = new TestTempDir("test-bash-no-kill-display");
398398
const tool = createBashTool({
399399
cwd: process.cwd(),
400-
runtime: createRuntime({ type: "local" }),
400+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
401401
tempDir: tempDir.path,
402402
});
403403

@@ -439,7 +439,7 @@ describe("bash tool", () => {
439439
const tempDir = new TestTempDir("test-bash-per-line-kill");
440440
const tool = createBashTool({
441441
cwd: process.cwd(),
442-
runtime: createRuntime({ type: "local" }),
442+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
443443
tempDir: tempDir.path,
444444
});
445445

@@ -479,7 +479,7 @@ describe("bash tool", () => {
479479
const tempDir = new TestTempDir("test-bash-under-limit");
480480
const tool = createBashTool({
481481
cwd: process.cwd(),
482-
runtime: createRuntime({ type: "local" }),
482+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
483483
tempDir: tempDir.path,
484484
});
485485

@@ -509,7 +509,7 @@ describe("bash tool", () => {
509509
const tempDir = new TestTempDir("test-bash-exact-limit");
510510
const tool = createBashTool({
511511
cwd: process.cwd(),
512-
runtime: createRuntime({ type: "local" }),
512+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
513513
tempDir: tempDir.path,
514514
});
515515

src/services/tools/file_edit_insert.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function createTestFileEditInsertTool(options?: { cwd?: string }) {
2020
const tempDir = new TestTempDir("test-file-edit-insert");
2121
const tool = createFileEditInsertTool({
2222
cwd: options?.cwd ?? process.cwd(),
23-
runtime: createRuntime({ type: "local" }),
23+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
2424
tempDir: tempDir.path,
2525
});
2626

@@ -213,7 +213,7 @@ describe("file_edit_insert tool", () => {
213213

214214
const tool = createFileEditInsertTool({
215215
cwd: testDir,
216-
runtime: createRuntime({ type: "local" }),
216+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
217217
tempDir: "/tmp",
218218
});
219219
const args: FileEditInsertToolArgs = {
@@ -239,7 +239,7 @@ describe("file_edit_insert tool", () => {
239239

240240
const tool = createFileEditInsertTool({
241241
cwd: testDir,
242-
runtime: createRuntime({ type: "local" }),
242+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
243243
tempDir: "/tmp",
244244
});
245245
const args: FileEditInsertToolArgs = {
@@ -266,7 +266,7 @@ describe("file_edit_insert tool", () => {
266266

267267
const tool = createFileEditInsertTool({
268268
cwd: testDir,
269-
runtime: createRuntime({ type: "local" }),
269+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
270270
tempDir: "/tmp",
271271
});
272272
const args: FileEditInsertToolArgs = {

src/services/tools/file_edit_operation.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { createRuntime } from "@/runtime/runtimeFactory";
66
const TEST_CWD = "/tmp";
77

88
function createConfig() {
9-
return { cwd: TEST_CWD, runtime: createRuntime({ type: "local" }), tempDir: "/tmp" };
9+
return { cwd: TEST_CWD, runtime: createRuntime({ type: "local", workdir: TEST_CWD }), tempDir: "/tmp" };
1010
}
1111

1212
describe("executeFileEditOperation", () => {

src/services/tools/file_edit_replace.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe("file_edit_replace_string tool", () => {
5959
await setupFile(testFilePath, "Hello world\nThis is a test\nGoodbye world");
6060
const tool = createFileEditReplaceStringTool({
6161
cwd: testDir,
62-
runtime: createRuntime({ type: "local" }),
62+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
6363
tempDir: "/tmp",
6464
});
6565

@@ -97,7 +97,7 @@ describe("file_edit_replace_lines tool", () => {
9797
await setupFile(testFilePath, "line1\nline2\nline3\nline4");
9898
const tool = createFileEditReplaceLinesTool({
9999
cwd: testDir,
100-
runtime: createRuntime({ type: "local" }),
100+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
101101
tempDir: "/tmp",
102102
});
103103

src/services/tools/file_read.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function createTestFileReadTool(options?: { cwd?: string }) {
2020
const tempDir = new TestTempDir("test-file-read");
2121
const tool = createFileReadTool({
2222
cwd: options?.cwd ?? process.cwd(),
23-
runtime: createRuntime({ type: "local" }),
23+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
2424
tempDir: tempDir.path,
2525
});
2626

@@ -334,7 +334,7 @@ describe("file_read tool", () => {
334334
// Try to read file outside cwd by going up
335335
const tool = createFileReadTool({
336336
cwd: subDir,
337-
runtime: createRuntime({ type: "local" }),
337+
runtime: createRuntime({ type: "local", workdir: "/tmp" }),
338338
tempDir: "/tmp",
339339
});
340340
const args: FileReadToolArgs = {

src/types/runtime.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
*/
44

55
export type RuntimeConfig =
6-
| { type: "local" }
6+
| {
7+
type: "local";
8+
/** Working directory on local host */
9+
workdir: string;
10+
}
711
| {
812
type: "ssh";
913
/** SSH host (can be hostname, user@host, or SSH config alias) */

0 commit comments

Comments
 (0)