@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.
- package/dist/bin/rig.js +144 -28
- package/dist/src/commands/_operator-view.js +145 -29
- package/dist/src/commands/_pi-frontend.js +145 -29
- package/dist/src/commands/_pi-worker-bridge-extension.js +145 -29
- package/dist/src/commands/run.js +145 -29
- package/dist/src/commands/task.js +145 -29
- package/dist/src/commands.js +144 -28
- package/dist/src/index.js +144 -28
- package/package.json +6 -6
package/dist/src/commands/run.js
CHANGED
|
@@ -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.
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
}
|
|
724
|
-
|
|
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
|
-
|
|
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
|
-
|
|
823
|
-
|
|
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
|
-
|
|
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.
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
}
|
|
1127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1226
|
-
|
|
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
|
-
|
|
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));
|