@h-rig/cli 0.0.6-alpha.87 → 0.0.6-alpha.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -91,6 +91,21 @@ async function captureConsole(fn) {
91
91
  console.error = original.error;
92
92
  }
93
93
  }
94
+ async function releaseRendererForExternalTui(ctx) {
95
+ const renderer = ctx.renderer;
96
+ if (!renderer)
97
+ return;
98
+ if (renderer.suspend) {
99
+ await renderer.suspend();
100
+ return;
101
+ }
102
+ if (renderer.destroy) {
103
+ await renderer.destroy();
104
+ }
105
+ }
106
+ async function resumeRendererAfterExternalTui(ctx) {
107
+ await ctx.renderer?.resume?.();
108
+ }
94
109
  function arrayFromPayload(value) {
95
110
  if (Array.isArray(value)) {
96
111
  return value.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
@@ -9310,6 +9325,14 @@ var init__operator_surface = __esm(() => {
9310
9325
  });
9311
9326
 
9312
9327
  // packages/cli/src/commands/_pi-frontend.ts
9328
+ var exports__pi_frontend = {};
9329
+ __export(exports__pi_frontend, {
9330
+ shouldRequireOperatorTranscript: () => shouldRequireOperatorTranscript,
9331
+ runWithProcessExitGuard: () => runWithProcessExitGuard,
9332
+ missingOperatorTranscriptMessage: () => missingOperatorTranscriptMessage,
9333
+ buildOperatorPiEnv: () => buildOperatorPiEnv,
9334
+ attachRunBundledPiFrontend: () => attachRunBundledPiFrontend
9335
+ });
9313
9336
  import { existsSync as existsSync15, mkdirSync as mkdirSync10, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
9314
9337
  import { homedir as homedir6, tmpdir } from "os";
9315
9338
  import { join as join3 } from "path";
@@ -9422,6 +9445,29 @@ function installRigPiTheme() {
9422
9445
  }
9423
9446
  } catch {}
9424
9447
  }
9448
+ async function runWithProcessExitGuard(body) {
9449
+ const realExit = process.exit;
9450
+ let exitCode = 0;
9451
+ let signalQuit = () => {};
9452
+ const quit = new Promise((resolve23) => {
9453
+ signalQuit = resolve23;
9454
+ });
9455
+ const guardedExit = (code) => {
9456
+ exitCode = typeof code === "number" ? code : 0;
9457
+ signalQuit();
9458
+ return;
9459
+ };
9460
+ process.exit = guardedExit;
9461
+ try {
9462
+ await Promise.race([Promise.resolve().then(body), quit]);
9463
+ } finally {
9464
+ process.exit = realExit;
9465
+ }
9466
+ return exitCode;
9467
+ }
9468
+ function runPiMainReturningOnQuit(args, options) {
9469
+ return runWithProcessExitGuard(() => runPiMain(args, options));
9470
+ }
9425
9471
  async function attachRunBundledPiFrontend(context, input) {
9426
9472
  const tempSessionDir = mkdtempSync(join3(tmpdir(), "rig-pi-frontend-sessions-"));
9427
9473
  const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
@@ -9437,16 +9483,20 @@ async function attachRunBundledPiFrontend(context, input) {
9437
9483
  };
9438
9484
  let detached = false;
9439
9485
  try {
9440
- await runPiMain([
9486
+ const piArgs = [
9441
9487
  "--offline",
9442
9488
  "--no-extensions",
9443
9489
  "--no-skills",
9444
9490
  "--no-prompt-templates",
9445
9491
  "--no-context-files",
9446
9492
  ...sessionFileArg
9447
- ], {
9448
- extensionFactories: [piRigExtensionFactory]
9449
- });
9493
+ ];
9494
+ const piOptions = { extensionFactories: [piRigExtensionFactory] };
9495
+ if (input.returnOnQuit) {
9496
+ await runPiMainReturningOnQuit(piArgs, piOptions);
9497
+ } else {
9498
+ await runPiMain(piArgs, piOptions);
9499
+ }
9450
9500
  detached = true;
9451
9501
  } finally {
9452
9502
  restoreEnv();
@@ -14712,397 +14762,6 @@ async function stopFleetRun(ctx, runId) {
14712
14762
  }
14713
14763
  var init_fleet = () => {};
14714
14764
 
14715
- // packages/cli/src/app-opentui/pi-pty-host.ts
14716
- import { fileURLToPath as fileURLToPath2 } from "url";
14717
- import { basename as basename4 } from "path";
14718
- import { RGBA, StyledText, TextAttributes } from "@opentui/core";
14719
- import { Terminal as XtermTerminal2 } from "@xterm/headless";
14720
- function clampCols2(cols) {
14721
- return Math.max(MIN_COLS2, Math.trunc(cols || 100));
14722
- }
14723
- function clampRows2(rows) {
14724
- return Math.max(MIN_ROWS2, Math.min(MAX_ROWS2, Math.trunc(rows || 35)));
14725
- }
14726
- function rgbaFromXtermColor(mode, value) {
14727
- if (mode === 1 || mode === 2 || mode === XTERM_COLOR_MODE_INDEXED_16 || mode === XTERM_COLOR_MODE_INDEXED_256) {
14728
- return RGBA.fromIndex(value);
14729
- }
14730
- if (mode === 3 || mode === XTERM_COLOR_MODE_RGB) {
14731
- return RGBA.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255);
14732
- }
14733
- return;
14734
- }
14735
- function textAttributesFromCell(cell) {
14736
- let attributes = TextAttributes.NONE;
14737
- if (cell.isBold())
14738
- attributes |= TextAttributes.BOLD;
14739
- if (cell.isDim())
14740
- attributes |= TextAttributes.DIM;
14741
- if (cell.isItalic())
14742
- attributes |= TextAttributes.ITALIC;
14743
- if (cell.isUnderline())
14744
- attributes |= TextAttributes.UNDERLINE;
14745
- if (cell.isBlink())
14746
- attributes |= TextAttributes.BLINK;
14747
- if (cell.isInverse())
14748
- attributes |= TextAttributes.INVERSE;
14749
- if (cell.isInvisible())
14750
- attributes |= TextAttributes.HIDDEN;
14751
- if (cell.isStrikethrough())
14752
- attributes |= TextAttributes.STRIKETHROUGH;
14753
- return attributes;
14754
- }
14755
- function sameRgba(a, b) {
14756
- if (!a && !b)
14757
- return true;
14758
- return Boolean(a && b && a.equals(b));
14759
- }
14760
- function sameStyle(a, b) {
14761
- return sameRgba(a.fg, b.fg) && sameRgba(a.bg, b.bg) && (a.attributes ?? 0) === (b.attributes ?? 0);
14762
- }
14763
- function styleFromCell(cell) {
14764
- return {
14765
- fg: rgbaFromXtermColor(cell.getFgColorMode(), cell.getFgColor()),
14766
- bg: rgbaFromXtermColor(cell.getBgColorMode(), cell.getBgColor()),
14767
- attributes: textAttributesFromCell(cell)
14768
- };
14769
- }
14770
- function lineToStyledText(line, cols) {
14771
- if (!line)
14772
- return new StyledText([{ __isChunk: true, text: "" }]);
14773
- const chunks = [];
14774
- let run = "";
14775
- let runStyle = null;
14776
- const flush = () => {
14777
- if (!run)
14778
- return;
14779
- chunks.push({
14780
- __isChunk: true,
14781
- text: run,
14782
- ...runStyle?.fg ? { fg: runStyle.fg } : {},
14783
- ...runStyle?.bg ? { bg: runStyle.bg } : {},
14784
- ...(runStyle?.attributes ?? 0) !== 0 ? { attributes: runStyle.attributes } : {}
14785
- });
14786
- run = "";
14787
- };
14788
- for (let index = 0;index < cols; index += 1) {
14789
- const cell = line.getCell(index);
14790
- if (!cell) {
14791
- if (runStyle !== null)
14792
- flush();
14793
- runStyle = null;
14794
- run += " ";
14795
- continue;
14796
- }
14797
- if (cell.getWidth() === 0)
14798
- continue;
14799
- const style = styleFromCell(cell);
14800
- if (!runStyle || !sameStyle(runStyle, style)) {
14801
- flush();
14802
- runStyle = style;
14803
- }
14804
- run += cell.getChars() || " ";
14805
- }
14806
- flush();
14807
- return new StyledText(chunks.length > 0 ? chunks : [{ __isChunk: true, text: "" }]);
14808
- }
14809
- function childCommandPrefix2() {
14810
- const execName = basename4(process.execPath).toLowerCase();
14811
- const currentEntry = process.argv[1];
14812
- if (execName === "bun" || execName === "bun.exe") {
14813
- return currentEntry ? [process.execPath, currentEntry, "__opentui-pi-host"] : [process.execPath, fallbackChildScriptPath];
14814
- }
14815
- return [process.execPath, "__opentui-pi-host"];
14816
- }
14817
- function withEnv2(base) {
14818
- const env = {};
14819
- for (const [key, value] of Object.entries(base ?? process.env)) {
14820
- if (key === "RIG_PLAIN" || key === "RIG_CLI_PLAIN_HELP" || key === "NO_COLOR")
14821
- continue;
14822
- if (typeof value === "string")
14823
- env[key] = value;
14824
- }
14825
- return {
14826
- ...env,
14827
- TERM: "xterm-256color",
14828
- COLORTERM: "truecolor",
14829
- FORCE_COLOR: env.FORCE_COLOR ?? "1",
14830
- RIG_OPENTUI_PI_HOST: "1"
14831
- };
14832
- }
14833
- function getActivePiHost() {
14834
- return activeHost2 && !activeHost2.disposed ? activeHost2 : null;
14835
- }
14836
- async function startPiPtyHost(options) {
14837
- activeHost2?.dispose("replace");
14838
- const host = new PiPtyHost(options);
14839
- activeHost2 = host;
14840
- await host.start();
14841
- return host;
14842
- }
14843
- function stopActivePiHost(reason = "detach") {
14844
- activeHost2?.dispose(reason);
14845
- activeHost2 = null;
14846
- }
14847
-
14848
- class PiPtyHost {
14849
- runId;
14850
- projectRoot;
14851
- onSnapshot;
14852
- onExit;
14853
- onError;
14854
- terminal;
14855
- disposables = [];
14856
- decoder = new TextDecoder("utf-8");
14857
- proc = null;
14858
- pty = null;
14859
- status = "starting";
14860
- cols;
14861
- rows;
14862
- message = "starting bundled Pi";
14863
- lastResizeError = null;
14864
- exitCode;
14865
- signal;
14866
- notifyTimer = null;
14867
- lastStreamKey = "";
14868
- _disposed = false;
14869
- constructor(options) {
14870
- this.runId = options.runId;
14871
- this.projectRoot = options.projectRoot;
14872
- this.cols = clampCols2(options.cols);
14873
- this.rows = clampRows2(options.rows);
14874
- this.onSnapshot = options.onSnapshot;
14875
- this.onExit = options.onExit;
14876
- this.onError = options.onError;
14877
- this.terminal = new XtermTerminal2({
14878
- allowProposedApi: true,
14879
- cols: this.cols,
14880
- rows: this.rows,
14881
- scrollback: 1000
14882
- });
14883
- this.registerTerminalResponders();
14884
- this.disposables.push(this.terminal.onWriteParsed(() => {
14885
- if (this._disposed)
14886
- return;
14887
- const snapshot = this.createSnapshot();
14888
- const key = snapshot.lines.join(`
14889
- `);
14890
- if (key === this.lastStreamKey)
14891
- return;
14892
- this.lastStreamKey = key;
14893
- this.onSnapshot?.(snapshot);
14894
- }));
14895
- }
14896
- get disposed() {
14897
- return this._disposed;
14898
- }
14899
- get snapshot() {
14900
- return this.createSnapshot();
14901
- }
14902
- async start() {
14903
- if (this._disposed)
14904
- throw new Error("Pi PTY host is disposed.");
14905
- if (typeof Bun.Terminal !== "function") {
14906
- throw new Error("Bun native PTY is unavailable in this runtime. Pi host requires Bun.Terminal.");
14907
- }
14908
- const spawnOptions = {
14909
- cwd: this.projectRoot,
14910
- env: withEnv2(process.env),
14911
- terminal: {
14912
- cols: this.cols,
14913
- rows: this.rows,
14914
- name: "xterm-256color",
14915
- data: (_terminal, data) => this.handlePtyData(data)
14916
- }
14917
- };
14918
- const proc = Bun.spawn([
14919
- ...childCommandPrefix2(),
14920
- "--run-id",
14921
- this.runId,
14922
- "--project-root",
14923
- this.projectRoot
14924
- ], spawnOptions);
14925
- if (!proc.terminal)
14926
- throw new Error("Bun did not attach a terminal to the bundled Pi child process.");
14927
- this.proc = proc;
14928
- this.pty = proc.terminal;
14929
- this.status = "running";
14930
- this.message = "bundled Pi running inside this app";
14931
- this.emitSnapshotSoon(0);
14932
- proc.exited.then((exitCode) => {
14933
- if (this._disposed)
14934
- return;
14935
- this.status = exitCode === 0 ? "exited" : "failed";
14936
- this.exitCode = exitCode;
14937
- this.signal = null;
14938
- this.message = exitCode === 0 ? "bundled Pi exited" : `bundled Pi exited with code ${exitCode}`;
14939
- const snapshot = this.createSnapshot();
14940
- this.onSnapshot?.(snapshot);
14941
- this.onExit?.(snapshot);
14942
- if (activeHost2 === this)
14943
- activeHost2 = null;
14944
- this.dispose("exit", { kill: false, notify: false });
14945
- }).catch((error) => {
14946
- if (this._disposed)
14947
- return;
14948
- this.status = "failed";
14949
- this.message = error instanceof Error ? error.message : String(error);
14950
- const snapshot = this.createSnapshot();
14951
- this.onSnapshot?.(snapshot);
14952
- this.onError?.(error, snapshot);
14953
- if (activeHost2 === this)
14954
- activeHost2 = null;
14955
- this.dispose("error", { kill: false, notify: false });
14956
- });
14957
- }
14958
- write(data) {
14959
- if (this._disposed || !this.pty)
14960
- return;
14961
- try {
14962
- this.pty.write(data);
14963
- } catch (error) {
14964
- this.status = "failed";
14965
- this.message = error instanceof Error ? error.message : String(error);
14966
- const snapshot = this.createSnapshot();
14967
- this.onSnapshot?.(snapshot);
14968
- this.onError?.(error, snapshot);
14969
- }
14970
- }
14971
- resize(cols, rows) {
14972
- const nextCols = clampCols2(cols);
14973
- const nextRows = clampRows2(rows);
14974
- if (nextCols === this.cols && nextRows === this.rows)
14975
- return;
14976
- this.cols = nextCols;
14977
- this.rows = nextRows;
14978
- this.terminal.resize(nextCols, nextRows);
14979
- try {
14980
- this.pty?.resize(nextCols, nextRows);
14981
- this.lastResizeError = null;
14982
- } catch (error) {
14983
- this.lastResizeError = error instanceof Error ? error.message : String(error);
14984
- }
14985
- this.emitSnapshotSoon(0);
14986
- }
14987
- detach() {
14988
- this.dispose("detach");
14989
- return this.createSnapshot("detached from bundled Pi");
14990
- }
14991
- dispose(reason = "dispose", options = {}) {
14992
- if (this._disposed)
14993
- return;
14994
- this._disposed = true;
14995
- if (this.notifyTimer)
14996
- clearTimeout(this.notifyTimer);
14997
- this.notifyTimer = null;
14998
- for (const disposable of this.disposables.splice(0)) {
14999
- try {
15000
- disposable.dispose();
15001
- } catch {}
15002
- }
15003
- if (options.kill !== false) {
15004
- try {
15005
- this.proc?.kill("SIGTERM");
15006
- } catch {}
15007
- }
15008
- try {
15009
- this.pty?.close();
15010
- } catch {}
15011
- try {
15012
- this.terminal.dispose();
15013
- } catch {}
15014
- if (activeHost2 === this)
15015
- activeHost2 = null;
15016
- if (options.notify) {
15017
- this.message = reason;
15018
- this.onSnapshot?.(this.createSnapshot(reason));
15019
- }
15020
- }
15021
- handlePtyData(data) {
15022
- if (this._disposed)
15023
- return;
15024
- const text2 = this.decoder.decode(data, { stream: true });
15025
- this.respondToRawTerminalQueries(text2);
15026
- this.terminal.write(data);
15027
- }
15028
- emitSnapshotSoon(delayMs = SNAPSHOT_DELAY_MS2) {
15029
- if (this._disposed || this.notifyTimer)
15030
- return;
15031
- this.notifyTimer = setTimeout(() => {
15032
- this.notifyTimer = null;
15033
- if (this._disposed)
15034
- return;
15035
- this.onSnapshot?.(this.createSnapshot());
15036
- }, delayMs);
15037
- }
15038
- createSnapshot(message2 = this.message) {
15039
- const buffer = this.terminal.buffer.active;
15040
- const end = buffer.length;
15041
- const start = Math.max(0, end - MAX_SNAPSHOT_LINES2);
15042
- const styledStart = Math.max(start, end - this.rows - STYLED_SNAPSHOT_MARGIN);
15043
- const lines = [];
15044
- const styledLines = [];
15045
- for (let row = start;row < end; row += 1) {
15046
- const line = buffer.getLine(row);
15047
- lines.push((line?.translateToString(false) ?? "").slice(0, this.cols));
15048
- if (row >= styledStart)
15049
- styledLines[lines.length - 1] = lineToStyledText(line, this.cols);
15050
- }
15051
- while (lines.length < this.rows) {
15052
- lines.push("");
15053
- styledLines[lines.length - 1] = new StyledText([{ __isChunk: true, text: "" }]);
15054
- }
15055
- const resolvedMessage = this.lastResizeError ? `resize degraded: ${this.lastResizeError}` : message2;
15056
- return {
15057
- runId: this.runId,
15058
- status: this.status,
15059
- cols: this.cols,
15060
- rows: this.rows,
15061
- lines,
15062
- styledLines,
15063
- message: resolvedMessage,
15064
- ...this.lastResizeError ? { resizeError: this.lastResizeError } : {},
15065
- ...this.exitCode !== undefined ? { exitCode: this.exitCode } : {},
15066
- ...this.signal !== undefined ? { signal: this.signal } : {}
15067
- };
15068
- }
15069
- registerTerminalResponders() {
15070
- this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "c" }, (params) => {
15071
- if (params.length === 0 || params[0] === 0)
15072
- this.write("\x1B[?62;22c");
15073
- return false;
15074
- }));
15075
- this.disposables.push(this.terminal.parser.registerCsiHandler({ prefix: ">", final: "c" }, (params) => {
15076
- if (params.length === 0 || params[0] === 0)
15077
- this.write("\x1B[>0;0;0c");
15078
- return false;
15079
- }));
15080
- this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "n" }, (params) => {
15081
- if (params[0] === 5) {
15082
- this.write("\x1B[0n");
15083
- } else if (params[0] === 6) {
15084
- const row = this.terminal.buffer.active.cursorY + 1;
15085
- const col = this.terminal.buffer.active.cursorX + 1;
15086
- this.write(`\x1B[${row};${col}R`);
15087
- }
15088
- return false;
15089
- }));
15090
- }
15091
- respondToRawTerminalQueries(text2) {
15092
- if (!text2)
15093
- return;
15094
- const decrqm = /\x1b\[\?(\d+)\$p/g;
15095
- let match;
15096
- while ((match = decrqm.exec(text2)) !== null) {
15097
- this.write(`\x1B[?${match[1]};2$y`);
15098
- }
15099
- }
15100
- }
15101
- var MIN_COLS2 = 40, MIN_ROWS2 = 12, MAX_ROWS2 = 300, MAX_SNAPSHOT_LINES2 = 360, STYLED_SNAPSHOT_MARGIN = 6, SNAPSHOT_DELAY_MS2 = 120, fallbackChildScriptPath, activeHost2 = null, XTERM_COLOR_MODE_INDEXED_16 = 16777216, XTERM_COLOR_MODE_INDEXED_256 = 33554432, XTERM_COLOR_MODE_RGB = 50331648;
15102
- var init_pi_pty_host = __esm(() => {
15103
- fallbackChildScriptPath = fileURLToPath2(new URL("./pi-host-child.ts", import.meta.url));
15104
- });
15105
-
15106
14765
  // packages/cli/src/app-opentui/adapters/inspect.ts
