@@ -32,10 +32,16 @@ import {
3232 PIPELINE_IMAGE_ENTRY_DEFAULT ,
3333 PIPELINE_IMAGE_ENTRY_ENV ,
3434 PIPELINE_IMAGE_MONOREPO_ROOT_DEFAULT ,
35+ PIPELINE_IMAGE_TSX_CLI_CANDIDATES ,
36+ PIPELINE_IMAGE_TSX_LOADER_CANDIDATES ,
3537 PIPELINE_SANDBOX_IMAGE_ENV ,
3638 PIPELINE_STDERR_PATH ,
3739 PIPELINE_STDOUT_PATH ,
40+ SANDBOX_PIPELINE_EXIT_CODE_PATH ,
41+ SANDBOX_PIPELINE_STDERR_PATH ,
42+ SANDBOX_PIPELINE_STDOUT_PATH ,
3843 SANDBOX_PNPM_VERSION ,
44+ SANDBOX_TELEMETRY_PATH ,
3945 SANDBOX_WORKING_DIRECTORY ,
4046 SOURCE_OVERLAY_PATCH_PATH ,
4147 TELEMETRY_PATH ,
@@ -293,33 +299,18 @@ function buildPipelineImageCommands(
293299
294300 if ( mode === 'asset_pack_pipeline' ) {
295301 // Prefer sandbox-uploaded runner (hot-fixed) over image-baked copy.
296- // tsx loads monorepo .ts package sources under BITCODE_MONOREPO_ROOT.
297- const sandboxLiveRunner = `${ SANDBOX_WORKING_DIRECTORY } /${ HOST_RUN_DIRECTORY } /run-live-asset-pack-pipeline.mjs` ;
298- const monorepoRoot = PIPELINE_IMAGE_MONOREPO_ROOT_DEFAULT ;
299- const pipelineRunScript = [
300- `cd ${ shellQuote ( monorepoRoot ) } ` ,
301- // tsx loads monorepo .ts package sources (plain node cannot).
302- `if ! node --import tsx -e "process.exit(0)" >/dev/null 2>&1; then` ,
303- ` npm install -g tsx@4.19.3 || npm install --no-save --prefix ${ shellQuote ( monorepoRoot ) } tsx@4.19.3` ,
304- `fi` ,
305- `RUNNER=${ shellQuote ( sandboxLiveRunner ) } ` ,
306- `if [ ! -f "$RUNNER" ]; then RUNNER=${ shellQuote ( `${ monorepoRoot } /.proofs/pipeline-host/run-live-asset-pack-pipeline.mjs` ) } ; fi` ,
307- `if [ ! -f "$RUNNER" ]; then echo "live runner missing; falling back to image dispatcher" >&2; node ${ shellQuote ( pipelineImageEntry ) } ; else node --import tsx "$RUNNER"; fi` ,
308- ] . join ( ' && ' ) ;
302+ // Live runner imports monorepo .ts packages — needs tsx, resolved from the
303+ // image workspace (pnpm does not hoist to monorepo root). Never hang on
304+ // runtime npm install; fail closed with logs + exit-code for host poll.
305+ const pipelineRunScript = buildPipelineImageRunShellScript ( pipelineImageEntry ) ;
309306 commands . push ( {
310307 label : 'asset-pack-pipeline-run' ,
311308 cmd : 'sh' ,
312- args : [
313- '-lc' ,
314- [
315- `( ${ pipelineRunScript } ) > ${ shellQuote ( PIPELINE_STDOUT_PATH ) } 2> ${ shellQuote ( PIPELINE_STDERR_PATH ) } ` ,
316- 'code=$?' ,
317- `printf "%s" "$code" > ${ shellQuote ( PIPELINE_EXIT_CODE_PATH ) } ` ,
318- 'exit "$code"' ,
319- ] . join ( '; ' ) ,
320- ] ,
309+ args : [ '-lc' , pipelineRunScript ] ,
310+ cwd : SANDBOX_WORKING_DIRECTORY ,
321311 env : commandEnvironment ,
322312 detached : true ,
313+ // Relative paths: Sandbox readFile is rooted at /vercel/sandbox.
323314 exitCodePath : PIPELINE_EXIT_CODE_PATH ,
324315 stdoutPath : PIPELINE_STDOUT_PATH ,
325316 stderrPath : PIPELINE_STDERR_PATH ,
@@ -456,6 +447,7 @@ function buildCommands(
456447 'exit "$code"' ,
457448 ] . join ( '; ' ) ,
458449 ] ,
450+ cwd : SANDBOX_WORKING_DIRECTORY ,
459451 env : commandEnvironment ,
460452 detached : true ,
461453 exitCodePath : PIPELINE_EXIT_CODE_PATH ,
@@ -480,6 +472,96 @@ function buildCommands(
480472 return commands ;
481473}
482474
475+ /**
476+ * Detached in-box shell for Pipeliner image mode.
477+ *
478+ * Guarantees (for host poll + operator debug):
479+ * - Absolute stdout/stderr/exit-code under /vercel/sandbox/.proofs/pipeline-host
480+ * - Early telemetry.jsonl line so UI is not stuck on bare command-started
481+ * - tsx resolved from workspace install paths (no runtime npm install hang)
482+ * - Always writes pipeline.exit-code so waitForDetachedCommand terminates
483+ */
484+ export function buildPipelineImageRunShellScript ( pipelineImageEntry : string ) : string {
485+ const monorepoRoot = PIPELINE_IMAGE_MONOREPO_ROOT_DEFAULT ;
486+ const sandboxLiveRunner = `${ SANDBOX_WORKING_DIRECTORY } /${ HOST_RUN_DIRECTORY } /run-live-asset-pack-pipeline.mjs` ;
487+ const imageLiveRunner = `${ monorepoRoot } /.proofs/pipeline-host/run-live-asset-pack-pipeline.mjs` ;
488+ const stdoutPath = SANDBOX_PIPELINE_STDOUT_PATH ;
489+ const stderrPath = SANDBOX_PIPELINE_STDERR_PATH ;
490+ const exitCodePath = SANDBOX_PIPELINE_EXIT_CODE_PATH ;
491+ const telemetryPath = SANDBOX_TELEMETRY_PATH ;
492+
493+ const loaderCandidates = PIPELINE_IMAGE_TSX_LOADER_CANDIDATES . map (
494+ ( p ) => shellQuote ( p ) ,
495+ ) . join ( ' ' ) ;
496+ const cliCandidates = PIPELINE_IMAGE_TSX_CLI_CANDIDATES . map ( ( p ) => shellQuote ( p ) ) . join ( ' ' ) ;
497+
498+ // Single shell program: markers first, then resolve tsx, then run.
499+ // Redirects use absolute paths so cwd surprises cannot hide artifacts.
500+ return [
501+ 'set +e' ,
502+ `HOST_DIR=${ shellQuote ( SANDBOX_WORKING_DIRECTORY + '/' + HOST_RUN_DIRECTORY ) } ` ,
503+ 'mkdir -p "$HOST_DIR"' ,
504+ `STDOUT=${ shellQuote ( stdoutPath ) } ` ,
505+ `STDERR=${ shellQuote ( stderrPath ) } ` ,
506+ `EXITF=${ shellQuote ( exitCodePath ) } ` ,
507+ `TELEM=${ shellQuote ( telemetryPath ) } ` ,
508+ // Truncate / create markers immediately (host + interactive debug).
509+ ': > "$STDOUT"' ,
510+ ': > "$STDERR"' ,
511+ 'TS=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo unknown)' ,
512+ 'echo "pipeline-shell: start ts=$TS monorepo=' +
513+ monorepoRoot +
514+ ' node=$(command -v node 2>/dev/null)" >> "$STDERR"' ,
515+ 'printf "%s\\n" "{\\"type\\":\\"pipeline-shell-start\\",\\"timestamp\\":\\"$TS\\",\\"cwd\\":\\"$(pwd)\\"}" >> "$TELEM"' ,
516+ '(' ,
517+ ` cd ${ shellQuote ( monorepoRoot ) } || { echo "pipeline-shell: cd monorepo failed" >&2; exit 127; }` ,
518+ ' TSX_LOADER=""' ,
519+ ` for c in ${ loaderCandidates } ; do` ,
520+ ' if [ -f "$c" ]; then TSX_LOADER=$c; break; fi' ,
521+ ' done' ,
522+ ' TSX_CLI=""' ,
523+ ` for c in ${ cliCandidates } ; do` ,
524+ ' if [ -f "$c" ]; then TSX_CLI=$c; break; fi' ,
525+ ' done' ,
526+ ' if [ -z "$TSX_LOADER" ] && [ -z "$TSX_CLI" ]; then' ,
527+ ' if node --import tsx -e "process.exit(0)" >/dev/null 2>&1; then' ,
528+ ' TSX_MODE=package' ,
529+ ' else' ,
530+ ' echo "pipeline-shell: tsx not found under monorepo (loader/cli/package). Refusing runtime npm install." >&2' ,
531+ ' echo "pipeline-shell: candidates checked; rebuild Pipeliner with pipeline-hosts tsx dep." >&2' ,
532+ ' exit 127' ,
533+ ' fi' ,
534+ ' else' ,
535+ ' TSX_MODE=path' ,
536+ ' fi' ,
537+ ` RUNNER=${ shellQuote ( sandboxLiveRunner ) } ` ,
538+ ` if [ ! -f "$RUNNER" ]; then RUNNER=${ shellQuote ( imageLiveRunner ) } ; fi` ,
539+ ' echo "pipeline-shell: tsx_mode=$TSX_MODE loader=${TSX_LOADER:-none} cli=${TSX_CLI:-none} runner=$RUNNER" >&2' ,
540+ ` if [ ! -f "$RUNNER" ]; then` ,
541+ ` echo "pipeline-shell: live runner missing; falling back to image dispatcher" >&2` ,
542+ ` node ${ shellQuote ( pipelineImageEntry ) } ` ,
543+ ' exit $?' ,
544+ ' fi' ,
545+ ' if [ -n "$TSX_LOADER" ]; then' ,
546+ ' node --import "$TSX_LOADER" "$RUNNER"' ,
547+ ' exit $?' ,
548+ ' fi' ,
549+ ' if [ -n "$TSX_CLI" ]; then' ,
550+ ' node "$TSX_CLI" "$RUNNER"' ,
551+ ' exit $?' ,
552+ ' fi' ,
553+ ' node --import tsx "$RUNNER"' ,
554+ ' exit $?' ,
555+ ') > "$STDOUT" 2>> "$STDERR"' ,
556+ 'code=$?' ,
557+ 'TS2=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo unknown)' ,
558+ 'echo "pipeline-shell: finish ts=$TS2 exit=$code" >> "$STDERR"' ,
559+ 'printf "%s\\n" "{\\"type\\":\\"pipeline-shell-finish\\",\\"timestamp\\":\\"$TS2\\",\\"exitCode\\":$code}" >> "$TELEM"' ,
560+ 'printf "%s" "$code" > "$EXITF"' ,
561+ 'exit "$code"' ,
562+ ] . join ( '\n' ) ;
563+ }
564+
483565function shellQuote ( value : string ) : string {
484566 return `'${ value . replace ( / ' / g, `'\\''` ) } '` ;
485567}
0 commit comments