mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-14 13:53:02 -07:00
feat(offload): implement WorkerProvider abstraction and robust IAP fallback
This commit is contained in:
@@ -3,10 +3,10 @@
|
||||
*
|
||||
* Automatically connects to your dedicated worker and launches a persistent tmux task.
|
||||
*/
|
||||
import { spawnSync } from 'child_process';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { ProviderFactory } from './providers/ProviderFactory.ts';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const REPO_ROOT = path.resolve(__dirname, '../../../..');
|
||||
@@ -24,38 +24,35 @@ export async function runOrchestrator(args: string[], env: NodeJS.ProcessEnv = p
|
||||
|
||||
// 1. Load Settings
|
||||
const settingsPath = path.join(REPO_ROOT, '.gemini/settings.json');
|
||||
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
||||
const config = settings.maintainer?.deepReview;
|
||||
if (!config) {
|
||||
if (!fs.existsSync(settingsPath)) {
|
||||
console.error('❌ Settings not found. Run "npm run offload:setup" first.');
|
||||
return 1;
|
||||
}
|
||||
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
||||
const config = settings.maintainer?.deepReview;
|
||||
if (!config) {
|
||||
console.error('❌ Deep Review configuration not found.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { projectId, zone, remoteHost, remoteWorkDir } = config;
|
||||
const { projectId, zone, remoteWorkDir } = config;
|
||||
const targetVM = `gcli-offload-${env.USER || 'mattkorwel'}`;
|
||||
|
||||
const provider = ProviderFactory.getProvider({ projectId, zone, instanceName: targetVM });
|
||||
|
||||
// 2. Wake Worker
|
||||
const statusCheck = spawnSync(`gcloud compute instances describe ${targetVM} --project ${projectId} --zone ${zone} --format="get(status)"`, { shell: true });
|
||||
const status = statusCheck.stdout.toString().trim();
|
||||
|
||||
if (status !== 'RUNNING' && status !== 'PROVISIONING' && status !== 'STAGING') {
|
||||
console.log(`⚠️ Worker ${targetVM} is ${status}. Waking it up...`);
|
||||
spawnSync(`gcloud compute instances start ${targetVM} --project ${projectId} --zone ${zone}`, { shell: true, stdio: 'inherit' });
|
||||
}
|
||||
await provider.ensureReady();
|
||||
|
||||
const remotePolicyPath = `~/.gemini/policies/offload-policy.toml`;
|
||||
const persistentScripts = `~/.offload/scripts`;
|
||||
const sessionName = `offload-${prNumber}-${action}`;
|
||||
const remoteWorktreeDir = `~/dev/worktrees/${sessionName}`;
|
||||
const sshConfigPath = path.join(REPO_ROOT, '.gemini/offload_ssh_config');
|
||||
const sshBase = `ssh -F ${sshConfigPath}`;
|
||||
|
||||
// 3. Remote Context Setup (Parallel Worktree)
|
||||
console.log(`🚀 Provisioning persistent worktree for ${action} on #${prNumber}...`);
|
||||
|
||||
let setupCmd = '';
|
||||
if (action === 'implement') {
|
||||
// FIX: Always use explicit base (upstream/main) to prevent Branch Bleeding
|
||||
const branchName = `impl-${prNumber}`;
|
||||
setupCmd = `
|
||||
mkdir -p ~/dev/worktrees && \
|
||||
@@ -64,7 +61,6 @@ export async function runOrchestrator(args: string[], env: NodeJS.ProcessEnv = p
|
||||
git worktree add -f -b ${branchName} ${remoteWorktreeDir} upstream/main
|
||||
`;
|
||||
} else {
|
||||
// For PR-based actions, we fetch the PR head
|
||||
setupCmd = `
|
||||
mkdir -p ~/dev/worktrees && \
|
||||
cd ${remoteWorkDir} && \
|
||||
@@ -73,52 +69,19 @@ export async function runOrchestrator(args: string[], env: NodeJS.ProcessEnv = p
|
||||
`;
|
||||
}
|
||||
|
||||
// Wrap in docker exec if needed
|
||||
if (useContainer) {
|
||||
setupCmd = `docker exec maintainer-worker sh -c ${q(setupCmd)}`;
|
||||
}
|
||||
|
||||
spawnSync(`${sshBase} ${remoteHost} ${q(setupCmd)}`, { shell: true, stdio: 'inherit' });
|
||||
await provider.exec(setupCmd, { wrapContainer: 'maintainer-worker' });
|
||||
|
||||
// 4. Execution Logic (Persistent Workstation Mode)
|
||||
// We use docker exec if container mode is enabled, otherwise run on host.
|
||||
const remoteWorker = `tsx ${persistentScripts}/entrypoint.ts ${prNumber} remote-branch ${remotePolicyPath} ${action}`;
|
||||
|
||||
let tmuxCmd = `cd ${remoteWorktreeDir} && ${remoteWorker}; exec $SHELL`;
|
||||
if (useContainer) {
|
||||
// If in container mode, we jump into the shared 'maintainer-worker' container
|
||||
// We must use -i and -t for the interactive tmux session
|
||||
tmuxCmd = `docker exec -it -w /home/node/dev/worktrees/offload-${prNumber}-${action} maintainer-worker sh -c "${remoteWorker}; exec $SHELL"`;
|
||||
}
|
||||
// We launch a tmux session inside the container
|
||||
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)}`;
|
||||
|
||||
const sshInternal = `tmux attach-session -t ${sessionName} 2>/dev/null || tmux new-session -s ${sessionName} -n 'offload' ${q(tmuxCmd)}`;
|
||||
|
||||
// High-performance primary SSH with IAP fallback
|
||||
const finalSSH = `${sshBase} -o ConnectTimeout=5 -t ${remoteHost} ${q(sshInternal)} || gcloud compute ssh ${targetVM} --project ${projectId} --zone ${zone} --tunnel-through-iap --command ${q(sshInternal)}`;
|
||||
// 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 });
|
||||
|
||||
// 5. Open in iTerm2
|
||||
const isWithinGemini = !!env.GEMINI_CLI || !!env.GEMINI_SESSION_ID || !!env.GCLI_SESSION_ID;
|
||||
if (isWithinGemini) {
|
||||
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 = `
|
||||
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
|
||||
`;
|
||||
spawnSync('osascript', ['-', tempCmdPath], { input: appleScript });
|
||||
console.log(`✅ iTerm2 window opened on ${remoteHost} (Persistent Session).`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
spawnSync(finalSSH, { stdio: 'inherit', shell: true });
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user