@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.
@@ -65,6 +65,21 @@ function patchData(ctx, data) {
65
65
  function patchFooter(ctx, footer) {
66
66
  ctx.emit({ type: "footer.patch", footer });
67
67
  }
68
+ async function releaseRendererForExternalTui(ctx) {
69
+ const renderer = ctx.renderer;
70
+ if (!renderer)
71
+ return;
72
+ if (renderer.suspend) {
73
+ await renderer.suspend();
74
+ return;
75
+ }
76
+ if (renderer.destroy) {
77
+ await renderer.destroy();
78
+ }
79
+ }
80
+ async function resumeRendererAfterExternalTui(ctx) {
81
+ await ctx.renderer?.resume?.();
82
+ }
68
83
  function arrayFromPayload(value) {
69
84
  if (Array.isArray(value)) {
70
85
  return value.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
@@ -1714,6 +1729,14 @@ var init__async_ui = __esm(() => {
1714
1729
  });
1715
1730
 
1716
1731
  // packages/cli/src/commands/_pi-frontend.ts
1732
+ var exports__pi_frontend = {};
1733
+ __export(exports__pi_frontend, {
1734
+ shouldRequireOperatorTranscript: () => shouldRequireOperatorTranscript,
1735
+ runWithProcessExitGuard: () => runWithProcessExitGuard,
1736
+ missingOperatorTranscriptMessage: () => missingOperatorTranscriptMessage,
1737
+ buildOperatorPiEnv: () => buildOperatorPiEnv,
1738
+ attachRunBundledPiFrontend: () => attachRunBundledPiFrontend
1739
+ });
1717
1740
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, mkdtempSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
1718
1741
  import { homedir as homedir2, tmpdir } from "os";
1719
1742
  import { join } from "path";
@@ -1826,6 +1849,29 @@ function installRigPiTheme() {
1826
1849
  }
1827
1850
  } catch {}
1828
1851
  }
1852
+ async function runWithProcessExitGuard(body) {
1853
+ const realExit = process.exit;
1854
+ let exitCode = 0;
1855
+ let signalQuit = () => {};
1856
+ const quit = new Promise((resolve3) => {
1857
+ signalQuit = resolve3;
1858
+ });
1859
+ const guardedExit = (code) => {
1860
+ exitCode = typeof code === "number" ? code : 0;
1861
+ signalQuit();
1862
+ return;
1863
+ };
1864
+ process.exit = guardedExit;
1865
+ try {
1866
+ await Promise.race([Promise.resolve().then(body), quit]);
1867
+ } finally {
1868
+ process.exit = realExit;
1869
+ }
1870
+ return exitCode;
1871
+ }
1872
+ function runPiMainReturningOnQuit(args, options) {
1873
+ return runWithProcessExitGuard(() => runPiMain(args, options));
1874
+ }
1829
1875
  async function attachRunBundledPiFrontend(context, input) {
1830
1876
  const tempSessionDir = mkdtempSync(join(tmpdir(), "rig-pi-frontend-sessions-"));
1831
1877
  const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
@@ -1841,16 +1887,20 @@ async function attachRunBundledPiFrontend(context, input) {
1841
1887
  };
1842
1888
  let detached = false;
1843
1889
  try {
1844
- await runPiMain([
1890
+ const piArgs = [
1845
1891
  "--offline",
1846
1892
  "--no-extensions",
1847
1893
  "--no-skills",
1848
1894
  "--no-prompt-templates",
1849
1895
  "--no-context-files",
1850
1896
  ...sessionFileArg
1851
- ], {
1852
- extensionFactories: [piRigExtensionFactory]
1853
- });
1897
+ ];
1898
+ const piOptions = { extensionFactories: [piRigExtensionFactory] };
1899
+ if (input.returnOnQuit) {
1900
+ await runPiMainReturningOnQuit(piArgs, piOptions);
1901
+ } else {
1902
+ await runPiMain(piArgs, piOptions);
1903
+ }
1854
1904
  detached = true;
1855
1905
  } finally {
1856
1906
  restoreEnv();
@@ -3238,390 +3288,6 @@ var init_task = __esm(() => {
3238
3288
  init__connection_state();
3239
3289
  });
3240
3290
 
3241
- // packages/cli/src/app-opentui/pi-pty-host.ts
3242
- import { fileURLToPath } from "url";
3243
- import { basename } from "path";
3244
- import { RGBA, StyledText, TextAttributes } from "@opentui/core";
3245
- import { Terminal as XtermTerminal } from "@xterm/headless";
3246
- function clampCols(cols) {
3247
- return Math.max(MIN_COLS, Math.trunc(cols || 100));
3248
- }
3249
- function clampRows(rows) {
3250
- return Math.max(MIN_ROWS, Math.min(MAX_ROWS, Math.trunc(rows || 35)));
3251
- }
3252
- function rgbaFromXtermColor(mode, value) {
3253
- if (mode === 1 || mode === 2 || mode === XTERM_COLOR_MODE_INDEXED_16 || mode === XTERM_COLOR_MODE_INDEXED_256) {
3254
- return RGBA.fromIndex(value);
3255
- }
3256
- if (mode === 3 || mode === XTERM_COLOR_MODE_RGB) {
3257
- return RGBA.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255);
3258
- }
3259
- return;
3260
- }
3261
- function textAttributesFromCell(cell) {
3262
- let attributes = TextAttributes.NONE;
3263
- if (cell.isBold())
3264
- attributes |= TextAttributes.BOLD;
3265
- if (cell.isDim())
3266
- attributes |= TextAttributes.DIM;
3267
- if (cell.isItalic())
3268
- attributes |= TextAttributes.ITALIC;
3269
- if (cell.isUnderline())
3270
- attributes |= TextAttributes.UNDERLINE;
3271
- if (cell.isBlink())
3272
- attributes |= TextAttributes.BLINK;
3273
- if (cell.isInverse())
3274
- attributes |= TextAttributes.INVERSE;
3275
- if (cell.isInvisible())
3276
- attributes |= TextAttributes.HIDDEN;
3277
- if (cell.isStrikethrough())
3278
- attributes |= TextAttributes.STRIKETHROUGH;
3279
- return attributes;
3280
- }
3281
- function sameRgba(a, b) {
3282
- if (!a && !b)
3283
- return true;
3284
- return Boolean(a && b && a.equals(b));
3285
- }
3286
- function sameStyle(a, b) {
3287
- return sameRgba(a.fg, b.fg) && sameRgba(a.bg, b.bg) && (a.attributes ?? 0) === (b.attributes ?? 0);
3288
- }
3289
- function styleFromCell(cell) {
3290
- return {
3291
- fg: rgbaFromXtermColor(cell.getFgColorMode(), cell.getFgColor()),
3292
- bg: rgbaFromXtermColor(cell.getBgColorMode(), cell.getBgColor()),
3293
- attributes: textAttributesFromCell(cell)
3294
- };
3295
- }
3296
- function lineToStyledText(line, cols) {
3297
- if (!line)
3298
- return new StyledText([{ __isChunk: true, text: "" }]);
3299
- const chunks = [];
3300
- let run = "";
3301
- let runStyle = null;
3302
- const flush = () => {
3303
- if (!run)
3304
- return;
3305
- chunks.push({
3306
- __isChunk: true,
3307
- text: run,
3308
- ...runStyle?.fg ? { fg: runStyle.fg } : {},
3309
- ...runStyle?.bg ? { bg: runStyle.bg } : {},
3310
- ...(runStyle?.attributes ?? 0) !== 0 ? { attributes: runStyle.attributes } : {}
3311
- });
3312
- run = "";
3313
- };
3314
- for (let index = 0;index < cols; index += 1) {
3315
- const cell = line.getCell(index);
3316
- if (!cell) {
3317
- if (runStyle !== null)
3318
- flush();
3319
- runStyle = null;
3320
- run += " ";
3321
- continue;
3322
- }
3323
- if (cell.getWidth() === 0)
3324
- continue;
3325
- const style = styleFromCell(cell);
3326
- if (!runStyle || !sameStyle(runStyle, style)) {
3327
- flush();
3328
- runStyle = style;
3329
- }
3330
- run += cell.getChars() || " ";
3331
- }
3332
- flush();
3333
- return new StyledText(chunks.length > 0 ? chunks : [{ __isChunk: true, text: "" }]);
3334
- }
3335
- function childCommandPrefix() {
3336
- const execName = basename(process.execPath).toLowerCase();
3337
- const currentEntry = process.argv[1];
3338
- if (execName === "bun" || execName === "bun.exe") {
3339
- return currentEntry ? [process.execPath, currentEntry, "__opentui-pi-host"] : [process.execPath, fallbackChildScriptPath];
3340
- }
3341
- return [process.execPath, "__opentui-pi-host"];
3342
- }
3343
- function withEnv(base) {
3344
- const env = {};
3345
- for (const [key, value] of Object.entries(base ?? process.env)) {
3346
- if (key === "RIG_PLAIN" || key === "RIG_CLI_PLAIN_HELP" || key === "NO_COLOR")
3347
- continue;
3348
- if (typeof value === "string")
3349
- env[key] = value;
3350
- }
3351
- return {
3352
- ...env,
3353
- TERM: "xterm-256color",
3354
- COLORTERM: "truecolor",
3355
- FORCE_COLOR: env.FORCE_COLOR ?? "1",
3356
- RIG_OPENTUI_PI_HOST: "1"
3357
- };
3358
- }
3359
- async function startPiPtyHost(options) {
3360
- activeHost?.dispose("replace");
3361
- const host = new PiPtyHost(options);
3362
- activeHost = host;
3363
- await host.start();
3364
- return host;
3365
- }
3366
-
3367
- class PiPtyHost {
3368
- runId;
3369
- projectRoot;
3370
- onSnapshot;
3371
- onExit;
3372
- onError;
3373
- terminal;
3374
- disposables = [];
3375
- decoder = new TextDecoder("utf-8");
3376
- proc = null;
3377
- pty = null;
3378
- status = "starting";
3379
- cols;
3380
- rows;
3381
- message = "starting bundled Pi";
3382
- lastResizeError = null;
3383
- exitCode;
3384
- signal;
3385
- notifyTimer = null;
3386
- lastStreamKey = "";
3387
- _disposed = false;
3388
- constructor(options) {
3389
- this.runId = options.runId;
3390
- this.projectRoot = options.projectRoot;
3391
- this.cols = clampCols(options.cols);
3392
- this.rows = clampRows(options.rows);
3393
- this.onSnapshot = options.onSnapshot;
3394
- this.onExit = options.onExit;
3395
- this.onError = options.onError;
3396
- this.terminal = new XtermTerminal({
3397
- allowProposedApi: true,
3398
- cols: this.cols,
3399
- rows: this.rows,
3400
- scrollback: 1000
3401
- });
3402
- this.registerTerminalResponders();
3403
- this.disposables.push(this.terminal.onWriteParsed(() => {
3404
- if (this._disposed)
3405
- return;
3406
- const snapshot = this.createSnapshot();
3407
- const key = snapshot.lines.join(`
3408
- `);
3409
- if (key === this.lastStreamKey)
3410
- return;
3411
- this.lastStreamKey = key;
3412
- this.onSnapshot?.(snapshot);
3413
- }));
3414
- }
3415
- get disposed() {
3416
- return this._disposed;
3417
- }
3418
- get snapshot() {
3419
- return this.createSnapshot();
3420
- }
3421
- async start() {
3422
- if (this._disposed)
3423
- throw new Error("Pi PTY host is disposed.");
3424
- if (typeof Bun.Terminal !== "function") {
3425
- throw new Error("Bun native PTY is unavailable in this runtime. Pi host requires Bun.Terminal.");
3426
- }
3427
- const spawnOptions = {
3428
- cwd: this.projectRoot,
3429
- env: withEnv(process.env),
3430
- terminal: {
3431
- cols: this.cols,
3432
- rows: this.rows,
3433
- name: "xterm-256color",
3434
- data: (_terminal, data) => this.handlePtyData(data)
3435
- }
3436
- };
3437
- const proc = Bun.spawn([
3438
- ...childCommandPrefix(),
3439
- "--run-id",
3440
- this.runId,
3441
- "--project-root",
3442
- this.projectRoot
3443
- ], spawnOptions);
3444
- if (!proc.terminal)
3445
- throw new Error("Bun did not attach a terminal to the bundled Pi child process.");
3446
- this.proc = proc;
3447
- this.pty = proc.terminal;
3448
- this.status = "running";
3449
- this.message = "bundled Pi running inside this app";
3450
- this.emitSnapshotSoon(0);
3451
- proc.exited.then((exitCode) => {
3452
- if (this._disposed)
3453
- return;
3454
- this.status = exitCode === 0 ? "exited" : "failed";
3455
- this.exitCode = exitCode;
3456
- this.signal = null;
3457
- this.message = exitCode === 0 ? "bundled Pi exited" : `bundled Pi exited with code ${exitCode}`;
3458
- const snapshot = this.createSnapshot();
3459
- this.onSnapshot?.(snapshot);
3460
- this.onExit?.(snapshot);
3461
- if (activeHost === this)
3462
- activeHost = null;
3463
- this.dispose("exit", { kill: false, notify: false });
3464
- }).catch((error) => {
3465
- if (this._disposed)
3466
- return;
3467
- this.status = "failed";
3468
- this.message = error instanceof Error ? error.message : String(error);
3469
- const snapshot = this.createSnapshot();
3470
- this.onSnapshot?.(snapshot);
3471
- this.onError?.(error, snapshot);
3472
- if (activeHost === this)
3473
- activeHost = null;
3474
- this.dispose("error", { kill: false, notify: false });
3475
- });
3476
- }
3477
- write(data) {
3478
- if (this._disposed || !this.pty)
3479
- return;
3480
- try {
3481
- this.pty.write(data);
3482
- } catch (error) {
3483
- this.status = "failed";
3484
- this.message = error instanceof Error ? error.message : String(error);
3485
- const snapshot = this.createSnapshot();
3486
- this.onSnapshot?.(snapshot);
3487
- this.onError?.(error, snapshot);
3488
- }
3489
- }
3490
- resize(cols, rows) {
3491
- const nextCols = clampCols(cols);
3492
- const nextRows = clampRows(rows);
3493
- if (nextCols === this.cols && nextRows === this.rows)
3494
- return;
3495
- this.cols = nextCols;
3496
- this.rows = nextRows;
3497
- this.terminal.resize(nextCols, nextRows);
3498
- try {
3499
- this.pty?.resize(nextCols, nextRows);
3500
- this.lastResizeError = null;
3501
- } catch (error) {
3502
- this.lastResizeError = error instanceof Error ? error.message : String(error);
3503
- }
3504
- this.emitSnapshotSoon(0);
3505
- }
3506
- detach() {
3507
- this.dispose("detach");
3508
- return this.createSnapshot("detached from bundled Pi");
3509
- }
3510
- dispose(reason = "dispose", options = {}) {
3511
- if (this._disposed)
3512
- return;
3513
- this._disposed = true;
3514
- if (this.notifyTimer)
3515
- clearTimeout(this.notifyTimer);
3516
- this.notifyTimer = null;
3517
- for (const disposable of this.disposables.splice(0)) {
3518
- try {
3519
- disposable.dispose();
3520
- } catch {}
3521
- }
3522
- if (options.kill !== false) {
3523
- try {
3524
- this.proc?.kill("SIGTERM");
3525
- } catch {}
3526
- }
3527
- try {
3528
- this.pty?.close();
3529
- } catch {}
3530
- try {
3531
- this.terminal.dispose();
3532
- } catch {}
3533
- if (activeHost === this)
3534
- activeHost = null;
3535
- if (options.notify) {
3536
- this.message = reason;
3537
- this.onSnapshot?.(this.createSnapshot(reason));
3538
- }
3539
- }
3540
- handlePtyData(data) {
3541
- if (this._disposed)
3542
- return;
3543
- const text = this.decoder.decode(data, { stream: true });
3544
- this.respondToRawTerminalQueries(text);
3545
- this.terminal.write(data);
3546
- }
3547
- emitSnapshotSoon(delayMs = SNAPSHOT_DELAY_MS) {
3548
- if (this._disposed || this.notifyTimer)
3549
- return;
3550
- this.notifyTimer = setTimeout(() => {
3551
- this.notifyTimer = null;
3552
- if (this._disposed)
3553
- return;
3554
- this.onSnapshot?.(this.createSnapshot());
3555
- }, delayMs);
3556
- }
3557
- createSnapshot(message2 = this.message) {
3558
- const buffer = this.terminal.buffer.active;
3559
- const end = buffer.length;
3560
- const start = Math.max(0, end - MAX_SNAPSHOT_LINES);
3561
- const styledStart = Math.max(start, end - this.rows - STYLED_SNAPSHOT_MARGIN);
3562
- const lines = [];
3563
- const styledLines = [];
3564
- for (let row = start;row < end; row += 1) {
3565
- const line = buffer.getLine(row);
3566
- lines.push((line?.translateToString(false) ?? "").slice(0, this.cols));
3567
- if (row >= styledStart)
3568
- styledLines[lines.length - 1] = lineToStyledText(line, this.cols);
3569
- }
3570
- while (lines.length < this.rows) {
3571
- lines.push("");
3572
- styledLines[lines.length - 1] = new StyledText([{ __isChunk: true, text: "" }]);
3573
- }
3574
- const resolvedMessage = this.lastResizeError ? `resize degraded: ${this.lastResizeError}` : message2;
3575
- return {
3576
- runId: this.runId,
3577
- status: this.status,
3578
- cols: this.cols,
3579
- rows: this.rows,
3580
- lines,
3581
- styledLines,
3582
- message: resolvedMessage,
3583
- ...this.lastResizeError ? { resizeError: this.lastResizeError } : {},
3584
- ...this.exitCode !== undefined ? { exitCode: this.exitCode } : {},
3585
- ...this.signal !== undefined ? { signal: this.signal } : {}
3586
- };
3587
- }
3588
- registerTerminalResponders() {
3589
- this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "c" }, (params) => {
3590
- if (params.length === 0 || params[0] === 0)
3591
- this.write("\x1B[?62;22c");
3592
- return false;
3593
- }));
3594
- this.disposables.push(this.terminal.parser.registerCsiHandler({ prefix: ">", final: "c" }, (params) => {
3595
- if (params.length === 0 || params[0] === 0)
3596
- this.write("\x1B[>0;0;0c");
3597
- return false;
3598
- }));
3599
- this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "n" }, (params) => {
3600
- if (params[0] === 5) {
3601
- this.write("\x1B[0n");
3602
- } else if (params[0] === 6) {
3603
- const row = this.terminal.buffer.active.cursorY + 1;
3604
- const col = this.terminal.buffer.active.cursorX + 1;
3605
- this.write(`\x1B[${row};${col}R`);
3606
- }
3607
- return false;
3608
- }));
3609
- }
3610
- respondToRawTerminalQueries(text) {
3611
- if (!text)
3612
- return;
3613
- const decrqm = /\x1b\[\?(\d+)\$p/g;
3614
- let match;
3615
- while ((match = decrqm.exec(text)) !== null) {
3616
- this.write(`\x1B[?${match[1]};2$y`);
3617
- }
3618
- }
3619
- }
3620
- var MIN_COLS = 40, MIN_ROWS = 12, MAX_ROWS = 300, MAX_SNAPSHOT_LINES = 360, STYLED_SNAPSHOT_MARGIN = 6, SNAPSHOT_DELAY_MS = 120, fallbackChildScriptPath, activeHost = null, XTERM_COLOR_MODE_INDEXED_16 = 16777216, XTERM_COLOR_MODE_INDEXED_256 = 33554432, XTERM_COLOR_MODE_RGB = 50331648;
3621
- var init_pi_pty_host = __esm(() => {
3622
- fallbackChildScriptPath = fileURLToPath(new URL("./pi-host-child.ts", import.meta.url));
3623
- });
3624
-
3625
3291
  // packages/cli/src/app-opentui/adapters/fleet.ts
