@@ -31,6 +31,7 @@ import { secretsToRecord } from "@/types/secrets";
3131import { DisposableTempDir } from "@/services/tempDir" ;
3232import { BashExecutionService } from "@/services/bashExecutionService" ;
3333import { InitStateManager } from "@/services/initStateManager" ;
34+ import { createRuntime } from "@/runtime/runtimeFactory" ;
3435
3536/**
3637 * IpcMain - Manages all IPC handlers and service coordination
@@ -272,13 +273,41 @@ export class IpcMain {
272273 // Generate stable workspace ID (stored in config, not used for directory name)
273274 const workspaceId = this . config . generateStableId ( ) ;
274275
275- // Create the git worktree with the workspace name as directory name
276- const result = await createWorktree ( this . config , projectPath , branchName , {
276+ // Create runtime for workspace creation (defaults to local)
277+ const workspacePath = this . config . getWorkspacePath ( projectPath , branchName ) ;
278+ const runtimeConfig = { type : "local" as const , workdir : workspacePath } ;
279+ const runtime = createRuntime ( runtimeConfig ) ;
280+
281+ // Start init tracking (creates in-memory state + emits init-start event)
282+ // This MUST complete before workspace creation returns so replayInit() finds state
283+ this . initStateManager . startInit ( workspaceId , projectPath ) ;
284+
285+ // Create InitLogger that bridges to InitStateManager
286+ const initLogger = {
287+ logStep : ( message : string ) => {
288+ this . initStateManager . appendOutput ( workspaceId , message , false ) ;
289+ } ,
290+ logStdout : ( line : string ) => {
291+ this . initStateManager . appendOutput ( workspaceId , line , false ) ;
292+ } ,
293+ logStderr : ( line : string ) => {
294+ this . initStateManager . appendOutput ( workspaceId , line , true ) ;
295+ } ,
296+ logComplete : ( exitCode : number ) => {
297+ void this . initStateManager . endInit ( workspaceId , exitCode ) ;
298+ } ,
299+ } ;
300+
301+ // Create workspace through runtime abstraction
302+ const result = await runtime . createWorkspace ( {
303+ projectPath,
304+ branchName,
277305 trunkBranch : normalizedTrunkBranch ,
278- workspaceId : branchName , // Use name for directory (workspaceId param is misnamed, it's directoryName)
306+ workspaceId : branchName , // Use name for directory
307+ initLogger,
279308 } ) ;
280309
281- if ( result . success && result . path ) {
310+ if ( result . success && result . workspacePath ) {
282311 const projectName =
283312 projectPath . split ( "/" ) . pop ( ) ?? projectPath . split ( "\\" ) . pop ( ) ?? "unknown" ;
284313
@@ -304,7 +333,7 @@ export class IpcMain {
304333 }
305334 // Add workspace to project config with full metadata
306335 projectConfig . workspaces . push ( {
307- path : result . path ! ,
336+ path : result . workspacePath ! ,
308337 id : workspaceId ,
309338 name : branchName ,
310339 createdAt : metadata . createdAt ,
@@ -325,13 +354,8 @@ export class IpcMain {
325354 const session = this . getOrCreateSession ( workspaceId ) ;
326355 session . emitMetadata ( completeMetadata ) ;
327356
328- // Start optional .cmux/init hook (waits for state creation, then returns)
329- // This ensures replayInit() will find state when frontend subscribes
330- await this . startWorkspaceInitHook ( {
331- projectPath,
332- worktreePath : result . path ,
333- workspaceId,
334- } ) ;
357+ // Init hook has already been run by the runtime
358+ // No need to call startWorkspaceInitHook here anymore
335359
336360 // Return complete metadata with paths for frontend
337361 return {
@@ -839,6 +863,7 @@ export class IpcMain {
839863 // All IPC bash calls are from UI (background operations) - use truncate to avoid temp file spam
840864 const bashTool = createBashTool ( {
841865 cwd : namedPath ,
866+ runtime : createRuntime ( { type : "local" , workdir : namedPath } ) ,
842867 secrets : secretsToRecord ( projectSecrets ) ,
843868 niceness : options ?. niceness ,
844869 tempDir : tempDir . path ,
0 commit comments