15107
14766
  var exports_inspect = {};
15108
14767
  __export(exports_inspect, {
@@ -15325,14 +14984,6 @@ function recordStep(ctx, runId, label, emitLabel) {
15325
14984
  });
15326
14985
  emitProgress(ctx, emitLabel, label);
15327
14986
  }
15328
- function settleSteps(ctx, runId) {
15329
- const now = Date.now();
15330
- const previous = currentAttachState(ctx, runId);
15331
- if (!previous?.steps)
15332
- return;
15333
- const steps = previous.steps.map((step) => step.status === "running" ? { ...step, status: "done", endedAtMs: step.endedAtMs ?? now } : step);
15334
- patchData(ctx, { piAttach: { ...previous, steps } });
15335
- }
15336
14987
  async function preparePiAttachHandoff(ctx, runId) {
15337
14988
  const cleanRunId = runId.trim();
15338
14989
  const label = `Preparing Pi ${cleanRunId.slice(0, 8)}`;
@@ -15380,85 +15031,41 @@ async function attachRunWithBundledPi(ctx, runId) {
15380
15031
  throw error;
15381
15032
  }
15382
15033
  emitStarted(ctx, label, { piAttach: { runId: cleanRunId, status: "entering-pi" } });
15383
- patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "starting bundled Pi", steps: [] } });
15034
+ patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "handing the terminal to worker Pi\u2026" } });
15035
+ const projectRoot = projectRootOf(ctx);
15036
+ const outputMode = ctx.rig?.outputMode ?? "text";
15037
+ await releaseRendererForExternalTui(ctx);
15038
+ let outcome = null;
15039
+ let attachError = null;
15384
15040
  try {
15385
- recordStep(ctx, cleanRunId, "connecting to server and fetching run transcript", label);
15386
- await preparePiAttachHandoff(ctx, cleanRunId).catch((error) => {
15387
- patchData(ctx, { lastRefreshError: normalizeAppError(error).message });
15388
- return null;
15389
- });
15390
- recordStep(ctx, cleanRunId, "spawning Bun PTY host for bundled Pi", label);
15391
- const projectRoot = projectRootOf(ctx);
15392
- const cols = typeof process.stdout.columns === "number" ? process.stdout.columns : 100;
15393
- const rows = Math.max(1, (typeof process.stdout.rows === "number" ? process.stdout.rows : 35) - 1);
15394
- const patchTerminal = (snapshot) => {
15395
- const previousSteps = currentAttachState(ctx, cleanRunId)?.steps;
15396
- const failed = snapshot.status === "failed";
15397
- const reason = failed ? snapshot.message ?? `bundled Pi exited with code ${snapshot.exitCode ?? "unknown"}` : undefined;
15398
- patchData(ctx, {
15399
- piTerminal: snapshot,
15400
- piAttach: {
15401
- runId: cleanRunId,
15402
- status: failed ? "failed" : snapshot.status === "exited" ? "returned" : "entering-pi",
15403
- message: snapshot.message ?? "bundled Pi running",
15404
- result: { runId: cleanRunId, status: snapshot.status, exitCode: snapshot.exitCode },
15405
- ...previousSteps ? { steps: previousSteps } : {},
15406
- ...reason ? { failureReason: reason } : {}
15407
- }
15408
- });
15409
- };
15410
- recordStep(ctx, cleanRunId, "starting bundled Pi", label);
15411
- await startPiPtyHost({
15412
- runId: cleanRunId,
15413
- projectRoot,
15414
- cols,
15415
- rows,
15416
- onSnapshot: (snapshot) => {
15417
- if (snapshot.status === "running" && snapshot.lines.some((entry) => entry.trim().length > 0)) {
15418
- settleSteps(ctx, cleanRunId);
15419
- }
15420
- patchTerminal(snapshot);
15421
- },
15422
- onExit: (snapshot) => {
15423
- patchTerminal(snapshot);
15424
- if (snapshot.status === "failed") {
15425
- Promise.resolve().then(() => (init_inspect2(), exports_inspect)).then(({ loadInspect: loadInspect2 }) => loadInspect2(ctx, cleanRunId)).catch(() => null);
15426
- emitFailed(ctx, label, new Error(snapshot.message ?? `bundled Pi exited with code ${snapshot.exitCode ?? "unknown"}`), { runId: cleanRunId, result: { status: snapshot.status, exitCode: snapshot.exitCode } });
15427
- return;
15428
- }
15429
- emitCompleted(ctx, label, { runId: cleanRunId, result: { status: snapshot.status, exitCode: snapshot.exitCode } });
15430
- ctx.emit({ type: "scene.change", scene: "fleet", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } } });
15431
- refreshFleet(ctx).catch((error) => emitFailed(ctx, "Refresh runs", error));
15432
- },
15433
- onError: (error, snapshot) => {
15434
- patchTerminal(snapshot);
15435
- emitFailed(ctx, label, error, { runId: cleanRunId });
15436
- }
15437
- });
15438
- const record = {
15439
- runId: cleanRunId,
15440
- status: "running",
15441
- rendered: "bundled Pi embedded via Bun.Terminal + @xterm/headless"
15442
- };
15443
- emitProgress(ctx, label, "bundled Pi is running", { piAttach: { runId: cleanRunId, status: "entering-pi", message: "bundled Pi running", result: record } });
15444
- return record;
15041
+ const { attachRunBundledPiFrontend: attachRunBundledPiFrontend2 } = await Promise.resolve().then(() => (init__pi_frontend(), exports__pi_frontend));
15042
+ outcome = await attachRunBundledPiFrontend2({ projectRoot, outputMode }, { runId: cleanRunId, returnOnQuit: true });
15445
15043
  } catch (error) {
15446
- const reason = error instanceof Error ? error.message : String(error);
15044
+ attachError = error;
15045
+ } finally {
15046
+ await resumeRendererAfterExternalTui(ctx);
15047
+ }
15048
+ if (attachError) {
15049
+ const reason = normalizeAppError(attachError).message;
15447
15050
  patchData(ctx, {
15448
- piAttach: {
15449
- runId: cleanRunId,
15450
- status: "failed",
15451
- message: reason,
15452
- failureReason: reason,
15453
- ...currentAttachState(ctx, cleanRunId)?.steps ? { steps: currentAttachState(ctx, cleanRunId).steps } : {}
15454
- }
15051
+ piTerminal: undefined,
15052
+ piAttach: { runId: cleanRunId, status: "failed", message: reason, failureReason: reason }
15455
15053
  });
15456
- emitFailed(ctx, label, error, { runId: cleanRunId });
15457
- throw error;
15054
+ Promise.resolve().then(() => (init_inspect2(), exports_inspect)).then(({ loadInspect: loadInspect2 }) => loadInspect2(ctx, cleanRunId)).catch(() => null);
15055
+ emitFailed(ctx, label, attachError, { runId: cleanRunId });
15056
+ throw attachError;
15458
15057
  }
