@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)));
@@ -8882,6 +8897,14 @@ var init__operator_surface = __esm(() => {
8882
8897
  });
8883
8898
 
8884
8899
  // packages/cli/src/commands/_pi-frontend.ts
8900
+ var exports__pi_frontend = {};
8901
+ __export(exports__pi_frontend, {
8902
+ shouldRequireOperatorTranscript: () => shouldRequireOperatorTranscript,
8903
+ runWithProcessExitGuard: () => runWithProcessExitGuard,
8904
+ missingOperatorTranscriptMessage: () => missingOperatorTranscriptMessage,
8905
+ buildOperatorPiEnv: () => buildOperatorPiEnv,
8906
+ attachRunBundledPiFrontend: () => attachRunBundledPiFrontend
8907
+ });
8885
8908
  import { existsSync as existsSync14, mkdirSync as mkdirSync9, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
8886
8909
  import { homedir as homedir6, tmpdir } from "os";
8887
8910
  import { join as join3 } from "path";
@@ -8994,6 +9017,29 @@ function installRigPiTheme() {
8994
9017
  }
8995
9018
  } catch {}
8996
9019
  }
9020
+ async function runWithProcessExitGuard(body) {
9021
+ const realExit = process.exit;
9022
+ let exitCode = 0;
9023
+ let signalQuit = () => {};
9024
+ const quit = new Promise((resolve22) => {
9025
+ signalQuit = resolve22;
9026
+ });
9027
+ const guardedExit = (code) => {
9028
+ exitCode = typeof code === "number" ? code : 0;
9029
+ signalQuit();
9030
+ return;
9031
+ };
9032
+ process.exit = guardedExit;
9033
+ try {
9034
+ await Promise.race([Promise.resolve().then(body), quit]);
9035
+ } finally {
9036
+ process.exit = realExit;
9037
+ }
9038
+ return exitCode;
9039
+ }
9040
+ function runPiMainReturningOnQuit(args, options) {
9041
+ return runWithProcessExitGuard(() => runPiMain(args, options));
9042
+ }
8997
9043
  async function attachRunBundledPiFrontend(context, input) {
8998
9044
  const tempSessionDir = mkdtempSync(join3(tmpdir(), "rig-pi-frontend-sessions-"));
8999
9045
  const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
@@ -9009,16 +9055,20 @@ async function attachRunBundledPiFrontend(context, input) {
9009
9055
  };
9010
9056
  let detached = false;
9011
9057
  try {
9012
- await runPiMain([
9058
+ const piArgs = [
9013
9059
  "--offline",
9014
9060
  "--no-extensions",
9015
9061
  "--no-skills",
9016
9062
  "--no-prompt-templates",
9017
9063
  "--no-context-files",
9018
9064
  ...sessionFileArg
9019
- ], {
9020
- extensionFactories: [piRigExtensionFactory]
9021
- });
9065
+ ];
9066
+ const piOptions = { extensionFactories: [piRigExtensionFactory] };
9067
+ if (input.returnOnQuit) {
9068
+ await runPiMainReturningOnQuit(piArgs, piOptions);
9069
+ } else {
9070
+ await runPiMain(piArgs, piOptions);
9071
+ }
9022
9072
  detached = true;
9023
9073
  } finally {
9024
9074
  restoreEnv();
@@ -14284,390 +14334,6 @@ async function stopFleetRun(ctx, runId) {
14284
14334
  }
14285
14335
  var init_fleet = () => {};
14286
14336
 
14287
- // packages/cli/src/app-opentui/pi-pty-host.ts
14288
- import { fileURLToPath as fileURLToPath2 } from "url";
14289
- import { basename as basename4 } from "path";
14290
- import { RGBA, StyledText, TextAttributes } from "@opentui/core";
14291
- import { Terminal as XtermTerminal2 } from "@xterm/headless";
14292
- function clampCols2(cols) {
14293
- return Math.max(MIN_COLS2, Math.trunc(cols || 100));
14294
- }
14295
- function clampRows2(rows) {
14296
- return Math.max(MIN_ROWS2, Math.min(MAX_ROWS2, Math.trunc(rows || 35)));
14297
- }
14298
- function rgbaFromXtermColor(mode, value) {
14299
- if (mode === 1 || mode === 2 || mode === XTERM_COLOR_MODE_INDEXED_16 || mode === XTERM_COLOR_MODE_INDEXED_256) {
14300
- return RGBA.fromIndex(value);
14301
- }
14302
- if (mode === 3 || mode === XTERM_COLOR_MODE_RGB) {
14303
- return RGBA.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255);
14304
- }
14305
- return;
14306
- }
14307
- function textAttributesFromCell(cell) {
14308
- let attributes = TextAttributes.NONE;
14309
- if (cell.isBold())
14310
- attributes |= TextAttributes.BOLD;
14311
- if (cell.isDim())
14312
- attributes |= TextAttributes.DIM;
14313
- if (cell.isItalic())
14314
- attributes |= TextAttributes.ITALIC;
14315
- if (cell.isUnderline())
14316
- attributes |= TextAttributes.UNDERLINE;
14317
- if (cell.isBlink())
14318
- attributes |= TextAttributes.BLINK;
14319
- if (cell.isInverse())
14320
- attributes |= TextAttributes.INVERSE;
14321
- if (cell.isInvisible())
14322
- attributes |= TextAttributes.HIDDEN;
14323
- if (cell.isStrikethrough())
14324
- attributes |= TextAttributes.STRIKETHROUGH;
14325
- return attributes;
14326
- }
14327
- function sameRgba(a, b) {
14328
- if (!a && !b)
14329
- return true;
14330
- return Boolean(a && b && a.equals(b));
14331
- }
14332
- function sameStyle(a, b) {
14333
- return sameRgba(a.fg, b.fg) && sameRgba(a.bg, b.bg) && (a.attributes ?? 0) === (b.attributes ?? 0);
14334
- }
14335
- function styleFromCell(cell) {
14336
- return {
14337
- fg: rgbaFromXtermColor(cell.getFgColorMode(), cell.getFgColor()),
14338
- bg: rgbaFromXtermColor(cell.getBgColorMode(), cell.getBgColor()),
14339
- attributes: textAttributesFromCell(cell)
14340
- };
14341
- }
14342
- function lineToStyledText(line, cols) {
14343
- if (!line)
14344
- return new StyledText([{ __isChunk: true, text: "" }]);
14345
- const chunks = [];
14346
- let run = "";
14347
- let runStyle = null;
14348
- const flush = () => {
14349
- if (!run)
14350
- return;
14351
- chunks.push({
14352
- __isChunk: true,
14353
- text: run,
14354
- ...runStyle?.fg ? { fg: runStyle.fg } : {},
14355
- ...runStyle?.bg ? { bg: runStyle.bg } : {},
14356
- ...(runStyle?.attributes ?? 0) !== 0 ? { attributes: runStyle.attributes } : {}
14357
- });
14358
- run = "";
14359
- };
14360
- for (let index = 0;index < cols; index += 1) {
14361
- const cell = line.getCell(index);
14362
- if (!cell) {
14363
- if (runStyle !== null)
14364
- flush();
14365
- runStyle = null;
14366
- run += " ";
14367
- continue;
14368
- }
14369
- if (cell.getWidth() === 0)
14370
- continue;
14371
- const style = styleFromCell(cell);
14372
- if (!runStyle || !sameStyle(runStyle, style)) {
14373
- flush();
14374
- runStyle = style;
14375
- }
14376
- run += cell.getChars() || " ";
14377
- }
14378
- flush();
14379
- return new StyledText(chunks.length > 0 ? chunks : [{ __isChunk: true, text: "" }]);
14380
- }
14381
- function childCommandPrefix2() {
14382
- const execName = basename4(process.execPath).toLowerCase();
14383
- const currentEntry = process.argv[1];
14384
- if (execName === "bun" || execName === "bun.exe") {
14385
- return currentEntry ? [process.execPath, currentEntry, "__opentui-pi-host"] : [process.execPath, fallbackChildScriptPath];
14386
- }
14387
- return [process.execPath, "__opentui-pi-host"];
14388
- }
14389
- function withEnv2(base) {
14390
- const env = {};
14391
- for (const [key, value] of Object.entries(base ?? process.env)) {
14392
- if (key === "RIG_PLAIN" || key === "RIG_CLI_PLAIN_HELP" || key === "NO_COLOR")
14393
- continue;
14394
- if (typeof value === "string")
14395
- env[key] = value;
14396
- }
14397
- return {
14398
- ...env,
14399
- TERM: "xterm-256color",
14400
- COLORTERM: "truecolor",
14401
- FORCE_COLOR: env.FORCE_COLOR ?? "1",
14402
- RIG_OPENTUI_PI_HOST: "1"
14403
- };
14404
- }
14405
- async function startPiPtyHost(options) {
14406
- activeHost2?.dispose("replace");
14407
- const host = new PiPtyHost(options);
14408
- activeHost2 = host;
14409
- await host.start();
14410
- return host;
14411
- }
14412
-
14413
- class PiPtyHost {
14414
- runId;
14415
- projectRoot;
14416
- onSnapshot;
14417
- onExit;
14418
- onError;
14419
- terminal;
14420
- disposables = [];
14421
- decoder = new TextDecoder("utf-8");
14422
- proc = null;
14423
- pty = null;
14424
- status = "starting";
14425
- cols;
14426
- rows;
14427
- message = "starting bundled Pi";
14428
- lastResizeError = null;
14429
- exitCode;
14430
- signal;
14431
- notifyTimer = null;
14432
- lastStreamKey = "";
14433
- _disposed = false;
14434
- constructor(options) {
14435
- this.runId = options.runId;
14436
- this.projectRoot = options.projectRoot;
14437
- this.cols = clampCols2(options.cols);
14438
- this.rows = clampRows2(options.rows);
14439
- this.onSnapshot = options.onSnapshot;
14440
- this.onExit = options.onExit;
14441
- this.onError = options.onError;
14442
- this.terminal = new XtermTerminal2({
14443
- allowProposedApi: true,
14444
- cols: this.cols,
14445
- rows: this.rows,
14446
- scrollback: 1000
14447
- });
14448
- this.registerTerminalResponders();
14449
- this.disposables.push(this.terminal.onWriteParsed(() => {
14450
- if (this._disposed)
14451
- return;
14452
- const snapshot = this.createSnapshot();
14453
- const key = snapshot.lines.join(`
14454
- `);
14455
- if (key === this.lastStreamKey)
14456
- return;
14457
- this.lastStreamKey = key;
14458
- this.onSnapshot?.(snapshot);
14459
- }));
14460
- }
14461
- get disposed() {
14462
- return this._disposed;
14463
- }
14464
- get snapshot() {
14465
- return this.createSnapshot();
14466
- }
14467
- async start() {
14468
- if (this._disposed)
14469
- throw new Error("Pi PTY host is disposed.");
14470
- if (typeof Bun.Terminal !== "function") {
14471
- throw new Error("Bun native PTY is unavailable in this runtime. Pi host requires Bun.Terminal.");
14472
- }
14473
- const spawnOptions = {
14474
- cwd: this.projectRoot,
14475
- env: withEnv2(process.env),
14476
- terminal: {
14477
- cols: this.cols,
14478
- rows: this.rows,
14479
- name: "xterm-256color",
14480
- data: (_terminal, data) => this.handlePtyData(data)
14481
- }
14482
- };
14483
- const proc = Bun.spawn([
14484
- ...childCommandPrefix2(),
14485
- "--run-id",
14486
- this.runId,
14487
- "--project-root",
14488
- this.projectRoot
14489
- ], spawnOptions);
14490
- if (!proc.terminal)
14491
- throw new Error("Bun did not attach a terminal to the bundled Pi child process.");
14492
- this.proc = proc;
14493
- this.pty = proc.terminal;
14494
- this.status = "running";
14495
- this.message = "bundled Pi running inside this app";
14496
- this.emitSnapshotSoon(0);
14497
- proc.exited.then((exitCode) => {
14498
- if (this._disposed)
14499
- return;
14500
- this.status = exitCode === 0 ? "exited" : "failed";
14501
- this.exitCode = exitCode;
14502
- this.signal = null;
14503
- this.message = exitCode === 0 ? "bundled Pi exited" : `bundled Pi exited with code ${exitCode}`;
14504
- const snapshot = this.createSnapshot();
14505
- this.onSnapshot?.(snapshot);
14506
- this.onExit?.(snapshot);
14507
- if (activeHost2 === this)
14508
- activeHost2 = null;
14509
- this.dispose("exit", { kill: false, notify: false });
14510
- }).catch((error) => {
14511
- if (this._disposed)
14512
- return;
14513
- this.status = "failed";
14514
- this.message = error instanceof Error ? error.message : String(error);
14515
- const snapshot = this.createSnapshot();
14516
- this.onSnapshot?.(snapshot);
14517
- this.onError?.(error, snapshot);
14518
- if (activeHost2 === this)
14519
- activeHost2 = null;
14520
- this.dispose("error", { kill: false, notify: false });
14521
- });
14522
- }
14523
- write(data) {
14524
- if (this._disposed || !this.pty)
14525
- return;
14526
- try {
14527
- this.pty.write(data);
14528
- } catch (error) {
14529
- this.status = "failed";
14530
- this.message = error instanceof Error ? error.message : String(error);
14531
- const snapshot = this.createSnapshot();
14532
- this.onSnapshot?.(snapshot);
14533
- this.onError?.(error, snapshot);
14534
- }
14535
- }
14536
- resize(cols, rows) {
14537
- const nextCols = clampCols2(cols);
14538
- const nextRows = clampRows2(rows);
14539
- if (nextCols === this.cols && nextRows === this.rows)
14540
- return;
14541
- this.cols = nextCols;
14542
- this.rows = nextRows;
14543
- this.terminal.resize(nextCols, nextRows);
14544
- try {
14545
- this.pty?.resize(nextCols, nextRows);
14546
- this.lastResizeError = null;
14547
- } catch (error) {
14548
- this.lastResizeError = error instanceof Error ? error.message : String(error);
14549
- }
14550
- this.emitSnapshotSoon(0);
14551
- }
14552
- detach() {
14553
- this.dispose("detach");
14554
- return this.createSnapshot("detached from bundled Pi");
14555
- }
14556
- dispose(reason = "dispose", options = {}) {
14557
- if (this._disposed)
14558
- return;
14559
- this._disposed = true;
14560
- if (this.notifyTimer)
14561
- clearTimeout(this.notifyTimer);
14562
- this.notifyTimer = null;
14563
- for (const disposable of this.disposables.splice(0)) {
14564
- try {
14565
- disposable.dispose();
14566
- } catch {}
14567
- }
14568
- if (options.kill !== false) {
14569
- try {
14570
- this.proc?.kill("SIGTERM");
14571
- } catch {}
14572
- }
14573
- try {
14574
- this.pty?.close();
14575
- } catch {}
14576
- try {
14577
- this.terminal.dispose();
14578
- } catch {}
14579
- if (activeHost2 === this)
14580
- activeHost2 = null;
14581
- if (options.notify) {
14582
- this.message = reason;
14583
- this.onSnapshot?.(this.createSnapshot(reason));
14584
- }
14585
- }
14586
- handlePtyData(data) {
14587
- if (this._disposed)
14588
- return;
14589
- const text2 = this.decoder.decode(data, { stream: true });
14590
- this.respondToRawTerminalQueries(text2);
14591
- this.terminal.write(data);
14592
- }
14593
- emitSnapshotSoon(delayMs = SNAPSHOT_DELAY_MS2) {
14594
- if (this._disposed || this.notifyTimer)
14595
- return;
14596
- this.notifyTimer = setTimeout(() => {
14597
- this.notifyTimer = null;
14598
- if (this._disposed)
14599
- return;
14600
- this.onSnapshot?.(this.createSnapshot());
14601
- }, delayMs);
14602
- }
14603
- createSnapshot(message2 = this.message) {
14604
- const buffer = this.terminal.buffer.active;
14605
- const end = buffer.length;
14606
- const start = Math.max(0, end - MAX_SNAPSHOT_LINES2);
14607
- const styledStart = Math.max(start, end - this.rows - STYLED_SNAPSHOT_MARGIN);
14608
- const lines = [];
14609
- const styledLines = [];
14610
- for (let row = start;row < end; row += 1) {
14611
- const line = buffer.getLine(row);
14612
- lines.push((line?.translateToString(false) ?? "").slice(0, this.cols));
14613
- if (row >= styledStart)
14614
- styledLines[lines.length - 1] = lineToStyledText(line, this.cols);
14615
- }
14616
- while (lines.length < this.rows) {
14617
- lines.push("");
14618
- styledLines[lines.length - 1] = new StyledText([{ __isChunk: true, text: "" }]);
14619
- }
14620
- const resolvedMessage = this.lastResizeError ? `resize degraded: ${this.lastResizeError}` : message2;
14621
- return {
14622
- runId: this.runId,
14623
- status: this.status,
14624
- cols: this.cols,
14625
- rows: this.rows,
14626
- lines,
14627
- styledLines,
14628
- message: resolvedMessage,
14629
- ...this.lastResizeError ? { resizeError: this.lastResizeError } : {},
14630
- ...this.exitCode !== undefined ? { exitCode: this.exitCode } : {},
14631
- ...this.signal !== undefined ? { signal: this.signal } : {}
14632
- };
14633
- }
14634
- registerTerminalResponders() {
14635
- this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "c" }, (params) => {
14636
- if (params.length === 0 || params[0] === 0)
14637
- this.write("\x1B[?62;22c");
14638
- return false;
14639
- }));
14640
- this.disposables.push(this.terminal.parser.registerCsiHandler({ prefix: ">", final: "c" }, (params) => {
14641
- if (params.length === 0 || params[0] === 0)
14642
- this.write("\x1B[>0;0;0c");
14643
- return false;
14644
- }));
14645
- this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "n" }, (params) => {
14646
- if (params[0] === 5) {
14647
- this.write("\x1B[0n");
14648
- } else if (params[0] === 6) {
14649
- const row = this.terminal.buffer.active.cursorY + 1;
14650
- const col = this.terminal.buffer.active.cursorX + 1;
14651
- this.write(`\x1B[${row};${col}R`);
14652
- }
14653
- return false;
14654
- }));
14655
- }
14656
- respondToRawTerminalQueries(text2) {
14657
- if (!text2)
14658
- return;
14659
- const decrqm = /\x1b\[\?(\d+)\$p/g;
14660
- let match;
14661
- while ((match = decrqm.exec(text2)) !== null) {
14662
- this.write(`\x1B[?${match[1]};2$y`);
14663
- }
14664
- }
14665
- }
14666
- 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;
14667
- var init_pi_pty_host = __esm(() => {
14668
- fallbackChildScriptPath = fileURLToPath2(new URL("./pi-host-child.ts", import.meta.url));
14669
- });
14670
-
14671
14337
  // packages/cli/src/app-opentui/adapters/inspect.ts
