@@ -2629,51 +2629,20 @@ export class TaskService {
26292629
26302630 /**
26312631 * Guests sometimes boot from snapshots where `agent` is missing or off PATH.
2632- * Prefer locating a baked-in binary. Only attempt a short online install if
2633- * cursor.com is reachable — never hang for minutes on SSL timeouts.
2632+ * Prefer locating a baked-in binary. Online install is a short fallback — the
2633+ * installer often succeeds while a naive `test -x /root/...` check still fails
2634+ * (HOME mismatch, progress bars on stderr, --version quirks).
26342635 */
26352636 private async ensureCursorAgentInSandbox (
26362637 runtime : RuntimeClient ,
26372638 taskId : string ,
26382639 ) : Promise < void > {
2639- const findCmd = [
2640- "set +e" ,
2641- 'export PATH="/usr/local/bin:/root/.local/bin:$PATH"' ,
2642- "for candidate in \\" ,
2643- " /usr/local/bin/agent \\" ,
2644- " /root/.local/bin/agent \\" ,
2645- " $(command -v agent 2>/dev/null) \\" ,
2646- " $(command -v cursor-agent 2>/dev/null) \\" ,
2647- " $(ls -1 /root/.local/share/cursor-agent/versions/*/cursor-agent 2>/dev/null | sort | tail -1)" ,
2648- "do" ,
2649- ' [ -n "$candidate" ] || continue' ,
2650- ' [ -x "$candidate" ] || continue' ,
2651- ' if "$candidate" --version >/dev/null 2>&1; then' ,
2652- ' printf "%s\\n" "$candidate"' ,
2653- ' "$candidate" --version 2>&1 | head -1' ,
2654- " exit 0" ,
2655- " fi" ,
2656- "done" ,
2657- "exit 1" ,
2658- ] . join ( "\n" ) ;
2659-
2660- const probe = await runtime . terminalAllowFailure ( {
2661- taskId,
2662- command : findCmd ,
2663- } ) ;
2664- if ( probe . exitCode === 0 ) {
2665- const detail = ( probe . stdout || "" ) . trim ( ) ;
2640+ const located = await this . findCursorAgentBinary ( runtime , taskId ) ;
2641+ if ( located ) {
26662642 this . emit ( "agent.log" , taskId , "cursor agent CLI ready in sandbox" , {
2667- detail : detail . slice ( 0 , 240 ) ,
2643+ detail : located . slice ( 0 , 240 ) ,
26682644 } ) ;
2669- // Ensure /usr/local/bin/agent exists for older runtime supervisors.
2670- const binLine = detail . split ( "\n" ) [ 0 ] ?. trim ( ) ;
2671- if ( binLine && binLine !== "/usr/local/bin/agent" ) {
2672- await runtime . terminalAllowFailure ( {
2673- taskId,
2674- command : `ln -sfn '${ escapeShell ( binLine ) } ' /usr/local/bin/agent` ,
2675- } ) ;
2676- }
2645+ await this . linkCursorAgentBinary ( runtime , taskId , located ) ;
26772646 return ;
26782647 }
26792648
@@ -2695,35 +2664,121 @@ export class TaskService {
26952664 taskId ,
26962665 "cursor agent CLI missing — attempting short online install" ,
26972666 {
2698- probe : ( probe . stderr || probe . stdout || "" ) . trim ( ) . slice ( 0 , 300 ) ,
26992667 cursorCom : installHost . detail ,
27002668 } ,
27012669 ) ;
27022670
2671+ // Official installer. Do not use `set -e` across the pipe — curl progress
2672+ // noise on stderr previously masked a successful install, and HOME may not
2673+ // be /root inside the guest.
27032674 const install = await runtime . terminalAllowFailure ( {
27042675 taskId,
27052676 command : [
2706- "set -e" ,
2707- 'export PATH="/usr/local/bin:/root/.local/bin:$PATH"' ,
2708- "curl https://cursor.com/install -fsS --connect-timeout 10 --max-time 45 | bash" ,
2709- "test -x /root/.local/bin/agent" ,
2710- "ln -sfn /root/.local/bin/agent /usr/local/bin/agent" ,
2711- "command -v agent" ,
2712- "agent --version" ,
2677+ "set +e" ,
2678+ 'export HOME="${HOME:-/root}"' ,
2679+ 'export PATH="/usr/local/bin:/root/.local/bin:$HOME/.local/bin:$PATH"' ,
2680+ "curl https://cursor.com/install -fsS | bash" ,
2681+ "ec=$?" ,
2682+ 'echo "cursor_install_exit=$ec home=$HOME"' ,
2683+ 'ls -la /usr/local/bin/agent /root/.local/bin/agent "$HOME/.local/bin/agent" 2>&1 | head -20' ,
2684+ "ls -la /root/.local/share/cursor-agent/versions 2>&1 | tail -5" ,
2685+ 'ls -la "$HOME/.local/share/cursor-agent/versions" 2>&1 | tail -5' ,
2686+ "exit 0" ,
27132687 ] . join ( "\n" ) ,
27142688 } ) ;
2715- if ( install . exitCode !== 0 ) {
2716- const detail = ( install . stderr || install . stdout || "" ) . trim ( ) ;
2717- throw new Error (
2718- "cursor agent CLI is not available in the sandbox and install failed" +
2719- ( detail ? `: ${ detail . slice ( 0 , 400 ) } ` : "" ) +
2720- ". Rebuild the agent Firecracker snapshot" +
2721- " (./infra/scripts/rebuild-agent-snapshot.sh <instance-id>)." ,
2722- ) ;
2689+
2690+ const afterInstall = await this . findCursorAgentBinary ( runtime , taskId ) ;
2691+ if ( afterInstall ) {
2692+ await this . linkCursorAgentBinary ( runtime , taskId , afterInstall ) ;
2693+ this . emit ( "agent.log" , taskId , "cursor agent CLI installed in sandbox" , {
2694+ detail : afterInstall . slice ( 0 , 240 ) ,
2695+ installLog : ( install . stdout || "" ) . trim ( ) . slice ( 0 , 300 ) ,
2696+ } ) ;
2697+ return ;
27232698 }
27242699
2725- this . emit ( "agent.log" , taskId , "cursor agent CLI installed in sandbox" , {
2726- detail : ( install . stdout || "" ) . trim ( ) . slice ( 0 , 200 ) ,
2700+ const stdout = ( install . stdout || "" ) . trim ( ) ;
2701+ const stderr = ( install . stderr || "" ) . trim ( ) ;
2702+ // Prefer installer text over curl progress-bar spam on stderr.
2703+ const detail =
2704+ stdout
2705+ . split ( "\n" )
2706+ . filter ( ( line ) => ! / ^ # / . test ( line . trim ( ) ) && ! / ^ \s * [ \d . ] + % $ / . test ( line ) )
2707+ . join ( "\n" )
2708+ . trim ( )
2709+ . slice ( 0 , 500 ) ||
2710+ stderr
2711+ . split ( "\n" )
2712+ . filter ( ( line ) => ! line . includes ( "#" ) && ! / \d + \. \d + % / . test ( line ) )
2713+ . join ( "\n" )
2714+ . trim ( )
2715+ . slice ( 0 , 400 ) ||
2716+ "agent binary not found after install" ;
2717+
2718+ throw new Error (
2719+ "cursor agent CLI is not available in the sandbox after install" +
2720+ ( detail ? `: ${ detail } ` : "" ) +
2721+ ". Rebuild the agent Firecracker snapshot" +
2722+ " (./infra/scripts/rebuild-agent-snapshot.sh <instance-id>)." ,
2723+ ) ;
2724+ }
2725+
2726+ private async findCursorAgentBinary (
2727+ runtime : RuntimeClient ,
2728+ taskId : string ,
2729+ ) : Promise < string | null > {
2730+ const findCmd = [
2731+ "set +e" ,
2732+ 'export HOME="${HOME:-/root}"' ,
2733+ 'export PATH="/usr/local/bin:/root/.local/bin:$HOME/.local/bin:$PATH"' ,
2734+ "for candidate in \\" ,
2735+ " /usr/local/bin/agent \\" ,
2736+ " /root/.local/bin/agent \\" ,
2737+ ' "$HOME/.local/bin/agent" \\' ,
2738+ " $(command -v agent 2>/dev/null) \\" ,
2739+ " $(command -v cursor-agent 2>/dev/null) \\" ,
2740+ " $(ls -1 /root/.local/share/cursor-agent/versions/*/cursor-agent 2>/dev/null | sort | tail -1) \\" ,
2741+ ' $(ls -1 "$HOME/.local/share/cursor-agent/versions/"*/cursor-agent 2>/dev/null | sort | tail -1)' ,
2742+ "do" ,
2743+ ' [ -n "$candidate" ] || continue' ,
2744+ ' [ -e "$candidate" ] || continue' ,
2745+ // Accept a present executable even if --version is flaky in the guest.
2746+ ' if [ -x "$candidate" ] || [ -L "$candidate" ]; then' ,
2747+ ' if "$candidate" --version >/dev/null 2>&1 || [ -x "$candidate" ]; then' ,
2748+ ' printf "%s\\n" "$candidate"' ,
2749+ " exit 0" ,
2750+ " fi" ,
2751+ " fi" ,
2752+ "done" ,
2753+ "exit 1" ,
2754+ ] . join ( "\n" ) ;
2755+
2756+ const probe = await runtime . terminalAllowFailure ( {
2757+ taskId,
2758+ command : findCmd ,
2759+ } ) ;
2760+ if ( probe . exitCode !== 0 ) {
2761+ return null ;
2762+ }
2763+ const bin = probe . stdout
2764+ . trim ( )
2765+ . split ( "\n" )
2766+ . map ( ( line ) => line . trim ( ) )
2767+ . find ( ( line ) => line . startsWith ( "/" ) || line . includes ( "agent" ) ) ;
2768+ return bin || null ;
2769+ }
2770+
2771+ private async linkCursorAgentBinary (
2772+ runtime : RuntimeClient ,
2773+ taskId : string ,
2774+ bin : string ,
2775+ ) : Promise < void > {
2776+ if ( ! bin || bin === "/usr/local/bin/agent" ) {
2777+ return ;
2778+ }
2779+ await runtime . terminalAllowFailure ( {
2780+ taskId,
2781+ command : `mkdir -p /usr/local/bin /root/.local/bin && ln -sfn '${ escapeShell ( bin ) } ' /usr/local/bin/agent && ln -sfn '${ escapeShell ( bin ) } ' /root/.local/bin/agent` ,
27272782 } ) ;
27282783 }
27292784
0 commit comments