@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
package/dist/bin/rig.js
CHANGED
|
@@ -3209,6 +3209,10 @@ async function getRunPiCommandsViaServer(context, runId) {
|
|
|
3209
3209
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
|
|
3210
3210
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
|
|
3211
3211
|
}
|
|
3212
|
+
async function getRunPiCapabilitiesViaServer(context, runId) {
|
|
3213
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
|
|
3214
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
|
|
3215
|
+
}
|
|
3212
3216
|
async function sendRunPiPromptViaServer(context, runId, text2, streamingBehavior) {
|
|
3213
3217
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
|
|
3214
3218
|
method: "POST",
|
|
@@ -4448,7 +4452,7 @@ function formatSubmittedRun(input) {
|
|
|
4448
4452
|
...formatNextSteps([
|
|
4449
4453
|
`Attach: \`rig run attach ${input.runId} --follow\``,
|
|
4450
4454
|
`Inspect: \`rig run show ${input.runId}\``,
|
|
4451
|
-
input.detached ? "Submitted detached; attach when you are ready." : "Interactive mode opens the
|
|
4455
|
+
input.detached ? "Submitted detached; attach when you are ready." : "Interactive mode opens the enriched bundled Pi (native UI + Rig layers, worker brain)."
|
|
4452
4456
|
])
|
|
4453
4457
|
].join(`
|
|
4454
4458
|
`);
|
|
@@ -7390,7 +7394,7 @@ import {
|
|
|
7390
7394
|
} from "@earendil-works/pi-coding-agent";
|
|
7391
7395
|
|
|
7392
7396
|
// packages/cli/src/commands/_pi-remote-session.ts
|
|
7393
|
-
import { AgentSession as PiAgentSession } from "@earendil-works/pi-coding-agent";
|
|
7397
|
+
import { AgentSession as PiAgentSession, expandPromptTemplate } from "@earendil-works/pi-coding-agent";
|
|
7394
7398
|
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
7395
7399
|
function defaultTransport() {
|
|
7396
7400
|
return {
|
|
@@ -7398,6 +7402,7 @@ function defaultTransport() {
|
|
|
7398
7402
|
getMessages: getRunPiMessagesViaServer,
|
|
7399
7403
|
getStatus: getRunPiStatusViaServer,
|
|
7400
7404
|
getCommands: getRunPiCommandsViaServer,
|
|
7405
|
+
getCapabilities: getRunPiCapabilitiesViaServer,
|
|
7401
7406
|
sendPrompt: sendRunPiPromptViaServer,
|
|
7402
7407
|
sendShell: sendRunPiShellViaServer,
|
|
7403
7408
|
runCommand: runRunPiCommandViaServer,
|
|
@@ -7485,16 +7490,18 @@ class RigRemoteSessionController {
|
|
|
7485
7490
|
this.hooks.onStatusText?.("worker Pi WebSocket disconnected");
|
|
7486
7491
|
};
|
|
7487
7492
|
try {
|
|
7488
|
-
const [messagesPayload, statusPayload, commandsPayload] = await Promise.all([
|
|
7493
|
+
const [messagesPayload, statusPayload, commandsPayload, capabilitiesPayload] = await Promise.all([
|
|
7489
7494
|
this.transport.getMessages(this.context, this.runId),
|
|
7490
7495
|
this.transport.getStatus(this.context, this.runId),
|
|
7491
|
-
this.transport.getCommands(this.context, this.runId)
|
|
7496
|
+
this.transport.getCommands(this.context, this.runId),
|
|
7497
|
+
this.transport.getCapabilities(this.context, this.runId).catch(() => ({ capabilities: null }))
|
|
7492
7498
|
]);
|
|
7493
7499
|
const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
|
|
7494
7500
|
this.applyStatusPayload(statusPayload);
|
|
7495
7501
|
this.session?.replaceRemoteMessages(messages);
|
|
7496
7502
|
const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
|
|
7497
|
-
|
|
7503
|
+
const capabilities = capabilitiesPayload.capabilities && typeof capabilitiesPayload.capabilities === "object" && !Array.isArray(capabilitiesPayload.capabilities) ? capabilitiesPayload.capabilities : null;
|
|
7504
|
+
this.hooks.onCatchUp?.(messages, commands, capabilities);
|
|
7498
7505
|
catchupDone = true;
|
|
7499
7506
|
for (const payload of buffered.splice(0))
|
|
7500
7507
|
this.applyEnvelope(payload);
|
|
@@ -7671,6 +7678,20 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
7671
7678
|
replaceRemoteMessages(messages) {
|
|
7672
7679
|
this.agent.state.messages = messages;
|
|
7673
7680
|
}
|
|
7681
|
+
async expandLocalInput(text2) {
|
|
7682
|
+
let current = text2;
|
|
7683
|
+
const runner = this.extensionRunner;
|
|
7684
|
+
if (runner.hasHandlers("input")) {
|
|
7685
|
+
const result = await runner.emitInput(current, undefined, "interactive");
|
|
7686
|
+
if (result.action === "handled")
|
|
7687
|
+
return null;
|
|
7688
|
+
if (result.action === "transform")
|
|
7689
|
+
current = result.text;
|
|
7690
|
+
}
|
|
7691
|
+
current = this._expandSkillCommand(current);
|
|
7692
|
+
current = expandPromptTemplate(current, [...this.promptTemplates]);
|
|
7693
|
+
return current;
|
|
7694
|
+
}
|
|
7674
7695
|
async prompt(text2, options) {
|
|
7675
7696
|
const trimmed = text2.trim();
|
|
7676
7697
|
if (!trimmed)
|
|
@@ -7678,6 +7699,15 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
7678
7699
|
if (trimmed.startsWith("/")) {
|
|
7679
7700
|
if (await this._tryExecuteExtensionCommand(trimmed))
|
|
7680
7701
|
return;
|
|
7702
|
+
const expanded2 = await this.expandLocalInput(trimmed);
|
|
7703
|
+
if (expanded2 === null)
|
|
7704
|
+
return;
|
|
7705
|
+
if (expanded2 !== trimmed) {
|
|
7706
|
+
const behavior2 = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
7707
|
+
options?.preflightResult?.(true);
|
|
7708
|
+
await this.remote.sendPrompt(expanded2, behavior2);
|
|
7709
|
+
return;
|
|
7710
|
+
}
|
|
7681
7711
|
await this.remote.sendCommand(trimmed);
|
|
7682
7712
|
return;
|
|
7683
7713
|
}
|
|
@@ -7685,15 +7715,30 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
7685
7715
|
await this.remote.sendShell(trimmed);
|
|
7686
7716
|
return;
|
|
7687
7717
|
}
|
|
7718
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
7719
|
+
if (expanded === null)
|
|
7720
|
+
return;
|
|
7688
7721
|
const behavior = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
7689
7722
|
options?.preflightResult?.(true);
|
|
7690
|
-
await this.remote.sendPrompt(
|
|
7723
|
+
await this.remote.sendPrompt(expanded, behavior);
|
|
7691
7724
|
}
|
|
7692
7725
|
async steer(text2) {
|
|
7693
|
-
|
|
7726
|
+
const trimmed = text2.trim();
|
|
7727
|
+
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
7728
|
+
return;
|
|
7729
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
7730
|
+
if (expanded === null)
|
|
7731
|
+
return;
|
|
7732
|
+
await this.remote.sendPrompt(expanded, "steer");
|
|
7694
7733
|
}
|
|
7695
7734
|
async followUp(text2) {
|
|
7696
|
-
|
|
7735
|
+
const trimmed = text2.trim();
|
|
7736
|
+
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
7737
|
+
return;
|
|
7738
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
7739
|
+
if (expanded === null)
|
|
7740
|
+
return;
|
|
7741
|
+
await this.remote.sendPrompt(expanded, "followUp");
|
|
7697
7742
|
}
|
|
7698
7743
|
async abort() {
|
|
7699
7744
|
await this.remote.abort();
|
|
@@ -7806,6 +7851,9 @@ function createRemoteBashOperations(controller, excludeFromContext) {
|
|
|
7806
7851
|
};
|
|
7807
7852
|
}
|
|
7808
7853
|
|
|
7854
|
+
// packages/cli/src/commands/_spinner.ts
|
|
7855
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
7856
|
+
|
|
7809
7857
|
// packages/cli/src/commands/_pi-worker-bridge-extension.ts
|
|
7810
7858
|
function recordOf2(value) {
|
|
7811
7859
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -7823,6 +7871,50 @@ function asText(value) {
|
|
|
7823
7871
|
return String(value);
|
|
7824
7872
|
}
|
|
7825
7873
|
}
|
|
7874
|
+
function names(value, key = "name") {
|
|
7875
|
+
if (!Array.isArray(value))
|
|
7876
|
+
return [];
|
|
7877
|
+
return value.flatMap((entry) => {
|
|
7878
|
+
if (typeof entry === "string")
|
|
7879
|
+
return [entry];
|
|
7880
|
+
const record = recordOf2(entry);
|
|
7881
|
+
const name = record?.[key];
|
|
7882
|
+
return typeof name === "string" ? [name] : [];
|
|
7883
|
+
});
|
|
7884
|
+
}
|
|
7885
|
+
function renderWorkerCapabilities(capabilities) {
|
|
7886
|
+
const lines = ["Worker session capabilities (in effect for this run)"];
|
|
7887
|
+
const tools = Array.isArray(capabilities.tools) ? capabilities.tools : [];
|
|
7888
|
+
const activeTools = tools.flatMap((tool) => {
|
|
7889
|
+
const record = recordOf2(tool);
|
|
7890
|
+
return record && record.active === true && typeof record.name === "string" ? [record.name] : [];
|
|
7891
|
+
});
|
|
7892
|
+
const inactiveCount = tools.length - activeTools.length;
|
|
7893
|
+
lines.push(` tools ${activeTools.join(", ") || "(none)"}${inactiveCount > 0 ? ` (+${inactiveCount} inactive)` : ""}`);
|
|
7894
|
+
const extensions = Array.isArray(capabilities.extensions) ? capabilities.extensions : [];
|
|
7895
|
+
const extensionLabels = extensions.flatMap((entry) => {
|
|
7896
|
+
const record = recordOf2(entry);
|
|
7897
|
+
if (!record)
|
|
7898
|
+
return [];
|
|
7899
|
+
const path = typeof record.path === "string" ? record.path : "";
|
|
7900
|
+
const short = path.split("/").filter(Boolean).slice(-2).join("/") || path || "extension";
|
|
7901
|
+
return [short];
|
|
7902
|
+
});
|
|
7903
|
+
lines.push(` extensions ${extensionLabels.join(", ") || "(none)"}`);
|
|
7904
|
+
const hookEvents = names(capabilities.hookEvents);
|
|
7905
|
+
const hookList = Array.isArray(capabilities.hookEvents) ? capabilities.hookEvents.map(asText).filter(Boolean) : hookEvents;
|
|
7906
|
+
lines.push(` hooks ${hookList.join(", ") || "(none)"}`);
|
|
7907
|
+
lines.push(` skills ${names(capabilities.skills).join(", ") || "(none)"}`);
|
|
7908
|
+
lines.push(` prompts ${names(capabilities.prompts).join(", ") || "(none)"}`);
|
|
7909
|
+
const model = typeof capabilities.model === "string" ? capabilities.model : "(unknown)";
|
|
7910
|
+
const thinking = typeof capabilities.thinkingLevel === "string" ? capabilities.thinkingLevel : "";
|
|
7911
|
+
const cwd = typeof capabilities.cwd === "string" ? capabilities.cwd : "";
|
|
7912
|
+
lines.push(` model ${model}${thinking ? ` \xB7 ${thinking}` : ""}`);
|
|
7913
|
+
if (cwd)
|
|
7914
|
+
lines.push(` cwd ${cwd}`);
|
|
7915
|
+
lines.push(" (worker commands are in your palette as [worker \u2026] \xB7 /worker shows this again)");
|
|
7916
|
+
return lines;
|
|
7917
|
+
}
|
|
7826
7918
|
async function answerExtensionUiRequest(options, ctx, request) {
|
|
7827
7919
|
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
7828
7920
|
const method = String(request.method ?? request.type ?? "input");
|
|
@@ -7849,7 +7941,7 @@ async function answerExtensionUiRequest(options, ctx, request) {
|
|
|
7849
7941
|
ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
7850
7942
|
}
|
|
7851
7943
|
}
|
|
7852
|
-
var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
|
|
7944
|
+
var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "worker", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
|
|
7853
7945
|
function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
7854
7946
|
for (const command of commands) {
|
|
7855
7947
|
const record = recordOf2(command);
|
|
@@ -7877,6 +7969,21 @@ function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
|
7877
7969
|
function createRigWorkerPiBridgeExtension(options) {
|
|
7878
7970
|
return (pi) => {
|
|
7879
7971
|
const registeredDaemonCommands = new Set;
|
|
7972
|
+
let capabilityLines = null;
|
|
7973
|
+
let statusText = "connecting to worker session";
|
|
7974
|
+
let busy = true;
|
|
7975
|
+
let connected = false;
|
|
7976
|
+
let frame = 0;
|
|
7977
|
+
let spinnerTimer = null;
|
|
7978
|
+
const renderStatus = (ctx) => {
|
|
7979
|
+
const prefix = busy ? `${SPINNER_FRAMES[frame]} ` : connected ? "\u25CF " : "\u25CB ";
|
|
7980
|
+
ctx.ui.setStatus("rig-worker-pi", `${prefix}${statusText}`);
|
|
7981
|
+
};
|
|
7982
|
+
const setStatus = (ctx, text2, isBusy) => {
|
|
7983
|
+
statusText = text2;
|
|
7984
|
+
busy = isBusy;
|
|
7985
|
+
renderStatus(ctx);
|
|
7986
|
+
};
|
|
7880
7987
|
pi.registerCommand("detach", {
|
|
7881
7988
|
description: "Detach from this run; the worker keeps going",
|
|
7882
7989
|
handler: async (_args, ctx) => {
|
|
@@ -7892,31 +7999,57 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
7892
7999
|
ctx.shutdown();
|
|
7893
8000
|
}
|
|
7894
8001
|
});
|
|
8002
|
+
pi.registerCommand("worker", {
|
|
8003
|
+
description: "Show the worker session's real capabilities (tools, extensions, hooks, skills)",
|
|
8004
|
+
handler: async (_args, ctx) => {
|
|
8005
|
+
if (capabilityLines)
|
|
8006
|
+
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
8007
|
+
else
|
|
8008
|
+
ctx.ui.notify("Worker capabilities are not available yet (still connecting).", "info");
|
|
8009
|
+
}
|
|
8010
|
+
});
|
|
7895
8011
|
pi.on("user_bash", (event) => ({
|
|
7896
8012
|
operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
|
|
7897
8013
|
}));
|
|
7898
8014
|
pi.on("session_start", async (_event, ctx) => {
|
|
7899
|
-
ctx.ui.setTitle("Rig \xB7
|
|
7900
|
-
|
|
7901
|
-
|
|
8015
|
+
ctx.ui.setTitle("Rig \xB7 enriched bundled Pi");
|
|
8016
|
+
setStatus(ctx, "waiting for worker Pi daemon", true);
|
|
8017
|
+
spinnerTimer = setInterval(() => {
|
|
8018
|
+
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
8019
|
+
if (busy)
|
|
8020
|
+
renderStatus(ctx);
|
|
8021
|
+
}, 150);
|
|
8022
|
+
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");
|
|
7902
8023
|
if (options.initialMessageSent)
|
|
7903
8024
|
ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
|
|
7904
8025
|
const nativeUi = ctx.ui;
|
|
7905
8026
|
options.controller.setUiHooks({
|
|
7906
|
-
onStatusText: (text2) =>
|
|
7907
|
-
onActivity: (label, detail) =>
|
|
7908
|
-
|
|
8027
|
+
onStatusText: (text2) => setStatus(ctx, text2, !connected),
|
|
8028
|
+
onActivity: (label, detail) => {
|
|
8029
|
+
const active = label !== "idle" && !/complete|ready/i.test(label);
|
|
8030
|
+
setStatus(ctx, [label, detail].filter(Boolean).join(" \u2014 "), active);
|
|
8031
|
+
if (/agent running|tool:/.test(label))
|
|
8032
|
+
ctx.ui.setWidget("rig-worker-capabilities", undefined);
|
|
8033
|
+
},
|
|
8034
|
+
onConnectionChange: (nextConnected) => {
|
|
8035
|
+
connected = nextConnected;
|
|
8036
|
+
setStatus(ctx, nextConnected ? "worker session live" : "worker session disconnected", false);
|
|
8037
|
+
},
|
|
7909
8038
|
onError: (message2) => ctx.ui.notify(message2, "error"),
|
|
7910
8039
|
onExtensionUiRequest: (request) => {
|
|
7911
8040
|
answerExtensionUiRequest(options, ctx, request);
|
|
7912
8041
|
},
|
|
7913
|
-
onCatchUp: (messages, commands) => {
|
|
8042
|
+
onCatchUp: (messages, commands, capabilities) => {
|
|
7914
8043
|
if (nativeUi.appendSessionMessages)
|
|
7915
8044
|
nativeUi.appendSessionMessages(messages);
|
|
7916
8045
|
const cwd = options.controller.status.cwd;
|
|
7917
8046
|
if (nativeUi.setDisplayCwd && cwd)
|
|
7918
8047
|
nativeUi.setDisplayCwd(cwd);
|
|
7919
8048
|
registerDaemonCommands(pi, options, ctx, commands, registeredDaemonCommands);
|
|
8049
|
+
if (capabilities) {
|
|
8050
|
+
capabilityLines = renderWorkerCapabilities(capabilities);
|
|
8051
|
+
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
8052
|
+
}
|
|
7920
8053
|
}
|
|
7921
8054
|
});
|
|
7922
8055
|
options.controller.connect().catch((error) => {
|
|
@@ -7924,6 +8057,8 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
7924
8057
|
});
|
|
7925
8058
|
});
|
|
7926
8059
|
pi.on("session_shutdown", () => {
|
|
8060
|
+
if (spinnerTimer)
|
|
8061
|
+
clearInterval(spinnerTimer);
|
|
7927
8062
|
options.controller.close();
|
|
7928
8063
|
});
|
|
7929
8064
|
};
|
|
@@ -7965,6 +8100,9 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
7965
8100
|
initialMessageSent: input.steered === true
|
|
7966
8101
|
})
|
|
7967
8102
|
],
|
|
8103
|
+
noExtensions: true,
|
|
8104
|
+
noSkills: true,
|
|
8105
|
+
noPromptTemplates: true,
|
|
7968
8106
|
noContextFiles: true
|
|
7969
8107
|
}
|
|
7970
8108
|
});
|
|
@@ -7999,7 +8137,7 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
7999
8137
|
timelineCursor: null,
|
|
8000
8138
|
steered: input.steered === true,
|
|
8001
8139
|
detached,
|
|
8002
|
-
rendered: "
|
|
8140
|
+
rendered: "enriched bundled Pi frontend with remote worker session runtime"
|
|
8003
8141
|
};
|
|
8004
8142
|
}
|
|
8005
8143
|
|
|
@@ -8796,7 +8934,7 @@ var PRIMARY_GROUPS = [
|
|
|
8796
8934
|
{ command: "list", description: "List recent runs from the selected server or local state.", primary: true },
|
|
8797
8935
|
{ command: "status", description: "Render active and recent run groups.", primary: true },
|
|
8798
8936
|
{ command: "show <id>|--run <id> [--raw]", description: "Show a human run summary; --raw prints the full payload.", primary: true },
|
|
8799
|
-
{ command: "attach <run-id>|--run <id> [--follow]", description: "Attach to the run; --follow
|
|
8937
|
+
{ command: "attach <run-id>|--run <id> [--follow]", description: "Attach to the run; --follow opens the enriched bundled Pi (worker brain).", primary: true },
|
|
8800
8938
|
{ command: "stop [<run-id>|--run <id>]", description: "Request stop for one run or local active runs.", primary: true },
|
|
8801
8939
|
{ command: "steer <run-id> --message <text>", description: "Queue a steering message into a live worker without attaching." },
|
|
8802
8940
|
{ command: "timeline --run <id> [--follow]", description: "Stream raw run timeline events." },
|
|
@@ -219,7 +219,7 @@ function formatSubmittedRun(input) {
|
|
|
219
219
|
...formatNextSteps([
|
|
220
220
|
`Attach: \`rig run attach ${input.runId} --follow\``,
|
|
221
221
|
`Inspect: \`rig run show ${input.runId}\``,
|
|
222
|
-
input.detached ? "Submitted detached; attach when you are ready." : "Interactive mode opens the
|
|
222
|
+
input.detached ? "Submitted detached; attach when you are ready." : "Interactive mode opens the enriched bundled Pi (native UI + Rig layers, worker brain)."
|
|
223
223
|
])
|
|
224
224
|
].join(`
|
|
225
225
|
`);
|
|
@@ -108,7 +108,7 @@ var PRIMARY_GROUPS = [
|
|
|
108
108
|
{ command: "list", description: "List recent runs from the selected server or local state.", primary: true },
|
|
109
109
|
{ command: "status", description: "Render active and recent run groups.", primary: true },
|
|
110
110
|
{ command: "show <id>|--run <id> [--raw]", description: "Show a human run summary; --raw prints the full payload.", primary: true },
|
|
111
|
-
{ command: "attach <run-id>|--run <id> [--follow]", description: "Attach to the run; --follow
|
|
111
|
+
{ command: "attach <run-id>|--run <id> [--follow]", description: "Attach to the run; --follow opens the enriched bundled Pi (worker brain).", primary: true },
|
|
112
112
|
{ command: "stop [<run-id>|--run <id>]", description: "Request stop for one run or local active runs.", primary: true },
|
|
113
113
|
{ command: "steer <run-id> --message <text>", description: "Queue a steering message into a live worker without attaching." },
|
|
114
114
|
{ command: "timeline --run <id> [--follow]", description: "Stream raw run timeline events." },
|
|
@@ -241,6 +241,10 @@ async function getRunPiCommandsViaServer(context, runId) {
|
|
|
241
241
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
|
|
242
242
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
|
|
243
243
|
}
|
|
244
|
+
async function getRunPiCapabilitiesViaServer(context, runId) {
|
|
245
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
|
|
246
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
|
|
247
|
+
}
|
|
244
248
|
async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
|
|
245
249
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
|
|
246
250
|
method: "POST",
|
|
@@ -489,7 +493,7 @@ import {
|
|
|
489
493
|
} from "@earendil-works/pi-coding-agent";
|
|
490
494
|
|
|
491
495
|
// packages/cli/src/commands/_pi-remote-session.ts
|
|
492
|
-
import { AgentSession as PiAgentSession } from "@earendil-works/pi-coding-agent";
|
|
496
|
+
import { AgentSession as PiAgentSession, expandPromptTemplate } from "@earendil-works/pi-coding-agent";
|
|
493
497
|
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
494
498
|
function defaultTransport() {
|
|
495
499
|
return {
|
|
@@ -497,6 +501,7 @@ function defaultTransport() {
|
|
|
497
501
|
getMessages: getRunPiMessagesViaServer,
|
|
498
502
|
getStatus: getRunPiStatusViaServer,
|
|
499
503
|
getCommands: getRunPiCommandsViaServer,
|
|
504
|
+
getCapabilities: getRunPiCapabilitiesViaServer,
|
|
500
505
|
sendPrompt: sendRunPiPromptViaServer,
|
|
501
506
|
sendShell: sendRunPiShellViaServer,
|
|
502
507
|
runCommand: runRunPiCommandViaServer,
|
|
@@ -584,16 +589,18 @@ class RigRemoteSessionController {
|
|
|
584
589
|
this.hooks.onStatusText?.("worker Pi WebSocket disconnected");
|
|
585
590
|
};
|
|
586
591
|
try {
|
|
587
|
-
const [messagesPayload, statusPayload, commandsPayload] = await Promise.all([
|
|
592
|
+
const [messagesPayload, statusPayload, commandsPayload, capabilitiesPayload] = await Promise.all([
|
|
588
593
|
this.transport.getMessages(this.context, this.runId),
|
|
589
594
|
this.transport.getStatus(this.context, this.runId),
|
|
590
|
-
this.transport.getCommands(this.context, this.runId)
|
|
595
|
+
this.transport.getCommands(this.context, this.runId),
|
|
596
|
+
this.transport.getCapabilities(this.context, this.runId).catch(() => ({ capabilities: null }))
|
|
591
597
|
]);
|
|
592
598
|
const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
|
|
593
599
|
this.applyStatusPayload(statusPayload);
|
|
594
600
|
this.session?.replaceRemoteMessages(messages);
|
|
595
601
|
const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
|
|
596
|
-
|
|
602
|
+
const capabilities = capabilitiesPayload.capabilities && typeof capabilitiesPayload.capabilities === "object" && !Array.isArray(capabilitiesPayload.capabilities) ? capabilitiesPayload.capabilities : null;
|
|
603
|
+
this.hooks.onCatchUp?.(messages, commands, capabilities);
|
|
597
604
|
catchupDone = true;
|
|
598
605
|
for (const payload of buffered.splice(0))
|
|
599
606
|
this.applyEnvelope(payload);
|
|
@@ -770,6 +777,20 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
770
777
|
replaceRemoteMessages(messages) {
|
|
771
778
|
this.agent.state.messages = messages;
|
|
772
779
|
}
|
|
780
|
+
async expandLocalInput(text) {
|
|
781
|
+
let current = text;
|
|
782
|
+
const runner = this.extensionRunner;
|
|
783
|
+
if (runner.hasHandlers("input")) {
|
|
784
|
+
const result = await runner.emitInput(current, undefined, "interactive");
|
|
785
|
+
if (result.action === "handled")
|
|
786
|
+
return null;
|
|
787
|
+
if (result.action === "transform")
|
|
788
|
+
current = result.text;
|
|
789
|
+
}
|
|
790
|
+
current = this._expandSkillCommand(current);
|
|
791
|
+
current = expandPromptTemplate(current, [...this.promptTemplates]);
|
|
792
|
+
return current;
|
|
793
|
+
}
|
|
773
794
|
async prompt(text, options) {
|
|
774
795
|
const trimmed = text.trim();
|
|
775
796
|
if (!trimmed)
|
|
@@ -777,6 +798,15 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
777
798
|
if (trimmed.startsWith("/")) {
|
|
778
799
|
if (await this._tryExecuteExtensionCommand(trimmed))
|
|
779
800
|
return;
|
|
801
|
+
const expanded2 = await this.expandLocalInput(trimmed);
|
|
802
|
+
if (expanded2 === null)
|
|
803
|
+
return;
|
|
804
|
+
if (expanded2 !== trimmed) {
|
|
805
|
+
const behavior2 = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
806
|
+
options?.preflightResult?.(true);
|
|
807
|
+
await this.remote.sendPrompt(expanded2, behavior2);
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
780
810
|
await this.remote.sendCommand(trimmed);
|
|
781
811
|
return;
|
|
782
812
|
}
|
|
@@ -784,15 +814,30 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
784
814
|
await this.remote.sendShell(trimmed);
|
|
785
815
|
return;
|
|
786
816
|
}
|
|
817
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
818
|
+
if (expanded === null)
|
|
819
|
+
return;
|
|
787
820
|
const behavior = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
788
821
|
options?.preflightResult?.(true);
|
|
789
|
-
await this.remote.sendPrompt(
|
|
822
|
+
await this.remote.sendPrompt(expanded, behavior);
|
|
790
823
|
}
|
|
791
824
|
async steer(text) {
|
|
792
|
-
|
|
825
|
+
const trimmed = text.trim();
|
|
826
|
+
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
827
|
+
return;
|
|
828
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
829
|
+
if (expanded === null)
|
|
830
|
+
return;
|
|
831
|
+
await this.remote.sendPrompt(expanded, "steer");
|
|
793
832
|
}
|
|
794
833
|
async followUp(text) {
|
|
795
|
-
|
|
834
|
+
const trimmed = text.trim();
|
|
835
|
+
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
836
|
+
return;
|
|
837
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
838
|
+
if (expanded === null)
|
|
839
|
+
return;
|
|
840
|
+
await this.remote.sendPrompt(expanded, "followUp");
|
|
796
841
|
}
|
|
797
842
|
async abort() {
|
|
798
843
|
await this.remote.abort();
|
|
@@ -905,6 +950,9 @@ function createRemoteBashOperations(controller, excludeFromContext) {
|
|
|
905
950
|
};
|
|
906
951
|
}
|
|
907
952
|
|
|
953
|
+
// packages/cli/src/commands/_spinner.ts
|
|
954
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
955
|
+
|
|
908
956
|
// packages/cli/src/commands/_pi-worker-bridge-extension.ts
|
|
909
957
|
function recordOf2(value) {
|
|
910
958
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -922,6 +970,50 @@ function asText(value) {
|
|
|
922
970
|
return String(value);
|
|
923
971
|
}
|
|
924
972
|
}
|
|
973
|
+
function names(value, key = "name") {
|
|
974
|
+
if (!Array.isArray(value))
|
|
975
|
+
return [];
|
|
976
|
+
return value.flatMap((entry) => {
|
|
977
|
+
if (typeof entry === "string")
|
|
978
|
+
return [entry];
|
|
979
|
+
const record = recordOf2(entry);
|
|
980
|
+
const name = record?.[key];
|
|
981
|
+
return typeof name === "string" ? [name] : [];
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
function renderWorkerCapabilities(capabilities) {
|
|
985
|
+
const lines = ["Worker session capabilities (in effect for this run)"];
|
|
986
|
+
const tools = Array.isArray(capabilities.tools) ? capabilities.tools : [];
|
|
987
|
+
const activeTools = tools.flatMap((tool) => {
|
|
988
|
+
const record = recordOf2(tool);
|
|
989
|
+
return record && record.active === true && typeof record.name === "string" ? [record.name] : [];
|
|
990
|
+
});
|
|
991
|
+
const inactiveCount = tools.length - activeTools.length;
|
|
992
|
+
lines.push(` tools ${activeTools.join(", ") || "(none)"}${inactiveCount > 0 ? ` (+${inactiveCount} inactive)` : ""}`);
|
|
993
|
+
const extensions = Array.isArray(capabilities.extensions) ? capabilities.extensions : [];
|
|
994
|
+
const extensionLabels = extensions.flatMap((entry) => {
|
|
995
|
+
const record = recordOf2(entry);
|
|
996
|
+
if (!record)
|
|
997
|
+
return [];
|
|
998
|
+
const path = typeof record.path === "string" ? record.path : "";
|
|
999
|
+
const short = path.split("/").filter(Boolean).slice(-2).join("/") || path || "extension";
|
|
1000
|
+
return [short];
|
|
1001
|
+
});
|
|
1002
|
+
lines.push(` extensions ${extensionLabels.join(", ") || "(none)"}`);
|
|
1003
|
+
const hookEvents = names(capabilities.hookEvents);
|
|
1004
|
+
const hookList = Array.isArray(capabilities.hookEvents) ? capabilities.hookEvents.map(asText).filter(Boolean) : hookEvents;
|
|
1005
|
+
lines.push(` hooks ${hookList.join(", ") || "(none)"}`);
|
|
1006
|
+
lines.push(` skills ${names(capabilities.skills).join(", ") || "(none)"}`);
|
|
1007
|
+
lines.push(` prompts ${names(capabilities.prompts).join(", ") || "(none)"}`);
|
|
1008
|
+
const model = typeof capabilities.model === "string" ? capabilities.model : "(unknown)";
|
|
1009
|
+
const thinking = typeof capabilities.thinkingLevel === "string" ? capabilities.thinkingLevel : "";
|
|
1010
|
+
const cwd = typeof capabilities.cwd === "string" ? capabilities.cwd : "";
|
|
1011
|
+
lines.push(` model ${model}${thinking ? ` \xB7 ${thinking}` : ""}`);
|
|
1012
|
+
if (cwd)
|
|
1013
|
+
lines.push(` cwd ${cwd}`);
|
|
1014
|
+
lines.push(" (worker commands are in your palette as [worker \u2026] \xB7 /worker shows this again)");
|
|
1015
|
+
return lines;
|
|
1016
|
+
}
|
|
925
1017
|
async function answerExtensionUiRequest(options, ctx, request) {
|
|
926
1018
|
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
927
1019
|
const method = String(request.method ?? request.type ?? "input");
|
|
@@ -948,7 +1040,7 @@ async function answerExtensionUiRequest(options, ctx, request) {
|
|
|
948
1040
|
ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
949
1041
|
}
|
|
950
1042
|
}
|
|
951
|
-
var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
|
|
1043
|
+
var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "worker", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
|
|
952
1044
|
function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
953
1045
|
for (const command of commands) {
|
|
954
1046
|
const record = recordOf2(command);
|
|
@@ -976,6 +1068,21 @@ function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
|
976
1068
|
function createRigWorkerPiBridgeExtension(options) {
|
|
977
1069
|
return (pi) => {
|
|
978
1070
|
const registeredDaemonCommands = new Set;
|
|
1071
|
+
let capabilityLines = null;
|
|
1072
|
+
let statusText = "connecting to worker session";
|
|
1073
|
+
let busy = true;
|
|
1074
|
+
let connected = false;
|
|
1075
|
+
let frame = 0;
|
|
1076
|
+
let spinnerTimer = null;
|
|
1077
|
+
const renderStatus = (ctx) => {
|
|
1078
|
+
const prefix = busy ? `${SPINNER_FRAMES[frame]} ` : connected ? "\u25CF " : "\u25CB ";
|
|
1079
|
+
ctx.ui.setStatus("rig-worker-pi", `${prefix}${statusText}`);
|
|
1080
|
+
};
|
|
1081
|
+
const setStatus = (ctx, text, isBusy) => {
|
|
1082
|
+
statusText = text;
|
|
1083
|
+
busy = isBusy;
|
|
1084
|
+
renderStatus(ctx);
|
|
1085
|
+
};
|
|
979
1086
|
pi.registerCommand("detach", {
|
|
980
1087
|
description: "Detach from this run; the worker keeps going",
|
|
981
1088
|
handler: async (_args, ctx) => {
|
|
@@ -991,31 +1098,57 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
991
1098
|
ctx.shutdown();
|
|
992
1099
|
}
|
|
993
1100
|
});
|
|
1101
|
+
pi.registerCommand("worker", {
|
|
1102
|
+
description: "Show the worker session's real capabilities (tools, extensions, hooks, skills)",
|
|
1103
|
+
handler: async (_args, ctx) => {
|
|
1104
|
+
if (capabilityLines)
|
|
1105
|
+
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
1106
|
+
else
|
|
1107
|
+
ctx.ui.notify("Worker capabilities are not available yet (still connecting).", "info");
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
994
1110
|
pi.on("user_bash", (event) => ({
|
|
995
1111
|
operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
|
|
996
1112
|
}));
|
|
997
1113
|
pi.on("session_start", async (_event, ctx) => {
|
|
998
|
-
ctx.ui.setTitle("Rig \xB7
|
|
999
|
-
|
|
1000
|
-
|
|
1114
|
+
ctx.ui.setTitle("Rig \xB7 enriched bundled Pi");
|
|
1115
|
+
setStatus(ctx, "waiting for worker Pi daemon", true);
|
|
1116
|
+
spinnerTimer = setInterval(() => {
|
|
1117
|
+
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
1118
|
+
if (busy)
|
|
1119
|
+
renderStatus(ctx);
|
|
1120
|
+
}, 150);
|
|
1121
|
+
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");
|
|
1001
1122
|
if (options.initialMessageSent)
|
|
1002
1123
|
ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
|
|
1003
1124
|
const nativeUi = ctx.ui;
|
|
1004
1125
|
options.controller.setUiHooks({
|
|
1005
|
-
onStatusText: (text) =>
|
|
1006
|
-
onActivity: (label, detail) =>
|
|
1007
|
-
|
|
1126
|
+
onStatusText: (text) => setStatus(ctx, text, !connected),
|
|
1127
|
+
onActivity: (label, detail) => {
|
|
1128
|
+
const active = label !== "idle" && !/complete|ready/i.test(label);
|
|
1129
|
+
setStatus(ctx, [label, detail].filter(Boolean).join(" \u2014 "), active);
|
|
1130
|
+
if (/agent running|tool:/.test(label))
|
|
1131
|
+
ctx.ui.setWidget("rig-worker-capabilities", undefined);
|
|
1132
|
+
},
|
|
1133
|
+
onConnectionChange: (nextConnected) => {
|
|
1134
|
+
connected = nextConnected;
|
|
1135
|
+
setStatus(ctx, nextConnected ? "worker session live" : "worker session disconnected", false);
|
|
1136
|
+
},
|
|
1008
1137
|
onError: (message) => ctx.ui.notify(message, "error"),
|
|
1009
1138
|
onExtensionUiRequest: (request) => {
|
|
1010
1139
|
answerExtensionUiRequest(options, ctx, request);
|
|
1011
1140
|
},
|
|
1012
|
-
onCatchUp: (messages, commands) => {
|
|
1141
|
+
onCatchUp: (messages, commands, capabilities) => {
|
|
1013
1142
|
if (nativeUi.appendSessionMessages)
|
|
1014
1143
|
nativeUi.appendSessionMessages(messages);
|
|
1015
1144
|
const cwd = options.controller.status.cwd;
|
|
1016
1145
|
if (nativeUi.setDisplayCwd && cwd)
|
|
1017
1146
|
nativeUi.setDisplayCwd(cwd);
|
|
1018
1147
|
registerDaemonCommands(pi, options, ctx, commands, registeredDaemonCommands);
|
|
1148
|
+
if (capabilities) {
|
|
1149
|
+
capabilityLines = renderWorkerCapabilities(capabilities);
|
|
1150
|
+
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
1151
|
+
}
|
|
1019
1152
|
}
|
|
1020
1153
|
});
|
|
1021
1154
|
options.controller.connect().catch((error) => {
|
|
@@ -1023,6 +1156,8 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
1023
1156
|
});
|
|
1024
1157
|
});
|
|
1025
1158
|
pi.on("session_shutdown", () => {
|
|
1159
|
+
if (spinnerTimer)
|
|
1160
|
+
clearInterval(spinnerTimer);
|
|
1026
1161
|
options.controller.close();
|
|
1027
1162
|
});
|
|
1028
1163
|
};
|
|
@@ -1064,6 +1199,9 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
1064
1199
|
initialMessageSent: input.steered === true
|
|
1065
1200
|
})
|
|
1066
1201
|
],
|
|
1202
|
+
noExtensions: true,
|
|
1203
|
+
noSkills: true,
|
|
1204
|
+
noPromptTemplates: true,
|
|
1067
1205
|
noContextFiles: true
|
|
1068
1206
|
}
|
|
1069
1207
|
});
|
|
@@ -1098,7 +1236,7 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
1098
1236
|
timelineCursor: null,
|
|
1099
1237
|
steered: input.steered === true,
|
|
1100
1238
|
detached,
|
|
1101
|
-
rendered: "
|
|
1239
|
+
rendered: "enriched bundled Pi frontend with remote worker session runtime"
|
|
1102
1240
|
};
|
|
1103
1241
|
}
|
|
1104
1242
|
|