14672
14338
  var exports_inspect = {};
14673
14339
  __export(exports_inspect, {
@@ -14890,14 +14556,6 @@ function recordStep(ctx, runId, label, emitLabel) {
14890
14556
  });
14891
14557
  emitProgress(ctx, emitLabel, label);
14892
14558
  }
14893
- function settleSteps(ctx, runId) {
14894
- const now = Date.now();
14895
- const previous = currentAttachState(ctx, runId);
14896
- if (!previous?.steps)
14897
- return;
14898
- const steps = previous.steps.map((step) => step.status === "running" ? { ...step, status: "done", endedAtMs: step.endedAtMs ?? now } : step);
14899
- patchData(ctx, { piAttach: { ...previous, steps } });
14900
- }
14901
14559
  async function preparePiAttachHandoff(ctx, runId) {
14902
14560
  const cleanRunId = runId.trim();
14903
14561
  const label = `Preparing Pi ${cleanRunId.slice(0, 8)}`;
@@ -14945,85 +14603,41 @@ async function attachRunWithBundledPi(ctx, runId) {
14945
14603
  throw error;
14946
14604
  }
14947
14605
  emitStarted(ctx, label, { piAttach: { runId: cleanRunId, status: "entering-pi" } });
14948
- patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "starting bundled Pi", steps: [] } });
14606
+ patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "handing the terminal to worker Pi\u2026" } });
14607
+ const projectRoot = projectRootOf(ctx);
14608
+ const outputMode = ctx.rig?.outputMode ?? "text";
14609
+ await releaseRendererForExternalTui(ctx);
14610
+ let outcome = null;
14611
+ let attachError = null;
14949
14612
  try {
14950
- recordStep(ctx, cleanRunId, "connecting to server and fetching run transcript", label);
14951
- await preparePiAttachHandoff(ctx, cleanRunId).catch((error) => {
14952
- patchData(ctx, { lastRefreshError: normalizeAppError(error).message });
14953
- return null;
14954
- });
14955
- recordStep(ctx, cleanRunId, "spawning Bun PTY host for bundled Pi", label);
14956
- const projectRoot = projectRootOf(ctx);
14957
- const cols = typeof process.stdout.columns === "number" ? process.stdout.columns : 100;
14958
- const rows = Math.max(1, (typeof process.stdout.rows === "number" ? process.stdout.rows : 35) - 1);
14959
- const patchTerminal = (snapshot) => {
14960
- const previousSteps = currentAttachState(ctx, cleanRunId)?.steps;
14961
- const failed = snapshot.status === "failed";
14962
- const reason = failed ? snapshot.message ?? `bundled Pi exited with code ${snapshot.exitCode ?? "unknown"}` : undefined;
14963
- patchData(ctx, {
14964
- piTerminal: snapshot,
14965
- piAttach: {
14966
- runId: cleanRunId,
14967
- status: failed ? "failed" : snapshot.status === "exited" ? "returned" : "entering-pi",
14968
- message: snapshot.message ?? "bundled Pi running",
14969
- result: { runId: cleanRunId, status: snapshot.status, exitCode: snapshot.exitCode },
14970
- ...previousSteps ? { steps: previousSteps } : {},
14971
- ...reason ? { failureReason: reason } : {}
14972
- }
14973
- });
14974
- };
14975
- recordStep(ctx, cleanRunId, "starting bundled Pi", label);
14976
- await startPiPtyHost({
14977
- runId: cleanRunId,
14978
- projectRoot,
14979
- cols,
14980
- rows,
14981
- onSnapshot: (snapshot) => {
14982
- if (snapshot.status === "running" && snapshot.lines.some((entry) => entry.trim().length > 0)) {
14983
- settleSteps(ctx, cleanRunId);
14984
- }
14985
- patchTerminal(snapshot);
14986
- },
14987
- onExit: (snapshot) => {
14988
- patchTerminal(snapshot);
14989
- if (snapshot.status === "failed") {
14990
- Promise.resolve().then(() => (init_inspect2(), exports_inspect)).then(({ loadInspect: loadInspect2 }) => loadInspect2(ctx, cleanRunId)).catch(() => null);
14991
- emitFailed(ctx, label, new Error(snapshot.message ?? `bundled Pi exited with code ${snapshot.exitCode ?? "unknown"}`), { runId: cleanRunId, result: { status: snapshot.status, exitCode: snapshot.exitCode } });
14992
- return;
14993
- }
14994
- emitCompleted(ctx, label, { runId: cleanRunId, result: { status: snapshot.status, exitCode: snapshot.exitCode } });
14995
- ctx.emit({ type: "scene.change", scene: "fleet", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } } });
14996
- refreshFleet(ctx).catch((error) => emitFailed(ctx, "Refresh runs", error));
14997
- },
14998
- onError: (error, snapshot) => {
14999
- patchTerminal(snapshot);
15000
- emitFailed(ctx, label, error, { runId: cleanRunId });
15001
- }
15002
- });
15003
- const record = {
15004
- runId: cleanRunId,
15005
- status: "running",
15006
- rendered: "bundled Pi embedded via Bun.Terminal + @xterm/headless"
15007
- };
15008
- emitProgress(ctx, label, "bundled Pi is running", { piAttach: { runId: cleanRunId, status: "entering-pi", message: "bundled Pi running", result: record } });
15009
- return record;
14613
+ const { attachRunBundledPiFrontend: attachRunBundledPiFrontend2 } = await Promise.resolve().then(() => (init__pi_frontend(), exports__pi_frontend));
14614
+ outcome = await attachRunBundledPiFrontend2({ projectRoot, outputMode }, { runId: cleanRunId, returnOnQuit: true });
15010
14615
  } catch (error) {
15011
- const reason = error instanceof Error ? error.message : String(error);
14616
+ attachError = error;
14617
+ } finally {
14618
+ await resumeRendererAfterExternalTui(ctx);
14619
+ }
14620
+ if (attachError) {
14621
+ const reason = normalizeAppError(attachError).message;
15012
14622
  patchData(ctx, {
15013
- piAttach: {
15014
- runId: cleanRunId,
15015
- status: "failed",
15016
- message: reason,
15017
- failureReason: reason,
15018
- ...currentAttachState(ctx, cleanRunId)?.steps ? { steps: currentAttachState(ctx, cleanRunId).steps } : {}
15019
- }
14623
+ piTerminal: undefined,
14624
+ piAttach: { runId: cleanRunId, status: "failed", message: reason, failureReason: reason }
15020
14625
  });
15021
- emitFailed(ctx, label, error, { runId: cleanRunId });
15022
- throw error;
14626
+ Promise.resolve().then(() => (init_inspect2(), exports_inspect)).then(({ loadInspect: loadInspect2 }) => loadInspect2(ctx, cleanRunId)).catch(() => null);
14627
+ emitFailed(ctx, label, attachError, { runId: cleanRunId });
14628
+ throw attachError;
15023
14629
  }
