@h-rig/pi-rig 0.0.6-alpha.77 → 0.0.6-alpha.78
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/src/client.js +26 -0
- package/dist/src/index.js +449 -120
- package/dist/src/live-mirror.js +216 -0
- package/package.json +2 -2
package/dist/src/client.js
CHANGED
|
@@ -285,6 +285,32 @@ class RigBridgeClient {
|
|
|
285
285
|
const payload = await this.piProxy("abort", { method: "POST" }, runId);
|
|
286
286
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
287
287
|
}
|
|
288
|
+
async workerCapabilities(runId = this.context.runId) {
|
|
289
|
+
const payload = await this.piProxy("capabilities", undefined, runId);
|
|
290
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
291
|
+
}
|
|
292
|
+
async workerRespondExtensionUi(requestId, response, runId = this.context.runId) {
|
|
293
|
+
const payload = await this.piProxy("extension-ui/respond", {
|
|
294
|
+
method: "POST",
|
|
295
|
+
headers: { "content-type": "application/json" },
|
|
296
|
+
body: JSON.stringify({ requestId, response })
|
|
297
|
+
}, runId);
|
|
298
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
299
|
+
}
|
|
300
|
+
async fetchRunSessionFile(runId = this.context.runId) {
|
|
301
|
+
if (!runId)
|
|
302
|
+
return null;
|
|
303
|
+
const payload = await this.request(`/api/runs/${encodeURIComponent(runId)}/session-file`);
|
|
304
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
305
|
+
return null;
|
|
306
|
+
const record = payload;
|
|
307
|
+
if (record.ok !== true || typeof record.content !== "string" || !record.content.trim())
|
|
308
|
+
return null;
|
|
309
|
+
return {
|
|
310
|
+
fileName: typeof record.fileName === "string" && record.fileName.trim() ? record.fileName : `rig-run-${runId}.jsonl`,
|
|
311
|
+
content: record.content
|
|
312
|
+
};
|
|
313
|
+
}
|
|
288
314
|
}
|
|
289
315
|
function buildRigWebSocketUrl(serverUrl, authToken) {
|
|
290
316
|
const url = new URL(serverUrl);
|
package/dist/src/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
2
4
|
// packages/pi-rig/src/client.ts
|
|
3
5
|
import { existsSync, readFileSync } from "fs";
|
|
4
6
|
import { homedir } from "os";
|
|
@@ -285,6 +287,32 @@ class RigBridgeClient {
|
|
|
285
287
|
const payload = await this.piProxy("abort", { method: "POST" }, runId);
|
|
286
288
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
287
289
|
}
|
|
290
|
+
async workerCapabilities(runId = this.context.runId) {
|
|
291
|
+
const payload = await this.piProxy("capabilities", undefined, runId);
|
|
292
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
293
|
+
}
|
|
294
|
+
async workerRespondExtensionUi(requestId, response, runId = this.context.runId) {
|
|
295
|
+
const payload = await this.piProxy("extension-ui/respond", {
|
|
296
|
+
method: "POST",
|
|
297
|
+
headers: { "content-type": "application/json" },
|
|
298
|
+
body: JSON.stringify({ requestId, response })
|
|
299
|
+
}, runId);
|
|
300
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
301
|
+
}
|
|
302
|
+
async fetchRunSessionFile(runId = this.context.runId) {
|
|
303
|
+
if (!runId)
|
|
304
|
+
return null;
|
|
305
|
+
const payload = await this.request(`/api/runs/${encodeURIComponent(runId)}/session-file`);
|
|
306
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
307
|
+
return null;
|
|
308
|
+
const record = payload;
|
|
309
|
+
if (record.ok !== true || typeof record.content !== "string" || !record.content.trim())
|
|
310
|
+
return null;
|
|
311
|
+
return {
|
|
312
|
+
fileName: typeof record.fileName === "string" && record.fileName.trim() ? record.fileName : `rig-run-${runId}.jsonl`,
|
|
313
|
+
content: record.content
|
|
314
|
+
};
|
|
315
|
+
}
|
|
288
316
|
}
|
|
289
317
|
function buildRigWebSocketUrl(serverUrl, authToken) {
|
|
290
318
|
const url = new URL(serverUrl);
|
|
@@ -675,6 +703,215 @@ function createRigSlashCommands(input) {
|
|
|
675
703
|
};
|
|
676
704
|
}
|
|
677
705
|
|
|
706
|
+
// packages/pi-rig/src/live-mirror.ts
|
|
707
|
+
async function loadPiModules() {
|
|
708
|
+
const components = await import("@earendil-works/pi-coding-agent");
|
|
709
|
+
return { components };
|
|
710
|
+
}
|
|
711
|
+
function createRootContainer() {
|
|
712
|
+
const children = [];
|
|
713
|
+
return {
|
|
714
|
+
addChild(child) {
|
|
715
|
+
children.push(child);
|
|
716
|
+
},
|
|
717
|
+
removeChild(child) {
|
|
718
|
+
const index = children.indexOf(child);
|
|
719
|
+
if (index >= 0)
|
|
720
|
+
children.splice(index, 1);
|
|
721
|
+
},
|
|
722
|
+
clear() {
|
|
723
|
+
children.length = 0;
|
|
724
|
+
},
|
|
725
|
+
render(width) {
|
|
726
|
+
return children.flatMap((child) => child.render(width));
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
var DRONE_MESSAGE_TYPE = "rig-drone";
|
|
731
|
+
var DRONE_USER_MESSAGE_TYPE = "rig-drone-user";
|
|
732
|
+
function recordOf(value) {
|
|
733
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
734
|
+
}
|
|
735
|
+
function messageRole(event) {
|
|
736
|
+
const message = recordOf(event.message);
|
|
737
|
+
return message && typeof message.role === "string" ? message.role : null;
|
|
738
|
+
}
|
|
739
|
+
function userMessageText(message) {
|
|
740
|
+
const content = message.content;
|
|
741
|
+
if (typeof content === "string")
|
|
742
|
+
return content;
|
|
743
|
+
if (Array.isArray(content)) {
|
|
744
|
+
return content.flatMap((block) => {
|
|
745
|
+
const record = recordOf(block);
|
|
746
|
+
return record && record.type === "text" && typeof record.text === "string" ? [record.text] : [];
|
|
747
|
+
}).join(`
|
|
748
|
+
`);
|
|
749
|
+
}
|
|
750
|
+
return "";
|
|
751
|
+
}
|
|
752
|
+
async function createLiveMirror(input) {
|
|
753
|
+
const { pi } = input;
|
|
754
|
+
const modules = input.modules ?? await loadPiModules();
|
|
755
|
+
const cwd = input.workerCwd ?? process.cwd();
|
|
756
|
+
const turns = new Map;
|
|
757
|
+
const toolOwners = new Map;
|
|
758
|
+
const userComponents = new Map;
|
|
759
|
+
let streamingTurn = null;
|
|
760
|
+
let sequence = 0;
|
|
761
|
+
let tui = null;
|
|
762
|
+
const requestRender = () => {
|
|
763
|
+
tui?.requestRender?.();
|
|
764
|
+
};
|
|
765
|
+
pi.registerMessageRenderer?.(DRONE_MESSAGE_TYPE, (message) => {
|
|
766
|
+
const details = recordOf(recordOf(message)?.details);
|
|
767
|
+
const key = details && typeof details.key === "string" ? details.key : null;
|
|
768
|
+
return key ? turns.get(key)?.root : undefined;
|
|
769
|
+
});
|
|
770
|
+
pi.registerMessageRenderer?.(DRONE_USER_MESSAGE_TYPE, (message) => {
|
|
771
|
+
const details = recordOf(recordOf(message)?.details);
|
|
772
|
+
const key = details && typeof details.key === "string" ? details.key : null;
|
|
773
|
+
return key ? userComponents.get(key) : undefined;
|
|
774
|
+
});
|
|
775
|
+
const ensureToolComponent = (turn, toolCallId, toolName, args) => {
|
|
776
|
+
const existing = turn.tools.get(toolCallId);
|
|
777
|
+
if (existing)
|
|
778
|
+
return existing;
|
|
779
|
+
const component = new modules.components.ToolExecutionComponent(toolName, toolCallId, args, {}, undefined, tui, cwd);
|
|
780
|
+
turn.root.addChild(component);
|
|
781
|
+
turn.tools.set(toolCallId, component);
|
|
782
|
+
toolOwners.set(toolCallId, turn);
|
|
783
|
+
return component;
|
|
784
|
+
};
|
|
785
|
+
const startAssistantTurn = (message) => {
|
|
786
|
+
const key = `turn-${++sequence}`;
|
|
787
|
+
const root = createRootContainer();
|
|
788
|
+
const assistant = new modules.components.AssistantMessageComponent;
|
|
789
|
+
root.addChild(assistant);
|
|
790
|
+
const turn = { root, assistant, tools: new Map };
|
|
791
|
+
assistant.updateContent(message);
|
|
792
|
+
turns.set(key, turn);
|
|
793
|
+
streamingTurn = turn;
|
|
794
|
+
pi.sendMessage?.({ customType: DRONE_MESSAGE_TYPE, content: "drone turn", display: true, details: { key } }, { triggerTurn: false });
|
|
795
|
+
requestRender();
|
|
796
|
+
};
|
|
797
|
+
const updateAssistantTurn = (message) => {
|
|
798
|
+
const turn = streamingTurn;
|
|
799
|
+
if (!turn)
|
|
800
|
+
return;
|
|
801
|
+
turn.assistant.updateContent(message);
|
|
802
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
803
|
+
for (const block of content) {
|
|
804
|
+
const record = recordOf(block);
|
|
805
|
+
if (!record || record.type !== "toolCall")
|
|
806
|
+
continue;
|
|
807
|
+
const id = typeof record.id === "string" ? record.id : null;
|
|
808
|
+
const name = typeof record.name === "string" ? record.name : "tool";
|
|
809
|
+
if (!id)
|
|
810
|
+
continue;
|
|
811
|
+
const component = ensureToolComponent(turn, id, name, record.arguments);
|
|
812
|
+
component.updateArgs(record.arguments);
|
|
813
|
+
}
|
|
814
|
+
requestRender();
|
|
815
|
+
};
|
|
816
|
+
const endAssistantTurn = (message) => {
|
|
817
|
+
const turn = streamingTurn;
|
|
818
|
+
if (!turn)
|
|
819
|
+
return;
|
|
820
|
+
turn.assistant.updateContent(message);
|
|
821
|
+
const stopReason = typeof message.stopReason === "string" ? message.stopReason : "stop";
|
|
822
|
+
if (stopReason === "aborted" || stopReason === "error") {
|
|
823
|
+
const errorText = typeof message.errorMessage === "string" && message.errorMessage ? message.errorMessage : stopReason === "aborted" ? "Operation aborted" : "Error";
|
|
824
|
+
for (const component of turn.tools.values()) {
|
|
825
|
+
component.updateResult({ content: [{ type: "text", text: errorText }], isError: true });
|
|
826
|
+
}
|
|
827
|
+
} else {
|
|
828
|
+
for (const component of turn.tools.values()) {
|
|
829
|
+
component.setArgsComplete();
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
streamingTurn = null;
|
|
833
|
+
requestRender();
|
|
834
|
+
};
|
|
835
|
+
const mirrorUserMessage = (message) => {
|
|
836
|
+
const text = userMessageText(message).trim();
|
|
837
|
+
if (!text)
|
|
838
|
+
return;
|
|
839
|
+
const key = `user-${++sequence}`;
|
|
840
|
+
userComponents.set(key, new modules.components.UserMessageComponent(text));
|
|
841
|
+
pi.sendMessage?.({ customType: DRONE_USER_MESSAGE_TYPE, content: text, display: true, details: { key } }, { triggerTurn: false });
|
|
842
|
+
requestRender();
|
|
843
|
+
};
|
|
844
|
+
return {
|
|
845
|
+
captureTui(capturedTui) {
|
|
846
|
+
tui = recordOf(capturedTui);
|
|
847
|
+
return { render: () => [] };
|
|
848
|
+
},
|
|
849
|
+
handleWorkerEvent(event) {
|
|
850
|
+
const type = typeof event.type === "string" ? event.type : null;
|
|
851
|
+
switch (type) {
|
|
852
|
+
case "message_start": {
|
|
853
|
+
const message = recordOf(event.message);
|
|
854
|
+
if (!message)
|
|
855
|
+
return;
|
|
856
|
+
if (messageRole(event) === "assistant")
|
|
857
|
+
startAssistantTurn(message);
|
|
858
|
+
else if (messageRole(event) === "user")
|
|
859
|
+
mirrorUserMessage(message);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
case "message_update": {
|
|
863
|
+
const message = recordOf(event.message);
|
|
864
|
+
if (message && messageRole(event) === "assistant")
|
|
865
|
+
updateAssistantTurn(message);
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
case "message_end": {
|
|
869
|
+
const message = recordOf(event.message);
|
|
870
|
+
if (message && messageRole(event) === "assistant")
|
|
871
|
+
endAssistantTurn(message);
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
case "tool_execution_start": {
|
|
875
|
+
const id = typeof event.toolCallId === "string" ? event.toolCallId : null;
|
|
876
|
+
const name = typeof event.toolName === "string" ? event.toolName : "tool";
|
|
877
|
+
if (!id)
|
|
878
|
+
return;
|
|
879
|
+
const turn = toolOwners.get(id) ?? streamingTurn;
|
|
880
|
+
if (!turn)
|
|
881
|
+
return;
|
|
882
|
+
ensureToolComponent(turn, id, name, event.args).markExecutionStarted();
|
|
883
|
+
requestRender();
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
case "tool_execution_update": {
|
|
887
|
+
const id = typeof event.toolCallId === "string" ? event.toolCallId : null;
|
|
888
|
+
const component = id ? toolOwners.get(id)?.tools.get(id) : undefined;
|
|
889
|
+
const partial = recordOf(event.partialResult);
|
|
890
|
+
if (component && partial) {
|
|
891
|
+
const content = Array.isArray(partial.content) ? partial.content : [];
|
|
892
|
+
component.updateResult({ ...partial, content, isError: false }, true);
|
|
893
|
+
requestRender();
|
|
894
|
+
}
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
case "tool_execution_end": {
|
|
898
|
+
const id = typeof event.toolCallId === "string" ? event.toolCallId : null;
|
|
899
|
+
const component = id ? toolOwners.get(id)?.tools.get(id) : undefined;
|
|
900
|
+
const result = recordOf(event.result);
|
|
901
|
+
if (component && result) {
|
|
902
|
+
const content = Array.isArray(result.content) ? result.content : [];
|
|
903
|
+
component.updateResult({ ...result, content, isError: event.isError === true });
|
|
904
|
+
requestRender();
|
|
905
|
+
}
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
default:
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
|
|
678
915
|
// packages/pi-rig/src/tools.ts
|
|
679
916
|
function textResult(text, details) {
|
|
680
917
|
return { content: [{ type: "text", text }], ...details ? { details } : {} };
|
|
@@ -766,20 +1003,49 @@ function setStatus(ctx, id, text) {
|
|
|
766
1003
|
setStatusFn.call(ui, id, text);
|
|
767
1004
|
}
|
|
768
1005
|
}
|
|
769
|
-
function
|
|
1006
|
+
function uiOf(ctx) {
|
|
770
1007
|
const ui = ctx && typeof ctx === "object" ? ctx.ui : null;
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
1008
|
+
return ui && typeof ui === "object" ? ui : null;
|
|
1009
|
+
}
|
|
1010
|
+
function setTitle(ctx, title) {
|
|
1011
|
+
const ui = uiOf(ctx);
|
|
1012
|
+
const setTitleFn = ui?.setTitle;
|
|
1013
|
+
if (typeof setTitleFn === "function")
|
|
1014
|
+
setTitleFn.call(ui, title);
|
|
1015
|
+
}
|
|
1016
|
+
function shutdownPi(ctx) {
|
|
1017
|
+
const shutdownFn = ctx && typeof ctx === "object" ? ctx.shutdown : null;
|
|
1018
|
+
if (typeof shutdownFn === "function")
|
|
1019
|
+
shutdownFn.call(ctx);
|
|
1020
|
+
}
|
|
1021
|
+
async function askNativeDialog(ctx, request) {
|
|
1022
|
+
const ui = uiOf(ctx);
|
|
1023
|
+
if (!ui)
|
|
1024
|
+
return null;
|
|
1025
|
+
const { method, prompt, choices } = request;
|
|
1026
|
+
try {
|
|
1027
|
+
if (method === "confirm" && typeof ui.confirm === "function") {
|
|
1028
|
+
const confirmed = await ui.confirm("Worker request", prompt);
|
|
1029
|
+
return { value: confirmed, confirmed };
|
|
1030
|
+
}
|
|
1031
|
+
if (choices.length > 0 && typeof ui.select === "function") {
|
|
1032
|
+
const selected = await ui.select(prompt, choices);
|
|
1033
|
+
return selected === undefined ? { cancelled: true } : { value: selected };
|
|
1034
|
+
}
|
|
1035
|
+
if (typeof ui.input === "function") {
|
|
1036
|
+
const value = await ui.input("Worker request", prompt);
|
|
1037
|
+
return value === undefined ? { cancelled: true } : { value };
|
|
1038
|
+
}
|
|
1039
|
+
} catch {
|
|
1040
|
+
return { cancelled: true };
|
|
1041
|
+
}
|
|
1042
|
+
return null;
|
|
1043
|
+
}
|
|
1044
|
+
function shortPath(path, segments = 3) {
|
|
1045
|
+
const parts = path.split("/").filter(Boolean);
|
|
1046
|
+
if (parts.length <= segments)
|
|
1047
|
+
return path;
|
|
1048
|
+
return `\u2026/${parts.slice(-segments).join("/")}`;
|
|
783
1049
|
}
|
|
784
1050
|
function createBridgeGate(state) {
|
|
785
1051
|
let pending = null;
|
|
@@ -893,12 +1159,6 @@ async function handleOperatorInput(event, state, ctx, gate) {
|
|
|
893
1159
|
}
|
|
894
1160
|
return { action: "handled" };
|
|
895
1161
|
}
|
|
896
|
-
function shortEntry(entry) {
|
|
897
|
-
const type = String(entry.type ?? entry.title ?? "event");
|
|
898
|
-
const text = typeof entry.text === "string" ? entry.text : typeof entry.detail === "string" ? entry.detail : typeof entry.message === "string" ? entry.message : "";
|
|
899
|
-
return `${type}: ${text}`.slice(0, 160);
|
|
900
|
-
}
|
|
901
|
-
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
902
1162
|
function runLocation(run) {
|
|
903
1163
|
const worktree = typeof run.worktreePath === "string" && run.worktreePath.trim() ? run.worktreePath.trim() : null;
|
|
904
1164
|
const projectRoot = typeof run.projectRoot === "string" && run.projectRoot.trim() ? run.projectRoot.trim() : null;
|
|
@@ -908,42 +1168,23 @@ function runPayload(payload) {
|
|
|
908
1168
|
return payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
909
1169
|
}
|
|
910
1170
|
var OPERATOR_WIDGET_WS_FALLBACK_MS = 1e4;
|
|
911
|
-
function
|
|
1171
|
+
function startOperatorRunStatusLine(state, ctx, live) {
|
|
912
1172
|
if (!state.operatorSession || !state.active || !state.runId)
|
|
913
1173
|
return;
|
|
1174
|
+
const shortId = state.runId.slice(0, 8);
|
|
914
1175
|
let inFlight = false;
|
|
915
|
-
let frame = 0;
|
|
916
1176
|
let lastRefreshAt = 0;
|
|
917
1177
|
const refresh = async () => {
|
|
918
1178
|
if (inFlight)
|
|
919
1179
|
return;
|
|
920
1180
|
inFlight = true;
|
|
921
1181
|
lastRefreshAt = Date.now();
|
|
922
|
-
const spinner = SPINNER_FRAMES[frame++ % SPINNER_FRAMES.length] ?? "\u2022";
|
|
923
1182
|
try {
|
|
924
|
-
const
|
|
925
|
-
state.client.attach(state.runId),
|
|
926
|
-
state.client.runTimeline(state.runId, 8).catch(() => [])
|
|
927
|
-
]);
|
|
928
|
-
const run = runPayload(runPayloadRecord);
|
|
1183
|
+
const run = runPayload(await state.client.attach(state.runId));
|
|
929
1184
|
const status = String(run.status ?? "unknown");
|
|
930
|
-
|
|
931
|
-
const location = runLocation(run);
|
|
932
|
-
const detail = typeof run.statusDetail === "string" && run.statusDetail.trim() ? run.statusDetail.trim() : String(run.title ?? run.taskId ?? "");
|
|
933
|
-
const lines = [
|
|
934
|
-
header,
|
|
935
|
-
`worker: ${location}`.slice(0, 200),
|
|
936
|
-
...detail ? [detail.slice(0, 160)] : [],
|
|
937
|
-
...timeline.slice(-5).map(shortEntry)
|
|
938
|
-
];
|
|
939
|
-
setStatus(ctx, "rig", `${spinner} Rig ${status}`);
|
|
940
|
-
setFooter(ctx, `${spinner} Rig ${String(run.runId ?? state.runId)} \xB7 ${status} \xB7 worker ${location}`);
|
|
941
|
-
setWidget(ctx, "rig-run", lines);
|
|
1185
|
+
setStatus(ctx, "rig", `drone ${shortId} \xB7 ${status} \xB7 ${shortPath(runLocation(run))}`);
|
|
942
1186
|
} catch (error) {
|
|
943
|
-
|
|
944
|
-
setStatus(ctx, "rig", `${spinner} Rig unavailable`);
|
|
945
|
-
setFooter(ctx, `${spinner} Rig ${state.runId} \xB7 unavailable \xB7 ${message}`);
|
|
946
|
-
setWidget(ctx, "rig-run", [`${spinner} Rig ${state.runId} \xB7 unavailable`, message]);
|
|
1187
|
+
setStatus(ctx, "rig", `drone ${shortId} \xB7 server unreachable: ${error instanceof Error ? error.message : String(error)}`);
|
|
947
1188
|
} finally {
|
|
948
1189
|
inFlight = false;
|
|
949
1190
|
}
|
|
@@ -955,7 +1196,7 @@ function startOperatorRunWidget(state, ctx, live) {
|
|
|
955
1196
|
return;
|
|
956
1197
|
}
|
|
957
1198
|
refresh();
|
|
958
|
-
},
|
|
1199
|
+
}, 5000);
|
|
959
1200
|
unrefTimer(timer);
|
|
960
1201
|
}
|
|
961
1202
|
function operatorInboxNotification(event) {
|
|
@@ -1012,47 +1253,6 @@ function startOperatorBridge(state, ctx) {
|
|
|
1012
1253
|
}
|
|
1013
1254
|
};
|
|
1014
1255
|
}
|
|
1015
|
-
var MIRROR_TEXT_LIMIT = 2000;
|
|
1016
|
-
function clipMirrorText(text) {
|
|
1017
|
-
const trimmed = text.trim();
|
|
1018
|
-
return trimmed.length > MIRROR_TEXT_LIMIT ? `${trimmed.slice(0, MIRROR_TEXT_LIMIT)}
|
|
1019
|
-
\u2026 [clipped; full output in the worker session]` : trimmed;
|
|
1020
|
-
}
|
|
1021
|
-
function workerMirrorText(message) {
|
|
1022
|
-
const role = typeof message.role === "string" ? message.role : "assistant";
|
|
1023
|
-
const content = message.content;
|
|
1024
|
-
const parts = [];
|
|
1025
|
-
if (typeof content === "string") {
|
|
1026
|
-
parts.push(content);
|
|
1027
|
-
} else if (Array.isArray(content)) {
|
|
1028
|
-
for (const block of content) {
|
|
1029
|
-
if (!block || typeof block !== "object" || Array.isArray(block))
|
|
1030
|
-
continue;
|
|
1031
|
-
const record = block;
|
|
1032
|
-
if (record.type === "text" && typeof record.text === "string" && record.text.trim()) {
|
|
1033
|
-
parts.push(record.text);
|
|
1034
|
-
continue;
|
|
1035
|
-
}
|
|
1036
|
-
if (record.type === "toolCall") {
|
|
1037
|
-
const name = typeof record.name === "string" ? record.name : "tool";
|
|
1038
|
-
let args = "";
|
|
1039
|
-
try {
|
|
1040
|
-
args = record.arguments === undefined ? "" : JSON.stringify(record.arguments);
|
|
1041
|
-
} catch {
|
|
1042
|
-
args = "";
|
|
1043
|
-
}
|
|
1044
|
-
parts.push(`\u2699 ${name}(${args.length > 160 ? `${args.slice(0, 160)}\u2026` : args})`);
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
const body = parts.join(`
|
|
1049
|
-
`).trim();
|
|
1050
|
-
if (!body)
|
|
1051
|
-
return null;
|
|
1052
|
-
const label = role === "assistant" ? "worker" : role === "user" ? "worker \u21D0 input" : role === "toolResult" ? `worker \u2937 ${typeof message.toolName === "string" ? message.toolName : "tool"} result` : `worker (${role})`;
|
|
1053
|
-
return `[${label}]
|
|
1054
|
-
${clipMirrorText(body)}`;
|
|
1055
|
-
}
|
|
1056
1256
|
function workerStatusLine(status) {
|
|
1057
1257
|
const model = status.model && typeof status.model === "object" && !Array.isArray(status.model) ? status.model : null;
|
|
1058
1258
|
const modelId = model && typeof model.id === "string" ? model.id : null;
|
|
@@ -1062,23 +1262,129 @@ function workerStatusLine(status) {
|
|
|
1062
1262
|
const parts = [modelId, percent, streaming].filter((value) => Boolean(value));
|
|
1063
1263
|
return parts.length > 0 ? `worker ${parts.join(" \xB7 ")}` : "worker session connected";
|
|
1064
1264
|
}
|
|
1265
|
+
function applyRigTheme(ctx) {
|
|
1266
|
+
const ui = uiOf(ctx);
|
|
1267
|
+
if (!ui || typeof ui.getTheme !== "function" || typeof ui.setTheme !== "function")
|
|
1268
|
+
return;
|
|
1269
|
+
try {
|
|
1270
|
+
const theme = ui.getTheme("rig");
|
|
1271
|
+
if (theme)
|
|
1272
|
+
ui.setTheme(theme);
|
|
1273
|
+
} catch {}
|
|
1274
|
+
}
|
|
1275
|
+
var MICRO_DRONE_BLADES = ["---", "\\\\\\", "|||", "///"];
|
|
1276
|
+
var MICRO_DRONE_EYES = ["@", "o", "."];
|
|
1277
|
+
function applyDroneWorkingIndicator(ctx) {
|
|
1278
|
+
const ui = uiOf(ctx);
|
|
1279
|
+
if (!ui || typeof ui.setWorkingIndicator !== "function")
|
|
1280
|
+
return;
|
|
1281
|
+
const frames = Array.from({ length: 12 }, (_, index) => {
|
|
1282
|
+
const blade = MICRO_DRONE_BLADES[index % MICRO_DRONE_BLADES.length];
|
|
1283
|
+
const eye = MICRO_DRONE_EYES[Math.floor(index / 2) % MICRO_DRONE_EYES.length];
|
|
1284
|
+
return `(${blade})${eye}(${blade})`;
|
|
1285
|
+
});
|
|
1286
|
+
try {
|
|
1287
|
+
ui.setWorkingIndicator({ frames, intervalMs: 120 });
|
|
1288
|
+
} catch {}
|
|
1289
|
+
}
|
|
1290
|
+
function renderWorkerCapabilities(capabilities) {
|
|
1291
|
+
const names = (value, key = "name") => Array.isArray(value) ? value.flatMap((entry) => {
|
|
1292
|
+
if (typeof entry === "string")
|
|
1293
|
+
return [entry];
|
|
1294
|
+
if (entry && typeof entry === "object" && !Array.isArray(entry)) {
|
|
1295
|
+
const name = entry[key];
|
|
1296
|
+
return typeof name === "string" ? [name] : [];
|
|
1297
|
+
}
|
|
1298
|
+
return [];
|
|
1299
|
+
}) : [];
|
|
1300
|
+
const lines = ["Drone capabilities (in effect for this run)"];
|
|
1301
|
+
const tools = Array.isArray(capabilities.tools) ? capabilities.tools : [];
|
|
1302
|
+
const activeTools = tools.flatMap((tool) => {
|
|
1303
|
+
const record = tool && typeof tool === "object" && !Array.isArray(tool) ? tool : null;
|
|
1304
|
+
return record && record.active === true && typeof record.name === "string" ? [record.name] : [];
|
|
1305
|
+
});
|
|
1306
|
+
const inactiveCount = tools.length - activeTools.length;
|
|
1307
|
+
lines.push(` tools ${activeTools.join(", ") || "(none)"}${inactiveCount > 0 ? ` (+${inactiveCount} inactive)` : ""}`);
|
|
1308
|
+
const extensions = Array.isArray(capabilities.extensions) ? capabilities.extensions : [];
|
|
1309
|
+
const extensionLabels = extensions.flatMap((entry) => {
|
|
1310
|
+
const record = entry && typeof entry === "object" && !Array.isArray(entry) ? entry : null;
|
|
1311
|
+
if (!record)
|
|
1312
|
+
return [];
|
|
1313
|
+
const path = typeof record.path === "string" ? record.path : "";
|
|
1314
|
+
const short = path.split("/").filter(Boolean).slice(-2).join("/") || path || "extension";
|
|
1315
|
+
return [short];
|
|
1316
|
+
});
|
|
1317
|
+
lines.push(` extensions ${extensionLabels.join(", ") || "(none)"}`);
|
|
1318
|
+
lines.push(` hooks ${names(capabilities.hookEvents).join(", ") || "(none)"}`);
|
|
1319
|
+
lines.push(` skills ${names(capabilities.skills).join(", ") || "(none)"}`);
|
|
1320
|
+
lines.push(` prompts ${names(capabilities.prompts).join(", ") || "(none)"}`);
|
|
1321
|
+
const model = typeof capabilities.model === "string" ? capabilities.model : "(unknown)";
|
|
1322
|
+
const thinking = typeof capabilities.thinkingLevel === "string" ? capabilities.thinkingLevel : "";
|
|
1323
|
+
lines.push(` model ${model}${thinking ? ` \xB7 ${thinking}` : ""}`);
|
|
1324
|
+
if (typeof capabilities.cwd === "string" && capabilities.cwd)
|
|
1325
|
+
lines.push(` cwd ${capabilities.cwd}`);
|
|
1326
|
+
lines.push(" (drone commands are in your palette \xB7 /worker toggles this panel)");
|
|
1327
|
+
return lines;
|
|
1328
|
+
}
|
|
1329
|
+
function registerOperatorConsoleCommands(pi, state, tryRegister) {
|
|
1330
|
+
if (typeof pi.registerCommand !== "function")
|
|
1331
|
+
return;
|
|
1332
|
+
let capabilitiesShown = false;
|
|
1333
|
+
tryRegister("command:detach", () => pi.registerCommand?.("detach", {
|
|
1334
|
+
description: "Detach from this run; the drone keeps flying",
|
|
1335
|
+
handler: async (_args, ctx) => {
|
|
1336
|
+
notify(ctx, "Detached. The drone continues on the worker.");
|
|
1337
|
+
shutdownPi(ctx);
|
|
1338
|
+
}
|
|
1339
|
+
}));
|
|
1340
|
+
tryRegister("command:stop", () => pi.registerCommand?.("stop", {
|
|
1341
|
+
description: "Abort the drone's current turn and detach",
|
|
1342
|
+
handler: async (_args, ctx) => {
|
|
1343
|
+
try {
|
|
1344
|
+
await state.client.workerAbort(state.runId);
|
|
1345
|
+
notify(ctx, "Abort requested; detaching.");
|
|
1346
|
+
} catch (error) {
|
|
1347
|
+
notify(ctx, `Abort failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1348
|
+
}
|
|
1349
|
+
shutdownPi(ctx);
|
|
1350
|
+
}
|
|
1351
|
+
}));
|
|
1352
|
+
tryRegister("command:worker", () => pi.registerCommand?.("worker", {
|
|
1353
|
+
description: "Toggle the drone's capability panel (tools, extensions, hooks, skills, model)",
|
|
1354
|
+
handler: async (_args, ctx) => {
|
|
1355
|
+
if (capabilitiesShown) {
|
|
1356
|
+
const ui = uiOf(ctx);
|
|
1357
|
+
if (ui && typeof ui.setWidget === "function")
|
|
1358
|
+
ui.setWidget("rig-worker-capabilities", undefined);
|
|
1359
|
+
capabilitiesShown = false;
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
try {
|
|
1363
|
+
const capabilities = await state.client.workerCapabilities(state.runId);
|
|
1364
|
+
setWidget(ctx, "rig-worker-capabilities", renderWorkerCapabilities(capabilities));
|
|
1365
|
+
capabilitiesShown = true;
|
|
1366
|
+
} catch (error) {
|
|
1367
|
+
notify(ctx, `Drone capabilities unavailable: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}));
|
|
1371
|
+
}
|
|
1065
1372
|
function startWorkerSessionMirror(pi, state, ctx) {
|
|
1066
1373
|
if (!state.operatorSession || !state.active || !state.runId)
|
|
1067
1374
|
return;
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
if (mirrored.size > 1000) {
|
|
1076
|
-
const oldest = mirrored.values().next().value;
|
|
1077
|
-
if (typeof oldest === "string")
|
|
1078
|
-
mirrored.delete(oldest);
|
|
1375
|
+
let mirror = null;
|
|
1376
|
+
const pendingEvents = [];
|
|
1377
|
+
createLiveMirror({ pi }).then((created) => {
|
|
1378
|
+
mirror = created;
|
|
1379
|
+
const ui = uiOf(ctx);
|
|
1380
|
+
if (ui && typeof ui.setWidget === "function") {
|
|
1381
|
+
ui.setWidget("rig-tui-capture", (tui) => created.captureTui(tui));
|
|
1079
1382
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1383
|
+
for (const event of pendingEvents.splice(0))
|
|
1384
|
+
created.handleWorkerEvent(event);
|
|
1385
|
+
}).catch((error) => {
|
|
1386
|
+
notify(ctx, `Live drone transcript unavailable: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1387
|
+
});
|
|
1082
1388
|
const socket = new RigWorkerEventsSocket({
|
|
1083
1389
|
context: state,
|
|
1084
1390
|
webSocketFactory: state.webSocketFactory,
|
|
@@ -1093,29 +1399,51 @@ function startWorkerSessionMirror(pi, state, ctx) {
|
|
|
1093
1399
|
if (frame.type !== "pi.event")
|
|
1094
1400
|
return;
|
|
1095
1401
|
const event = frame.event && typeof frame.event === "object" && !Array.isArray(frame.event) ? frame.event : null;
|
|
1096
|
-
if (!event
|
|
1097
|
-
return;
|
|
1098
|
-
const message = event.message && typeof event.message === "object" && !Array.isArray(event.message) ? event.message : null;
|
|
1099
|
-
if (!message)
|
|
1100
|
-
return;
|
|
1101
|
-
const key = typeof message.id === "string" && message.id.trim() ? message.id : `${String(message.role ?? "message")}:${String(message.timestamp ?? "")}`;
|
|
1102
|
-
if (!rememberMirrored(key))
|
|
1402
|
+
if (!event)
|
|
1103
1403
|
return;
|
|
1104
|
-
|
|
1105
|
-
|
|
1404
|
+
if (event.type === "extension_ui_request") {
|
|
1405
|
+
forwardWorkerUiRequest(state, ctx, event);
|
|
1106
1406
|
return;
|
|
1107
|
-
|
|
1407
|
+
}
|
|
1408
|
+
if (mirror)
|
|
1409
|
+
mirror.handleWorkerEvent(event);
|
|
1410
|
+
else
|
|
1411
|
+
pendingEvents.push(event);
|
|
1108
1412
|
},
|
|
1109
1413
|
onConnect: () => {
|
|
1110
|
-
setStatus(ctx, "rig-worker", "
|
|
1414
|
+
setStatus(ctx, "rig-worker", "drone link live");
|
|
1111
1415
|
},
|
|
1112
1416
|
onDisconnect: () => {
|
|
1113
|
-
setStatus(ctx, "rig-worker", "
|
|
1417
|
+
setStatus(ctx, "rig-worker", "drone link down (reconnecting\u2026)");
|
|
1114
1418
|
}
|
|
1115
1419
|
}
|
|
1116
1420
|
});
|
|
1117
1421
|
socket.start();
|
|
1118
1422
|
}
|
|
1423
|
+
async function forwardWorkerUiRequest(state, ctx, event) {
|
|
1424
|
+
const request = event.request && typeof event.request === "object" && !Array.isArray(event.request) ? event.request : event;
|
|
1425
|
+
const requestId = String(request.requestId ?? request.id ?? `ui-${state.runId}-${event.timestamp ?? ""}`);
|
|
1426
|
+
const method = String(request.method ?? request.type ?? "input");
|
|
1427
|
+
const prompt = typeof request.prompt === "string" && request.prompt.trim() ? request.prompt : typeof request.message === "string" && request.message.trim() ? request.message : typeof request.title === "string" && request.title.trim() ? request.title : method;
|
|
1428
|
+
const rawChoices = Array.isArray(request.options) ? request.options : Array.isArray(request.items) ? request.items : [];
|
|
1429
|
+
const choices = rawChoices.map((option) => {
|
|
1430
|
+
if (typeof option === "string")
|
|
1431
|
+
return option;
|
|
1432
|
+
if (option && typeof option === "object" && !Array.isArray(option)) {
|
|
1433
|
+
const record = option;
|
|
1434
|
+
return String(record.label ?? record.value ?? record.name ?? "");
|
|
1435
|
+
}
|
|
1436
|
+
return "";
|
|
1437
|
+
}).filter(Boolean);
|
|
1438
|
+
const answer = await askNativeDialog(ctx, { method, prompt, choices });
|
|
1439
|
+
if (!answer)
|
|
1440
|
+
return;
|
|
1441
|
+
try {
|
|
1442
|
+
await state.client.workerRespondExtensionUi(requestId, answer, state.runId);
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
notify(ctx, `Failed to answer the drone's question: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1119
1447
|
async function registerWorkerCommands(pi, state, ctx) {
|
|
1120
1448
|
if (!state.operatorSession || !state.active || !state.runId)
|
|
1121
1449
|
return;
|
|
@@ -1243,6 +1571,9 @@ function createPiRigExtension(pi, options = {}) {
|
|
|
1243
1571
|
}
|
|
1244
1572
|
}));
|
|
1245
1573
|
}
|
|
1574
|
+
if (state.operatorSession && state.active && state.runId) {
|
|
1575
|
+
registerOperatorConsoleCommands(pi, state, tryRegister);
|
|
1576
|
+
}
|
|
1246
1577
|
if (state.active && state.runId) {
|
|
1247
1578
|
for (const tool of createRigTools({ context: state, client: state.client })) {
|
|
1248
1579
|
tryRegister(`tool:${String(tool.name ?? "rig-tool")}`, () => pi.registerTool?.({
|
|
@@ -1262,23 +1593,21 @@ function createPiRigExtension(pi, options = {}) {
|
|
|
1262
1593
|
pi.on?.("session_start", async (_event, ctx) => {
|
|
1263
1594
|
if (!state.active || !state.runId)
|
|
1264
1595
|
return;
|
|
1265
|
-
|
|
1596
|
+
const shortId = state.runId.slice(0, 8);
|
|
1597
|
+
setStatus(ctx, "rig", `drone ${shortId} \xB7 waiting for worker daemon\u2026`);
|
|
1266
1598
|
if (state.operatorSession) {
|
|
1267
|
-
|
|
1268
|
-
|
|
1599
|
+
setTitle(ctx, `Rig \xB7 run ${shortId}`);
|
|
1600
|
+
applyRigTheme(ctx);
|
|
1601
|
+
applyDroneWorkingIndicator(ctx);
|
|
1269
1602
|
}
|
|
1270
1603
|
const gateResult = await gate(ctx);
|
|
1271
1604
|
if (!gateResult.allowed) {
|
|
1272
|
-
|
|
1273
|
-
const message = gateResult.message ?? "Rig bridge disabled (protocol mismatch).";
|
|
1274
|
-
setFooter(ctx, `Rig ${state.runId} \xB7 bridge disabled (protocol mismatch)`);
|
|
1275
|
-
setWidget(ctx, "rig-run", [message]);
|
|
1276
|
-
}
|
|
1605
|
+
setStatus(ctx, "rig", `drone ${shortId} \xB7 bridge disabled (protocol mismatch)`);
|
|
1277
1606
|
return;
|
|
1278
1607
|
}
|
|
1279
|
-
setStatus(ctx, "rig", `
|
|
1608
|
+
setStatus(ctx, "rig", `drone ${shortId} \xB7 connecting\u2026`);
|
|
1280
1609
|
const live = gateResult.status === "compatible" ? startOperatorBridge(state, ctx) : undefined;
|
|
1281
|
-
|
|
1610
|
+
startOperatorRunStatusLine(state, ctx, live);
|
|
1282
1611
|
if (state.operatorSession && gateResult.status === "compatible") {
|
|
1283
1612
|
startWorkerSessionMirror(pi, state, ctx);
|
|
1284
1613
|
registerWorkerCommands(pi, state, ctx);
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
4
|
+
// packages/pi-rig/src/live-mirror.ts
|
|
5
|
+
async function loadPiModules() {
|
|
6
|
+
const components = await import("@earendil-works/pi-coding-agent");
|
|
7
|
+
return { components };
|
|
8
|
+
}
|
|
9
|
+
function createRootContainer() {
|
|
10
|
+
const children = [];
|
|
11
|
+
return {
|
|
12
|
+
addChild(child) {
|
|
13
|
+
children.push(child);
|
|
14
|
+
},
|
|
15
|
+
removeChild(child) {
|
|
16
|
+
const index = children.indexOf(child);
|
|
17
|
+
if (index >= 0)
|
|
18
|
+
children.splice(index, 1);
|
|
19
|
+
},
|
|
20
|
+
clear() {
|
|
21
|
+
children.length = 0;
|
|
22
|
+
},
|
|
23
|
+
render(width) {
|
|
24
|
+
return children.flatMap((child) => child.render(width));
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
var DRONE_MESSAGE_TYPE = "rig-drone";
|
|
29
|
+
var DRONE_USER_MESSAGE_TYPE = "rig-drone-user";
|
|
30
|
+
function recordOf(value) {
|
|
31
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
32
|
+
}
|
|
33
|
+
function messageRole(event) {
|
|
34
|
+
const message = recordOf(event.message);
|
|
35
|
+
return message && typeof message.role === "string" ? message.role : null;
|
|
36
|
+
}
|
|
37
|
+
function userMessageText(message) {
|
|
38
|
+
const content = message.content;
|
|
39
|
+
if (typeof content === "string")
|
|
40
|
+
return content;
|
|
41
|
+
if (Array.isArray(content)) {
|
|
42
|
+
return content.flatMap((block) => {
|
|
43
|
+
const record = recordOf(block);
|
|
44
|
+
return record && record.type === "text" && typeof record.text === "string" ? [record.text] : [];
|
|
45
|
+
}).join(`
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
async function createLiveMirror(input) {
|
|
51
|
+
const { pi } = input;
|
|
52
|
+
const modules = input.modules ?? await loadPiModules();
|
|
53
|
+
const cwd = input.workerCwd ?? process.cwd();
|
|
54
|
+
const turns = new Map;
|
|
55
|
+
const toolOwners = new Map;
|
|
56
|
+
const userComponents = new Map;
|
|
57
|
+
let streamingTurn = null;
|
|
58
|
+
let sequence = 0;
|
|
59
|
+
let tui = null;
|
|
60
|
+
const requestRender = () => {
|
|
61
|
+
tui?.requestRender?.();
|
|
62
|
+
};
|
|
63
|
+
pi.registerMessageRenderer?.(DRONE_MESSAGE_TYPE, (message) => {
|
|
64
|
+
const details = recordOf(recordOf(message)?.details);
|
|
65
|
+
const key = details && typeof details.key === "string" ? details.key : null;
|
|
66
|
+
return key ? turns.get(key)?.root : undefined;
|
|
67
|
+
});
|
|
68
|
+
pi.registerMessageRenderer?.(DRONE_USER_MESSAGE_TYPE, (message) => {
|
|
69
|
+
const details = recordOf(recordOf(message)?.details);
|
|
70
|
+
const key = details && typeof details.key === "string" ? details.key : null;
|
|
71
|
+
return key ? userComponents.get(key) : undefined;
|
|
72
|
+
});
|
|
73
|
+
const ensureToolComponent = (turn, toolCallId, toolName, args) => {
|
|
74
|
+
const existing = turn.tools.get(toolCallId);
|
|
75
|
+
if (existing)
|
|
76
|
+
return existing;
|
|
77
|
+
const component = new modules.components.ToolExecutionComponent(toolName, toolCallId, args, {}, undefined, tui, cwd);
|
|
78
|
+
turn.root.addChild(component);
|
|
79
|
+
turn.tools.set(toolCallId, component);
|
|
80
|
+
toolOwners.set(toolCallId, turn);
|
|
81
|
+
return component;
|
|
82
|
+
};
|
|
83
|
+
const startAssistantTurn = (message) => {
|
|
84
|
+
const key = `turn-${++sequence}`;
|
|
85
|
+
const root = createRootContainer();
|
|
86
|
+
const assistant = new modules.components.AssistantMessageComponent;
|
|
87
|
+
root.addChild(assistant);
|
|
88
|
+
const turn = { root, assistant, tools: new Map };
|
|
89
|
+
assistant.updateContent(message);
|
|
90
|
+
turns.set(key, turn);
|
|
91
|
+
streamingTurn = turn;
|
|
92
|
+
pi.sendMessage?.({ customType: DRONE_MESSAGE_TYPE, content: "drone turn", display: true, details: { key } }, { triggerTurn: false });
|
|
93
|
+
requestRender();
|
|
94
|
+
};
|
|
95
|
+
const updateAssistantTurn = (message) => {
|
|
96
|
+
const turn = streamingTurn;
|
|
97
|
+
if (!turn)
|
|
98
|
+
return;
|
|
99
|
+
turn.assistant.updateContent(message);
|
|
100
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
101
|
+
for (const block of content) {
|
|
102
|
+
const record = recordOf(block);
|
|
103
|
+
if (!record || record.type !== "toolCall")
|
|
104
|
+
continue;
|
|
105
|
+
const id = typeof record.id === "string" ? record.id : null;
|
|
106
|
+
const name = typeof record.name === "string" ? record.name : "tool";
|
|
107
|
+
if (!id)
|
|
108
|
+
continue;
|
|
109
|
+
const component = ensureToolComponent(turn, id, name, record.arguments);
|
|
110
|
+
component.updateArgs(record.arguments);
|
|
111
|
+
}
|
|
112
|
+
requestRender();
|
|
113
|
+
};
|
|
114
|
+
const endAssistantTurn = (message) => {
|
|
115
|
+
const turn = streamingTurn;
|
|
116
|
+
if (!turn)
|
|
117
|
+
return;
|
|
118
|
+
turn.assistant.updateContent(message);
|
|
119
|
+
const stopReason = typeof message.stopReason === "string" ? message.stopReason : "stop";
|
|
120
|
+
if (stopReason === "aborted" || stopReason === "error") {
|
|
121
|
+
const errorText = typeof message.errorMessage === "string" && message.errorMessage ? message.errorMessage : stopReason === "aborted" ? "Operation aborted" : "Error";
|
|
122
|
+
for (const component of turn.tools.values()) {
|
|
123
|
+
component.updateResult({ content: [{ type: "text", text: errorText }], isError: true });
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
for (const component of turn.tools.values()) {
|
|
127
|
+
component.setArgsComplete();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
streamingTurn = null;
|
|
131
|
+
requestRender();
|
|
132
|
+
};
|
|
133
|
+
const mirrorUserMessage = (message) => {
|
|
134
|
+
const text = userMessageText(message).trim();
|
|
135
|
+
if (!text)
|
|
136
|
+
return;
|
|
137
|
+
const key = `user-${++sequence}`;
|
|
138
|
+
userComponents.set(key, new modules.components.UserMessageComponent(text));
|
|
139
|
+
pi.sendMessage?.({ customType: DRONE_USER_MESSAGE_TYPE, content: text, display: true, details: { key } }, { triggerTurn: false });
|
|
140
|
+
requestRender();
|
|
141
|
+
};
|
|
142
|
+
return {
|
|
143
|
+
captureTui(capturedTui) {
|
|
144
|
+
tui = recordOf(capturedTui);
|
|
145
|
+
return { render: () => [] };
|
|
146
|
+
},
|
|
147
|
+
handleWorkerEvent(event) {
|
|
148
|
+
const type = typeof event.type === "string" ? event.type : null;
|
|
149
|
+
switch (type) {
|
|
150
|
+
case "message_start": {
|
|
151
|
+
const message = recordOf(event.message);
|
|
152
|
+
if (!message)
|
|
153
|
+
return;
|
|
154
|
+
if (messageRole(event) === "assistant")
|
|
155
|
+
startAssistantTurn(message);
|
|
156
|
+
else if (messageRole(event) === "user")
|
|
157
|
+
mirrorUserMessage(message);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
case "message_update": {
|
|
161
|
+
const message = recordOf(event.message);
|
|
162
|
+
if (message && messageRole(event) === "assistant")
|
|
163
|
+
updateAssistantTurn(message);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
case "message_end": {
|
|
167
|
+
const message = recordOf(event.message);
|
|
168
|
+
if (message && messageRole(event) === "assistant")
|
|
169
|
+
endAssistantTurn(message);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
case "tool_execution_start": {
|
|
173
|
+
const id = typeof event.toolCallId === "string" ? event.toolCallId : null;
|
|
174
|
+
const name = typeof event.toolName === "string" ? event.toolName : "tool";
|
|
175
|
+
if (!id)
|
|
176
|
+
return;
|
|
177
|
+
const turn = toolOwners.get(id) ?? streamingTurn;
|
|
178
|
+
if (!turn)
|
|
179
|
+
return;
|
|
180
|
+
ensureToolComponent(turn, id, name, event.args).markExecutionStarted();
|
|
181
|
+
requestRender();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
case "tool_execution_update": {
|
|
185
|
+
const id = typeof event.toolCallId === "string" ? event.toolCallId : null;
|
|
186
|
+
const component = id ? toolOwners.get(id)?.tools.get(id) : undefined;
|
|
187
|
+
const partial = recordOf(event.partialResult);
|
|
188
|
+
if (component && partial) {
|
|
189
|
+
const content = Array.isArray(partial.content) ? partial.content : [];
|
|
190
|
+
component.updateResult({ ...partial, content, isError: false }, true);
|
|
191
|
+
requestRender();
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
case "tool_execution_end": {
|
|
196
|
+
const id = typeof event.toolCallId === "string" ? event.toolCallId : null;
|
|
197
|
+
const component = id ? toolOwners.get(id)?.tools.get(id) : undefined;
|
|
198
|
+
const result = recordOf(event.result);
|
|
199
|
+
if (component && result) {
|
|
200
|
+
const content = Array.isArray(result.content) ? result.content : [];
|
|
201
|
+
component.updateResult({ ...result, content, isError: event.isError === true });
|
|
202
|
+
requestRender();
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
default:
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
export {
|
|
213
|
+
createLiveMirror,
|
|
214
|
+
DRONE_USER_MESSAGE_TYPE,
|
|
215
|
+
DRONE_MESSAGE_TYPE
|
|
216
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-rig/pi-rig",
|
|
3
|
-
"version": "0.0.6-alpha.
|
|
3
|
+
"version": "0.0.6-alpha.78",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Rig package",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
]
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.
|
|
36
|
+
"@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.78"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@earendil-works/pi-coding-agent": ">=0.79.0",
|