@h-rig/cli 0.0.6-alpha.41 → 0.0.6-alpha.43
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 +156 -18
- 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 +154 -16
- package/dist/src/commands/_pi-frontend.js +154 -16
- package/dist/src/commands/_pi-remote-session.js +52 -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 +154 -16
- package/dist/src/commands/task.js +156 -18
- package/dist/src/commands.js +156 -18
- package/dist/src/index.js +156 -18
- 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,15 +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();
|
|
600
|
+
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
601
|
+
return;
|
|
602
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
603
|
+
if (expanded === null)
|
|
604
|
+
return;
|
|
605
|
+
await this.remote.sendPrompt(expanded, "steer");
|
|
567
606
|
}
|
|
568
607
|
async followUp(text) {
|
|
569
|
-
|
|
608
|
+
const trimmed = text.trim();
|
|
609
|
+
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
610
|
+
return;
|
|
611
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
612
|
+
if (expanded === null)
|
|
613
|
+
return;
|
|
614
|
+
await this.remote.sendPrompt(expanded, "followUp");
|
|
570
615
|
}
|
|
571
616
|
async abort() {
|
|
572
617
|
await this.remote.abort();
|
|
@@ -679,6 +724,9 @@ function createRemoteBashOperations(controller, excludeFromContext) {
|
|
|
679
724
|
};
|
|
680
725
|
}
|
|
681
726
|
|
|
727
|
+
// packages/cli/src/commands/_spinner.ts
|
|
728
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
729
|
+
|
|
682
730
|
// packages/cli/src/commands/_pi-worker-bridge-extension.ts
|
|
683
731
|
function recordOf2(value) {
|
|
684
732
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -696,6 +744,50 @@ function asText(value) {
|
|
|
696
744
|
return String(value);
|
|
697
745
|
}
|
|
698
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
|
+
}
|
|
699
791
|
async function answerExtensionUiRequest(options, ctx, request) {
|
|
700
792
|
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
701
793
|
const method = String(request.method ?? request.type ?? "input");
|
|
@@ -722,7 +814,7 @@ async function answerExtensionUiRequest(options, ctx, request) {
|
|
|
722
814
|
ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
723
815
|
}
|
|
724
816
|
}
|
|
725
|
-
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"]);
|
|
726
818
|
function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
727
819
|
for (const command of commands) {
|
|
728
820
|
const record = recordOf2(command);
|
|
@@ -750,6 +842,21 @@ function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
|
750
842
|
function createRigWorkerPiBridgeExtension(options) {
|
|
751
843
|
return (pi) => {
|
|
752
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
|
+
};
|
|
753
860
|
pi.registerCommand("detach", {
|
|
754
861
|
description: "Detach from this run; the worker keeps going",
|
|
755
862
|
handler: async (_args, ctx) => {
|
|
@@ -765,31 +872,57 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
765
872
|
ctx.shutdown();
|
|
766
873
|
}
|
|
767
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
|
+
});
|
|
768
884
|
pi.on("user_bash", (event) => ({
|
|
769
885
|
operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
|
|
770
886
|
}));
|
|
771
887
|
pi.on("session_start", async (_event, ctx) => {
|
|
772
|
-
ctx.ui.setTitle("Rig \xB7
|
|
773
|
-
|
|
774
|
-
|
|
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");
|
|
775
896
|
if (options.initialMessageSent)
|
|
776
897
|
ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
|
|
777
898
|
const nativeUi = ctx.ui;
|
|
778
899
|
options.controller.setUiHooks({
|
|
779
|
-
onStatusText: (text) =>
|
|
780
|
-
onActivity: (label, detail) =>
|
|
781
|
-
|
|
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
|
+
},
|
|
782
911
|
onError: (message) => ctx.ui.notify(message, "error"),
|
|
783
912
|
onExtensionUiRequest: (request) => {
|
|
784
913
|
answerExtensionUiRequest(options, ctx, request);
|
|
785
914
|
},
|
|
786
|
-
onCatchUp: (messages, commands) => {
|
|
915
|
+
onCatchUp: (messages, commands, capabilities) => {
|
|
787
916
|
if (nativeUi.appendSessionMessages)
|
|
788
917
|
nativeUi.appendSessionMessages(messages);
|
|
789
918
|
const cwd = options.controller.status.cwd;
|
|
790
919
|
if (nativeUi.setDisplayCwd && cwd)
|
|
791
920
|
nativeUi.setDisplayCwd(cwd);
|
|
792
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
|
+
}
|
|
793
926
|
}
|
|
794
927
|
});
|
|
795
928
|
options.controller.connect().catch((error) => {
|
|
@@ -797,6 +930,8 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
797
930
|
});
|
|
798
931
|
});
|
|
799
932
|
pi.on("session_shutdown", () => {
|
|
933
|
+
if (spinnerTimer)
|
|
934
|
+
clearInterval(spinnerTimer);
|
|
800
935
|
options.controller.close();
|
|
801
936
|
});
|
|
802
937
|
};
|
|
@@ -838,6 +973,9 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
838
973
|
initialMessageSent: input.steered === true
|
|
839
974
|
})
|
|
840
975
|
],
|
|
976
|
+
noExtensions: true,
|
|
977
|
+
noSkills: true,
|
|
978
|
+
noPromptTemplates: true,
|
|
841
979
|
noContextFiles: true
|
|
842
980
|
}
|
|
843
981
|
});
|
|
@@ -872,7 +1010,7 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
872
1010
|
timelineCursor: null,
|
|
873
1011
|
steered: input.steered === true,
|
|
874
1012
|
detached,
|
|
875
|
-
rendered: "
|
|
1013
|
+
rendered: "enriched bundled Pi frontend with remote worker session runtime"
|
|
876
1014
|
};
|
|
877
1015
|
}
|
|
878
1016
|
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,15 +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();
|
|
580
|
+
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
581
|
+
return;
|
|
582
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
583
|
+
if (expanded === null)
|
|
584
|
+
return;
|
|
585
|
+
await this.remote.sendPrompt(expanded, "steer");
|
|
547
586
|
}
|
|
548
587
|
async followUp(text) {
|
|
549
|
-
|
|
588
|
+
const trimmed = text.trim();
|
|
589
|
+
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
590
|
+
return;
|
|
591
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
592
|
+
if (expanded === null)
|
|
593
|
+
return;
|
|
594
|
+
await this.remote.sendPrompt(expanded, "followUp");
|
|
550
595
|
}
|
|
551
596
|
async abort() {
|
|
552
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,
|