@h-rig/cli 0.0.6-alpha.23 → 0.0.6-alpha.24

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.
@@ -600,6 +600,15 @@ function appendTranscript(state, label, text) {
600
600
  state.transcript.splice(0, state.transcript.length - MAX_TRANSCRIPT_LINES);
601
601
  }
602
602
  }
603
+ function nativePiUi(ctx) {
604
+ const ui = ctx.ui;
605
+ return typeof ui.emitSessionEvent === "function" && typeof ui.appendSessionMessages === "function" ? ui : null;
606
+ }
607
+ function syncNativeDisplayCwd(ctx, state) {
608
+ const ui = nativePiUi(ctx);
609
+ if (ui?.setDisplayCwd && state.cwd)
610
+ ui.setDisplayCwd(state.cwd);
611
+ }
603
612
  function parseExtensionUiRequest(value) {
604
613
  const request = recordOf(value) ?? {};
605
614
  const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
@@ -643,8 +652,13 @@ function renderBridgeWidget(state) {
643
652
  function updatePiUi(ctx, state) {
644
653
  ctx.ui.setTitle("Pi \xB7 Rig worker daemon");
645
654
  ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker Pi WS live" : state.status);
655
+ syncNativeDisplayCwd(ctx, state);
656
+ if (state.nativeStream && nativePiUi(ctx)) {
657
+ ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
658
+ return;
659
+ }
646
660
  ctx.ui.setWorkingVisible(false);
647
- ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
661
+ ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
648
662
  }
649
663
  function applyStatus(state, payload) {
650
664
  const status = recordOf(payload.status) ?? payload;
@@ -662,7 +676,7 @@ function applyMessage(state, message) {
662
676
  const label = role === "assistant" ? "Pi" : role === "user" ? "You" : role === "tool" || role === "toolResult" ? "Tool" : "System";
663
677
  appendTranscript(state, label, textFromContent(record.content ?? record.message ?? record.text ?? ""));
664
678
  }
665
- function applyPiEvent(state, eventValue) {
679
+ function applyPiEvent(ctx, state, eventValue) {
666
680
  const event = recordOf(eventValue);
667
681
  if (!event)
668
682
  return;
@@ -670,11 +684,20 @@ function applyPiEvent(state, eventValue) {
670
684
  if (type === "agent_start") {
671
685
  state.streaming = true;
672
686
  state.status = "streaming";
687
+ } else if (type === "agent_end") {
688
+ state.streaming = false;
689
+ state.status = "idle";
690
+ } else if (type === "queue_update") {
691
+ const steering = Array.isArray(event.steering) ? event.steering.length : 0;
692
+ const followUp = Array.isArray(event.followUp) ? event.followUp.length : 0;
693
+ state.status = `queued \xB7 steer ${steering} \xB7 follow-up ${followUp}`;
694
+ }
695
+ const native = nativePiUi(ctx);
696
+ if (state.nativeStream && native?.emitSessionEvent) {
697
+ native.emitSessionEvent(eventValue);
673
698
  return;
674
699
  }
675
700
  if (type === "agent_end") {
676
- state.streaming = false;
677
- state.status = "idle";
678
701
  appendTranscript(state, "System", "Agent turn complete.");
679
702
  return;
680
703
  }
@@ -699,29 +722,60 @@ function applyPiEvent(state, eventValue) {
699
722
  }
700
723
  if (type === "tool_execution_end") {
701
724
  appendTranscript(state, event.isError === true ? "Error" : "Tool", asText(event.result ?? `${String(event.toolName ?? "tool")} complete`));
702
- return;
703
- }
704
- if (type === "queue_update") {
705
- const steering = Array.isArray(event.steering) ? event.steering.length : 0;
706
- const followUp = Array.isArray(event.followUp) ? event.followUp.length : 0;
707
- state.status = `queued \xB7 steer ${steering} \xB7 follow-up ${followUp}`;
708
725
  }
709
726
  }
727
+ function firstPendingShell(state) {
728
+ return state.pendingShells[0];
729
+ }
730
+ function finishPendingShell(state, shell, result) {
731
+ const index = state.pendingShells.indexOf(shell);
732
+ if (index !== -1)
733
+ state.pendingShells.splice(index, 1);
734
+ shell.resolve(result);
735
+ }
736
+ function failPendingShell(state, shell, error) {
737
+ const index = state.pendingShells.indexOf(shell);
738
+ if (index !== -1)
739
+ state.pendingShells.splice(index, 1);
740
+ shell.reject(error);
741
+ }
710
742
  function applyUiEvent(state, value) {
711
743
  const event = recordOf(value);
712
744
  if (!event)
713
745
  return;
714
746
  const type = String(event.type ?? "ui");
715
- if (type === "shell.start")
716
- appendTranscript(state, "Tool", `$ ${asText(event.command)}`);
717
- else if (type === "shell.chunk")
718
- appendTranscript(state, "Tool", asText(event.chunk));
719
- else if (type === "shell.end")
720
- appendTranscript(state, event.isError === true ? "Error" : "Tool", asText(event.output ?? `exit ${String(event.exitCode ?? "")}`));
721
- else
722
- appendTranscript(state, "System", `${type}: ${asText(event)}`);
723
- }
724
- function applyEnvelope(state, envelopeValue) {
747
+ if (type === "shell.chunk") {
748
+ const pending = firstPendingShell(state);
749
+ const chunk = asText(event.chunk);
750
+ if (pending) {
751
+ pending.sawChunk = true;
752
+ pending.onData(Buffer.from(chunk));
753
+ } else {
754
+ appendTranscript(state, "Tool", chunk);
755
+ }
756
+ return;
757
+ }
758
+ if (type === "shell.end") {
759
+ const pending = firstPendingShell(state);
760
+ const output = asText(event.output ?? "");
761
+ const exitCode = typeof event.exitCode === "number" ? event.exitCode : event.isError === true ? 1 : 0;
762
+ if (pending) {
763
+ if (output && !pending.sawChunk)
764
+ pending.onData(Buffer.from(output));
765
+ finishPendingShell(state, pending, { exitCode });
766
+ } else {
767
+ appendTranscript(state, event.isError === true ? "Error" : "Tool", output || `exit ${String(exitCode)}`);
768
+ }
769
+ return;
770
+ }
771
+ if (type === "shell.start") {
772
+ if (!firstPendingShell(state))
773
+ appendTranscript(state, "Tool", `$ ${asText(event.command)}`);
774
+ return;
775
+ }
776
+ appendTranscript(state, "System", `${type}: ${asText(event)}`);
777
+ }
778
+ function applyEnvelope(ctx, state, envelopeValue) {
725
779
  const envelope = recordOf(envelopeValue);
726
780
  if (!envelope)
727
781
  return;
@@ -730,7 +784,8 @@ function applyEnvelope(state, envelopeValue) {
730
784
  const metadata = recordOf(envelope.metadata);
731
785
  state.cwd = typeof metadata?.cwd === "string" ? metadata.cwd : state.cwd;
732
786
  state.status = "worker Pi daemon ready";
733
- appendTranscript(state, "System", "Connected to worker Pi daemon.");
787
+ if (!state.nativeStream)
788
+ appendTranscript(state, "System", "Connected to worker Pi daemon.");
734
789
  } else if (type === "status.update") {
735
790
  applyStatus(state, envelope);
736
791
  } else if (type === "activity.update") {
@@ -742,10 +797,11 @@ function applyEnvelope(state, envelopeValue) {
742
797
  } else if (type === "pi.ui_event") {
743
798
  applyUiEvent(state, envelope.event);
744
799
  } else if (type === "pi.event") {
745
- applyPiEvent(state, envelope.event);
800
+ applyPiEvent(ctx, state, envelope.event);
746
801
  } else if (type === "error") {
747
802
  appendTranscript(state, "Error", asText(envelope.message ?? envelope.detail ?? "unknown error"));
748
803
  }
804
+ syncNativeDisplayCwd(ctx, state);
749
805
  }
750
806
  async function waitForWorkerReady(options, ctx, state) {
751
807
  while (true) {
@@ -766,7 +822,7 @@ async function waitForWorkerReady(options, ctx, state) {
766
822
  continue;
767
823
  }
768
824
  const sessionRecord = recordOf(session) ?? {};
769
- applyEnvelope(state, { type: "ready", metadata: sessionRecord.metadata ?? sessionRecord });
825
+ applyEnvelope(ctx, state, { type: "ready", metadata: sessionRecord.metadata ?? sessionRecord });
770
826
  updatePiUi(ctx, state);
771
827
  return true;
772
828
  }
@@ -796,7 +852,7 @@ async function connectWorkerStream(options, ctx, state) {
796
852
  if (!catchupDone)
797
853
  buffered.push(payload);
798
854
  else {
799
- applyEnvelope(state, payload);
855
+ applyEnvelope(ctx, state, payload);
800
856
  updatePiUi(ctx, state);
801
857
  }
802
858
  } catch (error) {
@@ -819,8 +875,12 @@ async function connectWorkerStream(options, ctx, state) {
819
875
  getRunPiCommandsViaServer(options.context, options.runId)
820
876
  ]);
821
877
  const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
822
- for (const message of messages)
823
- applyMessage(state, message);
878
+ const native = nativePiUi(ctx);
879
+ if (state.nativeStream && native?.appendSessionMessages)
880
+ native.appendSessionMessages(messages);
881
+ else
882
+ for (const message of messages)
883
+ applyMessage(state, message);
824
884
  applyStatus(state, statusPayload);
825
885
  const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
826
886
  state.commands = commands.flatMap((command) => {
@@ -829,7 +889,7 @@ async function connectWorkerStream(options, ctx, state) {
829
889
  });
830
890
  catchupDone = true;
831
891
  for (const payload of buffered.splice(0))
832
- applyEnvelope(state, payload);
892
+ applyEnvelope(ctx, state, payload);
833
893
  updatePiUi(ctx, state);
834
894
  } catch (error) {
835
895
  appendTranscript(state, "Error", `Worker Pi catch-up failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -838,6 +898,51 @@ async function connectWorkerStream(options, ctx, state) {
838
898
  }
839
899
  await closePromise;
840
900
  }
901
+ function createRemoteBashOperations(options, state, excludeFromContext) {
902
+ return {
903
+ exec(command, _cwd, execOptions) {
904
+ return new Promise((resolve3, reject) => {
905
+ const pending = {
906
+ command,
907
+ onData: execOptions.onData,
908
+ resolve: resolve3,
909
+ reject,
910
+ sawChunk: false
911
+ };
912
+ const cleanup = () => {
913
+ execOptions.signal?.removeEventListener("abort", onAbort);
914
+ if (timer)
915
+ clearTimeout(timer);
916
+ };
917
+ const onAbort = () => {
918
+ cleanup();
919
+ failPendingShell(state, pending, new Error("Remote worker shell command aborted locally."));
920
+ };
921
+ const timeoutMs = typeof execOptions.timeout === "number" && execOptions.timeout > 0 ? execOptions.timeout : 0;
922
+ const timer = timeoutMs > 0 ? setTimeout(() => {
923
+ cleanup();
924
+ failPendingShell(state, pending, new Error(`Remote worker shell command timed out after ${timeoutMs}ms.`));
925
+ }, timeoutMs) : null;
926
+ const wrappedResolve = pending.resolve;
927
+ const wrappedReject = pending.reject;
928
+ pending.resolve = (result) => {
929
+ cleanup();
930
+ wrappedResolve(result);
931
+ };
932
+ pending.reject = (error) => {
933
+ cleanup();
934
+ wrappedReject(error);
935
+ };
936
+ execOptions.signal?.addEventListener("abort", onAbort, { once: true });
937
+ state.pendingShells.push(pending);
938
+ sendRunPiShellViaServer(options.context, options.runId, `${excludeFromContext ? "!!" : "!"}${command}`).catch((error) => {
939
+ cleanup();
940
+ failPendingShell(state, pending, error instanceof Error ? error : new Error(String(error)));
941
+ });
942
+ });
943
+ }
944
+ };
945
+ }
841
946
  async function answerPendingUi(options, state, line) {
842
947
  const pending = state.pendingUi;
843
948
  if (!pending)
@@ -903,13 +1008,22 @@ function createRigWorkerPiBridgeExtension(options) {
903
1008
  commands: [],
904
1009
  streaming: false,
905
1010
  pendingUi: null,
906
- wsConnected: false
1011
+ pendingShells: [],
1012
+ wsConnected: false,
1013
+ nativeStream: false
907
1014
  };
908
1015
  if (options.initialMessageSent)
909
1016
  appendTranscript(state, "System", "Initial message sent to worker Pi daemon.");
1017
+ let nativePiUiContextAvailable = false;
1018
+ pi.on("user_bash", (event) => {
1019
+ state.nativeStream = Boolean(state.nativeStream || nativePiUiContextAvailable);
1020
+ return { operations: createRemoteBashOperations(options, state, event.excludeFromContext === true) };
1021
+ });
910
1022
  pi.on("session_start", async (_event, ctx) => {
1023
+ nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
1024
+ state.nativeStream = nativePiUiContextAvailable;
911
1025
  updatePiUi(ctx, state);
912
- ctx.ui.notify("Rig worker Pi bridge extension loaded", "info");
1026
+ ctx.ui.notify(nativePiUiContextAvailable ? "Rig worker Pi native stream bridge loaded" : "Rig worker Pi bridge extension loaded (degraded widget fallback)", "info");
913
1027
  ctx.ui.onTerminalInput((data) => {
914
1028
  if (data.includes("\x04")) {
915
1029
  ctx.shutdown();
@@ -923,6 +1037,8 @@ function createRigWorkerPiBridgeExtension(options) {
923
1037
  const text = [editorText, inlineText].filter(Boolean).join(" ").trim();
924
1038
  if (!text)
925
1039
  return;
1040
+ if (text.startsWith("!"))
1041
+ return;
926
1042
  ctx.ui.setEditorText("");
927
1043
  routeInput(options, ctx, state, text).catch((error) => {
928
1044
  appendTranscript(state, "Error", error instanceof Error ? error.message : String(error));
@@ -1003,6 +1003,15 @@ function appendTranscript(state, label, text) {
1003
1003
  state.transcript.splice(0, state.transcript.length - MAX_TRANSCRIPT_LINES);
1004
1004
  }
1005
1005
  }
1006
+ function nativePiUi(ctx) {
1007
+ const ui = ctx.ui;
1008
+ return typeof ui.emitSessionEvent === "function" && typeof ui.appendSessionMessages === "function" ? ui : null;
1009
+ }
1010
+ function syncNativeDisplayCwd(ctx, state) {
1011
+ const ui = nativePiUi(ctx);
1012
+ if (ui?.setDisplayCwd && state.cwd)
1013
+ ui.setDisplayCwd(state.cwd);
1014
+ }
1006
1015
  function parseExtensionUiRequest(value) {
1007
1016
  const request = recordOf(value) ?? {};
1008
1017
  const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
@@ -1046,8 +1055,13 @@ function renderBridgeWidget(state) {
1046
1055
  function updatePiUi(ctx, state) {
1047
1056
  ctx.ui.setTitle("Pi \xB7 Rig worker daemon");
1048
1057
  ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker Pi WS live" : state.status);
1058
+ syncNativeDisplayCwd(ctx, state);
1059
+ if (state.nativeStream && nativePiUi(ctx)) {
1060
+ ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
1061
+ return;
1062
+ }
1049
1063
  ctx.ui.setWorkingVisible(false);
1050
- ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
1064
+ ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
1051
1065
  }
1052
1066
  function applyStatus(state, payload) {
1053
1067
  const status = recordOf(payload.status) ?? payload;
@@ -1065,7 +1079,7 @@ function applyMessage(state, message2) {
1065
1079
  const label = role === "assistant" ? "Pi" : role === "user" ? "You" : role === "tool" || role === "toolResult" ? "Tool" : "System";
1066
1080
  appendTranscript(state, label, textFromContent(record.content ?? record.message ?? record.text ?? ""));
1067
1081
  }
1068
- function applyPiEvent(state, eventValue) {
1082
+ function applyPiEvent(ctx, state, eventValue) {
1069
1083
  const event = recordOf(eventValue);
1070
1084
  if (!event)
1071
1085
  return;
@@ -1073,11 +1087,20 @@ function applyPiEvent(state, eventValue) {
1073
1087
  if (type === "agent_start") {
1074
1088
  state.streaming = true;
1075
1089
  state.status = "streaming";
1090
+ } else if (type === "agent_end") {
1091
+ state.streaming = false;
1092
+ state.status = "idle";
1093
+ } else if (type === "queue_update") {
1094
+ const steering = Array.isArray(event.steering) ? event.steering.length : 0;
1095
+ const followUp = Array.isArray(event.followUp) ? event.followUp.length : 0;
1096
+ state.status = `queued \xB7 steer ${steering} \xB7 follow-up ${followUp}`;
1097
+ }
1098
+ const native = nativePiUi(ctx);
1099
+ if (state.nativeStream && native?.emitSessionEvent) {
1100
+ native.emitSessionEvent(eventValue);
1076
1101
  return;
1077
1102
  }
1078
1103
  if (type === "agent_end") {
1079
- state.streaming = false;
1080
- state.status = "idle";
1081
1104
  appendTranscript(state, "System", "Agent turn complete.");
1082
1105
  return;
1083
1106
  }
@@ -1102,29 +1125,60 @@ function applyPiEvent(state, eventValue) {
1102
1125
  }
1103
1126
  if (type === "tool_execution_end") {
1104
1127
  appendTranscript(state, event.isError === true ? "Error" : "Tool", asText(event.result ?? `${String(event.toolName ?? "tool")} complete`));
1105
- return;
1106
- }
1107
- if (type === "queue_update") {
1108
- const steering = Array.isArray(event.steering) ? event.steering.length : 0;
1109
- const followUp = Array.isArray(event.followUp) ? event.followUp.length : 0;
1110
- state.status = `queued \xB7 steer ${steering} \xB7 follow-up ${followUp}`;
1111
1128
  }
1112
1129
  }
1130
+ function firstPendingShell(state) {
1131
+ return state.pendingShells[0];
1132
+ }
1133
+ function finishPendingShell(state, shell, result) {
1134
+ const index = state.pendingShells.indexOf(shell);
1135
+ if (index !== -1)
1136
+ state.pendingShells.splice(index, 1);
1137
+ shell.resolve(result);
1138
+ }
1139
+ function failPendingShell(state, shell, error) {
1140
+ const index = state.pendingShells.indexOf(shell);
1141
+ if (index !== -1)
1142
+ state.pendingShells.splice(index, 1);
1143
+ shell.reject(error);
1144
+ }
1113
1145
  function applyUiEvent(state, value) {
1114
1146
  const event = recordOf(value);
1115
1147
  if (!event)
1116
1148
  return;
1117
1149
  const type = String(event.type ?? "ui");
1118
- if (type === "shell.start")
1119
- appendTranscript(state, "Tool", `$ ${asText(event.command)}`);
1120
- else if (type === "shell.chunk")
1121
- appendTranscript(state, "Tool", asText(event.chunk));
1122
- else if (type === "shell.end")
1123
- appendTranscript(state, event.isError === true ? "Error" : "Tool", asText(event.output ?? `exit ${String(event.exitCode ?? "")}`));
1124
- else
1125
- appendTranscript(state, "System", `${type}: ${asText(event)}`);
1126
- }
1127
- function applyEnvelope(state, envelopeValue) {
1150
+ if (type === "shell.chunk") {
1151
+ const pending = firstPendingShell(state);
1152
+ const chunk = asText(event.chunk);
1153
+ if (pending) {
1154
+ pending.sawChunk = true;
1155
+ pending.onData(Buffer.from(chunk));
1156
+ } else {
1157
+ appendTranscript(state, "Tool", chunk);
1158
+ }
1159
+ return;
1160
+ }
1161
+ if (type === "shell.end") {
1162
+ const pending = firstPendingShell(state);
1163
+ const output = asText(event.output ?? "");
1164
+ const exitCode = typeof event.exitCode === "number" ? event.exitCode : event.isError === true ? 1 : 0;
1165
+ if (pending) {
1166
+ if (output && !pending.sawChunk)
1167
+ pending.onData(Buffer.from(output));
1168
+ finishPendingShell(state, pending, { exitCode });
1169
+ } else {
1170
+ appendTranscript(state, event.isError === true ? "Error" : "Tool", output || `exit ${String(exitCode)}`);
1171
+ }
1172
+ return;
1173
+ }
1174
+ if (type === "shell.start") {
1175
+ if (!firstPendingShell(state))
1176
+ appendTranscript(state, "Tool", `$ ${asText(event.command)}`);
1177
+ return;
1178
+ }
1179
+ appendTranscript(state, "System", `${type}: ${asText(event)}`);
1180
+ }
1181
+ function applyEnvelope(ctx, state, envelopeValue) {
1128
1182
  const envelope = recordOf(envelopeValue);
1129
1183
  if (!envelope)
1130
1184
  return;
@@ -1133,7 +1187,8 @@ function applyEnvelope(state, envelopeValue) {
1133
1187
  const metadata = recordOf(envelope.metadata);
1134
1188
  state.cwd = typeof metadata?.cwd === "string" ? metadata.cwd : state.cwd;
1135
1189
  state.status = "worker Pi daemon ready";
1136
- appendTranscript(state, "System", "Connected to worker Pi daemon.");
1190
+ if (!state.nativeStream)
1191
+ appendTranscript(state, "System", "Connected to worker Pi daemon.");
1137
1192
  } else if (type === "status.update") {
1138
1193
  applyStatus(state, envelope);
1139
1194
  } else if (type === "activity.update") {
@@ -1145,10 +1200,11 @@ function applyEnvelope(state, envelopeValue) {
1145
1200
  } else if (type === "pi.ui_event") {
1146
1201
  applyUiEvent(state, envelope.event);
1147
1202
  } else if (type === "pi.event") {
1148
- applyPiEvent(state, envelope.event);
1203
+ applyPiEvent(ctx, state, envelope.event);
1149
1204
  } else if (type === "error") {
1150
1205
  appendTranscript(state, "Error", asText(envelope.message ?? envelope.detail ?? "unknown error"));
1151
1206
  }
1207
+ syncNativeDisplayCwd(ctx, state);
1152
1208
  }
1153
1209
  async function waitForWorkerReady(options, ctx, state) {
1154
1210
  while (true) {
@@ -1169,7 +1225,7 @@ async function waitForWorkerReady(options, ctx, state) {
1169
1225
  continue;
1170
1226
  }
1171
1227
  const sessionRecord = recordOf(session) ?? {};
1172
- applyEnvelope(state, { type: "ready", metadata: sessionRecord.metadata ?? sessionRecord });
1228
+ applyEnvelope(ctx, state, { type: "ready", metadata: sessionRecord.metadata ?? sessionRecord });
1173
1229
  updatePiUi(ctx, state);
1174
1230
  return true;
1175
1231
  }
@@ -1199,7 +1255,7 @@ async function connectWorkerStream(options, ctx, state) {
1199
1255
  if (!catchupDone)
1200
1256
  buffered.push(payload);
1201
1257
  else {
1202
- applyEnvelope(state, payload);
1258
+ applyEnvelope(ctx, state, payload);
1203
1259
  updatePiUi(ctx, state);
1204
1260
  }
1205
1261
  } catch (error) {
@@ -1222,8 +1278,12 @@ async function connectWorkerStream(options, ctx, state) {
1222
1278
  getRunPiCommandsViaServer(options.context, options.runId)
1223
1279
  ]);
1224
1280
  const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
1225
- for (const message2 of messages)
1226
- applyMessage(state, message2);
1281
+ const native = nativePiUi(ctx);
1282
+ if (state.nativeStream && native?.appendSessionMessages)
1283
+ native.appendSessionMessages(messages);
1284
+ else
1285
+ for (const message2 of messages)
1286
+ applyMessage(state, message2);
1227
1287
  applyStatus(state, statusPayload);
1228
1288
  const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
1229
1289
  state.commands = commands.flatMap((command) => {
@@ -1232,7 +1292,7 @@ async function connectWorkerStream(options, ctx, state) {
1232
1292
  });
1233
1293
  catchupDone = true;
1234
1294
  for (const payload of buffered.splice(0))
1235
- applyEnvelope(state, payload);
1295
+ applyEnvelope(ctx, state, payload);
1236
1296
  updatePiUi(ctx, state);
1237
1297
  } catch (error) {
1238
1298
  appendTranscript(state, "Error", `Worker Pi catch-up failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -1241,6 +1301,51 @@ async function connectWorkerStream(options, ctx, state) {
1241
1301
  }
1242
1302
  await closePromise;
1243
1303
  }
1304
+ function createRemoteBashOperations(options, state, excludeFromContext) {
1305
+ return {
1306
+ exec(command, _cwd, execOptions) {
1307
+ return new Promise((resolve3, reject) => {
1308
+ const pending = {
1309
+ command,
1310
+ onData: execOptions.onData,
1311
+ resolve: resolve3,
1312
+ reject,
1313
+ sawChunk: false
1314
+ };
1315
+ const cleanup = () => {
1316
+ execOptions.signal?.removeEventListener("abort", onAbort);
1317
+ if (timer)
1318
+ clearTimeout(timer);
1319
+ };
1320
+ const onAbort = () => {
1321
+ cleanup();
1322
+ failPendingShell(state, pending, new Error("Remote worker shell command aborted locally."));
1323
+ };
1324
+ const timeoutMs = typeof execOptions.timeout === "number" && execOptions.timeout > 0 ? execOptions.timeout : 0;
1325
+ const timer = timeoutMs > 0 ? setTimeout(() => {
1326
+ cleanup();
1327
+ failPendingShell(state, pending, new Error(`Remote worker shell command timed out after ${timeoutMs}ms.`));
1328
+ }, timeoutMs) : null;
1329
+ const wrappedResolve = pending.resolve;
1330
+ const wrappedReject = pending.reject;
1331
+ pending.resolve = (result) => {
1332
+ cleanup();
1333
+ wrappedResolve(result);
1334
+ };
1335
+ pending.reject = (error) => {
1336
+ cleanup();
1337
+ wrappedReject(error);
1338
+ };
1339
+ execOptions.signal?.addEventListener("abort", onAbort, { once: true });
1340
+ state.pendingShells.push(pending);
1341
+ sendRunPiShellViaServer(options.context, options.runId, `${excludeFromContext ? "!!" : "!"}${command}`).catch((error) => {
1342
+ cleanup();
1343
+ failPendingShell(state, pending, error instanceof Error ? error : new Error(String(error)));
1344
+ });
1345
+ });
1346
+ }
1347
+ };
1348
+ }
1244
1349
  async function answerPendingUi(options, state, line) {
1245
1350
  const pending = state.pendingUi;
1246
1351
  if (!pending)
@@ -1306,13 +1411,22 @@ function createRigWorkerPiBridgeExtension(options) {
1306
1411
  commands: [],
1307
1412
  streaming: false,
1308
1413
  pendingUi: null,
1309
- wsConnected: false
1414
+ pendingShells: [],
1415
+ wsConnected: false,
1416
+ nativeStream: false
1310
1417
  };
1311
1418
  if (options.initialMessageSent)
1312
1419
  appendTranscript(state, "System", "Initial message sent to worker Pi daemon.");
1420
+ let nativePiUiContextAvailable = false;
1421
+ pi.on("user_bash", (event) => {
1422
+ state.nativeStream = Boolean(state.nativeStream || nativePiUiContextAvailable);
1423
+ return { operations: createRemoteBashOperations(options, state, event.excludeFromContext === true) };
1424
+ });
1313
1425
  pi.on("session_start", async (_event, ctx) => {
1426
+ nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
1427
+ state.nativeStream = nativePiUiContextAvailable;
1314
1428
  updatePiUi(ctx, state);
1315
- ctx.ui.notify("Rig worker Pi bridge extension loaded", "info");
1429
+ ctx.ui.notify(nativePiUiContextAvailable ? "Rig worker Pi native stream bridge loaded" : "Rig worker Pi bridge extension loaded (degraded widget fallback)", "info");
1316
1430
  ctx.ui.onTerminalInput((data) => {
1317
1431
  if (data.includes("\x04")) {
1318
1432
  ctx.shutdown();
@@ -1326,6 +1440,8 @@ function createRigWorkerPiBridgeExtension(options) {
1326
1440
  const text = [editorText, inlineText].filter(Boolean).join(" ").trim();
1327
1441
  if (!text)
1328
1442
  return;
1443
+ if (text.startsWith("!"))
1444
+ return;
1329
1445
  ctx.ui.setEditorText("");
1330
1446
  routeInput(options, ctx, state, text).catch((error) => {
1331
1447
  appendTranscript(state, "Error", error instanceof Error ? error.message : String(error));