@h-rig/cli 0.0.6-alpha.42 → 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 +150 -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 +148 -16
- package/dist/src/commands/_pi-frontend.js +148 -16
- 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 +148 -16
- package/dist/src/commands/task.js +150 -18
- package/dist/src/commands.js +150 -18
- package/dist/src/index.js +150 -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,21 +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();
|
|
1274
1307
|
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
1275
1308
|
return;
|
|
1276
|
-
await this.
|
|
1309
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
1310
|
+
if (expanded === null)
|
|
1311
|
+
return;
|
|
1312
|
+
await this.remote.sendPrompt(expanded, "steer");
|
|
1277
1313
|
}
|
|
1278
1314
|
async followUp(text) {
|
|
1279
1315
|
const trimmed = text.trim();
|
|
1280
1316
|
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
1281
1317
|
return;
|
|
1282
|
-
await this.
|
|
1318
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
1319
|
+
if (expanded === null)
|
|
1320
|
+
return;
|
|
1321
|
+
await this.remote.sendPrompt(expanded, "followUp");
|
|
1283
1322
|
}
|
|
1284
1323
|
async abort() {
|
|
1285
1324
|
await this.remote.abort();
|
|
@@ -1392,6 +1431,9 @@ function createRemoteBashOperations(controller, excludeFromContext) {
|
|
|
1392
1431
|
};
|
|
1393
1432
|
}
|
|
1394
1433
|
|
|
1434
|
+
// packages/cli/src/commands/_spinner.ts
|
|
1435
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1436
|
+
|
|
1395
1437
|
// packages/cli/src/commands/_pi-worker-bridge-extension.ts
|
|
1396
1438
|
function recordOf2(value) {
|
|
1397
1439
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -1409,6 +1451,50 @@ function asText(value) {
|
|
|
1409
1451
|
return String(value);
|
|
1410
1452
|
}
|
|
1411
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
|
+
}
|
|
1412
1498
|
async function answerExtensionUiRequest(options, ctx, request) {
|
|
1413
1499
|
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
1414
1500
|
const method = String(request.method ?? request.type ?? "input");
|
|
@@ -1435,7 +1521,7 @@ async function answerExtensionUiRequest(options, ctx, request) {
|
|
|
1435
1521
|
ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1436
1522
|
}
|
|
1437
1523
|
}
|
|
1438
|
-
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"]);
|
|
1439
1525
|
function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
1440
1526
|
for (const command of commands) {
|
|
1441
1527
|
const record = recordOf2(command);
|
|
@@ -1463,6 +1549,21 @@ function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
|
1463
1549
|
function createRigWorkerPiBridgeExtension(options) {
|
|
1464
1550
|
return (pi) => {
|
|
1465
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
|
+
};
|
|
1466
1567
|
pi.registerCommand("detach", {
|
|
1467
1568
|
description: "Detach from this run; the worker keeps going",
|
|
1468
1569
|
handler: async (_args, ctx) => {
|
|
@@ -1478,31 +1579,57 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
1478
1579
|
ctx.shutdown();
|
|
1479
1580
|
}
|
|
1480
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
|
+
});
|
|
1481
1591
|
pi.on("user_bash", (event) => ({
|
|
1482
1592
|
operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
|
|
1483
1593
|
}));
|
|
1484
1594
|
pi.on("session_start", async (_event, ctx) => {
|
|
1485
|
-
ctx.ui.setTitle("Rig \xB7
|
|
1486
|
-
|
|
1487
|
-
|
|
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");
|
|
1488
1603
|
if (options.initialMessageSent)
|
|
1489
1604
|
ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
|
|
1490
1605
|
const nativeUi = ctx.ui;
|
|
1491
1606
|
options.controller.setUiHooks({
|
|
1492
|
-
onStatusText: (text) =>
|
|
1493
|
-
onActivity: (label, detail) =>
|
|
1494
|
-
|
|
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
|
+
},
|
|
1495
1618
|
onError: (message2) => ctx.ui.notify(message2, "error"),
|
|
1496
1619
|
onExtensionUiRequest: (request) => {
|
|
1497
1620
|
answerExtensionUiRequest(options, ctx, request);
|
|
1498
1621
|
},
|
|
1499
|
-
onCatchUp: (messages, commands) => {
|
|
1622
|
+
onCatchUp: (messages, commands, capabilities) => {
|
|
1500
1623
|
if (nativeUi.appendSessionMessages)
|
|
1501
1624
|
nativeUi.appendSessionMessages(messages);
|
|
1502
1625
|
const cwd = options.controller.status.cwd;
|
|
1503
1626
|
if (nativeUi.setDisplayCwd && cwd)
|
|
1504
1627
|
nativeUi.setDisplayCwd(cwd);
|
|
1505
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
|
+
}
|
|
1506
1633
|
}
|
|
1507
1634
|
});
|
|
1508
1635
|
options.controller.connect().catch((error) => {
|
|
@@ -1510,6 +1637,8 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
1510
1637
|
});
|
|
1511
1638
|
});
|
|
1512
1639
|
pi.on("session_shutdown", () => {
|
|
1640
|
+
if (spinnerTimer)
|
|
1641
|
+
clearInterval(spinnerTimer);
|
|
1513
1642
|
options.controller.close();
|
|
1514
1643
|
});
|
|
1515
1644
|
};
|
|
@@ -1551,6 +1680,9 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
1551
1680
|
initialMessageSent: input.steered === true
|
|
1552
1681
|
})
|
|
1553
1682
|
],
|
|
1683
|
+
noExtensions: true,
|
|
1684
|
+
noSkills: true,
|
|
1685
|
+
noPromptTemplates: true,
|
|
1554
1686
|
noContextFiles: true
|
|
1555
1687
|
}
|
|
1556
1688
|
});
|
|
@@ -1585,7 +1717,7 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
1585
1717
|
timelineCursor: null,
|
|
1586
1718
|
steered: input.steered === true,
|
|
1587
1719
|
detached,
|
|
1588
|
-
rendered: "
|
|
1720
|
+
rendered: "enriched bundled Pi frontend with remote worker session runtime"
|
|
1589
1721
|
};
|
|
1590
1722
|
}
|
|
1591
1723
|
|
|
@@ -1843,7 +1975,7 @@ function formatSubmittedRun(input) {
|
|
|
1843
1975
|
...formatNextSteps([
|
|
1844
1976
|
`Attach: \`rig run attach ${input.runId} --follow\``,
|
|
1845
1977
|
`Inspect: \`rig run show ${input.runId}\``,
|
|
1846
|
-
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)."
|
|
1847
1979
|
])
|
|
1848
1980
|
].join(`
|
|
1849
1981
|
`);
|
|
@@ -1958,7 +2090,7 @@ var PRIMARY_GROUPS = [
|
|
|
1958
2090
|
{ command: "list", description: "List recent runs from the selected server or local state.", primary: true },
|
|
1959
2091
|
{ command: "status", description: "Render active and recent run groups.", primary: true },
|
|
1960
2092
|
{ command: "show <id>|--run <id> [--raw]", description: "Show a human run summary; --raw prints the full payload.", primary: true },
|
|
1961
|
-
{ 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 },
|
|
1962
2094
|
{ command: "stop [<run-id>|--run <id>]", description: "Request stop for one run or local active runs.", primary: true },
|
|
1963
2095
|
{ command: "steer <run-id> --message <text>", description: "Queue a steering message into a live worker without attaching." },
|
|
1964
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,21 +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();
|
|
7500
7533
|
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
7501
7534
|
return;
|
|
7502
|
-
await this.
|
|
7535
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
7536
|
+
if (expanded === null)
|
|
7537
|
+
return;
|
|
7538
|
+
await this.remote.sendPrompt(expanded, "steer");
|
|
7503
7539
|
}
|
|
7504
7540
|
async followUp(text2) {
|
|
7505
7541
|
const trimmed = text2.trim();
|
|
7506
7542
|
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
7507
7543
|
return;
|
|
7508
|
-
await this.
|
|
7544
|
+
const expanded = await this.expandLocalInput(trimmed);
|
|
7545
|
+
if (expanded === null)
|
|
7546
|
+
return;
|
|
7547
|
+
await this.remote.sendPrompt(expanded, "followUp");
|
|
7509
7548
|
}
|
|
7510
7549
|
async abort() {
|
|
7511
7550
|
await this.remote.abort();
|
|
@@ -7618,6 +7657,9 @@ function createRemoteBashOperations(controller, excludeFromContext) {
|
|
|
7618
7657
|
};
|
|
7619
7658
|
}
|
|
7620
7659
|
|
|
7660
|
+
// packages/cli/src/commands/_spinner.ts
|
|
7661
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
7662
|
+
|
|
7621
7663
|
// packages/cli/src/commands/_pi-worker-bridge-extension.ts
|
|
7622
7664
|
function recordOf2(value) {
|
|
7623
7665
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -7635,6 +7677,50 @@ function asText(value) {
|
|
|
7635
7677
|
return String(value);
|
|
7636
7678
|
}
|
|
7637
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
|
+
}
|
|
7638
7724
|
async function answerExtensionUiRequest(options, ctx, request) {
|
|
7639
7725
|
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
7640
7726
|
const method = String(request.method ?? request.type ?? "input");
|
|
@@ -7661,7 +7747,7 @@ async function answerExtensionUiRequest(options, ctx, request) {
|
|
|
7661
7747
|
ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
7662
7748
|
}
|
|
7663
7749
|
}
|
|
7664
|
-
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"]);
|
|
7665
7751
|
function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
7666
7752
|
for (const command of commands) {
|
|
7667
7753
|
const record = recordOf2(command);
|
|
@@ -7689,6 +7775,21 @@ function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
|
7689
7775
|
function createRigWorkerPiBridgeExtension(options) {
|
|
7690
7776
|
return (pi) => {
|
|
7691
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
|
+
};
|
|
7692
7793
|
pi.registerCommand("detach", {
|
|
7693
7794
|
description: "Detach from this run; the worker keeps going",
|
|
7694
7795
|
handler: async (_args, ctx) => {
|
|
@@ -7704,31 +7805,57 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
7704
7805
|
ctx.shutdown();
|
|
7705
7806
|
}
|
|
7706
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
|
+
});
|
|
7707
7817
|
pi.on("user_bash", (event) => ({
|
|
7708
7818
|
operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
|
|
7709
7819
|
}));
|
|
7710
7820
|
pi.on("session_start", async (_event, ctx) => {
|
|
7711
|
-
ctx.ui.setTitle("Rig \xB7
|
|
7712
|
-
|
|
7713
|
-
|
|
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");
|
|
7714
7829
|
if (options.initialMessageSent)
|
|
7715
7830
|
ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
|
|
7716
7831
|
const nativeUi = ctx.ui;
|
|
7717
7832
|
options.controller.setUiHooks({
|
|
7718
|
-
onStatusText: (text2) =>
|
|
7719
|
-
onActivity: (label, detail) =>
|
|
7720
|
-
|
|
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
|
+
},
|
|
7721
7844
|
onError: (message2) => ctx.ui.notify(message2, "error"),
|
|
7722
7845
|
onExtensionUiRequest: (request) => {
|
|
7723
7846
|
answerExtensionUiRequest(options, ctx, request);
|
|
7724
7847
|
},
|
|
7725
|
-
onCatchUp: (messages, commands) => {
|
|
7848
|
+
onCatchUp: (messages, commands, capabilities) => {
|
|
7726
7849
|
if (nativeUi.appendSessionMessages)
|
|
7727
7850
|
nativeUi.appendSessionMessages(messages);
|
|
7728
7851
|
const cwd = options.controller.status.cwd;
|
|
7729
7852
|
if (nativeUi.setDisplayCwd && cwd)
|
|
7730
7853
|
nativeUi.setDisplayCwd(cwd);
|
|
7731
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
|
+
}
|
|
7732
7859
|
}
|
|
7733
7860
|
});
|
|
7734
7861
|
options.controller.connect().catch((error) => {
|
|
@@ -7736,6 +7863,8 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
7736
7863
|
});
|
|
7737
7864
|
});
|
|
7738
7865
|
pi.on("session_shutdown", () => {
|
|
7866
|
+
if (spinnerTimer)
|
|
7867
|
+
clearInterval(spinnerTimer);
|
|
7739
7868
|
options.controller.close();
|
|
7740
7869
|
});
|
|
7741
7870
|
};
|
|
@@ -7777,6 +7906,9 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
7777
7906
|
initialMessageSent: input.steered === true
|
|
7778
7907
|
})
|
|
7779
7908
|
],
|
|
7909
|
+
noExtensions: true,
|
|
7910
|
+
noSkills: true,
|
|
7911
|
+
noPromptTemplates: true,
|
|
7780
7912
|
noContextFiles: true
|
|
7781
7913
|
}
|
|
7782
7914
|
});
|
|
@@ -7811,7 +7943,7 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
7811
7943
|
timelineCursor: null,
|
|
7812
7944
|
steered: input.steered === true,
|
|
7813
7945
|
detached,
|
|
7814
|
-
rendered: "
|
|
7946
|
+
rendered: "enriched bundled Pi frontend with remote worker session runtime"
|
|
7815
7947
|
};
|
|
7816
7948
|
}
|
|
7817
7949
|
|
|
@@ -8608,7 +8740,7 @@ var PRIMARY_GROUPS = [
|
|
|
8608
8740
|
{ command: "list", description: "List recent runs from the selected server or local state.", primary: true },
|
|
8609
8741
|
{ command: "status", description: "Render active and recent run groups.", primary: true },
|
|
8610
8742
|
{ command: "show <id>|--run <id> [--raw]", description: "Show a human run summary; --raw prints the full payload.", primary: true },
|
|
8611
|
-
{ 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 },
|
|
8612
8744
|
{ command: "stop [<run-id>|--run <id>]", description: "Request stop for one run or local active runs.", primary: true },
|
|
8613
8745
|
{ command: "steer <run-id> --message <text>", description: "Queue a steering message into a live worker without attaching." },
|
|
8614
8746
|
{ command: "timeline --run <id> [--follow]", description: "Stream raw run timeline events." },
|