@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
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,21 +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();
|
|
7694
7727
|
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
7695
7728
|
return;
|
|
7696
|
-
await this.
|
|
7729
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
7730
|
+
if (expanded === null)
|
|
7731
|
+
return;
|
|
7732
|
+
await this.remote.sendPrompt(expanded, "steer");
|
|
7697
7733
|
}
|
|
7698
7734
|
async followUp(text2) {
|
|
7699
7735
|
const trimmed = text2.trim();
|
|
7700
7736
|
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
7701
7737
|
return;
|
|
7702
|
-
await this.
|
|
7738
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
7739
|
+
if (expanded === null)
|
|
7740
|
+
return;
|
|
7741
|
+
await this.remote.sendPrompt(expanded, "followUp");
|
|
7703
7742
|
}
|
|
7704
7743
|
async abort() {
|
|
7705
7744
|
await this.remote.abort();
|
|
@@ -7812,6 +7851,9 @@ function createRemoteBashOperations(controller, excludeFromContext) {
|
|
|
7812
7851
|
};
|
|
7813
7852
|
}
|
|
7814
7853
|
|
|
7854
|
+
// packages/cli/src/commands/_spinner.ts
|
|
7855
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
7856
|
+
|
|
7815
7857
|
// packages/cli/src/commands/_pi-worker-bridge-extension.ts
|
|
7816
7858
|
function recordOf2(value) {
|
|
7817
7859
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -7829,6 +7871,50 @@ function asText(value) {
|
|
|
7829
7871
|
return String(value);
|
|
7830
7872
|
}
|
|
7831
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
|
+
}
|
|
7832
7918
|
async function answerExtensionUiRequest(options, ctx, request) {
|
|
7833
7919
|
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
7834
7920
|
const method = String(request.method ?? request.type ?? "input");
|
|
@@ -7855,7 +7941,7 @@ async function answerExtensionUiRequest(options, ctx, request) {
|
|
|
7855
7941
|
ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
7856
7942
|
}
|
|
7857
7943
|
}
|
|
7858
|
-
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"]);
|
|
7859
7945
|
function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
7860
7946
|
for (const command of commands) {
|
|
7861
7947
|
const record = recordOf2(command);
|
|
@@ -7883,6 +7969,21 @@ function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
|
7883
7969
|
function createRigWorkerPiBridgeExtension(options) {
|
|
7884
7970
|
return (pi) => {
|
|
7885
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
|
+
};
|
|
7886
7987
|
pi.registerCommand("detach", {
|
|
7887
7988
|
description: "Detach from this run; the worker keeps going",
|
|
7888
7989
|
handler: async (_args, ctx) => {
|
|
@@ -7898,31 +7999,57 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
7898
7999
|
ctx.shutdown();
|
|
7899
8000
|
}
|
|
7900
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
|
+
});
|
|
7901
8011
|
pi.on("user_bash", (event) => ({
|
|
7902
8012
|
operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
|
|
7903
8013
|
}));
|
|
7904
8014
|
pi.on("session_start", async (_event, ctx) => {
|
|
7905
|
-
ctx.ui.setTitle("Rig \xB7
|
|
7906
|
-
|
|
7907
|
-
|
|
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");
|
|
7908
8023
|
if (options.initialMessageSent)
|
|
7909
8024
|
ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
|
|
7910
8025
|
const nativeUi = ctx.ui;
|
|
7911
8026
|
options.controller.setUiHooks({
|
|
7912
|
-
onStatusText: (text2) =>
|
|
7913
|
-
onActivity: (label, detail) =>
|
|
7914
|
-
|
|
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
|
+
},
|
|
7915
8038
|
onError: (message2) => ctx.ui.notify(message2, "error"),
|
|
7916
8039
|
onExtensionUiRequest: (request) => {
|
|
7917
8040
|
answerExtensionUiRequest(options, ctx, request);
|
|
7918
8041
|
},
|
|
7919
|
-
onCatchUp: (messages, commands) => {
|
|
8042
|
+
onCatchUp: (messages, commands, capabilities) => {
|
|
7920
8043
|
if (nativeUi.appendSessionMessages)
|
|
7921
8044
|
nativeUi.appendSessionMessages(messages);
|
|
7922
8045
|
const cwd = options.controller.status.cwd;
|
|
7923
8046
|
if (nativeUi.setDisplayCwd && cwd)
|
|
7924
8047
|
nativeUi.setDisplayCwd(cwd);
|
|
7925
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
|
+
}
|
|
7926
8053
|
}
|
|
7927
8054
|
});
|
|
7928
8055
|
options.controller.connect().catch((error) => {
|
|
@@ -7930,6 +8057,8 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
7930
8057
|
});
|
|
7931
8058
|
});
|
|
7932
8059
|
pi.on("session_shutdown", () => {
|
|
8060
|
+
if (spinnerTimer)
|
|
8061
|
+
clearInterval(spinnerTimer);
|
|
7933
8062
|
options.controller.close();
|
|
7934
8063
|
});
|
|
7935
8064
|
};
|
|
@@ -7955,7 +8084,8 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
7955
8084
|
const tempSessionDir = mkdtempSync(join2(tmpdir(), "rig-pi-frontend-sessions-"));
|
|
7956
8085
|
const restoreEnv = setTemporaryEnv({
|
|
7957
8086
|
PI_CODING_AGENT_SESSION_DIR: tempSessionDir,
|
|
7958
|
-
PI_SKIP_VERSION_CHECK: "1"
|
|
8087
|
+
PI_SKIP_VERSION_CHECK: "1",
|
|
8088
|
+
PI_HIDDEN_COMMANDS: "import,fork,clone,tree,new,resume,trust"
|
|
7959
8089
|
});
|
|
7960
8090
|
const controller = new RigRemoteSessionController({ context, runId: input.runId });
|
|
7961
8091
|
const createRemoteRuntime = async ({ cwd, agentDir, sessionManager, sessionStartEvent }) => {
|
|
@@ -7971,6 +8101,9 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
7971
8101
|
initialMessageSent: input.steered === true
|
|
7972
8102
|
})
|
|
7973
8103
|
],
|
|
8104
|
+
noExtensions: true,
|
|
8105
|
+
noSkills: true,
|
|
8106
|
+
noPromptTemplates: true,
|
|
7974
8107
|
noContextFiles: true
|
|
7975
8108
|
}
|
|
7976
8109
|
});
|
|
@@ -8005,7 +8138,7 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
8005
8138
|
timelineCursor: null,
|
|
8006
8139
|
steered: input.steered === true,
|
|
8007
8140
|
detached,
|
|
8008
|
-
rendered: "
|
|
8141
|
+
rendered: "enriched bundled Pi frontend with remote worker session runtime"
|
|
8009
8142
|
};
|
|
8010
8143
|
}
|
|
8011
8144
|
|
|
@@ -8802,7 +8935,7 @@ var PRIMARY_GROUPS = [
|
|
|
8802
8935
|
{ command: "list", description: "List recent runs from the selected server or local state.", primary: true },
|
|
8803
8936
|
{ command: "status", description: "Render active and recent run groups.", primary: true },
|
|
8804
8937
|
{ command: "show <id>|--run <id> [--raw]", description: "Show a human run summary; --raw prints the full payload.", primary: true },
|
|
8805
|
-
{ command: "attach <run-id>|--run <id> [--follow]", description: "Attach to the run; --follow
|
|
8938
|
+
{ command: "attach <run-id>|--run <id> [--follow]", description: "Attach to the run; --follow opens the enriched bundled Pi (worker brain).", primary: true },
|
|
8806
8939
|
{ command: "stop [<run-id>|--run <id>]", description: "Request stop for one run or local active runs.", primary: true },
|
|
8807
8940
|
{ command: "steer <run-id> --message <text>", description: "Queue a steering message into a live worker without attaching." },
|
|
8808
8941
|
{ 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,21 +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();
|
|
793
826
|
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
794
827
|
return;
|
|
795
|
-
await this.
|
|
828
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
829
|
+
if (expanded === null)
|
|
830
|
+
return;
|
|
831
|
+
await this.remote.sendPrompt(expanded, "steer");
|
|
796
832
|
}
|
|
797
833
|
async followUp(text) {
|
|
798
834
|
const trimmed = text.trim();
|
|
799
835
|
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
800
836
|
return;
|
|
801
|
-
await this.
|
|
837
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
838
|
+
if (expanded === null)
|
|
839
|
+
return;
|
|
840
|
+
await this.remote.sendPrompt(expanded, "followUp");
|
|
802
841
|
}
|
|
803
842
|
async abort() {
|
|
804
843
|
await this.remote.abort();
|
|
@@ -911,6 +950,9 @@ function createRemoteBashOperations(controller, excludeFromContext) {
|
|
|
911
950
|
};
|
|
912
951
|
}
|
|
913
952
|
|
|
953
|
+
// packages/cli/src/commands/_spinner.ts
|
|
954
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
955
|
+
|
|
914
956
|
// packages/cli/src/commands/_pi-worker-bridge-extension.ts
|
|
915
957
|
function recordOf2(value) {
|
|
916
958
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -928,6 +970,50 @@ function asText(value) {
|
|
|
928
970
|
return String(value);
|
|
929
971
|
}
|
|
930
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
|
+
}
|
|
931
1017
|
async function answerExtensionUiRequest(options, ctx, request) {
|
|
932
1018
|
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
933
1019
|
const method = String(request.method ?? request.type ?? "input");
|
|
@@ -954,7 +1040,7 @@ async function answerExtensionUiRequest(options, ctx, request) {
|
|
|
954
1040
|
ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
955
1041
|
}
|
|
956
1042
|
}
|
|
957
|
-
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"]);
|
|
958
1044
|
function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
959
1045
|
for (const command of commands) {
|
|
960
1046
|
const record = recordOf2(command);
|
|
@@ -982,6 +1068,21 @@ function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
|
982
1068
|
function createRigWorkerPiBridgeExtension(options) {
|
|
983
1069
|
return (pi) => {
|
|
984
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
|
+
};
|
|
985
1086
|
pi.registerCommand("detach", {
|
|
986
1087
|
description: "Detach from this run; the worker keeps going",
|
|
987
1088
|
handler: async (_args, ctx) => {
|
|
@@ -997,31 +1098,57 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
997
1098
|
ctx.shutdown();
|
|
998
1099
|
}
|
|
999
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
|
+
});
|
|
1000
1110
|
pi.on("user_bash", (event) => ({
|
|
1001
1111
|
operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
|
|
1002
1112
|
}));
|
|
1003
1113
|
pi.on("session_start", async (_event, ctx) => {
|
|
1004
|
-
ctx.ui.setTitle("Rig \xB7
|
|
1005
|
-
|
|
1006
|
-
|
|
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");
|
|
1007
1122
|
if (options.initialMessageSent)
|
|
1008
1123
|
ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
|
|
1009
1124
|
const nativeUi = ctx.ui;
|
|
1010
1125
|
options.controller.setUiHooks({
|
|
1011
|
-
onStatusText: (text) =>
|
|
1012
|
-
onActivity: (label, detail) =>
|
|
1013
|
-
|
|
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
|
+
},
|
|
1014
1137
|
onError: (message) => ctx.ui.notify(message, "error"),
|
|
1015
1138
|
onExtensionUiRequest: (request) => {
|
|
1016
1139
|
answerExtensionUiRequest(options, ctx, request);
|
|
1017
1140
|
},
|
|
1018
|
-
onCatchUp: (messages, commands) => {
|
|
1141
|
+
onCatchUp: (messages, commands, capabilities) => {
|
|
1019
1142
|
if (nativeUi.appendSessionMessages)
|
|
1020
1143
|
nativeUi.appendSessionMessages(messages);
|
|
1021
1144
|
const cwd = options.controller.status.cwd;
|
|
1022
1145
|
if (nativeUi.setDisplayCwd && cwd)
|
|
1023
1146
|
nativeUi.setDisplayCwd(cwd);
|
|
1024
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
|
+
}
|
|
1025
1152
|
}
|
|
1026
1153
|
});
|
|
1027
1154
|
options.controller.connect().catch((error) => {
|
|
@@ -1029,6 +1156,8 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
1029
1156
|
});
|
|
1030
1157
|
});
|
|
1031
1158
|
pi.on("session_shutdown", () => {
|
|
1159
|
+
if (spinnerTimer)
|
|
1160
|
+
clearInterval(spinnerTimer);
|
|
1032
1161
|
options.controller.close();
|
|
1033
1162
|
});
|
|
1034
1163
|
};
|
|
@@ -1054,7 +1183,8 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
1054
1183
|
const tempSessionDir = mkdtempSync(join(tmpdir(), "rig-pi-frontend-sessions-"));
|
|
1055
1184
|
const restoreEnv = setTemporaryEnv({
|
|
1056
1185
|
PI_CODING_AGENT_SESSION_DIR: tempSessionDir,
|
|
1057
|
-
PI_SKIP_VERSION_CHECK: "1"
|
|
1186
|
+
PI_SKIP_VERSION_CHECK: "1",
|
|
1187
|
+
PI_HIDDEN_COMMANDS: "import,fork,clone,tree,new,resume,trust"
|
|
1058
1188
|
});
|
|
1059
1189
|
const controller = new RigRemoteSessionController({ context, runId: input.runId });
|
|
1060
1190
|
const createRemoteRuntime = async ({ cwd, agentDir, sessionManager, sessionStartEvent }) => {
|
|
@@ -1070,6 +1200,9 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
1070
1200
|
initialMessageSent: input.steered === true
|
|
1071
1201
|
})
|
|
1072
1202
|
],
|
|
1203
|
+
noExtensions: true,
|
|
1204
|
+
noSkills: true,
|
|
1205
|
+
noPromptTemplates: true,
|
|
1073
1206
|
noContextFiles: true
|
|
1074
1207
|
}
|
|
1075
1208
|
});
|
|
@@ -1104,7 +1237,7 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
1104
1237
|
timelineCursor: null,
|
|
1105
1238
|
steered: input.steered === true,
|
|
1106
1239
|
detached,
|
|
1107
|
-
rendered: "
|
|
1240
|
+
rendered: "enriched bundled Pi frontend with remote worker session runtime"
|
|
1108
1241
|
};
|
|
1109
1242
|
}
|
|
1110
1243
|
|