@h-rig/pi-rig 0.0.6-alpha.77 → 0.0.6-alpha.79
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.d.ts +186 -0
- package/dist/src/client.js +29 -2
- package/dist/src/commands.d.ts +10 -0
- package/dist/src/index.d.ts +59 -0
- package/dist/src/index.js +501 -129
- package/dist/src/live-mirror.d.ts +46 -0
- package/dist/src/live-mirror.js +223 -0
- package/dist/src/tools.d.ts +19 -0
- package/package.json +7 -2
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";
|
|
@@ -266,10 +268,11 @@ class RigBridgeClient {
|
|
|
266
268
|
return commands.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
|
|
267
269
|
}
|
|
268
270
|
async workerRunCommand(command, args, runId = this.context.runId) {
|
|
271
|
+
const text = `/${command}${args.trim() ? ` ${args.trim()}` : ""}`;
|
|
269
272
|
const payload = await this.piProxy("commands/run", {
|
|
270
273
|
method: "POST",
|
|
271
274
|
headers: { "content-type": "application/json" },
|
|
272
|
-
body: JSON.stringify({
|
|
275
|
+
body: JSON.stringify({ text })
|
|
273
276
|
}, runId);
|
|
274
277
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
275
278
|
}
|
|
@@ -277,7 +280,7 @@ class RigBridgeClient {
|
|
|
277
280
|
const payload = await this.piProxy("shell", {
|
|
278
281
|
method: "POST",
|
|
279
282
|
headers: { "content-type": "application/json" },
|
|
280
|
-
body: JSON.stringify({ command })
|
|
283
|
+
body: JSON.stringify({ text: command })
|
|
281
284
|
}, runId);
|
|
282
285
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
283
286
|
}
|
|
@@ -285,6 +288,32 @@ class RigBridgeClient {
|
|
|
285
288
|
const payload = await this.piProxy("abort", { method: "POST" }, runId);
|
|
286
289
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
287
290
|
}
|
|
291
|
+
async workerCapabilities(runId = this.context.runId) {
|
|
292
|
+
const payload = await this.piProxy("capabilities", undefined, runId);
|
|
293
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
294
|
+
}
|
|
295
|
+
async workerRespondExtensionUi(requestId, response, runId = this.context.runId) {
|
|
296
|
+
const payload = await this.piProxy("extension-ui/respond", {
|
|
297
|
+
method: "POST",
|
|
298
|
+
headers: { "content-type": "application/json" },
|
|
299
|
+
body: JSON.stringify({ requestId, ...response })
|
|
300
|
+
}, runId);
|
|
301
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
302
|
+
}
|
|
303
|
+
async fetchRunSessionFile(runId = this.context.runId) {
|
|
304
|
+
if (!runId)
|
|
305
|
+
return null;
|
|
306
|
+
const payload = await this.request(`/api/runs/${encodeURIComponent(runId)}/session-file`);
|
|
307
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
308
|
+
return null;
|
|
309
|
+
const record = payload;
|
|
310
|
+
if (record.ok !== true || typeof record.content !== "string" || !record.content.trim())
|
|
311
|
+
return null;
|
|
312
|
+
return {
|
|
313
|
+
fileName: typeof record.fileName === "string" && record.fileName.trim() ? record.fileName : `rig-run-${runId}.jsonl`,
|
|
314
|
+
content: record.content
|
|
315
|
+
};
|
|
316
|
+
}
|
|
288
317
|
}
|
|
289
318
|
function buildRigWebSocketUrl(serverUrl, authToken) {
|
|
290
319
|
const url = new URL(serverUrl);
|
|
@@ -675,6 +704,222 @@ function createRigSlashCommands(input) {
|
|
|
675
704
|
};
|
|
676
705
|
}
|
|
677
706
|
|
|
707
|
+
// packages/pi-rig/src/live-mirror.ts
|
|
708
|
+
async function loadPiModules() {
|
|
709
|
+
const components = await import("@earendil-works/pi-coding-agent");
|
|
710
|
+
return { components };
|
|
711
|
+
}
|
|
712
|
+
function createRootContainer() {
|
|
713
|
+
const children = [];
|
|
714
|
+
return {
|
|
715
|
+
addChild(child) {
|
|
716
|
+
children.push(child);
|
|
717
|
+
},
|
|
718
|
+
removeChild(child) {
|
|
719
|
+
const index = children.indexOf(child);
|
|
720
|
+
if (index >= 0)
|
|
721
|
+
children.splice(index, 1);
|
|
722
|
+
},
|
|
723
|
+
clear() {
|
|
724
|
+
children.length = 0;
|
|
725
|
+
},
|
|
726
|
+
render(width) {
|
|
727
|
+
return children.flatMap((child) => child.render(width));
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
var DRONE_MESSAGE_TYPE = "rig-drone";
|
|
732
|
+
var DRONE_USER_MESSAGE_TYPE = "rig-drone-user";
|
|
733
|
+
function recordOf(value) {
|
|
734
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
735
|
+
}
|
|
736
|
+
function messageRole(event) {
|
|
737
|
+
const message = recordOf(event.message);
|
|
738
|
+
return message && typeof message.role === "string" ? message.role : null;
|
|
739
|
+
}
|
|
740
|
+
function userMessageText(message) {
|
|
741
|
+
const content = message.content;
|
|
742
|
+
if (typeof content === "string")
|
|
743
|
+
return content;
|
|
744
|
+
if (Array.isArray(content)) {
|
|
745
|
+
return content.flatMap((block) => {
|
|
746
|
+
const record = recordOf(block);
|
|
747
|
+
return record && record.type === "text" && typeof record.text === "string" ? [record.text] : [];
|
|
748
|
+
}).join(`
|
|
749
|
+
`);
|
|
750
|
+
}
|
|
751
|
+
return "";
|
|
752
|
+
}
|
|
753
|
+
async function createLiveMirror(input) {
|
|
754
|
+
const { pi } = input;
|
|
755
|
+
const modules = input.modules ?? await loadPiModules();
|
|
756
|
+
const cwd = input.workerCwd ?? process.cwd();
|
|
757
|
+
const turns = new Map;
|
|
758
|
+
const toolOwners = new Map;
|
|
759
|
+
const userComponents = new Map;
|
|
760
|
+
let streamingTurn = null;
|
|
761
|
+
let sequence = 0;
|
|
762
|
+
let tui = null;
|
|
763
|
+
let renderTimer = null;
|
|
764
|
+
const requestRender = () => {
|
|
765
|
+
if (renderTimer)
|
|
766
|
+
return;
|
|
767
|
+
renderTimer = setTimeout(() => {
|
|
768
|
+
renderTimer = null;
|
|
769
|
+
tui?.requestRender?.();
|
|
770
|
+
}, 33);
|
|
771
|
+
renderTimer.unref?.();
|
|
772
|
+
};
|
|
773
|
+
pi.registerMessageRenderer?.(DRONE_MESSAGE_TYPE, (message) => {
|
|
774
|
+
const details = recordOf(recordOf(message)?.details);
|
|
775
|
+
const key = details && typeof details.key === "string" ? details.key : null;
|
|
776
|
+
return key ? turns.get(key)?.root : undefined;
|
|
777
|
+
});
|
|
778
|
+
pi.registerMessageRenderer?.(DRONE_USER_MESSAGE_TYPE, (message) => {
|
|
779
|
+
const details = recordOf(recordOf(message)?.details);
|
|
780
|
+
const key = details && typeof details.key === "string" ? details.key : null;
|
|
781
|
+
return key ? userComponents.get(key) : undefined;
|
|
782
|
+
});
|
|
783
|
+
const ensureToolComponent = (turn, toolCallId, toolName, args) => {
|
|
784
|
+
const existing = turn.tools.get(toolCallId);
|
|
785
|
+
if (existing)
|
|
786
|
+
return existing;
|
|
787
|
+
const component = new modules.components.ToolExecutionComponent(toolName, toolCallId, args, {}, undefined, tui, cwd);
|
|
788
|
+
turn.root.addChild(component);
|
|
789
|
+
turn.tools.set(toolCallId, component);
|
|
790
|
+
toolOwners.set(toolCallId, turn);
|
|
791
|
+
return component;
|
|
792
|
+
};
|
|
793
|
+
const startAssistantTurn = (message) => {
|
|
794
|
+
const key = `turn-${++sequence}`;
|
|
795
|
+
const root = createRootContainer();
|
|
796
|
+
const assistant = new modules.components.AssistantMessageComponent;
|
|
797
|
+
root.addChild(assistant);
|
|
798
|
+
const turn = { root, assistant, tools: new Map };
|
|
799
|
+
assistant.updateContent(message);
|
|
800
|
+
turns.set(key, turn);
|
|
801
|
+
streamingTurn = turn;
|
|
802
|
+
pi.sendMessage?.({ customType: DRONE_MESSAGE_TYPE, content: "drone turn", display: true, details: { key } }, { triggerTurn: false });
|
|
803
|
+
requestRender();
|
|
804
|
+
};
|
|
805
|
+
const updateAssistantTurn = (message) => {
|
|
806
|
+
const turn = streamingTurn;
|
|
807
|
+
if (!turn)
|
|
808
|
+
return;
|
|
809
|
+
turn.assistant.updateContent(message);
|
|
810
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
811
|
+
for (const block of content) {
|
|
812
|
+
const record = recordOf(block);
|
|
813
|
+
if (!record || record.type !== "toolCall")
|
|
814
|
+
continue;
|
|
815
|
+
const id = typeof record.id === "string" ? record.id : null;
|
|
816
|
+
const name = typeof record.name === "string" ? record.name : "tool";
|
|
817
|
+
if (!id)
|
|
818
|
+
continue;
|
|
819
|
+
const component = ensureToolComponent(turn, id, name, record.arguments);
|
|
820
|
+
component.updateArgs(record.arguments);
|
|
821
|
+
}
|
|
822
|
+
requestRender();
|
|
823
|
+
};
|
|
824
|
+
const endAssistantTurn = (message) => {
|
|
825
|
+
const turn = streamingTurn;
|
|
826
|
+
if (!turn)
|
|
827
|
+
return;
|
|
828
|
+
turn.assistant.updateContent(message);
|
|
829
|
+
const stopReason = typeof message.stopReason === "string" ? message.stopReason : "stop";
|
|
830
|
+
if (stopReason === "aborted" || stopReason === "error") {
|
|
831
|
+
const errorText = typeof message.errorMessage === "string" && message.errorMessage ? message.errorMessage : stopReason === "aborted" ? "Operation aborted" : "Error";
|
|
832
|
+
for (const component of turn.tools.values()) {
|
|
833
|
+
component.updateResult({ content: [{ type: "text", text: errorText }], isError: true });
|
|
834
|
+
}
|
|
835
|
+
} else {
|
|
836
|
+
for (const component of turn.tools.values()) {
|
|
837
|
+
component.setArgsComplete();
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
streamingTurn = null;
|
|
841
|
+
requestRender();
|
|
842
|
+
};
|
|
843
|
+
const mirrorUserMessage = (message) => {
|
|
844
|
+
const text = userMessageText(message).trim();
|
|
845
|
+
if (!text)
|
|
846
|
+
return;
|
|
847
|
+
const key = `user-${++sequence}`;
|
|
848
|
+
userComponents.set(key, new modules.components.UserMessageComponent(text));
|
|
849
|
+
pi.sendMessage?.({ customType: DRONE_USER_MESSAGE_TYPE, content: text, display: true, details: { key } }, { triggerTurn: false });
|
|
850
|
+
requestRender();
|
|
851
|
+
};
|
|
852
|
+
return {
|
|
853
|
+
captureTui(capturedTui) {
|
|
854
|
+
tui = recordOf(capturedTui);
|
|
855
|
+
return { render: () => [] };
|
|
856
|
+
},
|
|
857
|
+
handleWorkerEvent(event) {
|
|
858
|
+
const type = typeof event.type === "string" ? event.type : null;
|
|
859
|
+
switch (type) {
|
|
860
|
+
case "message_start": {
|
|
861
|
+
const message = recordOf(event.message);
|
|
862
|
+
if (!message)
|
|
863
|
+
return;
|
|
864
|
+
if (messageRole(event) === "assistant")
|
|
865
|
+
startAssistantTurn(message);
|
|
866
|
+
else if (messageRole(event) === "user")
|
|
867
|
+
mirrorUserMessage(message);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
case "message_update": {
|
|
871
|
+
const message = recordOf(event.message);
|
|
872
|
+
if (message && messageRole(event) === "assistant")
|
|
873
|
+
updateAssistantTurn(message);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
case "message_end": {
|
|
877
|
+
const message = recordOf(event.message);
|
|
878
|
+
if (message && messageRole(event) === "assistant")
|
|
879
|
+
endAssistantTurn(message);
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
case "tool_execution_start": {
|
|
883
|
+
const id = typeof event.toolCallId === "string" ? event.toolCallId : null;
|
|
884
|
+
const name = typeof event.toolName === "string" ? event.toolName : "tool";
|
|
885
|
+
if (!id)
|
|
886
|
+
return;
|
|
887
|
+
const turn = toolOwners.get(id) ?? streamingTurn;
|
|
888
|
+
if (!turn)
|
|
889
|
+
return;
|
|
890
|
+
ensureToolComponent(turn, id, name, event.args).markExecutionStarted();
|
|
891
|
+
requestRender();
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
case "tool_execution_update": {
|
|
895
|
+
const id = typeof event.toolCallId === "string" ? event.toolCallId : null;
|
|
896
|
+
const component = id ? toolOwners.get(id)?.tools.get(id) : undefined;
|
|
897
|
+
const partial = recordOf(event.partialResult);
|
|
898
|
+
if (component && partial) {
|
|
899
|
+
const content = Array.isArray(partial.content) ? partial.content : [];
|
|
900
|
+
component.updateResult({ ...partial, content, isError: false }, true);
|
|
901
|
+
requestRender();
|
|
902
|
+
}
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
case "tool_execution_end": {
|
|
906
|
+
const id = typeof event.toolCallId === "string" ? event.toolCallId : null;
|
|
907
|
+
const component = id ? toolOwners.get(id)?.tools.get(id) : undefined;
|
|
908
|
+
const result = recordOf(event.result);
|
|
909
|
+
if (component && result) {
|
|
910
|
+
const content = Array.isArray(result.content) ? result.content : [];
|
|
911
|
+
component.updateResult({ ...result, content, isError: event.isError === true });
|
|
912
|
+
requestRender();
|
|
913
|
+
}
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
default:
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
|
|
678
923
|
// packages/pi-rig/src/tools.ts
|
|
679
924
|
function textResult(text, details) {
|
|
680
925
|
return { content: [{ type: "text", text }], ...details ? { details } : {} };
|
|
@@ -766,20 +1011,49 @@ function setStatus(ctx, id, text) {
|
|
|
766
1011
|
setStatusFn.call(ui, id, text);
|
|
767
1012
|
}
|
|
768
1013
|
}
|
|
769
|
-
function
|
|
1014
|
+
function uiOf(ctx) {
|
|
770
1015
|
const ui = ctx && typeof ctx === "object" ? ctx.ui : null;
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
1016
|
+
return ui && typeof ui === "object" ? ui : null;
|
|
1017
|
+
}
|
|
1018
|
+
function setTitle(ctx, title) {
|
|
1019
|
+
const ui = uiOf(ctx);
|
|
1020
|
+
const setTitleFn = ui?.setTitle;
|
|
1021
|
+
if (typeof setTitleFn === "function")
|
|
1022
|
+
setTitleFn.call(ui, title);
|
|
1023
|
+
}
|
|
1024
|
+
function shutdownPi(ctx) {
|
|
1025
|
+
const shutdownFn = ctx && typeof ctx === "object" ? ctx.shutdown : null;
|
|
1026
|
+
if (typeof shutdownFn === "function")
|
|
1027
|
+
shutdownFn.call(ctx);
|
|
1028
|
+
}
|
|
1029
|
+
async function askNativeDialog(ctx, request) {
|
|
1030
|
+
const ui = uiOf(ctx);
|
|
1031
|
+
if (!ui)
|
|
1032
|
+
return null;
|
|
1033
|
+
const { method, prompt, choices } = request;
|
|
1034
|
+
try {
|
|
1035
|
+
if (method === "confirm" && typeof ui.confirm === "function") {
|
|
1036
|
+
const confirmed = await ui.confirm("Worker request", prompt);
|
|
1037
|
+
return { value: confirmed, confirmed };
|
|
1038
|
+
}
|
|
1039
|
+
if (choices.length > 0 && typeof ui.select === "function") {
|
|
1040
|
+
const selected = await ui.select(prompt, choices);
|
|
1041
|
+
return selected === undefined ? { cancelled: true } : { value: selected };
|
|
1042
|
+
}
|
|
1043
|
+
if (typeof ui.input === "function") {
|
|
1044
|
+
const value = await ui.input("Worker request", prompt);
|
|
1045
|
+
return value === undefined ? { cancelled: true } : { value };
|
|
1046
|
+
}
|
|
1047
|
+
} catch {
|
|
1048
|
+
return { cancelled: true };
|
|
1049
|
+
}
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1052
|
+
function shortPath(path, segments = 3) {
|
|
1053
|
+
const parts = path.split("/").filter(Boolean);
|
|
1054
|
+
if (parts.length <= segments)
|
|
1055
|
+
return path;
|
|
1056
|
+
return `\u2026/${parts.slice(-segments).join("/")}`;
|
|
783
1057
|
}
|
|
784
1058
|
function createBridgeGate(state) {
|
|
785
1059
|
let pending = null;
|
|
@@ -893,12 +1167,6 @@ async function handleOperatorInput(event, state, ctx, gate) {
|
|
|
893
1167
|
}
|
|
894
1168
|
return { action: "handled" };
|
|
895
1169
|
}
|
|
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
1170
|
function runLocation(run) {
|
|
903
1171
|
const worktree = typeof run.worktreePath === "string" && run.worktreePath.trim() ? run.worktreePath.trim() : null;
|
|
904
1172
|
const projectRoot = typeof run.projectRoot === "string" && run.projectRoot.trim() ? run.projectRoot.trim() : null;
|
|
@@ -908,42 +1176,23 @@ function runPayload(payload) {
|
|
|
908
1176
|
return payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
909
1177
|
}
|
|
910
1178
|
var OPERATOR_WIDGET_WS_FALLBACK_MS = 1e4;
|
|
911
|
-
function
|
|
1179
|
+
function startOperatorRunStatusLine(state, ctx, live) {
|
|
912
1180
|
if (!state.operatorSession || !state.active || !state.runId)
|
|
913
1181
|
return;
|
|
1182
|
+
const shortId = state.runId.slice(0, 8);
|
|
914
1183
|
let inFlight = false;
|
|
915
|
-
let frame = 0;
|
|
916
1184
|
let lastRefreshAt = 0;
|
|
917
1185
|
const refresh = async () => {
|
|
918
1186
|
if (inFlight)
|
|
919
1187
|
return;
|
|
920
1188
|
inFlight = true;
|
|
921
1189
|
lastRefreshAt = Date.now();
|
|
922
|
-
const spinner = SPINNER_FRAMES[frame++ % SPINNER_FRAMES.length] ?? "\u2022";
|
|
923
1190
|
try {
|
|
924
|
-
const
|
|
925
|
-
state.client.attach(state.runId),
|
|
926
|
-
state.client.runTimeline(state.runId, 8).catch(() => [])
|
|
927
|
-
]);
|
|
928
|
-
const run = runPayload(runPayloadRecord);
|
|
1191
|
+
const run = runPayload(await state.client.attach(state.runId));
|
|
929
1192
|
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);
|
|
1193
|
+
setStatus(ctx, "rig", `drone ${shortId} \xB7 ${status} \xB7 ${shortPath(runLocation(run))}`);
|
|
942
1194
|
} 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]);
|
|
1195
|
+
setStatus(ctx, "rig", `drone ${shortId} \xB7 server unreachable: ${error instanceof Error ? error.message : String(error)}`);
|
|
947
1196
|
} finally {
|
|
948
1197
|
inFlight = false;
|
|
949
1198
|
}
|
|
@@ -955,7 +1204,7 @@ function startOperatorRunWidget(state, ctx, live) {
|
|
|
955
1204
|
return;
|
|
956
1205
|
}
|
|
957
1206
|
refresh();
|
|
958
|
-
},
|
|
1207
|
+
}, 5000);
|
|
959
1208
|
unrefTimer(timer);
|
|
960
1209
|
}
|
|
961
1210
|
function operatorInboxNotification(event) {
|
|
@@ -1012,47 +1261,6 @@ function startOperatorBridge(state, ctx) {
|
|
|
1012
1261
|
}
|
|
1013
1262
|
};
|
|
1014
1263
|
}
|
|
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
1264
|
function workerStatusLine(status) {
|
|
1057
1265
|
const model = status.model && typeof status.model === "object" && !Array.isArray(status.model) ? status.model : null;
|
|
1058
1266
|
const modelId = model && typeof model.id === "string" ? model.id : null;
|
|
@@ -1062,23 +1270,129 @@ function workerStatusLine(status) {
|
|
|
1062
1270
|
const parts = [modelId, percent, streaming].filter((value) => Boolean(value));
|
|
1063
1271
|
return parts.length > 0 ? `worker ${parts.join(" \xB7 ")}` : "worker session connected";
|
|
1064
1272
|
}
|
|
1273
|
+
function applyRigTheme(ctx) {
|
|
1274
|
+
const ui = uiOf(ctx);
|
|
1275
|
+
if (!ui || typeof ui.getTheme !== "function" || typeof ui.setTheme !== "function")
|
|
1276
|
+
return;
|
|
1277
|
+
try {
|
|
1278
|
+
const theme = ui.getTheme("rig");
|
|
1279
|
+
if (theme)
|
|
1280
|
+
ui.setTheme(theme);
|
|
1281
|
+
} catch {}
|
|
1282
|
+
}
|
|
1283
|
+
var MICRO_DRONE_BLADES = ["---", "\\\\\\", "|||", "///"];
|
|
1284
|
+
var MICRO_DRONE_EYES = ["@", "o", "."];
|
|
1285
|
+
function applyDroneWorkingIndicator(ctx) {
|
|
1286
|
+
const ui = uiOf(ctx);
|
|
1287
|
+
if (!ui || typeof ui.setWorkingIndicator !== "function")
|
|
1288
|
+
return;
|
|
1289
|
+
const frames = Array.from({ length: 12 }, (_, index) => {
|
|
1290
|
+
const blade = MICRO_DRONE_BLADES[index % MICRO_DRONE_BLADES.length];
|
|
1291
|
+
const eye = MICRO_DRONE_EYES[Math.floor(index / 2) % MICRO_DRONE_EYES.length];
|
|
1292
|
+
return `(${blade})${eye}(${blade})`;
|
|
1293
|
+
});
|
|
1294
|
+
try {
|
|
1295
|
+
ui.setWorkingIndicator({ frames, intervalMs: 120 });
|
|
1296
|
+
} catch {}
|
|
1297
|
+
}
|
|
1298
|
+
function renderWorkerCapabilities(capabilities) {
|
|
1299
|
+
const names = (value, key = "name") => Array.isArray(value) ? value.flatMap((entry) => {
|
|
1300
|
+
if (typeof entry === "string")
|
|
1301
|
+
return [entry];
|
|
1302
|
+
if (entry && typeof entry === "object" && !Array.isArray(entry)) {
|
|
1303
|
+
const name = entry[key];
|
|
1304
|
+
return typeof name === "string" ? [name] : [];
|
|
1305
|
+
}
|
|
1306
|
+
return [];
|
|
1307
|
+
}) : [];
|
|
1308
|
+
const lines = ["Drone capabilities (in effect for this run)"];
|
|
1309
|
+
const tools = Array.isArray(capabilities.tools) ? capabilities.tools : [];
|
|
1310
|
+
const activeTools = tools.flatMap((tool) => {
|
|
1311
|
+
const record = tool && typeof tool === "object" && !Array.isArray(tool) ? tool : null;
|
|
1312
|
+
return record && record.active === true && typeof record.name === "string" ? [record.name] : [];
|
|
1313
|
+
});
|
|
1314
|
+
const inactiveCount = tools.length - activeTools.length;
|
|
1315
|
+
lines.push(` tools ${activeTools.join(", ") || "(none)"}${inactiveCount > 0 ? ` (+${inactiveCount} inactive)` : ""}`);
|
|
1316
|
+
const extensions = Array.isArray(capabilities.extensions) ? capabilities.extensions : [];
|
|
1317
|
+
const extensionLabels = extensions.flatMap((entry) => {
|
|
1318
|
+
const record = entry && typeof entry === "object" && !Array.isArray(entry) ? entry : null;
|
|
1319
|
+
if (!record)
|
|
1320
|
+
return [];
|
|
1321
|
+
const path = typeof record.path === "string" ? record.path : "";
|
|
1322
|
+
const short = path.split("/").filter(Boolean).slice(-2).join("/") || path || "extension";
|
|
1323
|
+
return [short];
|
|
1324
|
+
});
|
|
1325
|
+
lines.push(` extensions ${extensionLabels.join(", ") || "(none)"}`);
|
|
1326
|
+
lines.push(` hooks ${names(capabilities.hookEvents).join(", ") || "(none)"}`);
|
|
1327
|
+
lines.push(` skills ${names(capabilities.skills).join(", ") || "(none)"}`);
|
|
1328
|
+
lines.push(` prompts ${names(capabilities.prompts).join(", ") || "(none)"}`);
|
|
1329
|
+
const model = typeof capabilities.model === "string" ? capabilities.model : "(unknown)";
|
|
1330
|
+
const thinking = typeof capabilities.thinkingLevel === "string" ? capabilities.thinkingLevel : "";
|
|
1331
|
+
lines.push(` model ${model}${thinking ? ` \xB7 ${thinking}` : ""}`);
|
|
1332
|
+
if (typeof capabilities.cwd === "string" && capabilities.cwd)
|
|
1333
|
+
lines.push(` cwd ${capabilities.cwd}`);
|
|
1334
|
+
lines.push(" (drone commands are in your palette \xB7 /worker toggles this panel)");
|
|
1335
|
+
return lines;
|
|
1336
|
+
}
|
|
1337
|
+
function registerOperatorConsoleCommands(pi, state, tryRegister) {
|
|
1338
|
+
if (typeof pi.registerCommand !== "function")
|
|
1339
|
+
return;
|
|
1340
|
+
let capabilitiesShown = false;
|
|
1341
|
+
tryRegister("command:detach", () => pi.registerCommand?.("detach", {
|
|
1342
|
+
description: "Detach from this run; the drone keeps flying",
|
|
1343
|
+
handler: async (_args, ctx) => {
|
|
1344
|
+
notify(ctx, "Detached. The drone continues on the worker.");
|
|
1345
|
+
shutdownPi(ctx);
|
|
1346
|
+
}
|
|
1347
|
+
}));
|
|
1348
|
+
tryRegister("command:stop", () => pi.registerCommand?.("stop", {
|
|
1349
|
+
description: "Abort the drone's current turn and detach",
|
|
1350
|
+
handler: async (_args, ctx) => {
|
|
1351
|
+
try {
|
|
1352
|
+
await state.client.workerAbort(state.runId);
|
|
1353
|
+
notify(ctx, "Abort requested; detaching.");
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
notify(ctx, `Abort failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1356
|
+
}
|
|
1357
|
+
shutdownPi(ctx);
|
|
1358
|
+
}
|
|
1359
|
+
}));
|
|
1360
|
+
tryRegister("command:worker", () => pi.registerCommand?.("worker", {
|
|
1361
|
+
description: "Toggle the drone's capability panel (tools, extensions, hooks, skills, model)",
|
|
1362
|
+
handler: async (_args, ctx) => {
|
|
1363
|
+
if (capabilitiesShown) {
|
|
1364
|
+
const ui = uiOf(ctx);
|
|
1365
|
+
if (ui && typeof ui.setWidget === "function")
|
|
1366
|
+
ui.setWidget("rig-worker-capabilities", undefined);
|
|
1367
|
+
capabilitiesShown = false;
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
try {
|
|
1371
|
+
const capabilities = await state.client.workerCapabilities(state.runId);
|
|
1372
|
+
setWidget(ctx, "rig-worker-capabilities", renderWorkerCapabilities(capabilities));
|
|
1373
|
+
capabilitiesShown = true;
|
|
1374
|
+
} catch (error) {
|
|
1375
|
+
notify(ctx, `Drone capabilities unavailable: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}));
|
|
1379
|
+
}
|
|
1065
1380
|
function startWorkerSessionMirror(pi, state, ctx) {
|
|
1066
1381
|
if (!state.operatorSession || !state.active || !state.runId)
|
|
1067
1382
|
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);
|
|
1383
|
+
let mirror = null;
|
|
1384
|
+
const pendingEvents = [];
|
|
1385
|
+
createLiveMirror({ pi }).then((created) => {
|
|
1386
|
+
mirror = created;
|
|
1387
|
+
const ui = uiOf(ctx);
|
|
1388
|
+
if (ui && typeof ui.setWidget === "function") {
|
|
1389
|
+
ui.setWidget("rig-tui-capture", (tui) => created.captureTui(tui));
|
|
1079
1390
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1391
|
+
for (const event of pendingEvents.splice(0))
|
|
1392
|
+
created.handleWorkerEvent(event);
|
|
1393
|
+
}).catch((error) => {
|
|
1394
|
+
notify(ctx, `Live drone transcript unavailable: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1395
|
+
});
|
|
1082
1396
|
const socket = new RigWorkerEventsSocket({
|
|
1083
1397
|
context: state,
|
|
1084
1398
|
webSocketFactory: state.webSocketFactory,
|
|
@@ -1093,44 +1407,66 @@ function startWorkerSessionMirror(pi, state, ctx) {
|
|
|
1093
1407
|
if (frame.type !== "pi.event")
|
|
1094
1408
|
return;
|
|
1095
1409
|
const event = frame.event && typeof frame.event === "object" && !Array.isArray(frame.event) ? frame.event : null;
|
|
1096
|
-
if (!event
|
|
1410
|
+
if (!event)
|
|
1097
1411
|
return;
|
|
1098
|
-
|
|
1099
|
-
|
|
1412
|
+
if (event.type === "extension_ui_request") {
|
|
1413
|
+
forwardWorkerUiRequest(state, ctx, event);
|
|
1100
1414
|
return;
|
|
1101
|
-
|
|
1102
|
-
if (
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
return;
|
|
1107
|
-
pi.sendMessage?.({ customType: "rig-worker-mirror", content: text, display: true }, { triggerTurn: false });
|
|
1415
|
+
}
|
|
1416
|
+
if (mirror)
|
|
1417
|
+
mirror.handleWorkerEvent(event);
|
|
1418
|
+
else
|
|
1419
|
+
pendingEvents.push(event);
|
|
1108
1420
|
},
|
|
1109
1421
|
onConnect: () => {
|
|
1110
|
-
setStatus(ctx, "rig-worker", "
|
|
1422
|
+
setStatus(ctx, "rig-worker", "drone link live");
|
|
1111
1423
|
},
|
|
1112
1424
|
onDisconnect: () => {
|
|
1113
|
-
setStatus(ctx, "rig-worker", "
|
|
1425
|
+
setStatus(ctx, "rig-worker", "drone link down (reconnecting\u2026)");
|
|
1114
1426
|
}
|
|
1115
1427
|
}
|
|
1116
1428
|
});
|
|
1117
1429
|
socket.start();
|
|
1118
1430
|
}
|
|
1119
|
-
async function
|
|
1120
|
-
|
|
1431
|
+
async function forwardWorkerUiRequest(state, ctx, event) {
|
|
1432
|
+
const request = event.request && typeof event.request === "object" && !Array.isArray(event.request) ? event.request : event;
|
|
1433
|
+
const requestId = String(request.requestId ?? request.id ?? `ui-${state.runId}-${event.timestamp ?? ""}`);
|
|
1434
|
+
const method = String(request.method ?? request.type ?? "input");
|
|
1435
|
+
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;
|
|
1436
|
+
const rawChoices = Array.isArray(request.options) ? request.options : Array.isArray(request.items) ? request.items : [];
|
|
1437
|
+
const choices = rawChoices.map((option) => {
|
|
1438
|
+
if (typeof option === "string")
|
|
1439
|
+
return option;
|
|
1440
|
+
if (option && typeof option === "object" && !Array.isArray(option)) {
|
|
1441
|
+
const record = option;
|
|
1442
|
+
return String(record.label ?? record.value ?? record.name ?? "");
|
|
1443
|
+
}
|
|
1444
|
+
return "";
|
|
1445
|
+
}).filter(Boolean);
|
|
1446
|
+
const answer = await askNativeDialog(ctx, { method, prompt, choices });
|
|
1447
|
+
if (!answer)
|
|
1121
1448
|
return;
|
|
1449
|
+
try {
|
|
1450
|
+
await state.client.workerRespondExtensionUi(requestId, answer, state.runId);
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
notify(ctx, `Failed to answer the drone's question: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
async function registerWorkerCommands(pi, state, ctx, registeredNames = new Set) {
|
|
1456
|
+
if (!state.operatorSession || !state.active || !state.runId)
|
|
1457
|
+
return false;
|
|
1122
1458
|
if (typeof pi.registerCommand !== "function")
|
|
1123
|
-
return;
|
|
1459
|
+
return false;
|
|
1124
1460
|
let commands = [];
|
|
1125
1461
|
try {
|
|
1126
1462
|
commands = await state.client.workerCommands(state.runId);
|
|
1127
1463
|
} catch {
|
|
1128
|
-
return;
|
|
1464
|
+
return false;
|
|
1129
1465
|
}
|
|
1130
1466
|
let registered = 0;
|
|
1131
1467
|
for (const command of commands) {
|
|
1132
1468
|
const name = typeof command.name === "string" && command.name.trim() ? command.name.trim() : null;
|
|
1133
|
-
if (!name || name === "rig")
|
|
1469
|
+
if (!name || name === "rig" || registeredNames.has(name))
|
|
1134
1470
|
continue;
|
|
1135
1471
|
const description = typeof command.description === "string" && command.description.trim() ? `[worker] ${command.description.trim()}` : "[worker] remote session command";
|
|
1136
1472
|
try {
|
|
@@ -1145,12 +1481,47 @@ async function registerWorkerCommands(pi, state, ctx) {
|
|
|
1145
1481
|
}
|
|
1146
1482
|
}
|
|
1147
1483
|
});
|
|
1484
|
+
registeredNames.add(name);
|
|
1148
1485
|
registered += 1;
|
|
1149
|
-
} catch {
|
|
1486
|
+
} catch {
|
|
1487
|
+
registeredNames.add(name);
|
|
1488
|
+
}
|
|
1150
1489
|
}
|
|
1151
1490
|
if (registered > 0) {
|
|
1152
1491
|
notify(ctx, `Worker session commands available: ${registered} registered from the run's Pi session.`);
|
|
1153
1492
|
}
|
|
1493
|
+
return true;
|
|
1494
|
+
}
|
|
1495
|
+
function startWorkerCommandRegistration(pi, state, ctx) {
|
|
1496
|
+
const registeredNames = new Set;
|
|
1497
|
+
let attempts = 0;
|
|
1498
|
+
let inFlight = false;
|
|
1499
|
+
const attempt = async () => {
|
|
1500
|
+
if (inFlight)
|
|
1501
|
+
return false;
|
|
1502
|
+
inFlight = true;
|
|
1503
|
+
attempts += 1;
|
|
1504
|
+
try {
|
|
1505
|
+
return await registerWorkerCommands(pi, state, ctx, registeredNames);
|
|
1506
|
+
} finally {
|
|
1507
|
+
inFlight = false;
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
attempt().then((ready) => {
|
|
1511
|
+
if (ready)
|
|
1512
|
+
return;
|
|
1513
|
+
const timer = setInterval(() => {
|
|
1514
|
+
if (attempts >= 60) {
|
|
1515
|
+
clearInterval(timer);
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
attempt().then((nextReady) => {
|
|
1519
|
+
if (nextReady)
|
|
1520
|
+
clearInterval(timer);
|
|
1521
|
+
});
|
|
1522
|
+
}, 2000);
|
|
1523
|
+
unrefTimer(timer);
|
|
1524
|
+
});
|
|
1154
1525
|
}
|
|
1155
1526
|
function startSteeringBridge(pi, state, ctx, gate, deliveredIds) {
|
|
1156
1527
|
if (state.operatorSession || !state.active || !state.runId || typeof pi.sendUserMessage !== "function")
|
|
@@ -1243,6 +1614,9 @@ function createPiRigExtension(pi, options = {}) {
|
|
|
1243
1614
|
}
|
|
1244
1615
|
}));
|
|
1245
1616
|
}
|
|
1617
|
+
if (state.operatorSession && state.active && state.runId) {
|
|
1618
|
+
registerOperatorConsoleCommands(pi, state, tryRegister);
|
|
1619
|
+
}
|
|
1246
1620
|
if (state.active && state.runId) {
|
|
1247
1621
|
for (const tool of createRigTools({ context: state, client: state.client })) {
|
|
1248
1622
|
tryRegister(`tool:${String(tool.name ?? "rig-tool")}`, () => pi.registerTool?.({
|
|
@@ -1262,26 +1636,24 @@ function createPiRigExtension(pi, options = {}) {
|
|
|
1262
1636
|
pi.on?.("session_start", async (_event, ctx) => {
|
|
1263
1637
|
if (!state.active || !state.runId)
|
|
1264
1638
|
return;
|
|
1265
|
-
|
|
1639
|
+
const shortId = state.runId.slice(0, 8);
|
|
1640
|
+
setStatus(ctx, "rig", `drone ${shortId} \xB7 waiting for worker daemon\u2026`);
|
|
1266
1641
|
if (state.operatorSession) {
|
|
1267
|
-
|
|
1268
|
-
|
|
1642
|
+
setTitle(ctx, `Rig \xB7 run ${shortId}`);
|
|
1643
|
+
applyRigTheme(ctx);
|
|
1644
|
+
applyDroneWorkingIndicator(ctx);
|
|
1269
1645
|
}
|
|
1270
1646
|
const gateResult = await gate(ctx);
|
|
1271
1647
|
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
|
-
}
|
|
1648
|
+
setStatus(ctx, "rig", `drone ${shortId} \xB7 bridge disabled (protocol mismatch)`);
|
|
1277
1649
|
return;
|
|
1278
1650
|
}
|
|
1279
|
-
setStatus(ctx, "rig", `
|
|
1651
|
+
setStatus(ctx, "rig", `drone ${shortId} \xB7 connecting\u2026`);
|
|
1280
1652
|
const live = gateResult.status === "compatible" ? startOperatorBridge(state, ctx) : undefined;
|
|
1281
|
-
|
|
1653
|
+
startOperatorRunStatusLine(state, ctx, live);
|
|
1282
1654
|
if (state.operatorSession && gateResult.status === "compatible") {
|
|
1283
1655
|
startWorkerSessionMirror(pi, state, ctx);
|
|
1284
|
-
|
|
1656
|
+
startWorkerCommandRegistration(pi, state, ctx);
|
|
1285
1657
|
}
|
|
1286
1658
|
await consumeQueuedSteering(pi, state, ctx, gate, deliveredSteeringIds);
|
|
1287
1659
|
});
|