@h-rig/cli 0.0.6-alpha.86 → 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.
Files changed (80) hide show
  1. package/dist/bin/rig.js +287 -578
  2. package/dist/src/app-opentui/adapters/common.d.ts +3 -0
  3. package/dist/src/app-opentui/adapters/common.js +4 -0
  4. package/dist/src/app-opentui/adapters/family.js +31 -4
  5. package/dist/src/app-opentui/adapters/pi-attach.d.ts +7 -0
  6. package/dist/src/app-opentui/adapters/pi-attach.js +527 -473
  7. package/dist/src/app-opentui/adapters/tasks.js +82 -468
  8. package/dist/src/app-opentui/bootstrap.js +287 -578
  9. package/dist/src/app-opentui/command-palette.js +1 -0
  10. package/dist/src/app-opentui/drone.js +1 -0
  11. package/dist/src/app-opentui/index.js +127 -446
  12. package/dist/src/app-opentui/keymap.js +2 -387
  13. package/dist/src/app-opentui/list-search.d.ts +5 -1
  14. package/dist/src/app-opentui/list-search.js +2 -2
  15. package/dist/src/app-opentui/pi-host-child.js +31 -4
  16. package/dist/src/app-opentui/pi-pty-host.d.ts +14 -64
  17. package/dist/src/app-opentui/pi-pty-host.js +3 -397
  18. package/dist/src/app-opentui/react/App.js +107 -433
  19. package/dist/src/app-opentui/react/Backdrop.js +1 -0
  20. package/dist/src/app-opentui/react/ChromeHost.js +34 -410
  21. package/dist/src/app-opentui/react/SceneFrameView.js +55 -5
  22. package/dist/src/app-opentui/react/launch.js +171 -472
  23. package/dist/src/app-opentui/react/syntax.js +1 -0
  24. package/dist/src/app-opentui/registry.js +99 -487
  25. package/dist/src/app-opentui/render/graphics.js +1 -0
  26. package/dist/src/app-opentui/render/hover.d.ts +11 -0
  27. package/dist/src/app-opentui/render/hover.js +34 -0
  28. package/dist/src/app-opentui/render/native-host.js +1 -0
  29. package/dist/src/app-opentui/render/panels.js +50 -3
  30. package/dist/src/app-opentui/render/preloader.js +1 -0
  31. package/dist/src/app-opentui/render/scene.js +1 -0
  32. package/dist/src/app-opentui/render/terminal-handoff.d.ts +16 -0
  33. package/dist/src/app-opentui/render/terminal-handoff.js +14 -0
  34. package/dist/src/app-opentui/render/text.d.ts +13 -0
  35. package/dist/src/app-opentui/render/text.js +54 -5
  36. package/dist/src/app-opentui/render/type-bar.js +1 -0
  37. package/dist/src/app-opentui/runtime.js +127 -446
  38. package/dist/src/app-opentui/scenes/command.js +1 -0
  39. package/dist/src/app-opentui/scenes/doctor.js +1 -0
  40. package/dist/src/app-opentui/scenes/error.js +1 -0
  41. package/dist/src/app-opentui/scenes/family-domains/agent.js +1 -0
  42. package/dist/src/app-opentui/scenes/family-domains/browser.js +1 -0
  43. package/dist/src/app-opentui/scenes/family-domains/dist.js +1 -0
  44. package/dist/src/app-opentui/scenes/family-domains/git.js +1 -0
  45. package/dist/src/app-opentui/scenes/family-domains/github.js +1 -0
  46. package/dist/src/app-opentui/scenes/family-domains/harness.js +1 -0
  47. package/dist/src/app-opentui/scenes/family-domains/index.js +1 -0
  48. package/dist/src/app-opentui/scenes/family-domains/kit.js +1 -0
  49. package/dist/src/app-opentui/scenes/family-domains/profile.js +1 -0
  50. package/dist/src/app-opentui/scenes/family-domains/queue.js +1 -0
  51. package/dist/src/app-opentui/scenes/family-domains/remote.js +1 -0
  52. package/dist/src/app-opentui/scenes/family-domains/review.js +1 -0
  53. package/dist/src/app-opentui/scenes/family-domains/setup.js +1 -0
  54. package/dist/src/app-opentui/scenes/family-domains/stats.js +1 -0
  55. package/dist/src/app-opentui/scenes/family.js +1 -0
  56. package/dist/src/app-opentui/scenes/fleet.js +3 -5
  57. package/dist/src/app-opentui/scenes/handoff.js +1 -0
  58. package/dist/src/app-opentui/scenes/help.js +1 -0
  59. package/dist/src/app-opentui/scenes/inbox.js +1 -0
  60. package/dist/src/app-opentui/scenes/init.js +1 -0
  61. package/dist/src/app-opentui/scenes/inspect.js +1 -0
  62. package/dist/src/app-opentui/scenes/main.js +1 -0
  63. package/dist/src/app-opentui/scenes/pi.js +1 -0
  64. package/dist/src/app-opentui/scenes/plugin.js +1 -0
  65. package/dist/src/app-opentui/scenes/repo.js +1 -0
  66. package/dist/src/app-opentui/scenes/run-detail.js +1 -0
  67. package/dist/src/app-opentui/scenes/server.js +1 -0
  68. package/dist/src/app-opentui/scenes/tasks.js +3 -5
  69. package/dist/src/app-opentui/scenes/workspace.js +1 -0
  70. package/dist/src/app-opentui/selectable.js +1 -0
  71. package/dist/src/app-opentui/theme.d.ts +1 -0
  72. package/dist/src/app-opentui/theme.js +1 -0
  73. package/dist/src/commands/_operator-view.js +31 -4
  74. package/dist/src/commands/_pi-frontend.d.ts +25 -0
  75. package/dist/src/commands/_pi-frontend.js +32 -4
  76. package/dist/src/commands/run.js +31 -4
  77. package/dist/src/commands/task.js +31 -4
  78. package/dist/src/commands.js +31 -4
  79. package/dist/src/index.js +31 -4
  80. package/package.json +8 -8
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -15,6 +15,7 @@ var RIG_UI = {
15
15
  bg2: "#0b0c0e",
16
16
  panel: "#101115",
17
17
  panel2: "#14161b",
18
+ hover: "#181b24",
18
19
  glass: "#1e2230",
19
20
  border: "#2a2e3a",
20
21
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -284,11 +285,8 @@ function withSelectable(line2, item) {
284
285
  }
285
286
 
286
287
  // packages/cli/src/app-opentui/list-search.ts
287
- function isListSearchScene(scene) {
288
- return scene === "fleet" || scene === "tasks";
289
- }
290
- function listSearchQuery(state) {
291
- return isListSearchScene(state.scene) ? state.typeBar.value.trim() : "";
288
+ function listSearchQuery(_state) {
289
+ return "";
292
290
  }
293
291
  function valueText(value) {
294
292
  if (value === undefined || value === null)
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -284,11 +285,8 @@ function withSelectable(line2, item) {
284
285
  }
285
286
 
286
287
  // packages/cli/src/app-opentui/list-search.ts
287
- function isListSearchScene(scene) {
288
- return scene === "fleet" || scene === "tasks";
289
- }
290
- function listSearchQuery(state) {
291
- return isListSearchScene(state.scene) ? state.typeBar.value.trim() : "";
288
+ function listSearchQuery(_state) {
289
+ return "";
292
290
  }
293
291
  function valueText(value) {
294
292
  if (value === undefined || value === null)
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -4,6 +4,7 @@ export declare const RIG_UI: {
4
4
  readonly bg2: "#0b0c0e";
5
5
  readonly panel: "#101115";
6
6
  readonly panel2: "#14161b";
7
+ readonly hover: "#181b24";
7
8
  readonly glass: "#1e2230";
8
9
  readonly border: "#2a2e3a";
9
10
  readonly ink: "#f4f5f8";
@@ -12,6 +12,7 @@ var RIG_UI = {
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
14
  panel2: "#14161b",
15
+ hover: "#181b24",
15
16
  glass: "#1e2230",
16
17
  border: "#2a2e3a",
17
18
  ink: "#f4f5f8",
@@ -1008,6 +1008,29 @@ function installRigPiTheme() {
1008
1008
  }
1009
1009
  } catch {}
1010
1010
  }
1011
+ async function runWithProcessExitGuard(body) {
1012
+ const realExit = process.exit;
1013
+ let exitCode = 0;
1014
+ let signalQuit = () => {};
1015
+ const quit = new Promise((resolve3) => {
1016
+ signalQuit = resolve3;
1017
+ });
1018
+ const guardedExit = (code) => {
1019
+ exitCode = typeof code === "number" ? code : 0;
1020
+ signalQuit();
1021
+ return;
1022
+ };
1023
+ process.exit = guardedExit;
1024
+ try {
1025
+ await Promise.race([Promise.resolve().then(body), quit]);
1026
+ } finally {
1027
+ process.exit = realExit;
1028
+ }
1029
+ return exitCode;
1030
+ }
1031
+ function runPiMainReturningOnQuit(args, options) {
1032
+ return runWithProcessExitGuard(() => runPiMain(args, options));
1033
+ }
1011
1034
  async function attachRunBundledPiFrontend(context, input) {
1012
1035
  const tempSessionDir = mkdtempSync(join(tmpdir(), "rig-pi-frontend-sessions-"));
1013
1036
  const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
@@ -1023,16 +1046,20 @@ async function attachRunBundledPiFrontend(context, input) {
1023
1046
  };
1024
1047
  let detached = false;
1025
1048
  try {
1026
- await runPiMain([
1049
+ const piArgs = [
1027
1050
  "--offline",
1028
1051
  "--no-extensions",
1029
1052
  "--no-skills",
1030
1053
  "--no-prompt-templates",
1031
1054
  "--no-context-files",
1032
1055
  ...sessionFileArg
1033
- ], {
1034
- extensionFactories: [piRigExtensionFactory]
1035
- });
1056
+ ];
1057
+ const piOptions = { extensionFactories: [piRigExtensionFactory] };
1058
+ if (input.returnOnQuit) {
1059
+ await runPiMainReturningOnQuit(piArgs, piOptions);
1060
+ } else {
1061
+ await runPiMain(piArgs, piOptions);
1062
+ }
1036
1063
  detached = true;
1037
1064
  } finally {
1038
1065
  restoreEnv();
@@ -22,8 +22,33 @@ export declare function buildOperatorPiEnv(input: {
22
22
  }): Record<string, string>;
23
23
  export declare function shouldRequireOperatorTranscript(status: unknown): boolean;
24
24
  export declare function missingOperatorTranscriptMessage(runId: string, status: unknown): string;
25
+ /** Run Pi's `main()` in-process but RETURN to the caller when Pi quits, instead
26
+ * of letting it terminate the process. Pi's interactive quit calls
27
+ * `process.exit(...)` (interactive-mode.js); when the OpenTUI shell hands Pi the
28
+ * terminal IN-PROCESS (the eject), that would kill the whole rig process. We
29
+ * temporarily intercept `process.exit` for the duration of the Pi session,
30
+ * convert the exit into a thrown sentinel that unwinds back here, then restore
31
+ * the real `process.exit`. The OpenTUI store + React tree are still alive (the
32
+ * renderer was only suspended), so the caller resumes the shell at the same
33
+ * state/screen. Returns Pi's intended exit code. */
34
+ /** Run `body` in-process while RETURNING (instead of terminating) when it calls
35
+ * `process.exit`. Used to run Pi's `main()` from the OpenTUI shell so Pi's
36
+ * interactive quit (which calls `process.exit`, interactive-mode.js) returns
37
+ * control to the shell rather than killing the whole rig process.
38
+ *
39
+ * We replace `process.exit` with a guard that resolves a `quit` promise — raced
40
+ * against `body` — instead of terminating, then restore the real `process.exit`.
41
+ * We deliberately do NOT throw from the guard: Pi's `process.exit` calls are
42
+ * terminal (the last statement of their quit path, after Pi has already stopped
43
+ * its render loop and disposed), and Pi often calls exit from a detached input
44
+ * callback where a throw would escape to the event loop (the bug that first
45
+ * shipped — the shell hung). Resolving `quit` synchronously returns control
46
+ * whichever path the exit takes. Returns the intended exit code. Exported for
47
+ * unit testing both exit paths. */
48
+ export declare function runWithProcessExitGuard(body: () => Promise<unknown>): Promise<number>;
25
49
  export declare function attachRunBundledPiFrontend(context: Pick<RunnerContext, "projectRoot" | "outputMode">, input: {
26
50
  readonly runId: string;
27
51
  readonly steered?: boolean;
52
+ readonly returnOnQuit?: boolean;
28
53
  }): Promise<AttachResult>;
29
54
  export {};
@@ -774,6 +774,29 @@ function installRigPiTheme() {
774
774
  }
775
775
  } catch {}
776
776
  }
777
+ async function runWithProcessExitGuard(body) {
778
+ const realExit = process.exit;
779
+ let exitCode = 0;
780
+ let signalQuit = () => {};
781
+ const quit = new Promise((resolve3) => {
782
+ signalQuit = resolve3;
783
+ });
784
+ const guardedExit = (code) => {
785
+ exitCode = typeof code === "number" ? code : 0;
786
+ signalQuit();
787
+ return;
788
+ };
789
+ process.exit = guardedExit;
790
+ try {
791
+ await Promise.race([Promise.resolve().then(body), quit]);
792
+ } finally {
793
+ process.exit = realExit;
794
+ }
795
+ return exitCode;
796
+ }
797
+ function runPiMainReturningOnQuit(args, options) {
798
+ return runWithProcessExitGuard(() => runPiMain(args, options));
799
+ }
777
800
  async function attachRunBundledPiFrontend(context, input) {
778
801
  const tempSessionDir = mkdtempSync(join(tmpdir(), "rig-pi-frontend-sessions-"));
779
802
  const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
@@ -789,16 +812,20 @@ async function attachRunBundledPiFrontend(context, input) {
789
812
  };
790
813
  let detached = false;
791
814
  try {
792
- await runPiMain([
815
+ const piArgs = [
793
816
  "--offline",
794
817
  "--no-extensions",
795
818
  "--no-skills",
796
819
  "--no-prompt-templates",
797
820
  "--no-context-files",
798
821
  ...sessionFileArg
799
- ], {
800
- extensionFactories: [piRigExtensionFactory]
801
- });
822
+ ];
823
+ const piOptions = { extensionFactories: [piRigExtensionFactory] };
824
+ if (input.returnOnQuit) {
825
+ await runPiMainReturningOnQuit(piArgs, piOptions);
826
+ } else {
827
+ await runPiMain(piArgs, piOptions);
828
+ }
802
829
  detached = true;
803
830
  } finally {
804
831
  restoreEnv();
@@ -820,6 +847,7 @@ async function attachRunBundledPiFrontend(context, input) {
820
847
  }
821
848
  export {
822
849
  shouldRequireOperatorTranscript,
850
+ runWithProcessExitGuard,
823
851
  missingOperatorTranscriptMessage,
824
852
  buildOperatorPiEnv,
825
853
  attachRunBundledPiFrontend
@@ -1246,6 +1246,29 @@ function installRigPiTheme() {
1246
1246
  }
1247
1247
  } catch {}
1248
1248
  }
1249
+ async function runWithProcessExitGuard(body) {
1250
+ const realExit = process.exit;
1251
+ let exitCode = 0;
1252
+ let signalQuit = () => {};
1253
+ const quit = new Promise((resolve4) => {
1254
+ signalQuit = resolve4;
1255
+ });
1256
+ const guardedExit = (code) => {
1257
+ exitCode = typeof code === "number" ? code : 0;
1258
+ signalQuit();
1259
+ return;
1260
+ };
1261
+ process.exit = guardedExit;
1262
+ try {
1263
+ await Promise.race([Promise.resolve().then(body), quit]);
1264
+ } finally {
1265
+ process.exit = realExit;
1266
+ }
1267
+ return exitCode;
1268
+ }
1269
+ function runPiMainReturningOnQuit(args, options) {
1270
+ return runWithProcessExitGuard(() => runPiMain(args, options));
1271
+ }
1249
1272
  async function attachRunBundledPiFrontend(context, input) {
1250
1273
  const tempSessionDir = mkdtempSync(join2(tmpdir(), "rig-pi-frontend-sessions-"));
1251
1274
  const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
@@ -1261,16 +1284,20 @@ async function attachRunBundledPiFrontend(context, input) {
1261
1284
  };
1262
1285
  let detached = false;
1263
1286
  try {
1264
- await runPiMain([
1287
+ const piArgs = [
1265
1288
  "--offline",
1266
1289
  "--no-extensions",
1267
1290
  "--no-skills",
1268
1291
  "--no-prompt-templates",
1269
1292
  "--no-context-files",
1270
1293
  ...sessionFileArg
1271
- ], {
1272
- extensionFactories: [piRigExtensionFactory]
1273
- });
1294
+ ];
1295
+ const piOptions = { extensionFactories: [piRigExtensionFactory] };
1296
+ if (input.returnOnQuit) {
1297
+ await runPiMainReturningOnQuit(piArgs, piOptions);
1298
+ } else {
1299
+ await runPiMain(piArgs, piOptions);
1300
+ }
1274
1301
  detached = true;
1275
1302
  } finally {
1276
1303
  restoreEnv();
@@ -1551,6 +1551,29 @@ function installRigPiTheme() {
1551
1551
  }
1552
1552
  } catch {}
1553
1553
  }
1554
+ async function runWithProcessExitGuard(body) {
1555
+ const realExit = process.exit;
1556
+ let exitCode = 0;
1557
+ let signalQuit = () => {};
1558
+ const quit = new Promise((resolve3) => {
1559
+ signalQuit = resolve3;
1560
+ });
1561
+ const guardedExit = (code) => {
1562
+ exitCode = typeof code === "number" ? code : 0;
1563
+ signalQuit();
1564
+ return;
1565
+ };
1566
+ process.exit = guardedExit;
1567
+ try {
1568
+ await Promise.race([Promise.resolve().then(body), quit]);
1569
+ } finally {
1570
+ process.exit = realExit;
1571
+ }
1572
+ return exitCode;
1573
+ }
1574
+ function runPiMainReturningOnQuit(args, options) {
1575
+ return runWithProcessExitGuard(() => runPiMain(args, options));
1576
+ }
1554
1577
  async function attachRunBundledPiFrontend(context, input) {
1555
1578
  const tempSessionDir = mkdtempSync(join(tmpdir(), "rig-pi-frontend-sessions-"));
1556
1579
  const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
@@ -1566,16 +1589,20 @@ async function attachRunBundledPiFrontend(context, input) {
1566
1589
  };
1567
1590
  let detached = false;
1568
1591
  try {
1569
- await runPiMain([
1592
+ const piArgs = [
1570
1593
  "--offline",
1571
1594
  "--no-extensions",
1572
1595
  "--no-skills",
1573
1596
  "--no-prompt-templates",
1574
1597
  "--no-context-files",
1575
1598
  ...sessionFileArg
1576
- ], {
1577
- extensionFactories: [piRigExtensionFactory]
1578
- });
1599
+ ];
1600
+ const piOptions = { extensionFactories: [piRigExtensionFactory] };
1601
+ if (input.returnOnQuit) {
1602
+ await runPiMainReturningOnQuit(piArgs, piOptions);
1603
+ } else {
1604
+ await runPiMain(piArgs, piOptions);
1605
+ }
1579
1606
  detached = true;
1580
1607
  } finally {
1581
1608
  restoreEnv();