@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.
@@ -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
- this.hooks.onCatchUp?.(messages, commands);
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(trimmed, behavior);
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.remote.sendPrompt(trimmed, "steer");
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.remote.sendPrompt(trimmed, "followUp");
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 worker session");
1486
- ctx.ui.setStatus("rig-worker-pi", "connecting to worker session");
1487
- ctx.ui.notify(`Attached to Rig run ${options.runId}. Native Pi, remote brain \u2014 /detach exits, /stop cancels the run.`, "info");
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) => ctx.ui.setStatus("rig-worker-pi", text),
1493
- onActivity: (label, detail) => ctx.ui.setStatus("rig-worker-pi", [label, detail].filter(Boolean).join(" \u2014 ")),
1494
- onConnectionChange: (connected) => ctx.ui.setStatus("rig-worker-pi", connected ? "worker session live" : "worker session disconnected"),
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: "native bundled Pi frontend with remote worker session runtime"
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 native bundled Pi frontend."
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 launches native bundled Pi for live Pi runs.", primary: true },
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." },
@@ -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 native bundled Pi frontend."
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
- this.hooks.onCatchUp?.(messages, commands);
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(trimmed, behavior);
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.remote.sendPrompt(trimmed, "steer");
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.remote.sendPrompt(trimmed, "followUp");
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 worker session");
7712
- ctx.ui.setStatus("rig-worker-pi", "connecting to worker session");
7713
- ctx.ui.notify(`Attached to Rig run ${options.runId}. Native Pi, remote brain \u2014 /detach exits, /stop cancels the run.`, "info");
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) => ctx.ui.setStatus("rig-worker-pi", text2),
7719
- onActivity: (label, detail) => ctx.ui.setStatus("rig-worker-pi", [label, detail].filter(Boolean).join(" \u2014 ")),
7720
- onConnectionChange: (connected) => ctx.ui.setStatus("rig-worker-pi", connected ? "worker session live" : "worker session disconnected"),
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: "native bundled Pi frontend with remote worker session runtime"
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 launches native bundled Pi for live Pi runs.", primary: true },
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." },