@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
|
@@ -373,6 +373,10 @@ async function getRunPiCommandsViaServer(context, runId) {
|
|
|
373
373
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
|
|
374
374
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
|
|
375
375
|
}
|
|
376
|
+
async function getRunPiCapabilitiesViaServer(context, runId) {
|
|
377
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
|
|
378
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
|
|
379
|
+
}
|
|
376
380
|
async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
|
|
377
381
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
|
|
378
382
|
method: "POST",
|
|
@@ -970,7 +974,7 @@ import {
|
|
|
970
974
|
} from "@earendil-works/pi-coding-agent";
|
|
971
975
|
|
|
972
976
|
// packages/cli/src/commands/_pi-remote-session.ts
|
|
973
|
-
import { AgentSession as PiAgentSession } from "@earendil-works/pi-coding-agent";
|
|
977
|
+
import { AgentSession as PiAgentSession, expandPromptTemplate } from "@earendil-works/pi-coding-agent";
|
|
974
978
|
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
975
979
|
function defaultTransport() {
|
|
976
980
|
return {
|
|
@@ -978,6 +982,7 @@ function defaultTransport() {
|
|
|
978
982
|
getMessages: getRunPiMessagesViaServer,
|
|
979
983
|
getStatus: getRunPiStatusViaServer,
|
|
980
984
|
getCommands: getRunPiCommandsViaServer,
|
|
985
|
+
getCapabilities: getRunPiCapabilitiesViaServer,
|
|
981
986
|
sendPrompt: sendRunPiPromptViaServer,
|
|
982
987
|
sendShell: sendRunPiShellViaServer,
|
|
983
988
|
runCommand: runRunPiCommandViaServer,
|
|
@@ -1065,16 +1070,18 @@ class RigRemoteSessionController {
|
|
|
1065
1070
|
this.hooks.onStatusText?.("worker Pi WebSocket disconnected");
|
|
1066
1071
|
};
|
|
1067
1072
|
try {
|
|
1068
|
-
const [messagesPayload, statusPayload, commandsPayload] = await Promise.all([
|
|
1073
|
+
const [messagesPayload, statusPayload, commandsPayload, capabilitiesPayload] = await Promise.all([
|
|
1069
1074
|
this.transport.getMessages(this.context, this.runId),
|
|
1070
1075
|
this.transport.getStatus(this.context, this.runId),
|
|
1071
|
-
this.transport.getCommands(this.context, this.runId)
|
|
1076
|
+
this.transport.getCommands(this.context, this.runId),
|
|
1077
|
+
this.transport.getCapabilities(this.context, this.runId).catch(() => ({ capabilities: null }))
|
|
1072
1078
|
]);
|
|
1073
1079
|
const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
|
|
1074
1080
|
this.applyStatusPayload(statusPayload);
|
|
1075
1081
|
this.session?.replaceRemoteMessages(messages);
|
|
1076
1082
|
const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
|
|
1077
|
-
|
|
1083
|
+
const capabilities = capabilitiesPayload.capabilities && typeof capabilitiesPayload.capabilities === "object" && !Array.isArray(capabilitiesPayload.capabilities) ? capabilitiesPayload.capabilities : null;
|
|
1084
|
+
this.hooks.onCatchUp?.(messages, commands, capabilities);
|
|
1078
1085
|
catchupDone = true;
|
|
1079
1086
|
for (const payload of buffered.splice(0))
|
|
1080
1087
|
this.applyEnvelope(payload);
|
|
@@ -1251,6 +1258,20 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
1251
1258
|
replaceRemoteMessages(messages) {
|
|
1252
1259
|
this.agent.state.messages = messages;
|
|
1253
1260
|
}
|
|
1261
|
+
async expandLocalInput(text) {
|
|
1262
|
+
let current = text;
|
|
1263
|
+
const runner = this.extensionRunner;
|
|
1264
|
+
if (runner.hasHandlers("input")) {
|
|
1265
|
+
const result = await runner.emitInput(current, undefined, "interactive");
|
|
1266
|
+
if (result.action === "handled")
|
|
1267
|
+
return null;
|
|
1268
|
+
if (result.action === "transform")
|
|
1269
|
+
current = result.text;
|
|
1270
|
+
}
|
|
1271
|
+
current = this._expandSkillCommand(current);
|
|
1272
|
+
current = expandPromptTemplate(current, [...this.promptTemplates]);
|
|
1273
|
+
return current;
|
|
1274
|
+
}
|
|
1254
1275
|
async prompt(text, options) {
|
|
1255
1276
|
const trimmed = text.trim();
|
|
1256
1277
|
if (!trimmed)
|
|
@@ -1258,6 +1279,15 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
1258
1279
|
if (trimmed.startsWith("/")) {
|
|
1259
1280
|
if (await this._tryExecuteExtensionCommand(trimmed))
|
|
1260
1281
|
return;
|
|
1282
|
+
const expanded2 = await this.expandLocalInput(trimmed);
|
|
1283
|
+
if (expanded2 === null)
|
|
1284
|
+
return;
|
|
1285
|
+
if (expanded2 !== trimmed) {
|
|
1286
|
+
const behavior2 = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
1287
|
+
options?.preflightResult?.(true);
|
|
1288
|
+
await this.remote.sendPrompt(expanded2, behavior2);
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1261
1291
|
await this.remote.sendCommand(trimmed);
|
|
1262
1292
|
return;
|
|
1263
1293
|
}
|
|
@@ -1265,15 +1295,30 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
1265
1295
|
await this.remote.sendShell(trimmed);
|
|
1266
1296
|
return;
|
|
1267
1297
|
}
|
|
1298
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
1299
|
+
if (expanded === null)
|
|
1300
|
+
return;
|
|
1268
1301
|
const behavior = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
1269
1302
|
options?.preflightResult?.(true);
|
|
1270
|
-
await this.remote.sendPrompt(
|
|
1303
|
+
await this.remote.sendPrompt(expanded, behavior);
|
|
1271
1304
|
}
|
|
1272
1305
|
async steer(text) {
|
|
1273
|
-
|
|
1306
|
+
const trimmed = text.trim();
|
|
1307
|
+
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
1308
|
+
return;
|
|
1309
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
1310
|
+
if (expanded === null)
|
|
1311
|
+
return;
|
|
1312
|
+
await this.remote.sendPrompt(expanded, "steer");
|
|
1274
1313
|
}
|
|
1275
1314
|
async followUp(text) {
|
|
1276
|
-
|
|
1315
|
+
const trimmed = text.trim();
|
|
1316
|
+
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
1317
|
+
return;
|
|
1318
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
1319
|
+
if (expanded === null)
|
|
1320
|
+
return;
|
|
1321
|
+
await this.remote.sendPrompt(expanded, "followUp");
|
|
1277
1322
|
}
|
|
1278
1323
|
async abort() {
|
|
1279
1324
|
await this.remote.abort();
|
|
@@ -1386,6 +1431,9 @@ function createRemoteBashOperations(controller, excludeFromContext) {
|
|
|
1386
1431
|
};
|
|
1387
1432
|
}
|
|
1388
1433
|
|
|
1434
|
+
// packages/cli/src/commands/_spinner.ts
|
|
1435
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1436
|
+
|
|
1389
1437
|
// packages/cli/src/commands/_pi-worker-bridge-extension.ts
|
|
1390
1438
|
function recordOf2(value) {
|
|
1391
1439
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -1403,6 +1451,50 @@ function asText(value) {
|
|
|
1403
1451
|
return String(value);
|
|
1404
1452
|
}
|
|
1405
1453
|
}
|
|
1454
|
+
function names(value, key = "name") {
|
|
1455
|
+
if (!Array.isArray(value))
|
|
1456
|
+
return [];
|
|
1457
|
+
return value.flatMap((entry) => {
|
|
1458
|
+
if (typeof entry === "string")
|
|
1459
|
+
return [entry];
|
|
1460
|
+
const record = recordOf2(entry);
|
|
1461
|
+
const name = record?.[key];
|
|
1462
|
+
return typeof name === "string" ? [name] : [];
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
function renderWorkerCapabilities(capabilities) {
|
|
1466
|
+
const lines = ["Worker session capabilities (in effect for this run)"];
|
|
1467
|
+
const tools = Array.isArray(capabilities.tools) ? capabilities.tools : [];
|
|
1468
|
+
const activeTools = tools.flatMap((tool) => {
|
|
1469
|
+
const record = recordOf2(tool);
|
|
1470
|
+
return record && record.active === true && typeof record.name === "string" ? [record.name] : [];
|
|
1471
|
+
});
|
|
1472
|
+
const inactiveCount = tools.length - activeTools.length;
|
|
1473
|
+
lines.push(` tools ${activeTools.join(", ") || "(none)"}${inactiveCount > 0 ? ` (+${inactiveCount} inactive)` : ""}`);
|
|
1474
|
+
const extensions = Array.isArray(capabilities.extensions) ? capabilities.extensions : [];
|
|
1475
|
+
const extensionLabels = extensions.flatMap((entry) => {
|
|
1476
|
+
const record = recordOf2(entry);
|
|
1477
|
+
if (!record)
|
|
1478
|
+
return [];
|
|
1479
|
+
const path = typeof record.path === "string" ? record.path : "";
|
|
1480
|
+
const short = path.split("/").filter(Boolean).slice(-2).join("/") || path || "extension";
|
|
1481
|
+
return [short];
|
|
1482
|
+
});
|
|
1483
|
+
lines.push(` extensions ${extensionLabels.join(", ") || "(none)"}`);
|
|
1484
|
+
const hookEvents = names(capabilities.hookEvents);
|
|
1485
|
+
const hookList = Array.isArray(capabilities.hookEvents) ? capabilities.hookEvents.map(asText).filter(Boolean) : hookEvents;
|
|
1486
|
+
lines.push(` hooks ${hookList.join(", ") || "(none)"}`);
|
|
1487
|
+
lines.push(` skills ${names(capabilities.skills).join(", ") || "(none)"}`);
|
|
1488
|
+
lines.push(` prompts ${names(capabilities.prompts).join(", ") || "(none)"}`);
|
|
1489
|
+
const model = typeof capabilities.model === "string" ? capabilities.model : "(unknown)";
|
|
1490
|
+
const thinking = typeof capabilities.thinkingLevel === "string" ? capabilities.thinkingLevel : "";
|
|
1491
|
+
const cwd = typeof capabilities.cwd === "string" ? capabilities.cwd : "";
|
|
1492
|
+
lines.push(` model ${model}${thinking ? ` \xB7 ${thinking}` : ""}`);
|
|
1493
|
+
if (cwd)
|
|
1494
|
+
lines.push(` cwd ${cwd}`);
|
|
1495
|
+
lines.push(" (worker commands are in your palette as [worker \u2026] \xB7 /worker shows this again)");
|
|
1496
|
+
return lines;
|
|
1497
|
+
}
|
|
1406
1498
|
async function answerExtensionUiRequest(options, ctx, request) {
|
|
1407
1499
|
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
1408
1500
|
const method = String(request.method ?? request.type ?? "input");
|
|
@@ -1429,7 +1521,7 @@ async function answerExtensionUiRequest(options, ctx, request) {
|
|
|
1429
1521
|
ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1430
1522
|
}
|
|
1431
1523
|
}
|
|
1432
|
-
var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
|
|
1524
|
+
var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "worker", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
|
|
1433
1525
|
function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
1434
1526
|
for (const command of commands) {
|
|
1435
1527
|
const record = recordOf2(command);
|
|
@@ -1457,6 +1549,21 @@ function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
|
1457
1549
|
function createRigWorkerPiBridgeExtension(options) {
|
|
1458
1550
|
return (pi) => {
|
|
1459
1551
|
const registeredDaemonCommands = new Set;
|
|
1552
|
+
let capabilityLines = null;
|
|
1553
|
+
let statusText = "connecting to worker session";
|
|
1554
|
+
let busy = true;
|
|
1555
|
+
let connected = false;
|
|
1556
|
+
let frame = 0;
|
|
1557
|
+
let spinnerTimer = null;
|
|
1558
|
+
const renderStatus = (ctx) => {
|
|
1559
|
+
const prefix = busy ? `${SPINNER_FRAMES[frame]} ` : connected ? "\u25CF " : "\u25CB ";
|
|
1560
|
+
ctx.ui.setStatus("rig-worker-pi", `${prefix}${statusText}`);
|
|
1561
|
+
};
|
|
1562
|
+
const setStatus = (ctx, text, isBusy) => {
|
|
1563
|
+
statusText = text;
|
|
1564
|
+
busy = isBusy;
|
|
1565
|
+
renderStatus(ctx);
|
|
1566
|
+
};
|
|
1460
1567
|
pi.registerCommand("detach", {
|
|
1461
1568
|
description: "Detach from this run; the worker keeps going",
|
|
1462
1569
|
handler: async (_args, ctx) => {
|
|
@@ -1472,31 +1579,57 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
1472
1579
|
ctx.shutdown();
|
|
1473
1580
|
}
|
|
1474
1581
|
});
|
|
1582
|
+
pi.registerCommand("worker", {
|
|
1583
|
+
description: "Show the worker session's real capabilities (tools, extensions, hooks, skills)",
|
|
1584
|
+
handler: async (_args, ctx) => {
|
|
1585
|
+
if (capabilityLines)
|
|
1586
|
+
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
1587
|
+
else
|
|
1588
|
+
ctx.ui.notify("Worker capabilities are not available yet (still connecting).", "info");
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1475
1591
|
pi.on("user_bash", (event) => ({
|
|
1476
1592
|
operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
|
|
1477
1593
|
}));
|
|
1478
1594
|
pi.on("session_start", async (_event, ctx) => {
|
|
1479
|
-
ctx.ui.setTitle("Rig \xB7
|
|
1480
|
-
|
|
1481
|
-
|
|
1595
|
+
ctx.ui.setTitle("Rig \xB7 enriched bundled Pi");
|
|
1596
|
+
setStatus(ctx, "waiting for worker Pi daemon", true);
|
|
1597
|
+
spinnerTimer = setInterval(() => {
|
|
1598
|
+
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
1599
|
+
if (busy)
|
|
1600
|
+
renderStatus(ctx);
|
|
1601
|
+
}, 150);
|
|
1602
|
+
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");
|
|
1482
1603
|
if (options.initialMessageSent)
|
|
1483
1604
|
ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
|
|
1484
1605
|
const nativeUi = ctx.ui;
|
|
1485
1606
|
options.controller.setUiHooks({
|
|
1486
|
-
onStatusText: (text) =>
|
|
1487
|
-
onActivity: (label, detail) =>
|
|
1488
|
-
|
|
1607
|
+
onStatusText: (text) => setStatus(ctx, text, !connected),
|
|
1608
|
+
onActivity: (label, detail) => {
|
|
1609
|
+
const active = label !== "idle" && !/complete|ready/i.test(label);
|
|
1610
|
+
setStatus(ctx, [label, detail].filter(Boolean).join(" \u2014 "), active);
|
|
1611
|
+
if (/agent running|tool:/.test(label))
|
|
1612
|
+
ctx.ui.setWidget("rig-worker-capabilities", undefined);
|
|
1613
|
+
},
|
|
1614
|
+
onConnectionChange: (nextConnected) => {
|
|
1615
|
+
connected = nextConnected;
|
|
1616
|
+
setStatus(ctx, nextConnected ? "worker session live" : "worker session disconnected", false);
|
|
1617
|
+
},
|
|
1489
1618
|
onError: (message2) => ctx.ui.notify(message2, "error"),
|
|
1490
1619
|
onExtensionUiRequest: (request) => {
|
|
1491
1620
|
answerExtensionUiRequest(options, ctx, request);
|
|
1492
1621
|
},
|
|
1493
|
-
onCatchUp: (messages, commands) => {
|
|
1622
|
+
onCatchUp: (messages, commands, capabilities) => {
|
|
1494
1623
|
if (nativeUi.appendSessionMessages)
|
|
1495
1624
|
nativeUi.appendSessionMessages(messages);
|
|
1496
1625
|
const cwd = options.controller.status.cwd;
|
|
1497
1626
|
if (nativeUi.setDisplayCwd && cwd)
|
|
1498
1627
|
nativeUi.setDisplayCwd(cwd);
|
|
1499
1628
|
registerDaemonCommands(pi, options, ctx, commands, registeredDaemonCommands);
|
|
1629
|
+
if (capabilities) {
|
|
1630
|
+
capabilityLines = renderWorkerCapabilities(capabilities);
|
|
1631
|
+
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
1632
|
+
}
|
|
1500
1633
|
}
|
|
1501
1634
|
});
|
|
1502
1635
|
options.controller.connect().catch((error) => {
|
|
@@ -1504,6 +1637,8 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
1504
1637
|
});
|
|
1505
1638
|
});
|
|
1506
1639
|
pi.on("session_shutdown", () => {
|
|
1640
|
+
if (spinnerTimer)
|
|
1641
|
+
clearInterval(spinnerTimer);
|
|
1507
1642
|
options.controller.close();
|
|
1508
1643
|
});
|
|
1509
1644
|
};
|
|
@@ -1545,6 +1680,9 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
1545
1680
|
initialMessageSent: input.steered === true
|
|
1546
1681
|
})
|
|
1547
1682
|
],
|
|
1683
|
+
noExtensions: true,
|
|
1684
|
+
noSkills: true,
|
|
1685
|
+
noPromptTemplates: true,
|
|
1548
1686
|
noContextFiles: true
|
|
1549
1687
|
}
|
|
1550
1688
|
});
|
|
@@ -1579,7 +1717,7 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
1579
1717
|
timelineCursor: null,
|
|
1580
1718
|
steered: input.steered === true,
|
|
1581
1719
|
detached,
|
|
1582
|
-
rendered: "
|
|
1720
|
+
rendered: "enriched bundled Pi frontend with remote worker session runtime"
|
|
1583
1721
|
};
|
|
1584
1722
|
}
|
|
1585
1723
|
|
|
@@ -1837,7 +1975,7 @@ function formatSubmittedRun(input) {
|
|
|
1837
1975
|
...formatNextSteps([
|
|
1838
1976
|
`Attach: \`rig run attach ${input.runId} --follow\``,
|
|
1839
1977
|
`Inspect: \`rig run show ${input.runId}\``,
|
|
1840
|
-
input.detached ? "Submitted detached; attach when you are ready." : "Interactive mode opens the
|
|
1978
|
+
input.detached ? "Submitted detached; attach when you are ready." : "Interactive mode opens the enriched bundled Pi (native UI + Rig layers, worker brain)."
|
|
1841
1979
|
])
|
|
1842
1980
|
].join(`
|
|
1843
1981
|
`);
|
|
@@ -1952,7 +2090,7 @@ var PRIMARY_GROUPS = [
|
|
|
1952
2090
|
{ command: "list", description: "List recent runs from the selected server or local state.", primary: true },
|
|
1953
2091
|
{ command: "status", description: "Render active and recent run groups.", primary: true },
|
|
1954
2092
|
{ command: "show <id>|--run <id> [--raw]", description: "Show a human run summary; --raw prints the full payload.", primary: true },
|
|
1955
|
-
{ command: "attach <run-id>|--run <id> [--follow]", description: "Attach to the run; --follow
|
|
2093
|
+
{ command: "attach <run-id>|--run <id> [--follow]", description: "Attach to the run; --follow opens the enriched bundled Pi (worker brain).", primary: true },
|
|
1956
2094
|
{ command: "stop [<run-id>|--run <id>]", description: "Request stop for one run or local active runs.", primary: true },
|
|
1957
2095
|
{ command: "steer <run-id> --message <text>", description: "Queue a steering message into a live worker without attaching." },
|
|
1958
2096
|
{ command: "timeline --run <id> [--follow]", description: "Stream raw run timeline events." },
|
package/dist/src/commands.js
CHANGED
|
@@ -3015,6 +3015,10 @@ async function getRunPiCommandsViaServer(context, runId) {
|
|
|
3015
3015
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
|
|
3016
3016
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
|
|
3017
3017
|
}
|
|
3018
|
+
async function getRunPiCapabilitiesViaServer(context, runId) {
|
|
3019
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
|
|
3020
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
|
|
3021
|
+
}
|
|
3018
3022
|
async function sendRunPiPromptViaServer(context, runId, text2, streamingBehavior) {
|
|
3019
3023
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
|
|
3020
3024
|
method: "POST",
|
|
@@ -4254,7 +4258,7 @@ function formatSubmittedRun(input) {
|
|
|
4254
4258
|
...formatNextSteps([
|
|
4255
4259
|
`Attach: \`rig run attach ${input.runId} --follow\``,
|
|
4256
4260
|
`Inspect: \`rig run show ${input.runId}\``,
|
|
4257
|
-
input.detached ? "Submitted detached; attach when you are ready." : "Interactive mode opens the
|
|
4261
|
+
input.detached ? "Submitted detached; attach when you are ready." : "Interactive mode opens the enriched bundled Pi (native UI + Rig layers, worker brain)."
|
|
4258
4262
|
])
|
|
4259
4263
|
].join(`
|
|
4260
4264
|
`);
|
|
@@ -7196,7 +7200,7 @@ import {
|
|
|
7196
7200
|
} from "@earendil-works/pi-coding-agent";
|
|
7197
7201
|
|
|
7198
7202
|
// packages/cli/src/commands/_pi-remote-session.ts
|
|
7199
|
-
import { AgentSession as PiAgentSession } from "@earendil-works/pi-coding-agent";
|
|
7203
|
+
import { AgentSession as PiAgentSession, expandPromptTemplate } from "@earendil-works/pi-coding-agent";
|
|
7200
7204
|
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
7201
7205
|
function defaultTransport() {
|
|
7202
7206
|
return {
|
|
@@ -7204,6 +7208,7 @@ function defaultTransport() {
|
|
|
7204
7208
|
getMessages: getRunPiMessagesViaServer,
|
|
7205
7209
|
getStatus: getRunPiStatusViaServer,
|
|
7206
7210
|
getCommands: getRunPiCommandsViaServer,
|
|
7211
|
+
getCapabilities: getRunPiCapabilitiesViaServer,
|
|
7207
7212
|
sendPrompt: sendRunPiPromptViaServer,
|
|
7208
7213
|
sendShell: sendRunPiShellViaServer,
|
|
7209
7214
|
runCommand: runRunPiCommandViaServer,
|
|
@@ -7291,16 +7296,18 @@ class RigRemoteSessionController {
|
|
|
7291
7296
|
this.hooks.onStatusText?.("worker Pi WebSocket disconnected");
|
|
7292
7297
|
};
|
|
7293
7298
|
try {
|
|
7294
|
-
const [messagesPayload, statusPayload, commandsPayload] = await Promise.all([
|
|
7299
|
+
const [messagesPayload, statusPayload, commandsPayload, capabilitiesPayload] = await Promise.all([
|
|
7295
7300
|
this.transport.getMessages(this.context, this.runId),
|
|
7296
7301
|
this.transport.getStatus(this.context, this.runId),
|
|
7297
|
-
this.transport.getCommands(this.context, this.runId)
|
|
7302
|
+
this.transport.getCommands(this.context, this.runId),
|
|
7303
|
+
this.transport.getCapabilities(this.context, this.runId).catch(() => ({ capabilities: null }))
|
|
7298
7304
|
]);
|
|
7299
7305
|
const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
|
|
7300
7306
|
this.applyStatusPayload(statusPayload);
|
|
7301
7307
|
this.session?.replaceRemoteMessages(messages);
|
|
7302
7308
|
const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
|
|
7303
|
-
|
|
7309
|
+
const capabilities = capabilitiesPayload.capabilities && typeof capabilitiesPayload.capabilities === "object" && !Array.isArray(capabilitiesPayload.capabilities) ? capabilitiesPayload.capabilities : null;
|
|
7310
|
+
this.hooks.onCatchUp?.(messages, commands, capabilities);
|
|
7304
7311
|
catchupDone = true;
|
|
7305
7312
|
for (const payload of buffered.splice(0))
|
|
7306
7313
|
this.applyEnvelope(payload);
|
|
@@ -7477,6 +7484,20 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
7477
7484
|
replaceRemoteMessages(messages) {
|
|
7478
7485
|
this.agent.state.messages = messages;
|
|
7479
7486
|
}
|
|
7487
|
+
async expandLocalInput(text2) {
|
|
7488
|
+
let current = text2;
|
|
7489
|
+
const runner = this.extensionRunner;
|
|
7490
|
+
if (runner.hasHandlers("input")) {
|
|
7491
|
+
const result = await runner.emitInput(current, undefined, "interactive");
|
|
7492
|
+
if (result.action === "handled")
|
|
7493
|
+
return null;
|
|
7494
|
+
if (result.action === "transform")
|
|
7495
|
+
current = result.text;
|
|
7496
|
+
}
|
|
7497
|
+
current = this._expandSkillCommand(current);
|
|
7498
|
+
current = expandPromptTemplate(current, [...this.promptTemplates]);
|
|
7499
|
+
return current;
|
|
7500
|
+
}
|
|
7480
7501
|
async prompt(text2, options) {
|
|
7481
7502
|
const trimmed = text2.trim();
|
|
7482
7503
|
if (!trimmed)
|
|
@@ -7484,6 +7505,15 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
7484
7505
|
if (trimmed.startsWith("/")) {
|
|
7485
7506
|
if (await this._tryExecuteExtensionCommand(trimmed))
|
|
7486
7507
|
return;
|
|
7508
|
+
const expanded2 = await this.expandLocalInput(trimmed);
|
|
7509
|
+
if (expanded2 === null)
|
|
7510
|
+
return;
|
|
7511
|
+
if (expanded2 !== trimmed) {
|
|
7512
|
+
const behavior2 = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
7513
|
+
options?.preflightResult?.(true);
|
|
7514
|
+
await this.remote.sendPrompt(expanded2, behavior2);
|
|
7515
|
+
return;
|
|
7516
|
+
}
|
|
7487
7517
|
await this.remote.sendCommand(trimmed);
|
|
7488
7518
|
return;
|
|
7489
7519
|
}
|
|
@@ -7491,15 +7521,30 @@ class RigRemoteAgentSession extends PiAgentSession {
|
|
|
7491
7521
|
await this.remote.sendShell(trimmed);
|
|
7492
7522
|
return;
|
|
7493
7523
|
}
|
|
7524
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
7525
|
+
if (expanded === null)
|
|
7526
|
+
return;
|
|
7494
7527
|
const behavior = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
7495
7528
|
options?.preflightResult?.(true);
|
|
7496
|
-
await this.remote.sendPrompt(
|
|
7529
|
+
await this.remote.sendPrompt(expanded, behavior);
|
|
7497
7530
|
}
|
|
7498
7531
|
async steer(text2) {
|
|
7499
|
-
|
|
7532
|
+
const trimmed = text2.trim();
|
|
7533
|
+
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
7534
|
+
return;
|
|
7535
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
7536
|
+
if (expanded === null)
|
|
7537
|
+
return;
|
|
7538
|
+
await this.remote.sendPrompt(expanded, "steer");
|
|
7500
7539
|
}
|
|
7501
7540
|
async followUp(text2) {
|
|
7502
|
-
|
|
7541
|
+
const trimmed = text2.trim();
|
|
7542
|
+
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
7543
|
+
return;
|
|
7544
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
7545
|
+
if (expanded === null)
|
|
7546
|
+
return;
|
|
7547
|
+
await this.remote.sendPrompt(expanded, "followUp");
|
|
7503
7548
|
}
|
|
7504
7549
|
async abort() {
|
|
7505
7550
|
await this.remote.abort();
|
|
@@ -7612,6 +7657,9 @@ function createRemoteBashOperations(controller, excludeFromContext) {
|
|
|
7612
7657
|
};
|
|
7613
7658
|
}
|
|
7614
7659
|
|
|
7660
|
+
// packages/cli/src/commands/_spinner.ts
|
|
7661
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
7662
|
+
|
|
7615
7663
|
// packages/cli/src/commands/_pi-worker-bridge-extension.ts
|
|
7616
7664
|
function recordOf2(value) {
|
|
7617
7665
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -7629,6 +7677,50 @@ function asText(value) {
|
|
|
7629
7677
|
return String(value);
|
|
7630
7678
|
}
|
|
7631
7679
|
}
|
|
7680
|
+
function names(value, key = "name") {
|
|
7681
|
+
if (!Array.isArray(value))
|
|
7682
|
+
return [];
|
|
7683
|
+
return value.flatMap((entry) => {
|
|
7684
|
+
if (typeof entry === "string")
|
|
7685
|
+
return [entry];
|
|
7686
|
+
const record = recordOf2(entry);
|
|
7687
|
+
const name = record?.[key];
|
|
7688
|
+
return typeof name === "string" ? [name] : [];
|
|
7689
|
+
});
|
|
7690
|
+
}
|
|
7691
|
+
function renderWorkerCapabilities(capabilities) {
|
|
7692
|
+
const lines = ["Worker session capabilities (in effect for this run)"];
|
|
7693
|
+
const tools = Array.isArray(capabilities.tools) ? capabilities.tools : [];
|
|
7694
|
+
const activeTools = tools.flatMap((tool) => {
|
|
7695
|
+
const record = recordOf2(tool);
|
|
7696
|
+
return record && record.active === true && typeof record.name === "string" ? [record.name] : [];
|
|
7697
|
+
});
|
|
7698
|
+
const inactiveCount = tools.length - activeTools.length;
|
|
7699
|
+
lines.push(` tools ${activeTools.join(", ") || "(none)"}${inactiveCount > 0 ? ` (+${inactiveCount} inactive)` : ""}`);
|
|
7700
|
+
const extensions = Array.isArray(capabilities.extensions) ? capabilities.extensions : [];
|
|
7701
|
+
const extensionLabels = extensions.flatMap((entry) => {
|
|
7702
|
+
const record = recordOf2(entry);
|
|
7703
|
+
if (!record)
|
|
7704
|
+
return [];
|
|
7705
|
+
const path = typeof record.path === "string" ? record.path : "";
|
|
7706
|
+
const short = path.split("/").filter(Boolean).slice(-2).join("/") || path || "extension";
|
|
7707
|
+
return [short];
|
|
7708
|
+
});
|
|
7709
|
+
lines.push(` extensions ${extensionLabels.join(", ") || "(none)"}`);
|
|
7710
|
+
const hookEvents = names(capabilities.hookEvents);
|
|
7711
|
+
const hookList = Array.isArray(capabilities.hookEvents) ? capabilities.hookEvents.map(asText).filter(Boolean) : hookEvents;
|
|
7712
|
+
lines.push(` hooks ${hookList.join(", ") || "(none)"}`);
|
|
7713
|
+
lines.push(` skills ${names(capabilities.skills).join(", ") || "(none)"}`);
|
|
7714
|
+
lines.push(` prompts ${names(capabilities.prompts).join(", ") || "(none)"}`);
|
|
7715
|
+
const model = typeof capabilities.model === "string" ? capabilities.model : "(unknown)";
|
|
7716
|
+
const thinking = typeof capabilities.thinkingLevel === "string" ? capabilities.thinkingLevel : "";
|
|
7717
|
+
const cwd = typeof capabilities.cwd === "string" ? capabilities.cwd : "";
|
|
7718
|
+
lines.push(` model ${model}${thinking ? ` \xB7 ${thinking}` : ""}`);
|
|
7719
|
+
if (cwd)
|
|
7720
|
+
lines.push(` cwd ${cwd}`);
|
|
7721
|
+
lines.push(" (worker commands are in your palette as [worker \u2026] \xB7 /worker shows this again)");
|
|
7722
|
+
return lines;
|
|
7723
|
+
}
|
|
7632
7724
|
async function answerExtensionUiRequest(options, ctx, request) {
|
|
7633
7725
|
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
7634
7726
|
const method = String(request.method ?? request.type ?? "input");
|
|
@@ -7655,7 +7747,7 @@ async function answerExtensionUiRequest(options, ctx, request) {
|
|
|
7655
7747
|
ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
7656
7748
|
}
|
|
7657
7749
|
}
|
|
7658
|
-
var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
|
|
7750
|
+
var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "worker", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
|
|
7659
7751
|
function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
7660
7752
|
for (const command of commands) {
|
|
7661
7753
|
const record = recordOf2(command);
|
|
@@ -7683,6 +7775,21 @@ function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
|
7683
7775
|
function createRigWorkerPiBridgeExtension(options) {
|
|
7684
7776
|
return (pi) => {
|
|
7685
7777
|
const registeredDaemonCommands = new Set;
|
|
7778
|
+
let capabilityLines = null;
|
|
7779
|
+
let statusText = "connecting to worker session";
|
|
7780
|
+
let busy = true;
|
|
7781
|
+
let connected = false;
|
|
7782
|
+
let frame = 0;
|
|
7783
|
+
let spinnerTimer = null;
|
|
7784
|
+
const renderStatus = (ctx) => {
|
|
7785
|
+
const prefix = busy ? `${SPINNER_FRAMES[frame]} ` : connected ? "\u25CF " : "\u25CB ";
|
|
7786
|
+
ctx.ui.setStatus("rig-worker-pi", `${prefix}${statusText}`);
|
|
7787
|
+
};
|
|
7788
|
+
const setStatus = (ctx, text2, isBusy) => {
|
|
7789
|
+
statusText = text2;
|
|
7790
|
+
busy = isBusy;
|
|
7791
|
+
renderStatus(ctx);
|
|
7792
|
+
};
|
|
7686
7793
|
pi.registerCommand("detach", {
|
|
7687
7794
|
description: "Detach from this run; the worker keeps going",
|
|
7688
7795
|
handler: async (_args, ctx) => {
|
|
@@ -7698,31 +7805,57 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
7698
7805
|
ctx.shutdown();
|
|
7699
7806
|
}
|
|
7700
7807
|
});
|
|
7808
|
+
pi.registerCommand("worker", {
|
|
7809
|
+
description: "Show the worker session's real capabilities (tools, extensions, hooks, skills)",
|
|
7810
|
+
handler: async (_args, ctx) => {
|
|
7811
|
+
if (capabilityLines)
|
|
7812
|
+
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
7813
|
+
else
|
|
7814
|
+
ctx.ui.notify("Worker capabilities are not available yet (still connecting).", "info");
|
|
7815
|
+
}
|
|
7816
|
+
});
|
|
7701
7817
|
pi.on("user_bash", (event) => ({
|
|
7702
7818
|
operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
|
|
7703
7819
|
}));
|
|
7704
7820
|
pi.on("session_start", async (_event, ctx) => {
|
|
7705
|
-
ctx.ui.setTitle("Rig \xB7
|
|
7706
|
-
|
|
7707
|
-
|
|
7821
|
+
ctx.ui.setTitle("Rig \xB7 enriched bundled Pi");
|
|
7822
|
+
setStatus(ctx, "waiting for worker Pi daemon", true);
|
|
7823
|
+
spinnerTimer = setInterval(() => {
|
|
7824
|
+
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
7825
|
+
if (busy)
|
|
7826
|
+
renderStatus(ctx);
|
|
7827
|
+
}, 150);
|
|
7828
|
+
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");
|
|
7708
7829
|
if (options.initialMessageSent)
|
|
7709
7830
|
ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
|
|
7710
7831
|
const nativeUi = ctx.ui;
|
|
7711
7832
|
options.controller.setUiHooks({
|
|
7712
|
-
onStatusText: (text2) =>
|
|
7713
|
-
onActivity: (label, detail) =>
|
|
7714
|
-
|
|
7833
|
+
onStatusText: (text2) => setStatus(ctx, text2, !connected),
|
|
7834
|
+
onActivity: (label, detail) => {
|
|
7835
|
+
const active = label !== "idle" && !/complete|ready/i.test(label);
|
|
7836
|
+
setStatus(ctx, [label, detail].filter(Boolean).join(" \u2014 "), active);
|
|
7837
|
+
if (/agent running|tool:/.test(label))
|
|
7838
|
+
ctx.ui.setWidget("rig-worker-capabilities", undefined);
|
|
7839
|
+
},
|
|
7840
|
+
onConnectionChange: (nextConnected) => {
|
|
7841
|
+
connected = nextConnected;
|
|
7842
|
+
setStatus(ctx, nextConnected ? "worker session live" : "worker session disconnected", false);
|
|
7843
|
+
},
|
|
7715
7844
|
onError: (message2) => ctx.ui.notify(message2, "error"),
|
|
7716
7845
|
onExtensionUiRequest: (request) => {
|
|
7717
7846
|
answerExtensionUiRequest(options, ctx, request);
|
|
7718
7847
|
},
|
|
7719
|
-
onCatchUp: (messages, commands) => {
|
|
7848
|
+
onCatchUp: (messages, commands, capabilities) => {
|
|
7720
7849
|
if (nativeUi.appendSessionMessages)
|
|
7721
7850
|
nativeUi.appendSessionMessages(messages);
|
|
7722
7851
|
const cwd = options.controller.status.cwd;
|
|
7723
7852
|
if (nativeUi.setDisplayCwd && cwd)
|
|
7724
7853
|
nativeUi.setDisplayCwd(cwd);
|
|
7725
7854
|
registerDaemonCommands(pi, options, ctx, commands, registeredDaemonCommands);
|
|
7855
|
+
if (capabilities) {
|
|
7856
|
+
capabilityLines = renderWorkerCapabilities(capabilities);
|
|
7857
|
+
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
7858
|
+
}
|
|
7726
7859
|
}
|
|
7727
7860
|
});
|
|
7728
7861
|
options.controller.connect().catch((error) => {
|
|
@@ -7730,6 +7863,8 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
7730
7863
|
});
|
|
7731
7864
|
});
|
|
7732
7865
|
pi.on("session_shutdown", () => {
|
|
7866
|
+
if (spinnerTimer)
|
|
7867
|
+
clearInterval(spinnerTimer);
|
|
7733
7868
|
options.controller.close();
|
|
7734
7869
|
});
|
|
7735
7870
|
};
|
|
@@ -7771,6 +7906,9 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
7771
7906
|
initialMessageSent: input.steered === true
|
|
7772
7907
|
})
|
|
7773
7908
|
],
|
|
7909
|
+
noExtensions: true,
|
|
7910
|
+
noSkills: true,
|
|
7911
|
+
noPromptTemplates: true,
|
|
7774
7912
|
noContextFiles: true
|
|
7775
7913
|
}
|
|
7776
7914
|
});
|
|
@@ -7805,7 +7943,7 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
7805
7943
|
timelineCursor: null,
|
|
7806
7944
|
steered: input.steered === true,
|
|
7807
7945
|
detached,
|
|
7808
|
-
rendered: "
|
|
7946
|
+
rendered: "enriched bundled Pi frontend with remote worker session runtime"
|
|
7809
7947
|
};
|
|
7810
7948
|
}
|
|
7811
7949
|
|
|
@@ -8602,7 +8740,7 @@ var PRIMARY_GROUPS = [
|
|
|
8602
8740
|
{ command: "list", description: "List recent runs from the selected server or local state.", primary: true },
|
|
8603
8741
|
{ command: "status", description: "Render active and recent run groups.", primary: true },
|
|
8604
8742
|
{ command: "show <id>|--run <id> [--raw]", description: "Show a human run summary; --raw prints the full payload.", primary: true },
|
|
8605
|
-
{ command: "attach <run-id>|--run <id> [--follow]", description: "Attach to the run; --follow
|
|
8743
|
+
{ command: "attach <run-id>|--run <id> [--follow]", description: "Attach to the run; --follow opens the enriched bundled Pi (worker brain).", primary: true },
|
|
8606
8744
|
{ command: "stop [<run-id>|--run <id>]", description: "Request stop for one run or local active runs.", primary: true },
|
|
8607
8745
|
{ command: "steer <run-id> --message <text>", description: "Queue a steering message into a live worker without attaching." },
|
|
8608
8746
|
{ command: "timeline --run <id> [--follow]", description: "Stream raw run timeline events." },
|