@h-rig/cli 0.0.6-alpha.42 → 0.0.6-alpha.44
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 +152 -19
- package/dist/src/commands/_cli-format.js +1 -1
- package/dist/src/commands/_help-catalog.js +1 -1
- package/dist/src/commands/_operator-view.js +150 -17
- package/dist/src/commands/_pi-frontend.js +150 -17
- package/dist/src/commands/_pi-remote-session.js +46 -7
- package/dist/src/commands/_pi-worker-bridge-extension.js +109 -12
- package/dist/src/commands/_server-client.js +5 -0
- package/dist/src/commands/_spinner.js +63 -0
- package/dist/src/commands/run.js +150 -17
- package/dist/src/commands/task.js +152 -19
- package/dist/src/commands.js +152 -19
- package/dist/src/index.js +152 -19
- package/package.json +6 -6
|
@@ -217,6 +217,10 @@ async function getRunPiCommandsViaServer(context, runId) {
|
|
|
217
217
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
|
|
218
218
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
|
|
219
219
|
}
|
|
220
|
+
async function getRunPiCapabilitiesViaServer(context, runId) {
|
|
221
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
|
|
222
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
|
|
223
|
+
}
|
|
220
224
|
async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
|
|
221
225
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
|
|
222
226
|
method: "POST",
|
|
@@ -263,7 +267,7 @@ async function buildRunPiEventsWebSocketUrl(context, runId) {
|
|
|
263
267
|
}
|
|
264
268
|
|
|
265
269
|
// packages/cli/src/commands/_pi-remote-session.ts
|
|
266
|
-
import { AgentSession as PiAgentSession } from "@earendil-works/pi-coding-agent";
|
|
270
|
+
import { AgentSession as PiAgentSession, expandPromptTemplate } from "@earendil-works/pi-coding-agent";
|
|
267
271
|
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
268
272
|
function defaultTransport() {
|
|
269
273
|
return {
|
|
@@ -271,6 +275,7 @@ function defaultTransport() {
|
|
|
271
275
|
getMessages: getRunPiMessagesViaServer,
|
|
272
276
|
getStatus: getRunPiStatusViaServer,
|
|
273
277
|
getCommands: getRunPiCommandsViaServer,
|
|
278
|
+
getCapabilities: getRunPiCapabilitiesViaServer,
|
|
274
279
|
sendPrompt: sendRunPiPromptViaServer,
|
|
275
280
|
sendShell: sendRunPiShellViaServer,
|
|
276
281
|
runCommand: runRunPiCommandViaServer,
|
|
@@ -358,16 +363,18 @@ class RigRemoteSessionController {
|
|
|
358
363
|
this.hooks.onStatusText?.("worker Pi WebSocket disconnected");
|
|
359
364
|
};
|
|
360
365
|
try {
|
|
361
|
-
const [messagesPayload, statusPayload, commandsPayload] = await Promise.all([
|
|
366
|
+
const [messagesPayload, statusPayload, commandsPayload, capabilitiesPayload] = await Promise.all([
|
|
362
367
|
this.transport.getMessages(this.context, this.runId),
|
|
363
368
|
this.transport.getStatus(this.context, this.runId),
|
|
364
|
-
this.transport.getCommands(this.context, this.runId)
|
|
369
|
+
this.transport.getCommands(this.context, this.runId),
|
|
370
|
+
this.transport.getCapabilities(this.context, this.runId).catch(() => ({ capabilities: null }))
|
|
365
371
|
]);
|
|
366
372
|
const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
|
|
367
373
|
this.applyStatusPayload(statusPayload);
|
|
368
374
|
this.session?.replaceRemoteMessages(messages);
|
|
369
375
|
const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
|
|
370
|
-
|
|
376
|
+
const capabilities = capabilitiesPayload.capabilities && typeof capabilitiesPayload.capabilities === "object" && !Array.isArray(capabilitiesPayload.capabilities) ? capabilitiesPayload.capabilities : null;
|
|
377
|
+
this.hooks.onCatchUp?.(messages, commands, capabilities);
|
|
371
378
|
catchupDone = true;
|
|
372
379
|
for (const payload of buffered.splice(0))
|
|
373
380
|
this.applyEnvelope(payload);
|
|
@@ -544,6 +551,20 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
544
551
|
replaceRemoteMessages(messages) {
|
|
545
552
|
this.agent.state.messages = messages;
|
|
546
553
|
}
|
|
554
|
+
async expandLocalInput(text) {
|
|
555
|
+
let current = text;
|
|
556
|
+
const runner = this.extensionRunner;
|
|
557
|
+
if (runner.hasHandlers("input")) {
|
|
558
|
+
const result = await runner.emitInput(current, undefined, "interactive");
|
|
559
|
+
if (result.action === "handled")
|
|
560
|
+
return null;
|
|
561
|
+
if (result.action === "transform")
|
|
562
|
+
current = result.text;
|
|
563
|
+
}
|
|
564
|
+
current = this._expandSkillCommand(current);
|
|
565
|
+
current = expandPromptTemplate(current, [...this.promptTemplates]);
|
|
566
|
+
return current;
|
|
567
|
+
}
|
|
547
568
|
async prompt(text, options) {
|
|
548
569
|
const trimmed = text.trim();
|
|
549
570
|
if (!trimmed)
|
|
@@ -551,6 +572,15 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
551
572
|
if (trimmed.startsWith("/")) {
|
|
552
573
|
if (await this._tryExecuteExtensionCommand(trimmed))
|
|
553
574
|
return;
|
|
575
|
+
const expanded2 = await this.expandLocalInput(trimmed);
|
|
576
|
+
if (expanded2 === null)
|
|
577
|
+
return;
|
|
578
|
+
if (expanded2 !== trimmed) {
|
|
579
|
+
const behavior2 = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
580
|
+
options?.preflightResult?.(true);
|
|
581
|
+
await this.remote.sendPrompt(expanded2, behavior2);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
554
584
|
await this.remote.sendCommand(trimmed);
|
|
555
585
|
return;
|
|
556
586
|
}
|
|
@@ -558,21 +588,30 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
558
588
|
await this.remote.sendShell(trimmed);
|
|
559
589
|
return;
|
|
560
590
|
}
|
|
591
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
592
|
+
if (expanded === null)
|
|
593
|
+
return;
|
|
561
594
|
const behavior = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
562
595
|
options?.preflightResult?.(true);
|
|
563
|
-
await this.remote.sendPrompt(
|
|
596
|
+
await this.remote.sendPrompt(expanded, behavior);
|
|
564
597
|
}
|
|
565
598
|
async steer(text) {
|
|
566
599
|
const trimmed = text.trim();
|
|
567
600
|
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
568
601
|
return;
|
|
569
|
-
await this.
|
|
602
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
603
|
+
if (expanded === null)
|
|
604
|
+
return;
|
|
605
|
+
await this.remote.sendPrompt(expanded, "steer");
|
|
570
606
|
}
|
|
571
607
|
async followUp(text) {
|
|
572
608
|
const trimmed = text.trim();
|
|
573
609
|
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
574
610
|
return;
|
|
575
|
-
await this.
|
|
611
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
612
|
+
if (expanded === null)
|
|
613
|
+
return;
|
|
614
|
+
await this.remote.sendPrompt(expanded, "followUp");
|
|
576
615
|
}
|
|
577
616
|
async abort() {
|
|
578
617
|
await this.remote.abort();
|
|
@@ -685,6 +724,9 @@ function createRemoteBashOperations(controller, excludeFromContext) {
|
|
|
685
724
|
};
|
|
686
725
|
}
|
|
687
726
|
|
|
727
|
+
// packages/cli/src/commands/_spinner.ts
|
|
728
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
729
|
+
|
|
688
730
|
// packages/cli/src/commands/_pi-worker-bridge-extension.ts
|
|
689
731
|
function recordOf2(value) {
|
|
690
732
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -702,6 +744,50 @@ function asText(value) {
|
|
|
702
744
|
return String(value);
|
|
703
745
|
}
|
|
704
746
|
}
|
|
747
|
+
function names(value, key = "name") {
|
|
748
|
+
if (!Array.isArray(value))
|
|
749
|
+
return [];
|
|
750
|
+
return value.flatMap((entry) => {
|
|
751
|
+
if (typeof entry === "string")
|
|
752
|
+
return [entry];
|
|
753
|
+
const record = recordOf2(entry);
|
|
754
|
+
const name = record?.[key];
|
|
755
|
+
return typeof name === "string" ? [name] : [];
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
function renderWorkerCapabilities(capabilities) {
|
|
759
|
+
const lines = ["Worker session capabilities (in effect for this run)"];
|
|
760
|
+
const tools = Array.isArray(capabilities.tools) ? capabilities.tools : [];
|
|
761
|
+
const activeTools = tools.flatMap((tool) => {
|
|
762
|
+
const record = recordOf2(tool);
|
|
763
|
+
return record && record.active === true && typeof record.name === "string" ? [record.name] : [];
|
|
764
|
+
});
|
|
765
|
+
const inactiveCount = tools.length - activeTools.length;
|
|
766
|
+
lines.push(` tools ${activeTools.join(", ") || "(none)"}${inactiveCount > 0 ? ` (+${inactiveCount} inactive)` : ""}`);
|
|
767
|
+
const extensions = Array.isArray(capabilities.extensions) ? capabilities.extensions : [];
|
|
768
|
+
const extensionLabels = extensions.flatMap((entry) => {
|
|
769
|
+
const record = recordOf2(entry);
|
|
770
|
+
if (!record)
|
|
771
|
+
return [];
|
|
772
|
+
const path = typeof record.path === "string" ? record.path : "";
|
|
773
|
+
const short = path.split("/").filter(Boolean).slice(-2).join("/") || path || "extension";
|
|
774
|
+
return [short];
|
|
775
|
+
});
|
|
776
|
+
lines.push(` extensions ${extensionLabels.join(", ") || "(none)"}`);
|
|
777
|
+
const hookEvents = names(capabilities.hookEvents);
|
|
778
|
+
const hookList = Array.isArray(capabilities.hookEvents) ? capabilities.hookEvents.map(asText).filter(Boolean) : hookEvents;
|
|
779
|
+
lines.push(` hooks ${hookList.join(", ") || "(none)"}`);
|
|
780
|
+
lines.push(` skills ${names(capabilities.skills).join(", ") || "(none)"}`);
|
|
781
|
+
lines.push(` prompts ${names(capabilities.prompts).join(", ") || "(none)"}`);
|
|
782
|
+
const model = typeof capabilities.model === "string" ? capabilities.model : "(unknown)";
|
|
783
|
+
const thinking = typeof capabilities.thinkingLevel === "string" ? capabilities.thinkingLevel : "";
|
|
784
|
+
const cwd = typeof capabilities.cwd === "string" ? capabilities.cwd : "";
|
|
785
|
+
lines.push(` model ${model}${thinking ? ` \xB7 ${thinking}` : ""}`);
|
|
786
|
+
if (cwd)
|
|
787
|
+
lines.push(` cwd ${cwd}`);
|
|
788
|
+
lines.push(" (worker commands are in your palette as [worker \u2026] \xB7 /worker shows this again)");
|
|
789
|
+
return lines;
|
|
790
|
+
}
|
|
705
791
|
async function answerExtensionUiRequest(options, ctx, request) {
|
|
706
792
|
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
707
793
|
const method = String(request.method ?? request.type ?? "input");
|
|
@@ -728,7 +814,7 @@ async function answerExtensionUiRequest(options, ctx, request) {
|
|
|
728
814
|
ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
729
815
|
}
|
|
730
816
|
}
|
|
731
|
-
var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
|
|
817
|
+
var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "worker", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
|
|
732
818
|
function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
733
819
|
for (const command of commands) {
|
|
734
820
|
const record = recordOf2(command);
|
|
@@ -756,6 +842,21 @@ function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
|
756
842
|
function createRigWorkerPiBridgeExtension(options) {
|
|
757
843
|
return (pi) => {
|
|
758
844
|
const registeredDaemonCommands = new Set;
|
|
845
|
+
let capabilityLines = null;
|
|
846
|
+
let statusText = "connecting to worker session";
|
|
847
|
+
let busy = true;
|
|
848
|
+
let connected = false;
|
|
849
|
+
let frame = 0;
|
|
850
|
+
let spinnerTimer = null;
|
|
851
|
+
const renderStatus = (ctx) => {
|
|
852
|
+
const prefix = busy ? `${SPINNER_FRAMES[frame]} ` : connected ? "\u25CF " : "\u25CB ";
|
|
853
|
+
ctx.ui.setStatus("rig-worker-pi", `${prefix}${statusText}`);
|
|
854
|
+
};
|
|
855
|
+
const setStatus = (ctx, text, isBusy) => {
|
|
856
|
+
statusText = text;
|
|
857
|
+
busy = isBusy;
|
|
858
|
+
renderStatus(ctx);
|
|
859
|
+
};
|
|
759
860
|
pi.registerCommand("detach", {
|
|
760
861
|
description: "Detach from this run; the worker keeps going",
|
|
761
862
|
handler: async (_args, ctx) => {
|
|
@@ -771,31 +872,57 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
771
872
|
ctx.shutdown();
|
|
772
873
|
}
|
|
773
874
|
});
|
|
875
|
+
pi.registerCommand("worker", {
|
|
876
|
+
description: "Show the worker session's real capabilities (tools, extensions, hooks, skills)",
|
|
877
|
+
handler: async (_args, ctx) => {
|
|
878
|
+
if (capabilityLines)
|
|
879
|
+
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
880
|
+
else
|
|
881
|
+
ctx.ui.notify("Worker capabilities are not available yet (still connecting).", "info");
|
|
882
|
+
}
|
|
883
|
+
});
|
|
774
884
|
pi.on("user_bash", (event) => ({
|
|
775
885
|
operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
|
|
776
886
|
}));
|
|
777
887
|
pi.on("session_start", async (_event, ctx) => {
|
|
778
|
-
ctx.ui.setTitle("Rig \xB7
|
|
779
|
-
|
|
780
|
-
|
|
888
|
+
ctx.ui.setTitle("Rig \xB7 enriched bundled Pi");
|
|
889
|
+
setStatus(ctx, "waiting for worker Pi daemon", true);
|
|
890
|
+
spinnerTimer = setInterval(() => {
|
|
891
|
+
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
892
|
+
if (busy)
|
|
893
|
+
renderStatus(ctx);
|
|
894
|
+
}, 150);
|
|
895
|
+
ctx.ui.notify(`Enriched bundled Pi \u2014 native UI + Rig layers, worker brain. Attached to run ${options.runId}. /worker shows live capabilities \xB7 /detach exits \xB7 /stop cancels.`, "info");
|
|
781
896
|
if (options.initialMessageSent)
|
|
782
897
|
ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
|
|
783
898
|
const nativeUi = ctx.ui;
|
|
784
899
|
options.controller.setUiHooks({
|
|
785
|
-
onStatusText: (text) =>
|
|
786
|
-
onActivity: (label, detail) =>
|
|
787
|
-
|
|
900
|
+
onStatusText: (text) => setStatus(ctx, text, !connected),
|
|
901
|
+
onActivity: (label, detail) => {
|
|
902
|
+
const active = label !== "idle" && !/complete|ready/i.test(label);
|
|
903
|
+
setStatus(ctx, [label, detail].filter(Boolean).join(" \u2014 "), active);
|
|
904
|
+
if (/agent running|tool:/.test(label))
|
|
905
|
+
ctx.ui.setWidget("rig-worker-capabilities", undefined);
|
|
906
|
+
},
|
|
907
|
+
onConnectionChange: (nextConnected) => {
|
|
908
|
+
connected = nextConnected;
|
|
909
|
+
setStatus(ctx, nextConnected ? "worker session live" : "worker session disconnected", false);
|
|
910
|
+
},
|
|
788
911
|
onError: (message) => ctx.ui.notify(message, "error"),
|
|
789
912
|
onExtensionUiRequest: (request) => {
|
|
790
913
|
answerExtensionUiRequest(options, ctx, request);
|
|
791
914
|
},
|
|
792
|
-
onCatchUp: (messages, commands) => {
|
|
915
|
+
onCatchUp: (messages, commands, capabilities) => {
|
|
793
916
|
if (nativeUi.appendSessionMessages)
|
|
794
917
|
nativeUi.appendSessionMessages(messages);
|
|
795
918
|
const cwd = options.controller.status.cwd;
|
|
796
919
|
if (nativeUi.setDisplayCwd && cwd)
|
|
797
920
|
nativeUi.setDisplayCwd(cwd);
|
|
798
921
|
registerDaemonCommands(pi, options, ctx, commands, registeredDaemonCommands);
|
|
922
|
+
if (capabilities) {
|
|
923
|
+
capabilityLines = renderWorkerCapabilities(capabilities);
|
|
924
|
+
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
925
|
+
}
|
|
799
926
|
}
|
|
800
927
|
});
|
|
801
928
|
options.controller.connect().catch((error) => {
|
|
@@ -803,6 +930,8 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
803
930
|
});
|
|
804
931
|
});
|
|
805
932
|
pi.on("session_shutdown", () => {
|
|
933
|
+
if (spinnerTimer)
|
|
934
|
+
clearInterval(spinnerTimer);
|
|
806
935
|
options.controller.close();
|
|
807
936
|
});
|
|
808
937
|
};
|
|
@@ -828,7 +957,8 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
828
957
|
const tempSessionDir = mkdtempSync(join(tmpdir(), "rig-pi-frontend-sessions-"));
|
|
829
958
|
const restoreEnv = setTemporaryEnv({
|
|
830
959
|
PI_CODING_AGENT_SESSION_DIR: tempSessionDir,
|
|
831
|
-
PI_SKIP_VERSION_CHECK: "1"
|
|
960
|
+
PI_SKIP_VERSION_CHECK: "1",
|
|
961
|
+
PI_HIDDEN_COMMANDS: "import,fork,clone,tree,new,resume,trust"
|
|
832
962
|
});
|
|
833
963
|
const controller = new RigRemoteSessionController({ context, runId: input.runId });
|
|
834
964
|
const createRemoteRuntime = async ({ cwd, agentDir, sessionManager, sessionStartEvent }) => {
|
|
@@ -844,6 +974,9 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
844
974
|
initialMessageSent: input.steered === true
|
|
845
975
|
})
|
|
846
976
|
],
|
|
977
|
+
noExtensions: true,
|
|
978
|
+
noSkills: true,
|
|
979
|
+
noPromptTemplates: true,
|
|
847
980
|
noContextFiles: true
|
|
848
981
|
}
|
|
849
982
|
});
|
|
@@ -878,7 +1011,7 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
878
1011
|
timelineCursor: null,
|
|
879
1012
|
steered: input.steered === true,
|
|
880
1013
|
detached,
|
|
881
|
-
rendered: "
|
|
1014
|
+
rendered: "enriched bundled Pi frontend with remote worker session runtime"
|
|
882
1015
|
};
|
|
883
1016
|
}
|
|
884
1017
|
export {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/cli/src/commands/_pi-remote-session.ts
|
|
3
|
-
import { AgentSession as PiAgentSession } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { AgentSession as PiAgentSession, expandPromptTemplate } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
|
|
5
5
|
// packages/cli/src/commands/_server-client.ts
|
|
6
6
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
@@ -206,6 +206,10 @@ async function getRunPiCommandsViaServer(context, runId) {
|
|
|
206
206
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
|
|
207
207
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
|
|
208
208
|
}
|
|
209
|
+
async function getRunPiCapabilitiesViaServer(context, runId) {
|
|
210
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
|
|
211
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
|
|
212
|
+
}
|
|
209
213
|
async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
|
|
210
214
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
|
|
211
215
|
method: "POST",
|
|
@@ -251,6 +255,7 @@ function defaultTransport() {
|
|
|
251
255
|
getMessages: getRunPiMessagesViaServer,
|
|
252
256
|
getStatus: getRunPiStatusViaServer,
|
|
253
257
|
getCommands: getRunPiCommandsViaServer,
|
|
258
|
+
getCapabilities: getRunPiCapabilitiesViaServer,
|
|
254
259
|
sendPrompt: sendRunPiPromptViaServer,
|
|
255
260
|
sendShell: sendRunPiShellViaServer,
|
|
256
261
|
runCommand: runRunPiCommandViaServer,
|
|
@@ -338,16 +343,18 @@ class RigRemoteSessionController {
|
|
|
338
343
|
this.hooks.onStatusText?.("worker Pi WebSocket disconnected");
|
|
339
344
|
};
|
|
340
345
|
try {
|
|
341
|
-
const [messagesPayload, statusPayload, commandsPayload] = await Promise.all([
|
|
346
|
+
const [messagesPayload, statusPayload, commandsPayload, capabilitiesPayload] = await Promise.all([
|
|
342
347
|
this.transport.getMessages(this.context, this.runId),
|
|
343
348
|
this.transport.getStatus(this.context, this.runId),
|
|
344
|
-
this.transport.getCommands(this.context, this.runId)
|
|
349
|
+
this.transport.getCommands(this.context, this.runId),
|
|
350
|
+
this.transport.getCapabilities(this.context, this.runId).catch(() => ({ capabilities: null }))
|
|
345
351
|
]);
|
|
346
352
|
const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
|
|
347
353
|
this.applyStatusPayload(statusPayload);
|
|
348
354
|
this.session?.replaceRemoteMessages(messages);
|
|
349
355
|
const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
|
|
350
|
-
|
|
356
|
+
const capabilities = capabilitiesPayload.capabilities && typeof capabilitiesPayload.capabilities === "object" && !Array.isArray(capabilitiesPayload.capabilities) ? capabilitiesPayload.capabilities : null;
|
|
357
|
+
this.hooks.onCatchUp?.(messages, commands, capabilities);
|
|
351
358
|
catchupDone = true;
|
|
352
359
|
for (const payload of buffered.splice(0))
|
|
353
360
|
this.applyEnvelope(payload);
|
|
@@ -524,6 +531,20 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
524
531
|
replaceRemoteMessages(messages) {
|
|
525
532
|
this.agent.state.messages = messages;
|
|
526
533
|
}
|
|
534
|
+
async expandLocalInput(text) {
|
|
535
|
+
let current = text;
|
|
536
|
+
const runner = this.extensionRunner;
|
|
537
|
+
if (runner.hasHandlers("input")) {
|
|
538
|
+
const result = await runner.emitInput(current, undefined, "interactive");
|
|
539
|
+
if (result.action === "handled")
|
|
540
|
+
return null;
|
|
541
|
+
if (result.action === "transform")
|
|
542
|
+
current = result.text;
|
|
543
|
+
}
|
|
544
|
+
current = this._expandSkillCommand(current);
|
|
545
|
+
current = expandPromptTemplate(current, [...this.promptTemplates]);
|
|
546
|
+
return current;
|
|
547
|
+
}
|
|
527
548
|
async prompt(text, options) {
|
|
528
549
|
const trimmed = text.trim();
|
|
529
550
|
if (!trimmed)
|
|
@@ -531,6 +552,15 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
531
552
|
if (trimmed.startsWith("/")) {
|
|
532
553
|
if (await this._tryExecuteExtensionCommand(trimmed))
|
|
533
554
|
return;
|
|
555
|
+
const expanded2 = await this.expandLocalInput(trimmed);
|
|
556
|
+
if (expanded2 === null)
|
|
557
|
+
return;
|
|
558
|
+
if (expanded2 !== trimmed) {
|
|
559
|
+
const behavior2 = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
560
|
+
options?.preflightResult?.(true);
|
|
561
|
+
await this.remote.sendPrompt(expanded2, behavior2);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
534
564
|
await this.remote.sendCommand(trimmed);
|
|
535
565
|
return;
|
|
536
566
|
}
|
|
@@ -538,21 +568,30 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
538
568
|
await this.remote.sendShell(trimmed);
|
|
539
569
|
return;
|
|
540
570
|
}
|
|
571
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
572
|
+
if (expanded === null)
|
|
573
|
+
return;
|
|
541
574
|
const behavior = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
542
575
|
options?.preflightResult?.(true);
|
|
543
|
-
await this.remote.sendPrompt(
|
|
576
|
+
await this.remote.sendPrompt(expanded, behavior);
|
|
544
577
|
}
|
|
545
578
|
async steer(text) {
|
|
546
579
|
const trimmed = text.trim();
|
|
547
580
|
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
548
581
|
return;
|
|
549
|
-
await this.
|
|
582
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
583
|
+
if (expanded === null)
|
|
584
|
+
return;
|
|
585
|
+
await this.remote.sendPrompt(expanded, "steer");
|
|
550
586
|
}
|
|
551
587
|
async followUp(text) {
|
|
552
588
|
const trimmed = text.trim();
|
|
553
589
|
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
554
590
|
return;
|
|
555
|
-
await this.
|
|
591
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
592
|
+
if (expanded === null)
|
|
593
|
+
return;
|
|
594
|
+
await this.remote.sendPrompt(expanded, "followUp");
|
|
556
595
|
}
|
|
557
596
|
async abort() {
|
|
558
597
|
await this.remote.abort();
|
|
@@ -203,6 +203,10 @@ async function getRunPiCommandsViaServer(context, runId) {
|
|
|
203
203
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
|
|
204
204
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
|
|
205
205
|
}
|
|
206
|
+
async function getRunPiCapabilitiesViaServer(context, runId) {
|
|
207
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
|
|
208
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
|
|
209
|
+
}
|
|
206
210
|
async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
|
|
207
211
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
|
|
208
212
|
method: "POST",
|
|
@@ -249,7 +253,7 @@ async function buildRunPiEventsWebSocketUrl(context, runId) {
|
|
|
249
253
|
}
|
|
250
254
|
|
|
251
255
|
// packages/cli/src/commands/_pi-remote-session.ts
|
|
252
|
-
import { AgentSession as PiAgentSession } from "@earendil-works/pi-coding-agent";
|
|
256
|
+
import { AgentSession as PiAgentSession, expandPromptTemplate } from "@earendil-works/pi-coding-agent";
|
|
253
257
|
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
254
258
|
function defaultTransport() {
|
|
255
259
|
return {
|
|
@@ -257,6 +261,7 @@ function defaultTransport() {
|
|
|
257
261
|
getMessages: getRunPiMessagesViaServer,
|
|
258
262
|
getStatus: getRunPiStatusViaServer,
|
|
259
263
|
getCommands: getRunPiCommandsViaServer,
|
|
264
|
+
getCapabilities: getRunPiCapabilitiesViaServer,
|
|
260
265
|
sendPrompt: sendRunPiPromptViaServer,
|
|
261
266
|
sendShell: sendRunPiShellViaServer,
|
|
262
267
|
runCommand: runRunPiCommandViaServer,
|
|
@@ -344,16 +349,18 @@ class RigRemoteSessionController {
|
|
|
344
349
|
this.hooks.onStatusText?.("worker Pi WebSocket disconnected");
|
|
345
350
|
};
|
|
346
351
|
try {
|
|
347
|
-
const [messagesPayload, statusPayload, commandsPayload] = await Promise.all([
|
|
352
|
+
const [messagesPayload, statusPayload, commandsPayload, capabilitiesPayload] = await Promise.all([
|
|
348
353
|
this.transport.getMessages(this.context, this.runId),
|
|
349
354
|
this.transport.getStatus(this.context, this.runId),
|
|
350
|
-
this.transport.getCommands(this.context, this.runId)
|
|
355
|
+
this.transport.getCommands(this.context, this.runId),
|
|
356
|
+
this.transport.getCapabilities(this.context, this.runId).catch(() => ({ capabilities: null }))
|
|
351
357
|
]);
|
|
352
358
|
const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
|
|
353
359
|
this.applyStatusPayload(statusPayload);
|
|
354
360
|
this.session?.replaceRemoteMessages(messages);
|
|
355
361
|
const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
|
|
356
|
-
|
|
362
|
+
const capabilities = capabilitiesPayload.capabilities && typeof capabilitiesPayload.capabilities === "object" && !Array.isArray(capabilitiesPayload.capabilities) ? capabilitiesPayload.capabilities : null;
|
|
363
|
+
this.hooks.onCatchUp?.(messages, commands, capabilities);
|
|
357
364
|
catchupDone = true;
|
|
358
365
|
for (const payload of buffered.splice(0))
|
|
359
366
|
this.applyEnvelope(payload);
|
|
@@ -549,6 +556,9 @@ function createRemoteBashOperations(controller, excludeFromContext) {
|
|
|
549
556
|
};
|
|
550
557
|
}
|
|
551
558
|
|
|
559
|
+
// packages/cli/src/commands/_spinner.ts
|
|
560
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
561
|
+
|
|
552
562
|
// packages/cli/src/commands/_pi-worker-bridge-extension.ts
|
|
553
563
|
function recordOf2(value) {
|
|
554
564
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -566,6 +576,50 @@ function asText(value) {
|
|
|
566
576
|
return String(value);
|
|
567
577
|
}
|
|
568
578
|
}
|
|
579
|
+
function names(value, key = "name") {
|
|
580
|
+
if (!Array.isArray(value))
|
|
581
|
+
return [];
|
|
582
|
+
return value.flatMap((entry) => {
|
|
583
|
+
if (typeof entry === "string")
|
|
584
|
+
return [entry];
|
|
585
|
+
const record = recordOf2(entry);
|
|
586
|
+
const name = record?.[key];
|
|
587
|
+
return typeof name === "string" ? [name] : [];
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
function renderWorkerCapabilities(capabilities) {
|
|
591
|
+
const lines = ["Worker session capabilities (in effect for this run)"];
|
|
592
|
+
const tools = Array.isArray(capabilities.tools) ? capabilities.tools : [];
|
|
593
|
+
const activeTools = tools.flatMap((tool) => {
|
|
594
|
+
const record = recordOf2(tool);
|
|
595
|
+
return record && record.active === true && typeof record.name === "string" ? [record.name] : [];
|
|
596
|
+
});
|
|
597
|
+
const inactiveCount = tools.length - activeTools.length;
|
|
598
|
+
lines.push(` tools ${activeTools.join(", ") || "(none)"}${inactiveCount > 0 ? ` (+${inactiveCount} inactive)` : ""}`);
|
|
599
|
+
const extensions = Array.isArray(capabilities.extensions) ? capabilities.extensions : [];
|
|
600
|
+
const extensionLabels = extensions.flatMap((entry) => {
|
|
601
|
+
const record = recordOf2(entry);
|
|
602
|
+
if (!record)
|
|
603
|
+
return [];
|
|
604
|
+
const path = typeof record.path === "string" ? record.path : "";
|
|
605
|
+
const short = path.split("/").filter(Boolean).slice(-2).join("/") || path || "extension";
|
|
606
|
+
return [short];
|
|
607
|
+
});
|
|
608
|
+
lines.push(` extensions ${extensionLabels.join(", ") || "(none)"}`);
|
|
609
|
+
const hookEvents = names(capabilities.hookEvents);
|
|
610
|
+
const hookList = Array.isArray(capabilities.hookEvents) ? capabilities.hookEvents.map(asText).filter(Boolean) : hookEvents;
|
|
611
|
+
lines.push(` hooks ${hookList.join(", ") || "(none)"}`);
|
|
612
|
+
lines.push(` skills ${names(capabilities.skills).join(", ") || "(none)"}`);
|
|
613
|
+
lines.push(` prompts ${names(capabilities.prompts).join(", ") || "(none)"}`);
|
|
614
|
+
const model = typeof capabilities.model === "string" ? capabilities.model : "(unknown)";
|
|
615
|
+
const thinking = typeof capabilities.thinkingLevel === "string" ? capabilities.thinkingLevel : "";
|
|
616
|
+
const cwd = typeof capabilities.cwd === "string" ? capabilities.cwd : "";
|
|
617
|
+
lines.push(` model ${model}${thinking ? ` \xB7 ${thinking}` : ""}`);
|
|
618
|
+
if (cwd)
|
|
619
|
+
lines.push(` cwd ${cwd}`);
|
|
620
|
+
lines.push(" (worker commands are in your palette as [worker \u2026] \xB7 /worker shows this again)");
|
|
621
|
+
return lines;
|
|
622
|
+
}
|
|
569
623
|
async function answerExtensionUiRequest(options, ctx, request) {
|
|
570
624
|
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
571
625
|
const method = String(request.method ?? request.type ?? "input");
|
|
@@ -592,7 +646,7 @@ async function answerExtensionUiRequest(options, ctx, request) {
|
|
|
592
646
|
ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
593
647
|
}
|
|
594
648
|
}
|
|
595
|
-
var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
|
|
649
|
+
var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "worker", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
|
|
596
650
|
function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
597
651
|
for (const command of commands) {
|
|
598
652
|
const record = recordOf2(command);
|
|
@@ -620,6 +674,21 @@ function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
|
620
674
|
function createRigWorkerPiBridgeExtension(options) {
|
|
621
675
|
return (pi) => {
|
|
622
676
|
const registeredDaemonCommands = new Set;
|
|
677
|
+
let capabilityLines = null;
|
|
678
|
+
let statusText = "connecting to worker session";
|
|
679
|
+
let busy = true;
|
|
680
|
+
let connected = false;
|
|
681
|
+
let frame = 0;
|
|
682
|
+
let spinnerTimer = null;
|
|
683
|
+
const renderStatus = (ctx) => {
|
|
684
|
+
const prefix = busy ? `${SPINNER_FRAMES[frame]} ` : connected ? "\u25CF " : "\u25CB ";
|
|
685
|
+
ctx.ui.setStatus("rig-worker-pi", `${prefix}${statusText}`);
|
|
686
|
+
};
|
|
687
|
+
const setStatus = (ctx, text, isBusy) => {
|
|
688
|
+
statusText = text;
|
|
689
|
+
busy = isBusy;
|
|
690
|
+
renderStatus(ctx);
|
|
691
|
+
};
|
|
623
692
|
pi.registerCommand("detach", {
|
|
624
693
|
description: "Detach from this run; the worker keeps going",
|
|
625
694
|
handler: async (_args, ctx) => {
|
|
@@ -635,31 +704,57 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
635
704
|
ctx.shutdown();
|
|
636
705
|
}
|
|
637
706
|
});
|
|
707
|
+
pi.registerCommand("worker", {
|
|
708
|
+
description: "Show the worker session's real capabilities (tools, extensions, hooks, skills)",
|
|
709
|
+
handler: async (_args, ctx) => {
|
|
710
|
+
if (capabilityLines)
|
|
711
|
+
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
712
|
+
else
|
|
713
|
+
ctx.ui.notify("Worker capabilities are not available yet (still connecting).", "info");
|
|
714
|
+
}
|
|
715
|
+
});
|
|
638
716
|
pi.on("user_bash", (event) => ({
|
|
639
717
|
operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
|
|
640
718
|
}));
|
|
641
719
|
pi.on("session_start", async (_event, ctx) => {
|
|
642
|
-
ctx.ui.setTitle("Rig \xB7
|
|
643
|
-
|
|
644
|
-
|
|
720
|
+
ctx.ui.setTitle("Rig \xB7 enriched bundled Pi");
|
|
721
|
+
setStatus(ctx, "waiting for worker Pi daemon", true);
|
|
722
|
+
spinnerTimer = setInterval(() => {
|
|
723
|
+
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
724
|
+
if (busy)
|
|
725
|
+
renderStatus(ctx);
|
|
726
|
+
}, 150);
|
|
727
|
+
ctx.ui.notify(`Enriched bundled Pi \u2014 native UI + Rig layers, worker brain. Attached to run ${options.runId}. /worker shows live capabilities \xB7 /detach exits \xB7 /stop cancels.`, "info");
|
|
645
728
|
if (options.initialMessageSent)
|
|
646
729
|
ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
|
|
647
730
|
const nativeUi = ctx.ui;
|
|
648
731
|
options.controller.setUiHooks({
|
|
649
|
-
onStatusText: (text) =>
|
|
650
|
-
onActivity: (label, detail) =>
|
|
651
|
-
|
|
732
|
+
onStatusText: (text) => setStatus(ctx, text, !connected),
|
|
733
|
+
onActivity: (label, detail) => {
|
|
734
|
+
const active = label !== "idle" && !/complete|ready/i.test(label);
|
|
735
|
+
setStatus(ctx, [label, detail].filter(Boolean).join(" \u2014 "), active);
|
|
736
|
+
if (/agent running|tool:/.test(label))
|
|
737
|
+
ctx.ui.setWidget("rig-worker-capabilities", undefined);
|
|
738
|
+
},
|
|
739
|
+
onConnectionChange: (nextConnected) => {
|
|
740
|
+
connected = nextConnected;
|
|
741
|
+
setStatus(ctx, nextConnected ? "worker session live" : "worker session disconnected", false);
|
|
742
|
+
},
|
|
652
743
|
onError: (message) => ctx.ui.notify(message, "error"),
|
|
653
744
|
onExtensionUiRequest: (request) => {
|
|
654
745
|
answerExtensionUiRequest(options, ctx, request);
|
|
655
746
|
},
|
|
656
|
-
onCatchUp: (messages, commands) => {
|
|
747
|
+
onCatchUp: (messages, commands, capabilities) => {
|
|
657
748
|
if (nativeUi.appendSessionMessages)
|
|
658
749
|
nativeUi.appendSessionMessages(messages);
|
|
659
750
|
const cwd = options.controller.status.cwd;
|
|
660
751
|
if (nativeUi.setDisplayCwd && cwd)
|
|
661
752
|
nativeUi.setDisplayCwd(cwd);
|
|
662
753
|
registerDaemonCommands(pi, options, ctx, commands, registeredDaemonCommands);
|
|
754
|
+
if (capabilities) {
|
|
755
|
+
capabilityLines = renderWorkerCapabilities(capabilities);
|
|
756
|
+
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
757
|
+
}
|
|
663
758
|
}
|
|
664
759
|
});
|
|
665
760
|
options.controller.connect().catch((error) => {
|
|
@@ -667,6 +762,8 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
667
762
|
});
|
|
668
763
|
});
|
|
669
764
|
pi.on("session_shutdown", () => {
|
|
765
|
+
if (spinnerTimer)
|
|
766
|
+
clearInterval(spinnerTimer);
|
|
670
767
|
options.controller.close();
|
|
671
768
|
});
|
|
672
769
|
};
|
|
@@ -392,6 +392,10 @@ async function getRunPiCommandsViaServer(context, runId) {
|
|
|
392
392
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
|
|
393
393
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
|
|
394
394
|
}
|
|
395
|
+
async function getRunPiCapabilitiesViaServer(context, runId) {
|
|
396
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
|
|
397
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
|
|
398
|
+
}
|
|
395
399
|
async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
|
|
396
400
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
|
|
397
401
|
method: "POST",
|
|
@@ -492,6 +496,7 @@ export {
|
|
|
492
496
|
getRunPiSessionViaServer,
|
|
493
497
|
getRunPiMessagesViaServer,
|
|
494
498
|
getRunPiCommandsViaServer,
|
|
499
|
+
getRunPiCapabilitiesViaServer,
|
|
495
500
|
getRunLogsViaServer,
|
|
496
501
|
getRunDetailsViaServer,
|
|
497
502
|
getGitHubProjectStatusFieldViaServer,
|