2026-03-13 17:40:39 -07:00
import path from 'path' ;
import fs from 'fs' ;
2026-03-17 13:23:15 -07:00
import { spawnSync } from 'child_process' ;
2026-03-13 17:40:39 -07:00
import { fileURLToPath } from 'url' ;
2026-03-16 15:33:40 -07:00
import { ProviderFactory } from './providers/ProviderFactory.ts' ;
2026-03-13 17:40:39 -07:00
const __dirname = path . dirname ( fileURLToPath ( import . meta . url ) ) ;
const REPO_ROOT = path . resolve ( __dirname , '../../../..' ) ;
const q = ( str : string ) = > ` ' ${ str . replace ( /'/g , "'\\''" ) } ' ` ;
2026-03-13 18:45:06 -07:00
export async function runOrchestrator ( args : string [ ] , env : NodeJS.ProcessEnv = process . env ) {
const prNumber = args [ 0 ] ;
2026-03-14 00:43:53 -07:00
const action = args [ 1 ] || 'review' ;
2026-03-13 19:03:30 -07:00
2026-03-13 17:40:39 -07:00
if ( ! prNumber ) {
2026-03-13 19:03:30 -07:00
console . error ( 'Usage: npm run offload <PR_NUMBER> [action]' ) ;
2026-03-13 18:45:06 -07:00
return 1 ;
2026-03-13 17:40:39 -07:00
}
2026-03-14 09:23:40 -07:00
// 1. Load Settings
2026-03-16 15:48:11 -07:00
const settingsPath = path . join ( REPO_ROOT , '.gemini/offload/settings.json' ) ;
2026-03-16 15:33:40 -07:00
if ( ! fs . existsSync ( settingsPath ) ) {
console . error ( '❌ Settings not found. Run "npm run offload:setup" first.' ) ;
return 1 ;
}
2026-03-14 00:43:53 -07:00
const settings = JSON . parse ( fs . readFileSync ( settingsPath , 'utf8' ) ) ;
2026-03-16 15:48:11 -07:00
const config = settings . deepReview ;
2026-03-13 17:40:39 -07:00
if ( ! config ) {
2026-03-16 15:33:40 -07:00
console . error ( '❌ Deep Review configuration not found.' ) ;
2026-03-14 00:43:53 -07:00
return 1 ;
}
2026-03-17 15:34:13 -07:00
const { projectId , zone } = config ;
2026-03-14 00:55:18 -07:00
const targetVM = ` gcli-offload- ${ env . USER || 'mattkorwel' } ` ;
2026-03-16 15:33:40 -07:00
const provider = ProviderFactory . getProvider ( { projectId , zone , instanceName : targetVM } ) ;
2026-03-14 00:43:53 -07:00
2026-03-17 15:34:13 -07:00
// 2. Wake Worker & Verify Container
2026-03-16 15:33:40 -07:00
await provider . ensureReady ( ) ;
2026-03-13 17:40:39 -07:00
2026-03-17 21:22:23 -07:00
// Use Absolute Container Paths
2026-03-17 15:34:13 -07:00
const containerHome = '/home/node' ;
const remoteWorkDir = ` ${ containerHome } /dev/main ` ;
const remotePolicyPath = ` ${ containerHome } /.gemini/policies/offload-policy.toml ` ;
const persistentScripts = ` ${ containerHome } /.offload/scripts ` ;
2026-03-14 00:43:53 -07:00
const sessionName = ` offload- ${ prNumber } - ${ action } ` ;
2026-03-17 15:34:13 -07:00
const remoteWorktreeDir = ` ${ containerHome } /dev/worktrees/ ${ sessionName } ` ;
2026-03-14 00:43:53 -07:00
2026-03-17 21:22:23 -07:00
// 3. Remote Context Setup
console . log ( ` 🚀 Preparing remote environment for ${ action } on # ${ prNumber } ... ` ) ;
2026-03-15 18:37:56 -07:00
2026-03-17 21:22:23 -07:00
// Check if worktree exists
const check = await provider . getExecOutput ( ` ls -d ${ remoteWorktreeDir } /.git ` , { wrapContainer : 'maintainer-worker' } ) ;
if ( check . status !== 0 ) {
console . log ( ' - Provisioning isolated git worktree...' ) ;
2026-03-17 21:48:23 -07:00
// Only re-own the worktrees directory, NOT the entire home dir or scripts
await provider . exec ( ` sudo docker exec -u root maintainer-worker mkdir -p ${ containerHome } /dev/worktrees && sudo docker exec -u root maintainer-worker chown -R node:node ${ containerHome } /dev/worktrees ` ) ;
2026-03-17 21:22:23 -07:00
const setupCmd = `
git config --global --add safe.directory ${ remoteWorkDir } && \
mkdir -p ${ containerHome } /dev/worktrees && \
cd ${ remoteWorkDir } && \
git fetch upstream pull/ ${ prNumber } /head && \
git worktree add -f ${ remoteWorktreeDir } FETCH_HEAD
` ;
await provider . exec ( setupCmd , { wrapContainer : 'maintainer-worker' } ) ;
2026-03-15 18:37:56 -07:00
} else {
2026-03-17 21:22:23 -07:00
console . log ( ' ✅ Remote worktree ready.' ) ;
2026-03-15 18:37:56 -07:00
}
2026-03-17 21:22:23 -07:00
// 4. Execution Logic
const remoteWorker = ` tsx ${ persistentScripts } /entrypoint.ts ${ prNumber } . ${ remotePolicyPath } ${ action } ` ;
2026-03-17 15:34:13 -07:00
const remoteTmuxCmd = ` tmux attach-session -t ${ sessionName } 2>/dev/null || tmux new-session -s ${ sessionName } -n 'offload' 'cd ${ remoteWorktreeDir } && ${ remoteWorker } ; exec $ SHELL' ` ;
2026-03-17 15:25:39 -07:00
const containerWrap = ` sudo docker exec -it maintainer-worker sh -c ${ q ( remoteTmuxCmd ) } ` ;
2026-03-15 16:20:31 -07:00
2026-03-17 15:25:39 -07:00
const finalSSH = provider . getRunCommand ( containerWrap , { interactive : true } ) ;
2026-03-17 13:23:15 -07:00
const isWithinGemini = ! ! env . GEMINI_CLI || ! ! env . GEMINI_SESSION_ID || ! ! env . GCLI_SESSION_ID ;
2026-03-17 21:22:23 -07:00
const forceMainTerminal = true ; // For debugging
2026-03-17 13:23:15 -07:00
2026-03-17 15:34:13 -07:00
if ( ! forceMainTerminal && isWithinGemini && env . TERM_PROGRAM === 'iTerm.app' ) {
2026-03-17 13:23:15 -07:00
const tempCmdPath = path . join ( process . env . TMPDIR || '/tmp' , ` offload-ssh- ${ prNumber } .sh ` ) ;
fs . writeFileSync ( tempCmdPath , ` #!/bin/bash \ n ${ finalSSH } \ nrm " $ 0" ` , { mode : 0o755 } ) ;
2026-03-17 21:22:23 -07:00
const appleScript = ` on run argv \ ntell application "iTerm" \ ntell current window \ nset newTab to (create tab with default profile) \ ntell current session of newTab \ nwrite text (item 1 of argv) & return \ nend tell \ nend tell \ nactivate \ nend tell \ nend run ` ;
2026-03-17 13:23:15 -07:00
spawnSync ( 'osascript' , [ '-' , tempCmdPath ] , { input : appleScript } ) ;
2026-03-17 21:22:23 -07:00
console . log ( ` ✅ iTerm2 tab opened. ` ) ;
2026-03-17 13:23:15 -07:00
return 0 ;
}
console . log ( ` 📡 Connecting to session ${ sessionName } ... ` ) ;
spawnSync ( finalSSH , { stdio : 'inherit' , shell : true } ) ;
2026-03-13 17:40:39 -07:00
2026-03-14 00:43:53 -07:00
return 0 ;
2026-03-13 17:40:39 -07:00
}
2026-03-13 18:45:06 -07:00
if ( import . meta . url === ` file:// ${ process . argv [ 1 ] } ` ) {
runOrchestrator ( process . argv . slice ( 2 ) ) . catch ( console . error ) ;
}