15058
+ const detached = outcome?.detached === true;
15059
+ patchData(ctx, {
15060
+ piTerminal: undefined,
15061
+ piAttach: { runId: cleanRunId, status: "returned", message: detached ? "detached from worker Pi" : "returned from worker Pi" }
15062
+ });
15063
+ emitCompleted(ctx, label, { runId: cleanRunId, result: { runId: cleanRunId, detached } });
15064
+ ctx.emit({ type: "scene.change", scene: "fleet", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } } });
15065
+ refreshFleet(ctx).catch((error) => emitFailed(ctx, "Refresh runs", error));
15066
+ return { runId: cleanRunId, status: "returned", detached };
15459
15067
  }
15460
15068
  var init_pi_attach = __esm(() => {
15461
- init_pi_pty_host();
15462
15069
  init_fleet();
15463
15070
  });
15464
15071
 
@@ -15510,7 +15117,7 @@ import {
15510
15117
  dim as otuiDim,
15511
15118
  fg as otuiFg,
15512
15119
  t,
15513
- TextAttributes as TextAttributes2
15120
+ TextAttributes
15514
15121
  } from "@opentui/core";
15515
15122
  function statusColor3(status) {
15516
15123
  switch (status) {
@@ -15615,7 +15222,7 @@ var init_theme2 = __esm(() => {
15615
15222
  });
15616
15223
 
15617
15224
  // packages/cli/src/app-opentui/drone.ts
15618
- import { RGBA as RGBA2, StyledText as StyledText2, TextAttributes as TextAttributes3 } from "@opentui/core";
15225
+ import { RGBA, StyledText, TextAttributes as TextAttributes2 } from "@opentui/core";
15619
15226
  function bladeForTick(tick, phase = 0) {
15620
15227
  return BLADE_FRAMES2[(Math.floor(tick / 3) + phase) % BLADE_FRAMES2.length];
15621
15228
  }
@@ -15624,12 +15231,12 @@ function eyeForTick(tick, phase = 0) {
15624
15231
  return pulse > 0.55 ? EYE_FRAMES2[1] : pulse > 0.1 ? EYE_FRAMES2[0] : pulse > -0.45 ? EYE_FRAMES2[2] : EYE_FRAMES2[3];
15625
15232
  }
15626
15233
  function chunk(text2, fg2, bold2 = false, dim = false) {
15627
- let attributes = TextAttributes3.NONE;
15234
+ let attributes = TextAttributes2.NONE;
15628
15235
  if (bold2)
15629
- attributes |= TextAttributes3.BOLD;
15236
+ attributes |= TextAttributes2.BOLD;
15630
15237
  if (dim)
15631
- attributes |= TextAttributes3.DIM;
15632
- return { __isChunk: true, text: text2, fg: fg2, ...attributes !== TextAttributes3.NONE ? { attributes } : {} };
15238
+ attributes |= TextAttributes2.DIM;
15239
+ return { __isChunk: true, text: text2, fg: fg2, ...attributes !== TextAttributes2.NONE ? { attributes } : {} };
15633
15240
  }
15634
15241
  function styledLine(text2, colorFor) {
15635
15242
  const chunks = [];
@@ -15650,7 +15257,7 @@ function styledLine(text2, colorFor) {
15650
15257
  run += char;
15651
15258
  }
15652
15259
  flush();
15653
- return new StyledText2(chunks.length > 0 ? chunks : [chunk("", COLOR.ink)]);
15260
+ return new StyledText(chunks.length > 0 ? chunks : [chunk("", COLOR.ink)]);
15654
15261
  }
15655
15262
  function droneColor(char) {
15656
15263
  if (char === "?" || char === "o" || char === "@" || char === "\u2022")
@@ -15742,13 +15349,13 @@ var init_drone = __esm(() => {
15742
15349
  BLADE_FRAMES2 = ["---", "\\\\\\", "|||", "///"];
15743
15350
  EYE_FRAMES2 = ["o", "@", "\u2022", "."];
15744
15351
  COLOR = {
15745
- body: RGBA2.fromHex(RIG_UI.lime),
15746
- mini: RGBA2.fromHex(RIG_UI.limeDim),
15747
- rotor: RGBA2.fromHex(RIG_UI.cyan),
15748
- path: RGBA2.fromHex(RIG_UI.cyan),
15749
- eye: RGBA2.fromHex(RIG_UI.ink),
15750
- dim: RGBA2.fromHex(RIG_UI.ink4),
15751
- ink: RGBA2.fromHex(RIG_UI.ink2)
15352
+ body: RGBA.fromHex(RIG_UI.lime),
15353
+ mini: RGBA.fromHex(RIG_UI.limeDim),
15354
+ rotor: RGBA.fromHex(RIG_UI.cyan),
15355
+ path: RGBA.fromHex(RIG_UI.cyan),
15356
+ eye: RGBA.fromHex(RIG_UI.ink),
15357
+ dim: RGBA.fromHex(RIG_UI.ink4),
15358
+ ink: RGBA.fromHex(RIG_UI.ink2)
15752
15359
  };
15753
15360
  });
15754
15361
 
@@ -16058,7 +15665,7 @@ var init__json_output = __esm(() => {
16058
15665
 
16059
15666
  // packages/cli/src/launcher.ts
16060
15667
  import { existsSync as existsSync22 } from "fs";
16061
- import { basename as basename5, resolve as resolve29 } from "path";
15668
+ import { basename as basename4, resolve as resolve29 } from "path";
16062
15669
  import { loadDotEnvSecrets } from "@rig/runtime/baked-secrets";
16063
15670
  import { RIG_DEFINITION_DIRNAME, RIG_STATE_DIRNAME, resolveNearestRigProjectRoot } from "@rig/runtime/layout";
16064
15671
  function parsePolicyMode(value) {
@@ -16083,7 +15690,7 @@ function resolveProjectRoot({
16083
15690
  return resolve29(cwd, envProjectRoot);
16084
15691
  }
16085
15692
  const fallbackImportDir = importDir ?? cwd;
16086
- const execName = basename5(execPath).toLowerCase();
15693
+ const execName = basename4(execPath).toLowerCase();
16087
15694
  const execCandidates = execName === "rig" || execName === "rig.exe" ? [resolve29(execPath, "..", "..")] : [];
16088
15695
  const candidates = [cwd, ...execCandidates, resolve29(fallbackImportDir, "..")];
16089
15696
  for (const candidate of candidates) {
@@ -16952,6 +16559,12 @@ var init_autocomplete = __esm(() => {
16952
16559
  NAV_VERBS = ["runs", "tasks", "inbox", "server", "doctor", "help", "init", "main"];
16953
16560
  });
16954
16561
 
16562
+ // packages/cli/src/app-opentui/pi-pty-host.ts
16563
+ function getActivePiHost() {
16564
+ return null;
16565
+ }
16566
+ function stopActivePiHost(_reason) {}
16567
+
16955
16568
  // packages/cli/src/app-opentui/keymap.ts
16956
16569
  function clearTypeBar(context, message2) {
16957
16570
  const typeBar = context.getTypeBar();
@@ -17184,7 +16797,6 @@ var init_keymap = __esm(() => {
17184
16797
  init_autocomplete();
17185
16798
  init_command_pty_host();
17186
16799
  init_intent();
17187
- init_pi_pty_host();
17188
16800
  });
17189
16801
 
17190
16802
  // packages/cli/src/app-opentui/runtime-resources.ts
@@ -17341,7 +16953,7 @@ var init_constants = __esm(() => {
17341
16953
  });
17342
16954
 
17343
16955
  // packages/cli/src/app-opentui/render/graphics.ts
17344
- import { FrameBufferRenderable, RGBA as RGBA3 } from "@opentui/core";
16956
+ import { FrameBufferRenderable, RGBA as RGBA2 } from "@opentui/core";
17345
16957
  function sceneKind(scene) {
17346
16958
  if (scene === "tasks")
17347
16959
  return "loop";
@@ -17436,7 +17048,7 @@ function paletteColor(ac, c2, mg, ink5) {
17436
17048
  const b = (AC_RGB[2] * ac + C2_RGB[2] * c2 + MG_RGB[2] * mg + INK_RGB[2] * ink5) / total;
17437
17049
  const intensity = Math.min(1, total / 4);
17438
17050
  const lift = 0.34 + intensity * 0.66;
17439
- return RGBA3.fromInts(Math.round(r * lift), Math.round(g * lift), Math.round(b * lift), 255);
17051
+ return RGBA2.fromInts(Math.round(r * lift), Math.round(g * lift), Math.round(b * lift), 255);
17440
17052
  }
17441
17053
  function clearCanvas(canvas) {
17442
17054
  canvas.ac.fill(0);
@@ -17791,8 +17403,8 @@ function staticFleetTick(scene) {
17791
17403
  }
17792
17404
  function withAlpha(hex, alpha) {
17793
17405
  const a = Math.max(0, Math.min(255, Math.round(alpha * 255)));
17794
- const [r, g, b] = RGBA3.fromHex(hex).toInts();
17795
- return RGBA3.fromInts(r, g, b, a);
17406
+ const [r, g, b] = RGBA2.fromHex(hex).toInts();
17407
+ return RGBA2.fromInts(r, g, b, a);
17796
17408
  }
17797
17409
  function asciiFleetColor(char, row, alpha) {
17798
17410
  if (char === "@" || char === "$" || char === "o")
@@ -17800,7 +17412,7 @@ function asciiFleetColor(char, row, alpha) {
17800
17412
  if (char === "%" || char === "!" || char === "/" || char === "\\" || char === "|")
17801
17413
  return withAlpha(RIG_UI.cyan, alpha);
17802
17414
  if (char === "." || char === "'" || char === "_" || row < 3 || row > FLEET_GRID_HEIGHT - 4) {
17803
- return RGBA3.fromInts(108, 110, 121, Math.min(170, Math.max(0, Math.min(255, Math.round(alpha * 255)))));
17415
+ return RGBA2.fromInts(108, 110, 121, Math.min(170, Math.max(0, Math.min(255, Math.round(alpha * 255)))));
17804
17416
  }
17805
17417
  return withAlpha(RIG_UI.limeDim, alpha);
17806
17418
  }
@@ -17854,13 +17466,13 @@ var init_graphics = __esm(() => {
17854
17466
  init_theme2();
17855
17467
  init_ascii_fleet();
17856
17468
  init_constants();
17857
- TRANSPARENT = RGBA3.fromValues(0, 0, 0, 0);
17858
- BACKDROP = RGBA3.fromHex(RIG_UI.bg);
17859
- PANEL_BG = RGBA3.fromInts(16, 17, 21, 184);
17860
- PANEL_HEADER_BG = RGBA3.fromInts(16, 17, 21, 188);
17861
- PANEL_LINE = RGBA3.fromInts(255, 255, 255, 20);
17862
- PANEL_LINE_DIM = RGBA3.fromInts(255, 255, 255, 8);
17863
- PANEL_TEXT_DIM = RGBA3.fromInts(108, 110, 121, 255);
17469
+ TRANSPARENT = RGBA2.fromValues(0, 0, 0, 0);
17470
+ BACKDROP = RGBA2.fromHex(RIG_UI.bg);
17471
+ PANEL_BG = RGBA2.fromInts(16, 17, 21, 184);
17472
+ PANEL_HEADER_BG = RGBA2.fromInts(16, 17, 21, 188);
17473
+ PANEL_LINE = RGBA2.fromInts(255, 255, 255, 20);
17474
+ PANEL_LINE_DIM = RGBA2.fromInts(255, 255, 255, 8);
17475
+ PANEL_TEXT_DIM = RGBA2.fromInts(108, 110, 121, 255);
17864
17476
  AC_RGB = [204, 255, 77];
17865
17477
  C2_RGB = [86, 216, 255];
17866
17478
  MG_RGB = [255, 121, 176];
@@ -17903,7 +17515,7 @@ var init_hover = __esm(() => {
17903
17515
  });
17904
17516
 
17905
17517
  // packages/cli/src/app-opentui/render/text.ts
17906
- import { RGBA as RGBA4, TextAttributes as TextAttributes4, TextRenderable } from "@opentui/core";
17518
+ import { RGBA as RGBA3, TextAttributes as TextAttributes3, TextRenderable } from "@opentui/core";
17907
17519
  function lineIsClickable(line2) {
17908
17520
  return line2?.selectable !== undefined || line2?.selectableIndex !== undefined;
17909
17521
  }
@@ -17912,7 +17524,7 @@ function lineSelectableId(line2) {
17912
17524
  }
17913
17525
  function lineBackground(line2) {
17914
17526
  if (line2?.bg)
17915
- return RGBA4.fromHex(line2.bg);
17527
+ return RGBA3.fromHex(line2.bg);
17916
17528
  const id = lineSelectableId(line2);
17917
17529
  if (id !== undefined && isHovered(id))
17918
17530
  return HOVER_BG;
@@ -17981,7 +17593,7 @@ function applyTextLine(renderable, line2, top, left, width) {
17981
17593
  renderable.content = line2?.styledText ?? line2?.text ?? "";
17982
17594
  renderable.fg = line2?.fg ?? RIG_UI.ink2;
17983
17595
  renderable.bg = lineBackground(line2);
17984
- renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
17596
+ renderable.attributes = line2?.bold ? TextAttributes3.BOLD : line2?.dim ? TextAttributes3.DIM : 0;
17985
17597
  }
17986
17598
  function applyFlowTextLine(renderable, line2, width) {
17987
17599
  renderable.setRigSceneLine?.(line2);
@@ -17991,7 +17603,7 @@ function applyFlowTextLine(renderable, line2, width) {
17991
17603
  renderable.content = line2?.styledText ?? line2?.text ?? "";
17992
17604
  renderable.fg = line2?.fg ?? RIG_UI.ink2;
17993
17605
  renderable.bg = lineBackground(line2);
17994
- renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
17606
+ renderable.attributes = line2?.bold ? TextAttributes3.BOLD : line2?.dim ? TextAttributes3.DIM : 0;
17995
17607
  }
17996
17608
  function clearTextLines(renderables, from = 0) {
17997
17609
  for (let index = from;index < renderables.length; index += 1) {
@@ -18008,19 +17620,30 @@ var TRANSPARENT2, HOVER_BG;
18008
17620
  var init_text = __esm(() => {
18009
17621
  init_theme2();
18010
17622
  init_hover();
18011
- TRANSPARENT2 = RGBA4.fromInts(0, 0, 0, 0);
18012
- HOVER_BG = RGBA4.fromHex(RIG_UI.hover);
17623
+ TRANSPARENT2 = RGBA3.fromInts(0, 0, 0, 0);
17624
+ HOVER_BG = RGBA3.fromHex(RIG_UI.hover);
18013
17625
  });
18014
17626
 
17627
+ // packages/cli/src/app-opentui/render/terminal-handoff.ts
17628
+ function resumeRendererClean(renderer) {
17629
+ try {
17630
+ renderer.pendingSuspendedTerminalSetup = true;
17631
+ } catch {}
17632
+ renderer.resume();
17633
+ try {
17634
+ renderer.requestRender?.();
17635
+ } catch {}
17636
+ }
17637
+
18015
17638
  // packages/cli/src/app-opentui/render/panels.ts
18016
- import { RGBA as RGBA5, ScrollBoxRenderable } from "@opentui/core";
17639
+ import { RGBA as RGBA4, ScrollBoxRenderable } from "@opentui/core";
18017
17640
  function hexToRgba(hex, alpha) {
18018
17641
  const clean = hex.replace(/^#/, "");
18019
17642
  const full = clean.length === 3 ? clean.split("").map((part) => `${part}${part}`).join("") : clean;
18020
17643
  const value = Number.parseInt(full, 16);
18021
17644
  if (!Number.isFinite(value))
18022
- return RGBA5.fromInts(14, 15, 17, alpha);
18023
- return RGBA5.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255, alpha);
17645
+ return RGBA4.fromInts(14, 15, 17, alpha);
17646
+ return RGBA4.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255, alpha);
18024
17647
  }
18025
17648
  function panelBackground(panel) {
18026
17649
  return hexToRgba(panel.backgroundColor ?? RIG_UI.panel, PANEL_OPAQUE_ALPHA);
@@ -18137,12 +17760,12 @@ var TRANSPARENT3, MAX_PANEL_LINES = 420, PANEL_OPAQUE_ALPHA = 255, PANEL_BORDER;
18137
17760
  var init_panels = __esm(() => {
18138
17761
  init_theme2();
18139
17762
  init_text();
18140
- TRANSPARENT3 = RGBA5.fromInts(0, 0, 0, 0);
17763
+ TRANSPARENT3 = RGBA4.fromInts(0, 0, 0, 0);
18141
17764
  PANEL_BORDER = hexToRgba(RIG_UI.border, 255);
18142
17765
  });
18143
17766
 
18144
17767
  // packages/cli/src/app-opentui/render/type-bar.ts
18145
- import { BoxRenderable, InputRenderable, InputRenderableEvents, RGBA as RGBA6, StyledText as StyledText3, TextRenderable as TextRenderable2 } from "@opentui/core";
17768
+ import { BoxRenderable, InputRenderable, InputRenderableEvents, RGBA as RGBA5, StyledText as StyledText2, TextRenderable as TextRenderable2 } from "@opentui/core";
18146
17769
  function createTypeBar(renderer) {
18147
17770
  const background = new BoxRenderable(renderer, {
18148
17771
  id: "typebar-card",
@@ -18289,13 +17912,13 @@ function formatFooter(footer, width) {
18289
17912
  chunks.push(cell4.style(text2));
18290
17913
  used += visibleWidth(text2);
18291
17914
  });
18292
- return new StyledText3(chunks);
17915
+ return new StyledText2(chunks);
18293
17916
  }
18294
17917
  var TYPEBAR_BG, TYPEBAR_BORDER, FOOTER_SEPARATOR = " \xB7 ";
18295
17918
  var init_type_bar = __esm(() => {
18296
17919
  init_theme2();
18297
- TYPEBAR_BG = RGBA6.fromInts(20, 25, 14, 224);
18298
- TYPEBAR_BORDER = RGBA6.fromInts(204, 255, 77, 92);
17920
+ TYPEBAR_BG = RGBA5.fromInts(20, 25, 14, 224);
17921
+ TYPEBAR_BORDER = RGBA5.fromInts(204, 255, 77, 92);
18299
17922
  });
18300
17923
 
18301
17924
  // packages/cli/src/app-opentui/render/native-host.ts
@@ -18303,7 +17926,7 @@ import {
18303
17926
  BoxRenderable as BoxRenderable2,
18304
17927
  CodeRenderable,
18305
17928
  DiffRenderable,
18306
- RGBA as RGBA7,
17929
+ RGBA as RGBA6,
18307
17930
  ScrollBoxRenderable as ScrollBoxRenderable2,
18308
17931
  SyntaxStyle,
18309
17932
  TextRenderable as TextRenderable3,
@@ -18313,19 +17936,19 @@ function rigSyntaxStyle() {
18313
17936
  if (syntaxStyle)
18314
17937
  return syntaxStyle;
18315
17938
  syntaxStyle = SyntaxStyle.fromStyles({
18316
- keyword: { fg: RGBA7.fromHex(RIG_UI.magenta), bold: true },
18317
- string: { fg: RGBA7.fromHex(RIG_UI.lime) },
18318
- number: { fg: RGBA7.fromHex(RIG_UI.cyan) },
18319
- comment: { fg: RGBA7.fromHex(RIG_UI.ink4), italic: true },
18320
- function: { fg: RGBA7.fromHex(RIG_UI.cyan) },
18321
- type: { fg: RGBA7.fromHex(RIG_UI.limeDim) },
18322
- constant: { fg: RGBA7.fromHex(RIG_UI.yellow) },
18323
- default: { fg: RGBA7.fromHex(RIG_UI.ink2) }
17939
+ keyword: { fg: RGBA6.fromHex(RIG_UI.magenta), bold: true },
17940
+ string: { fg: RGBA6.fromHex(RIG_UI.lime) },
17941
+ number: { fg: RGBA6.fromHex(RIG_UI.cyan) },
17942
+ comment: { fg: RGBA6.fromHex(RIG_UI.ink4), italic: true },
17943
+ function: { fg: RGBA6.fromHex(RIG_UI.cyan) },
17944
+ type: { fg: RGBA6.fromHex(RIG_UI.limeDim) },
17945
+ constant: { fg: RGBA6.fromHex(RIG_UI.yellow) },
17946
+ default: { fg: RGBA6.fromHex(RIG_UI.ink2) }
18324
17947
  });
18325
17948
  return syntaxStyle;
18326
17949
  }
18327
17950
  function tableContent(rows) {
18328
- return rows.map((row, rowIndex) => row.map((cell4) => [{ __isChunk: true, text: cell4, fg: RGBA7.fromHex(rowIndex === 0 ? RIG_UI.ink3 : RIG_UI.ink2) }]));
17951
+ return rows.map((row, rowIndex) => row.map((cell4) => [{ __isChunk: true, text: cell4, fg: RGBA6.fromHex(rowIndex === 0 ? RIG_UI.ink3 : RIG_UI.ink2) }]));
18329
17952
  }
18330
17953
  function createNativeHost(renderer) {
18331
17954
  try {
@@ -18853,7 +18476,7 @@ __export(exports_runtime, {
18853
18476
  });
18854
18477
  import { existsSync as existsSync23 } from "fs";
18855
18478
  import { resolve as resolve30 } from "path";
18856
- import { BoxRenderable as BoxRenderable3, RGBA as RGBA8, StyledText as StyledText4, TextRenderable as TextRenderable4, createCliRenderer } from "@opentui/core";
18479
+ import { BoxRenderable as BoxRenderable3, RGBA as RGBA7, StyledText as StyledText3, TextRenderable as TextRenderable4, createCliRenderer } from "@opentui/core";
18857
18480
  function inboxPendingCount(state) {
18858
18481
  const inbox = state.data.inbox;
18859
18482
  if (!inbox || typeof inbox !== "object" || Array.isArray(inbox))
@@ -19022,7 +18645,7 @@ function buildNavStrip(state) {
19022
18645
  chunks.push(otuiBold(styles.yellow(String(badgeCount))));
19023
18646
  }
19024
18647
  });
19025
- return new StyledText4(chunks);
18648
+ return new StyledText3(chunks);
19026
18649
  }
19027
18650
  function requestRender(renderer) {
19028
18651
  renderer.requestRender?.();
@@ -19250,7 +18873,8 @@ async function launchRigOpenTuiApp(options) {
19250
18873
  },
19251
18874
  suspend: () => renderer?.suspend(),
19252
18875
  resume: () => {
19253
- renderer?.resume();
18876
+ if (renderer)
18877
+ resumeRendererClean(renderer);
19254
18878
  renderApp();
19255
18879
  },
19256
18880
  destroy: () => renderer?.destroy()
@@ -19676,8 +19300,7 @@ var init_runtime = __esm(() => {
19676
19300
  init_help();
19677
19301
  init_main();
19678
19302
  init_selectable();
19679
- init_pi_pty_host();
19680
- PRELOADER_BACKDROP = RGBA8.fromHex(RIG_UI.bg);
19303
+ PRELOADER_BACKDROP = RGBA7.fromHex(RIG_UI.bg);
19681
19304
  PRIMARY_NAV = [
19682
19305
  { id: "cockpit", label: "Cockpit", scene: "main", argv: ["main"], enterLabel: "Cockpit", member: ["main"] },
19683
19306
  { id: "runs", label: "Runs", scene: "fleet", argv: ["runs"], enterLabel: "Runs", member: ["fleet", "run-detail", "inspect", "handoff"] },
@@ -19794,10 +19417,10 @@ var init_scroll = __esm(() => {
19794
19417
  // packages/cli/src/app-opentui/react/SceneFrameView.tsx
19795
19418
  import { useEffect as useEffect2, useRef as useRef2 } from "react";
19796
19419
  import { useRenderer as useRenderer2 } from "@opentui/react";
19797
- import { TextAttributes as TextAttributes5 } from "@opentui/core";
19420
+ import { TextAttributes as TextAttributes4 } from "@opentui/core";
19798
19421
  import { jsxDEV, Fragment } from "@opentui/react/jsx-dev-runtime";
19799
19422
  function lineAttributes(line2) {
19800
- return line2?.bold ? TextAttributes5.BOLD : line2?.dim ? TextAttributes5.DIM : 0;
19423
+ return line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
19801
19424
  }
19802
19425
  function lineContent(line2) {
19803
19426
  return line2.styledText ?? line2.text ?? "";
@@ -20180,8 +19803,48 @@ async function launchRigReactApp(options) {
20180
19803
  const store = createAppStore(initialState);
20181
19804
  const events = createAppEventBus();
20182
19805
  let renderer = null;
19806
+ let root = null;
20183
19807
  let runnerPromise = null;
20184
19808
  let suppressHistoryPush = false;
19809
+ let resolveExit = () => {};
19810
+ const appExit = new Promise((resolve31) => {
19811
+ resolveExit = resolve31;
19812
+ });
19813
+ let relaunching = false;
19814
+ const mountRenderer = async () => {
19815
+ renderer = await createCliRenderer2({
19816
+ screenMode: "alternate-screen",
19817
+ exitOnCtrlC: false,
19818
+ targetFps: 30,
19819
+ maxFps: 30,
19820
+ useMouse: true,
19821
+ autoFocus: false,
19822
+ useKittyKeyboard: { disambiguate: true }
19823
+ });
19824
+ renderer.on("destroy", () => {
19825
+ if (!relaunching)
19826
+ resolveExit();
19827
+ });
19828
+ root = createRoot(renderer);
19829
+ root.render(/* @__PURE__ */ jsxDEV4(RigAppProvider, {
19830
+ value: { store, runtime, runAppAction },
19831
+ children: /* @__PURE__ */ jsxDEV4(App, {
19832
+ sceneRenderers: options.sceneRenderers
19833
+ }, undefined, false, undefined, this)
19834
+ }, undefined, false, undefined, this));
19835
+ relaunching = false;
19836
+ };
19837
+ const teardownRenderer = () => {
19838
+ relaunching = true;
19839
+ try {
19840
+ root?.unmount();
19841
+ } catch {}
19842
+ root = null;
19843
+ try {
19844
+ renderer?.destroy();
19845
+ } catch {}
19846
+ renderer = null;
19847
+ };
20185
19848
  const runAppAction = (label, action) => {
20186
19849
  action().catch((error) => {
20187
19850
  const n = normalizeError(error);
@@ -20223,8 +19886,10 @@ async function launchRigReactApp(options) {
20223
19886
  runnerPromise ??= options.initializeRuntime();
20224
19887
  return runnerPromise;
20225
19888
  },
20226
- suspend: () => renderer?.suspend(),
20227
- resume: () => renderer?.resume(),
19889
+ suspend: () => teardownRenderer(),
19890
+ resume: async () => {
19891
+ await mountRenderer();
19892
+ },
20228
19893
  destroy: () => renderer?.destroy()
20229
19894
  };
20230
19895
  events.subscribe((event) => store.patch(reduceAppEvent(store.getState(), event)));
@@ -20232,15 +19897,7 @@ async function launchRigReactApp(options) {
20232
19897
  if (typeof tick.unref === "function") {
20233
19898
  tick.unref();
20234
19899
  }
20235
- renderer = await createCliRenderer2({
20236
- screenMode: "alternate-screen",
20237
- exitOnCtrlC: false,
20238
- targetFps: 30,
20239
- maxFps: 30,
20240
- useMouse: true,
20241
- autoFocus: false,
20242
- useKittyKeyboard: { disambiguate: true }
20243
- });
19900
+ await mountRenderer();
20244
19901
  let liveEvents = null;
20245
19902
  const canAutoRefresh = (state) => {
20246
19903
  if (state.status === "action" || state.status === "loading")
@@ -20271,12 +19928,6 @@ async function launchRigReactApp(options) {
20271
19928
  }, FALLBACK_REFRESH_MS);
20272
19929
  if (typeof poll.unref === "function")
20273
19930
  poll.unref();
20274
- createRoot(renderer).render(/* @__PURE__ */ jsxDEV4(RigAppProvider, {
20275
- value: { store, runtime, runAppAction },
20276
- children: /* @__PURE__ */ jsxDEV4(App, {
20277
- sceneRenderers: options.sceneRenderers
20278
- }, undefined, false, undefined, this)
20279
- }, undefined, false, undefined, this));
20280
19931
  queueMicrotask(() => {
20281
19932
  (async () => {
20282
19933
  try {
@@ -20304,14 +19955,11 @@ async function launchRigReactApp(options) {
20304
19955
  }
20305
19956
  })();
20306
19957
  });
20307
- await new Promise((resolve31) => {
20308
- renderer?.on("destroy", () => {
20309
- clearInterval(tick);
20310
- clearInterval(poll);
20311
- liveEvents?.close();
20312
- resolve31();
20313
- });
20314
- });
19958
+ await appExit;
19959
+ clearInterval(tick);
19960
+ clearInterval(poll);
19961
+ liveEvents?.close();
19962
+ teardownRenderer();
20315
19963
  }
20316
19964
  var POLLABLE_SCENES;
20317
19965
  var init_launch = __esm(() => {
@@ -20325,7 +19973,7 @@ var init_launch = __esm(() => {
20325
19973
 
20326
19974
  // packages/cli/src/app-opentui/bootstrap.ts
20327
19975
  import { existsSync as existsSync24, readFileSync as readFileSync15 } from "fs";
20328
- import { basename as basename6, resolve as resolve31 } from "path";
19976
+ import { basename as basename5, resolve as resolve31 } from "path";
20329
19977
 
20330
19978
  // packages/cli/src/app-opentui/adapters/command.ts
20331
19979
  init_command_pty_host();
@@ -27256,7 +26904,7 @@ function parsePolicyMode2(value) {
27256
26904
  function resolveProjectRoot2(input) {
27257
26905
  if (input.envProjectRoot)
27258
26906
  return resolve31(input.cwd, input.envProjectRoot);
27259
- const execName = basename6(input.execPath).toLowerCase();
26907
+ const execName = basename5(input.execPath).toLowerCase();
27260
26908
  const execCandidates = execName === "rig" || execName === "rig.exe" ? [resolve31(input.execPath, "..", "..")] : [];
27261
26909
  const candidates = [input.cwd, ...execCandidates, resolve31(input.importDir, "..")];
27262
26910
  for (const candidate of candidates) {