Skip to content

Commit 1994c5f

Browse files
committed
🤖 feat: copy plan file when forking workspace
When a workspace is forked via /fork, the plan file is now copied along with the chat history, partial state, and usage tracking. This allows the forked workspace to continue working with the same plan context as the original. Changes: - Add copyPlanFile helper in src/node/utils/runtime/helpers.ts - Call copyPlanFile in workspaceService.fork() after session files Change-Id: Iaee04cb44c1b5a98e13eb693fa729a078b8d0013 Signed-off-by: Thomas Kosiewski <[email protected]>
1 parent a30adbc commit 1994c5f

File tree

2 files changed

+38
-2
lines changed

2 files changed

+38
-2
lines changed

src/node/services/workspaceService.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ import { createBashTool } from "@/node/services/tools/bash";
5454
import type { AskUserQuestionToolSuccessResult, BashToolResult } from "@/common/types/tools";
5555
import { secretsToRecord } from "@/common/types/secrets";
5656

57-
import { movePlanFile } from "@/node/utils/runtime/helpers";
57+
import { movePlanFile, copyPlanFile } from "@/node/utils/runtime/helpers";
5858

5959
/** Maximum number of retry attempts when workspace name collides */
6060
const MAX_WORKSPACE_NAME_COLLISION_RETRIES = 3;
@@ -933,6 +933,9 @@ export class WorkspaceService extends EventEmitter {
933933
return Err(`Failed to copy chat history: ${message}`);
934934
}
935935

936+
// Copy plan file if it exists (uses workspace name, not ID)
937+
await copyPlanFile(runtime, sourceMetadata.name, newName, projectName);
938+
936939
// Compute namedWorkspacePath for frontend metadata
937940
const namedWorkspacePath = runtime.getWorkspacePath(foundProjectPath, newName);
938941

src/node/utils/runtime/helpers.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,41 @@ export async function movePlanFile(
190190

191191
try {
192192
await runtime.stat(oldPath);
193-
await execBuffered(runtime, `mv "${oldPath}" "${newPath}"`, { cwd: "/tmp", timeout: 5 });
193+
// Resolve tildes to absolute paths - bash doesn't expand ~ inside quotes
194+
const resolvedOldPath = await runtime.resolvePath(oldPath);
195+
const resolvedNewPath = await runtime.resolvePath(newPath);
196+
await execBuffered(runtime, `mv "${resolvedOldPath}" "${resolvedNewPath}"`, {
197+
cwd: "/tmp",
198+
timeout: 5,
199+
});
194200
} catch {
195201
// No plan file to move, that's fine
196202
}
197203
}
204+
205+
/**
206+
* Copy a plan file from one workspace to another (e.g., during fork).
207+
* Silently succeeds if source file doesn't exist.
208+
*/
209+
export async function copyPlanFile(
210+
runtime: Runtime,
211+
sourceWorkspaceName: string,
212+
targetWorkspaceName: string,
213+
projectName: string
214+
): Promise<void> {
215+
const sourcePath = getPlanFilePath(sourceWorkspaceName, projectName);
216+
const targetPath = getPlanFilePath(targetWorkspaceName, projectName);
217+
218+
try {
219+
await runtime.stat(sourcePath);
220+
// Resolve tildes to absolute paths - bash doesn't expand ~ inside quotes
221+
const resolvedSourcePath = await runtime.resolvePath(sourcePath);
222+
const resolvedTargetPath = await runtime.resolvePath(targetPath);
223+
await execBuffered(runtime, `cp "${resolvedSourcePath}" "${resolvedTargetPath}"`, {
224+
cwd: "/tmp",
225+
timeout: 5,
226+
});
227+
} catch {
228+
// No plan file to copy, that's fine
229+
}
230+
}

0 commit comments

Comments
 (0)