14630
+ const detached = outcome?.detached === true;
14631
+ patchData(ctx, {
14632
+ piTerminal: undefined,
14633
+ piAttach: { runId: cleanRunId, status: "returned", message: detached ? "detached from worker Pi" : "returned from worker Pi" }
14634
+ });
14635
+ emitCompleted(ctx, label, { runId: cleanRunId, result: { runId: cleanRunId, detached } });
14636
+ ctx.emit({ type: "scene.change", scene: "fleet", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } } });
14637
+ refreshFleet(ctx).catch((error) => emitFailed(ctx, "Refresh runs", error));
14638
+ return { runId: cleanRunId, status: "returned", detached };
15024
14639
  }
15025
14640
  var init_pi_attach = __esm(() => {
15026
- init_pi_pty_host();
15027
14641
  init_fleet();
15028
14642
  });
15029
14643
  // packages/cli/src/app-opentui/command-pty-host.ts
@@ -17038,7 +16652,7 @@ import {
17038
16652
  dim as otuiDim,
17039
16653
  fg as otuiFg,
17040
16654
  t,
17041
- TextAttributes as TextAttributes2
16655
+ TextAttributes
17042
16656
  } from "@opentui/core";
17043
16657
  var RIG_UI = {
17044
16658
  bg: "#070809",
@@ -17140,7 +16754,7 @@ function statusLabel(status) {
17140
16754
  }
17141
16755
 
17142
16756
  // packages/cli/src/app-opentui/drone.ts
17143
- import { RGBA as RGBA2, StyledText as StyledText2, TextAttributes as TextAttributes3 } from "@opentui/core";
16757
+ import { RGBA, StyledText, TextAttributes as TextAttributes2 } from "@opentui/core";
17144
16758
  var MINI_DRONE = [
17145
16759
  "(!!!) (!!!)",
17146
16760
  " \\%==%/ ",
@@ -17156,13 +16770,13 @@ var LEAD_MARK = [
17156
16770
  var BLADE_FRAMES2 = ["---", "\\\\\\", "|||", "///"];
17157
16771
  var EYE_FRAMES2 = ["o", "@", "\u2022", "."];
17158
16772
  var COLOR = {
17159
- body: RGBA2.fromHex(RIG_UI.lime),
17160
- mini: RGBA2.fromHex(RIG_UI.limeDim),
17161
- rotor: RGBA2.fromHex(RIG_UI.cyan),
17162
- path: RGBA2.fromHex(RIG_UI.cyan),
17163
- eye: RGBA2.fromHex(RIG_UI.ink),
17164
- dim: RGBA2.fromHex(RIG_UI.ink4),
17165
- ink: RGBA2.fromHex(RIG_UI.ink2)
16773
+ body: RGBA.fromHex(RIG_UI.lime),
16774
+ mini: RGBA.fromHex(RIG_UI.limeDim),
16775
+ rotor: RGBA.fromHex(RIG_UI.cyan),
16776
+ path: RGBA.fromHex(RIG_UI.cyan),
16777
+ eye: RGBA.fromHex(RIG_UI.ink),
16778
+ dim: RGBA.fromHex(RIG_UI.ink4),
16779
+ ink: RGBA.fromHex(RIG_UI.ink2)
17166
16780
  };
17167
16781
  function bladeForTick(tick, phase = 0) {
17168
16782
  return BLADE_FRAMES2[(Math.floor(tick / 3) + phase) % BLADE_FRAMES2.length];
@@ -17172,12 +16786,12 @@ function eyeForTick(tick, phase = 0) {
17172
16786
  return pulse > 0.55 ? EYE_FRAMES2[1] : pulse > 0.1 ? EYE_FRAMES2[0] : pulse > -0.45 ? EYE_FRAMES2[2] : EYE_FRAMES2[3];
17173
16787
  }
17174
16788
  function chunk(text2, fg2, bold2 = false, dim = false) {
17175
- let attributes = TextAttributes3.NONE;
16789
+ let attributes = TextAttributes2.NONE;
17176
16790
  if (bold2)
17177
- attributes |= TextAttributes3.BOLD;
16791
+ attributes |= TextAttributes2.BOLD;
17178
16792
  if (dim)
17179
- attributes |= TextAttributes3.DIM;
17180
- return { __isChunk: true, text: text2, fg: fg2, ...attributes !== TextAttributes3.NONE ? { attributes } : {} };
16793
+ attributes |= TextAttributes2.DIM;
16794
+ return { __isChunk: true, text: text2, fg: fg2, ...attributes !== TextAttributes2.NONE ? { attributes } : {} };
17181
16795
  }
17182
16796
  function styledLine(text2, colorFor) {
17183
16797
  const chunks = [];
@@ -17198,7 +16812,7 @@ function styledLine(text2, colorFor) {
17198
16812
  run += char;
17199
16813
  }
17200
16814
  flush();
17201
- return new StyledText2(chunks.length > 0 ? chunks : [chunk("", COLOR.ink)]);
16815
+ return new StyledText(chunks.length > 0 ? chunks : [chunk("", COLOR.ink)]);
17202
16816
  }
17203
16817
  function droneColor(char) {
17204
16818
  if (char === "?" || char === "o" || char === "@" || char === "\u2022")
@@ -0,0 +1,16 @@
1
+ import type { CliRenderer } from "@opentui/core";
2
+ /** Resume the OpenTUI renderer after an external full-screen TUI (native Pi)
3
+ * owned the terminal.
4
+ *
5
+ * A plain `renderer.resume()` diff-renders against a now-stale frame buffer and
6
+ * leaves the external program's screen content behind: OpenTUI's `resume()` only
7
+ * re-runs `setupTerminal` (re-enter alt-screen + clear + force a full repaint)
8
+ * when its internal `pendingSuspendedTerminalSetup` flag is set — and the
9
+ * suspend path used for shelling out does NOT set it, so a plain resume takes
10
+ * the `resumeRenderer` fast path and the screen stays corrupted (Pi's exit
11
+ * banner, a misplaced cursor, the type-bar rule painted at the top).
12
+ *
13
+ * We force the flag so `resume()` does a clean terminal re-setup, then request a
14
+ * full render. The `as`-casts reach two @opentui/core internals; guarded so a
15
+ * version that renames them degrades to a plain resume rather than throwing. */
16
+ export declare function resumeRendererClean(renderer: CliRenderer): void;