@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.
@@ -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();
@@ -2691,6 +2691,29 @@ function installRigPiTheme() {
2691
2691
  }
2692
2692
  } catch {}
2693
2693
  }
2694
+ async function runWithProcessExitGuard(body) {
2695
+ const realExit = process.exit;
2696
+ let exitCode = 0;
2697
+ let signalQuit = () => {};
2698
+ const quit = new Promise((resolve22) => {
2699
+ signalQuit = resolve22;
2700
+ });
2701
+ const guardedExit = (code) => {
2702
+ exitCode = typeof code === "number" ? code : 0;
2703
+ signalQuit();
2704
+ return;
2705
+ };
2706
+ process.exit = guardedExit;
2707
+ try {
2708
+ await Promise.race([Promise.resolve().then(body), quit]);
2709
+ } finally {
2710
+ process.exit = realExit;
2711
+ }
2712
+ return exitCode;
2713
+ }
2714
+ function runPiMainReturningOnQuit(args, options) {
2715
+ return runWithProcessExitGuard(() => runPiMain(args, options));
2716
+ }
2694
2717
  async function attachRunBundledPiFrontend(context, input) {
2695
2718
  const tempSessionDir = mkdtempSync(join3(tmpdir(), "rig-pi-frontend-sessions-"));
2696
2719
  const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
@@ -2706,16 +2729,20 @@ async function attachRunBundledPiFrontend(context, input) {
2706
2729
  };
2707
2730
  let detached = false;
2708
2731
  try {
2709
- await runPiMain([
2732
+ const piArgs = [
2710
2733
  "--offline",
2711
2734
  "--no-extensions",
2712
2735
  "--no-skills",
2713
2736
  "--no-prompt-templates",
2714
2737
  "--no-context-files",
2715
2738
  ...sessionFileArg
2716
- ], {
2717
- extensionFactories: [piRigExtensionFactory]
2718
- });
2739
+ ];
2740
+ const piOptions = { extensionFactories: [piRigExtensionFactory] };
2741
+ if (input.returnOnQuit) {
2742
+ await runPiMainReturningOnQuit(piArgs, piOptions);
2743
+ } else {
2744
+ await runPiMain(piArgs, piOptions);
2745
+ }
2719
2746
  detached = true;
2720
2747
  } finally {
2721
2748
  restoreEnv();
package/dist/src/index.js CHANGED
@@ -2880,6 +2880,29 @@ function installRigPiTheme() {
2880
2880
  }
2881
2881
  } catch {}
2882
2882
  }
2883
+ async function runWithProcessExitGuard(body) {
2884
+ const realExit = process.exit;
2885
+ let exitCode = 0;
2886
+ let signalQuit = () => {};
2887
+ const quit = new Promise((resolve23) => {
2888
+ signalQuit = resolve23;
2889
+ });
2890
+ const guardedExit = (code) => {
2891
+ exitCode = typeof code === "number" ? code : 0;
2892
+ signalQuit();
2893
+ return;
2894
+ };
2895
+ process.exit = guardedExit;
2896
+ try {
2897
+ await Promise.race([Promise.resolve().then(body), quit]);
2898
+ } finally {
2899
+ process.exit = realExit;
2900
+ }
2901
+ return exitCode;
2902
+ }
2903
+ function runPiMainReturningOnQuit(args, options) {
2904
+ return runWithProcessExitGuard(() => runPiMain(args, options));
2905
+ }
2883
2906
  async function attachRunBundledPiFrontend(context, input) {
2884
2907
  const tempSessionDir = mkdtempSync(join3(tmpdir(), "rig-pi-frontend-sessions-"));
2885
2908
  const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
@@ -2895,16 +2918,20 @@ async function attachRunBundledPiFrontend(context, input) {
2895
2918
  };
2896
2919
  let detached = false;
2897
2920
  try {
2898
- await runPiMain([
2921
+ const piArgs = [
2899
2922
  "--offline",
2900
2923
  "--no-extensions",
2901
2924
  "--no-skills",
2902
2925
  "--no-prompt-templates",
2903
2926
  "--no-context-files",
2904
2927
  ...sessionFileArg
2905
- ], {
2906
- extensionFactories: [piRigExtensionFactory]
2907
- });
2928
+ ];
2929
+ const piOptions = { extensionFactories: [piRigExtensionFactory] };
2930
+ if (input.returnOnQuit) {
2931
+ await runPiMainReturningOnQuit(piArgs, piOptions);
2932
+ } else {
2933
+ await runPiMain(piArgs, piOptions);
2934
+ }
2908
2935
  detached = true;
2909
2936
  } finally {
2910
2937
  restoreEnv();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-rig/cli",
3
- "version": "0.0.6-alpha.87",
3
+ "version": "0.0.6-alpha.88",
4
4
  "type": "module",
5
5
  "description": "Rig package",
6
6
  "license": "UNLICENSED",
@@ -28,13 +28,13 @@
28
28
  "@earendil-works/pi-tui": "^0.79.0",
29
29
  "@opentui/core": "^0.4.1",
30
30
  "@opentui/react": "0.4.1",
31
- "@rig/client": "npm:@h-rig/client@0.0.6-alpha.87",
32
- "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.87",
33
- "@rig/core": "npm:@h-rig/core@0.0.6-alpha.87",
34
- "@rig/pi-rig": "npm:@h-rig/pi-rig@0.0.6-alpha.87",
35
- "@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.87",
36
- "@rig/server": "npm:@h-rig/server@0.0.6-alpha.87",
37
- "@rig/standard-plugin": "npm:@h-rig/standard-plugin@0.0.6-alpha.87",
31
+ "@rig/client": "npm:@h-rig/client@0.0.6-alpha.88",
32
+ "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.88",
33
+ "@rig/core": "npm:@h-rig/core@0.0.6-alpha.88",
34
+ "@rig/pi-rig": "npm:@h-rig/pi-rig@0.0.6-alpha.88",
35
+ "@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.88",
36
+ "@rig/server": "npm:@h-rig/server@0.0.6-alpha.88",
37
+ "@rig/standard-plugin": "npm:@h-rig/standard-plugin@0.0.6-alpha.88",
38
38
  "@xterm/headless": "^6.0.0",
39
39
  "effect": "4.0.0-beta.78",
40
40
  "picocolors": "^1.1.1",