3626
3292
  function normalizeRunRecord(record) {
3627
3293
  const runId = stringField(record, ["runId", "id"]);
@@ -3887,14 +3553,6 @@ function recordStep(ctx, runId, label, emitLabel) {
3887
3553
  });
3888
3554
  emitProgress(ctx, emitLabel, label);
3889
3555
  }
3890
- function settleSteps(ctx, runId) {
3891
- const now = Date.now();
3892
- const previous = currentAttachState(ctx, runId);
3893
- if (!previous?.steps)
3894
- return;
3895
- const steps = previous.steps.map((step) => step.status === "running" ? { ...step, status: "done", endedAtMs: step.endedAtMs ?? now } : step);
3896
- patchData(ctx, { piAttach: { ...previous, steps } });
3897
- }
3898
3556
  async function preparePiAttachHandoff(ctx, runId) {
3899
3557
  const cleanRunId = runId.trim();
3900
3558
  const label = `Preparing Pi ${cleanRunId.slice(0, 8)}`;
@@ -3942,85 +3600,41 @@ async function attachRunWithBundledPi(ctx, runId) {
3942
3600
  throw error;
3943
3601
  }
3944
3602
  emitStarted(ctx, label, { piAttach: { runId: cleanRunId, status: "entering-pi" } });
3945
- patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "starting bundled Pi", steps: [] } });
3603
+ patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "handing the terminal to worker Pi\u2026" } });
3604
+ const projectRoot = projectRootOf(ctx);
3605
+ const outputMode = ctx.rig?.outputMode ?? "text";
3606
+ await releaseRendererForExternalTui(ctx);
3607
+ let outcome = null;
3608
+ let attachError = null;
3946
3609
  try {
3947
- recordStep(ctx, cleanRunId, "connecting to server and fetching run transcript", label);
3948
- await preparePiAttachHandoff(ctx, cleanRunId).catch((error) => {
3949
- patchData(ctx, { lastRefreshError: normalizeAppError(error).message });
3950
- return null;
3951
- });
3952
- recordStep(ctx, cleanRunId, "spawning Bun PTY host for bundled Pi", label);
3953
- const projectRoot = projectRootOf(ctx);
3954
- const cols = typeof process.stdout.columns === "number" ? process.stdout.columns : 100;
3955
- const rows = Math.max(1, (typeof process.stdout.rows === "number" ? process.stdout.rows : 35) - 1);
3956
- const patchTerminal = (snapshot) => {
3957
- const previousSteps = currentAttachState(ctx, cleanRunId)?.steps;
3958
- const failed = snapshot.status === "failed";
3959
- const reason = failed ? snapshot.message ?? `bundled Pi exited with code ${snapshot.exitCode ?? "unknown"}` : undefined;
3960
- patchData(ctx, {
3961
- piTerminal: snapshot,
3962
- piAttach: {
3963
- runId: cleanRunId,
3964
- status: failed ? "failed" : snapshot.status === "exited" ? "returned" : "entering-pi",
3965
- message: snapshot.message ?? "bundled Pi running",
3966
- result: { runId: cleanRunId, status: snapshot.status, exitCode: snapshot.exitCode },
3967
- ...previousSteps ? { steps: previousSteps } : {},
3968
- ...reason ? { failureReason: reason } : {}
3969
- }
3970
- });
3971
- };
3972
- recordStep(ctx, cleanRunId, "starting bundled Pi", label);
3973
- await startPiPtyHost({
3974
- runId: cleanRunId,
3975
- projectRoot,
3976
- cols,
3977
- rows,
3978
- onSnapshot: (snapshot) => {
3979
- if (snapshot.status === "running" && snapshot.lines.some((entry) => entry.trim().length > 0)) {
3980
- settleSteps(ctx, cleanRunId);
3981
- }
3982
- patchTerminal(snapshot);
3983
- },
3984
- onExit: (snapshot) => {
3985
- patchTerminal(snapshot);
3986
- if (snapshot.status === "failed") {
3987
- Promise.resolve().then(() => (init_inspect(), exports_inspect)).then(({ loadInspect: loadInspect2 }) => loadInspect2(ctx, cleanRunId)).catch(() => null);
3988
- emitFailed(ctx, label, new Error(snapshot.message ?? `bundled Pi exited with code ${snapshot.exitCode ?? "unknown"}`), { runId: cleanRunId, result: { status: snapshot.status, exitCode: snapshot.exitCode } });
3989
- return;
3990
- }
3991
- emitCompleted(ctx, label, { runId: cleanRunId, result: { status: snapshot.status, exitCode: snapshot.exitCode } });
3992
- ctx.emit({ type: "scene.change", scene: "fleet", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } } });
3993
- refreshFleet(ctx).catch((error) => emitFailed(ctx, "Refresh runs", error));
3994
- },
3995
- onError: (error, snapshot) => {
3996
- patchTerminal(snapshot);
3997
- emitFailed(ctx, label, error, { runId: cleanRunId });
3998
- }
3999
- });
4000
- const record = {
4001
- runId: cleanRunId,
4002
- status: "running",
4003
- rendered: "bundled Pi embedded via Bun.Terminal + @xterm/headless"
4004
- };
4005
- emitProgress(ctx, label, "bundled Pi is running", { piAttach: { runId: cleanRunId, status: "entering-pi", message: "bundled Pi running", result: record } });
4006
- return record;
3610
+ const { attachRunBundledPiFrontend: attachRunBundledPiFrontend2 } = await Promise.resolve().then(() => (init__pi_frontend(), exports__pi_frontend));
3611
+ outcome = await attachRunBundledPiFrontend2({ projectRoot, outputMode }, { runId: cleanRunId, returnOnQuit: true });
4007
3612
  } catch (error) {
4008
- const reason = error instanceof Error ? error.message : String(error);
3613
+ attachError = error;
3614
+ } finally {
3615
+ await resumeRendererAfterExternalTui(ctx);
3616
+ }
3617
+ if (attachError) {
3618
+ const reason = normalizeAppError(attachError).message;
4009
3619
  patchData(ctx, {
4010
- piAttach: {
4011
- runId: cleanRunId,
4012
- status: "failed",
4013
- message: reason,
4014
- failureReason: reason,
4015
- ...currentAttachState(ctx, cleanRunId)?.steps ? { steps: currentAttachState(ctx, cleanRunId).steps } : {}
4016
- }
3620
+ piTerminal: undefined,
3621
+ piAttach: { runId: cleanRunId, status: "failed", message: reason, failureReason: reason }
4017
3622
  });
4018
- emitFailed(ctx, label, error, { runId: cleanRunId });
4019
- throw error;
3623
+ Promise.resolve().then(() => (init_inspect(), exports_inspect)).then(({ loadInspect: loadInspect2 }) => loadInspect2(ctx, cleanRunId)).catch(() => null);
3624
+ emitFailed(ctx, label, attachError, { runId: cleanRunId });
3625
+ throw attachError;
4020
3626
  }
3627
+ const detached = outcome?.detached === true;
3628
+ patchData(ctx, {
3629
+ piTerminal: undefined,
3630
+ piAttach: { runId: cleanRunId, status: "returned", message: detached ? "detached from worker Pi" : "returned from worker Pi" }
3631
+ });
3632
+ emitCompleted(ctx, label, { runId: cleanRunId, result: { runId: cleanRunId, detached } });
3633
+ ctx.emit({ type: "scene.change", scene: "fleet", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } } });
3634
+ refreshFleet(ctx).catch((error) => emitFailed(ctx, "Refresh runs", error));
3635
+ return { runId: cleanRunId, status: "returned", detached };
4021
3636
  }
4022
3637
  var init_pi_attach = __esm(() => {
4023
- init_pi_pty_host();
4024
3638
  init_fleet();
4025
3639
  });
4026
3640