From fe285a93c69f452e67a7ed2e961c7e3d41775994 Mon Sep 17 00:00:00 2001 From: mkorwel Date: Tue, 17 Mar 2026 13:23:15 -0700 Subject: [PATCH] feat(offload): restore and enhance iTerm2 auto-launch UI --- .../skills/offload/scripts/orchestrator.ts | 50 ++++++++++++++++--- .../offload/scripts/providers/BaseProvider.ts | 5 ++ .../scripts/providers/GceConnectionManager.ts | 8 ++- .../scripts/providers/GceCosProvider.ts | 8 +++ .gemini/skills/offload/scripts/setup.ts | 3 +- 5 files changed, 63 insertions(+), 11 deletions(-) diff --git a/.gemini/skills/offload/scripts/orchestrator.ts b/.gemini/skills/offload/scripts/orchestrator.ts index 41e85c106b..44a376d6dc 100644 --- a/.gemini/skills/offload/scripts/orchestrator.ts +++ b/.gemini/skills/offload/scripts/orchestrator.ts @@ -1,10 +1,6 @@ -/** - * Universal Offload Orchestrator (Local) - * - * Automatically connects to your dedicated worker and launches a persistent tmux task. - */ import path from 'path'; import fs from 'fs'; +import { spawnSync } from 'child_process'; import { fileURLToPath } from 'url'; import { ProviderFactory } from './providers/ProviderFactory.ts'; @@ -78,9 +74,47 @@ export async function runOrchestrator(args: string[], env: NodeJS.ProcessEnv = p const tmuxCmd = `docker exec -it -w /home/node/dev/worktrees/${sessionName} maintainer-worker sh -c ${q(`${remoteWorker}; exec $SHELL`)}`; const tmuxAttach = `tmux attach-session -t ${sessionName} 2>/dev/null || tmux new-session -s ${sessionName} -n 'offload' ${q(tmuxCmd)}`; - // High-performance primary SSH with IAP fallback via Provider.exec - // Note: We use provider.exec for consistency and robustness - await provider.exec(tmuxAttach, { interactive: true }); + const finalSSH = provider.getRunCommand(tmuxAttach, { interactive: true }); + + const isWithinGemini = !!env.GEMINI_CLI || !!env.GEMINI_SESSION_ID || !!env.GCLI_SESSION_ID; + const terminalTarget = config.terminalTarget || 'tab'; + + if (isWithinGemini && env.TERM_PROGRAM === 'iTerm.app') { + const tempCmdPath = path.join(process.env.TMPDIR || '/tmp', `offload-ssh-${prNumber}.sh`); + fs.writeFileSync(tempCmdPath, `#!/bin/bash\n${finalSSH}\nrm "$0"`, { mode: 0o755 }); + + const appleScript = terminalTarget === 'window' ? ` + on run argv + tell application "iTerm" + set newWindow to (create window with default profile) + tell current session of newWindow + write text (item 1 of argv) & return + end tell + activate + end tell + end run + ` : ` + on run argv + tell application "iTerm" + tell current window + set newTab to (create tab with default profile) + tell current session of newTab + write text (item 1 of argv) & return + end tell + end tell + activate + end tell + end run + `; + + spawnSync('osascript', ['-', tempCmdPath], { input: appleScript }); + console.log(`āœ… iTerm2 ${terminalTarget} opened for job #${prNumber}.`); + return 0; + } + + // Fallback: Run in current terminal + console.log(`šŸ“” Connecting to session ${sessionName}...`); + spawnSync(finalSSH, { stdio: 'inherit', shell: true }); return 0; } diff --git a/.gemini/skills/offload/scripts/providers/BaseProvider.ts b/.gemini/skills/offload/scripts/providers/BaseProvider.ts index 1dbe3ec969..98cfc21c4d 100644 --- a/.gemini/skills/offload/scripts/providers/BaseProvider.ts +++ b/.gemini/skills/offload/scripts/providers/BaseProvider.ts @@ -24,6 +24,11 @@ export interface WorkerProvider { */ setup(options: SetupOptions): Promise; + /** + * Returns the raw command string that would be used to execute a command. + */ + getRunCommand(command: string, options?: ExecOptions): string; + /** * Executes a command on the worker. */ diff --git a/.gemini/skills/offload/scripts/providers/GceConnectionManager.ts b/.gemini/skills/offload/scripts/providers/GceConnectionManager.ts index 6c9b395047..0da8731f5b 100644 --- a/.gemini/skills/offload/scripts/providers/GceConnectionManager.ts +++ b/.gemini/skills/offload/scripts/providers/GceConnectionManager.ts @@ -38,9 +38,13 @@ export class GceConnectionManager { ]; } - run(command: string, options: { interactive?: boolean; stdio?: 'pipe' | 'inherit' } = {}): { status: number; stdout: string; stderr: string } { + getRunCommand(command: string, options: { interactive?: boolean } = {}): string { const fullRemote = this.getMagicRemote(); - const sshCmd = `ssh ${this.getCommonArgs().join(' ')} ${options.interactive ? '-t' : ''} ${fullRemote} ${this.quote(command)}`; + return `ssh ${this.getCommonArgs().join(' ')} ${options.interactive ? '-t' : ''} ${fullRemote} ${this.quote(command)}`; + } + + run(command: string, options: { interactive?: boolean; stdio?: 'pipe' | 'inherit' } = {}): { status: number; stdout: string; stderr: string } { + const sshCmd = this.getRunCommand(command, options); // 1. Try Direct Path const directRes = spawnSync(sshCmd, { stdio: options.stdio || 'pipe', shell: true }); diff --git a/.gemini/skills/offload/scripts/providers/GceCosProvider.ts b/.gemini/skills/offload/scripts/providers/GceCosProvider.ts index 5646d05610..b75ff0e1f5 100644 --- a/.gemini/skills/offload/scripts/providers/GceCosProvider.ts +++ b/.gemini/skills/offload/scripts/providers/GceCosProvider.ts @@ -137,6 +137,14 @@ Host ${this.sshAlias} return 0; } + getRunCommand(command: string, options: ExecOptions = {}): string { + let finalCmd = command; + if (options.wrapContainer) { + finalCmd = `docker exec ${options.interactive ? '-it' : ''} ${options.cwd ? `-w ${options.cwd}` : ''} ${options.wrapContainer} sh -c ${this.quote(command)}`; + } + return this.conn.getRunCommand(finalCmd, { interactive: options.interactive }); + } + async exec(command: string, options: ExecOptions = {}): Promise { const res = await this.getExecOutput(command, options); return res.status; diff --git a/.gemini/skills/offload/scripts/setup.ts b/.gemini/skills/offload/scripts/setup.ts index 6944f18929..6a1bee861d 100644 --- a/.gemini/skills/offload/scripts/setup.ts +++ b/.gemini/skills/offload/scripts/setup.ts @@ -64,6 +64,7 @@ This script will: } const zone = await prompt('Compute Zone', 'us-west1-a'); + const terminalTarget = await prompt('Terminal UI Target (tab or window)', 'tab'); const targetVM = `gcli-offload-${env.USER || 'mattkorwel'}`; console.log('\nšŸ” Phase 1: Identity & Discovery'); @@ -71,7 +72,7 @@ This script will: const initialSettingsPath = path.join(REPO_ROOT, '.gemini/offload/settings.json'); if (!fs.existsSync(path.dirname(initialSettingsPath))) fs.mkdirSync(path.dirname(initialSettingsPath), { recursive: true }); const initialSettings = fs.existsSync(initialSettingsPath) ? JSON.parse(fs.readFileSync(initialSettingsPath, 'utf8')) : {}; - initialSettings.deepReview = { ...initialSettings.deepReview, projectId, zone }; + initialSettings.deepReview = { ...initialSettings.deepReview, projectId, zone, terminalTarget }; fs.writeFileSync(initialSettingsPath, JSON.stringify(initialSettings, null, 2)); const provider = ProviderFactory.getProvider({ projectId, zone, instanceName: targetVM });