From a8b48c0572ccaed1cd6f2d8235c03602ba74b7ad Mon Sep 17 00:00:00 2001 From: mkorwel Date: Thu, 19 Mar 2026 00:06:08 -0700 Subject: [PATCH] feat(workspaces): implement workspace:shell command for ad-hoc interactive sessions --- .../skills/workspaces/scripts/orchestrator.ts | 45 ++++++++++++------- package.json | 1 + 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/.gemini/skills/workspaces/scripts/orchestrator.ts b/.gemini/skills/workspaces/scripts/orchestrator.ts index 5a3fcac6b5..efa8d37dff 100644 --- a/.gemini/skills/workspaces/scripts/orchestrator.ts +++ b/.gemini/skills/workspaces/scripts/orchestrator.ts @@ -18,11 +18,18 @@ function q(str: string) { } export async function runOrchestrator(args: string[], env: NodeJS.ProcessEnv = process.env) { - const prNumber = args[0]; - const action = args[1] || 'review'; + let prNumber = args[0]; + let action = args[1] || 'review'; + + // Handle "shell" mode: npm run workspace:shell [identifier] + const isShellMode = prNumber === 'shell'; + if (isShellMode) { + prNumber = args[1] || `adhoc-${Math.floor(Math.random() * 10000)}`; + action = 'shell'; + } if (!prNumber) { - console.error('❌ Usage: npm run workspace [action]'); + console.error('❌ Usage: npm run workspace [action] OR npm run workspace:shell [identifier]'); return 1; } @@ -54,26 +61,32 @@ export async function runOrchestrator(args: string[], env: NodeJS.ProcessEnv = p const hostWorktreeDir = `${hostWorkspaceRoot}/worktrees/${sessionName}`; // 3. Remote Context Setup (Executed on HOST for permission simplicity) - console.log(`🚀 Preparing remote environment for ${action} on #${prNumber}...`); + console.log(`🚀 Preparing remote environment for ${action} on ${isShellMode ? 'branch/id' : '#'}${prNumber}...`); // FIX: Use the host path to check for existence const check = await provider.getExecOutput(`ls -d ${hostWorktreeDir}/.git`); // FIX: Ensure container user (node) owns the workspaces directories - // This resolves EACCES errors across all shared volumes. console.log(' - Synchronizing container permissions...'); await provider.exec(`sudo chown -R 1000:1000 /mnt/disks/data`); if (check.status !== 0) { - console.log(' - Provisioning isolated git worktree...'); + console.log(` - Provisioning isolated git worktree for ${prNumber}...`); // We run these on the host because the host user owns the data directory + // If it's a PR, we fetch specifically. If it's a shell, we just branch from main. + const gitFetch = isShellMode + ? `sudo -u chronos git -C ${hostWorkDir} fetch --quiet origin` + : `sudo -u chronos git -C ${hostWorkDir} fetch --quiet upstream pull/${prNumber}/head`; + + const gitTarget = isShellMode ? 'FETCH_HEAD' : 'FETCH_HEAD'; // For now just use main/fetched state + const setupCmd = ` sudo -u chronos git -C ${hostWorkDir} config --add safe.directory ${hostWorkDir} && \ sudo mkdir -p ${hostWorkspaceRoot}/worktrees && \ sudo chown chronos:chronos ${hostWorkspaceRoot}/worktrees && \ - sudo -u chronos git -C ${hostWorkDir} fetch --quiet upstream pull/${prNumber}/head && \ - sudo -u chronos git -C ${hostWorkDir} worktree add --quiet -f ${hostWorktreeDir} FETCH_HEAD 2>&1 + ${gitFetch} && \ + sudo -u chronos git -C ${hostWorkDir} worktree add --quiet -f ${hostWorktreeDir} ${gitTarget} 2>&1 `; const setupRes = await provider.getExecOutput(setupCmd); if (setupRes.status !== 0) { @@ -88,7 +101,6 @@ export async function runOrchestrator(args: string[], env: NodeJS.ProcessEnv = p } // REPAIR: Git worktrees use absolute paths. If the host and container paths differ, they break. - // We repair the worktree context inside the container. console.log(' - Repairing remote worktree context...'); await provider.exec(`sudo docker exec maintainer-worker git -C ${remoteWorktreeDir} worktree repair ${containerWorkspaceRoot}/main`); @@ -100,22 +112,23 @@ export async function runOrchestrator(args: string[], env: NodeJS.ProcessEnv = p const ghTokenRes = await provider.getExecOutput(`cat ${hostWorkspaceRoot}/.gh_token`); const remoteGhToken = ghTokenRes.stdout.trim(); - // AUTH: Inject credentials into a local .env in the worktree for all tools to find + // AUTH: Inject credentials into a local .env in the worktree console.log(' - Injecting remote authentication context...'); - const dotEnvContent = ` -GEMINI_API_KEY=${remoteApiKey} -`.trim(); + const dotEnvContent = `GEMINI_API_KEY=${remoteApiKey}`; await provider.exec(`sudo docker exec maintainer-worker sh -c ${q(`echo ${q(dotEnvContent)} > ${remoteWorktreeDir}/.env`)}`); // 4. Execution Logic - const remoteWorker = `tsx ${persistentScripts}/entrypoint.ts ${prNumber} . ${remotePolicyPath} ${action}`; + // In shell mode, we just start gemini. In action mode, we run the entrypoint. + const remoteWorker = isShellMode + ? `gemini` + : `tsx ${persistentScripts}/entrypoint.ts ${prNumber} . ${remotePolicyPath} ${action}`; // PERSISTENCE: Wrap the entire execution in a tmux session inside the container const tmuxStyle = ` tmux set -g status-bg colour238; tmux set -g status-fg colour136; tmux set -g status-left-length 50; - tmux set -g status-left '#[fg=colour238,bg=colour136,bold] WORKSPACE #[fg=colour136,bg=colour238,nobold] PR #${prNumber} (${action}) '; + tmux set -g status-left '#[fg=colour238,bg=colour136,bold] WORKSPACE #[fg=colour136,bg=colour238,nobold] ${isShellMode ? 'SHELL' : 'PR'} ${prNumber} '; tmux set -g status-right '#[fg=colour245] %H:%M #[fg=colour238,bg=colour245,bold] #H '; tmux setw -g window-status-current-format '#[fg=colour238,bg=colour136,bold] #I:#W #[fg=colour136,bg=colour238,nobold]'; `.replace(/\n/g, ''); @@ -157,7 +170,7 @@ GEMINI_API_KEY=${remoteApiKey} end run `; spawnSync('osascript', ['-', tempCmdPath], { input: appleScript }); - console.log(`✅ iTerm2 ${terminalTarget} opened for job #${prNumber}.`); + console.log(`✅ iTerm2 ${terminalTarget} opened for job ${prNumber}.`); return 0; } diff --git a/package.json b/package.json index 5d51a2b1bd..8d842b8e8f 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "clean": "node scripts/clean.js", "workspace": "tsx .gemini/skills/workspaces/scripts/orchestrator.ts", "workspace:setup": "tsx .gemini/skills/workspaces/scripts/setup.ts", + "workspace:shell": "tsx .gemini/skills/workspaces/scripts/orchestrator.ts shell", "workspace:check": "tsx .gemini/skills/workspaces/scripts/check.ts", "workspace:clean-all": "tsx .gemini/skills/workspaces/scripts/clean.ts", "workspace:kill": "tsx .gemini/skills/workspaces/scripts/clean.ts",