@h-rig/cli 0.0.6-alpha.42 → 0.0.6-alpha.44

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