@h-rig/cli 0.0.6-alpha.41 → 0.0.6-alpha.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/rig.js 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,15 +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
- await this.remote.sendPrompt(text2, "steer");
7726
+ const trimmed = text2.trim();
7727
+ if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
7728
+ return;
7729
+ const expanded = await this.expandLocalInput(trimmed);
7730
+ if (expanded === null)
7731
+ return;
7732
+ await this.remote.sendPrompt(expanded, "steer");
7694
7733
  }
7695
7734
  async followUp(text2) {
7696
- await this.remote.sendPrompt(text2, "followUp");
7735
+ const trimmed = text2.trim();
7736
+ if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
7737
+ return;
7738
+ const expanded = await this.expandLocalInput(trimmed);
7739
+ if (expanded === null)
7740
+ return;
7741
+ await this.remote.sendPrompt(expanded, "followUp");
7697
7742
  }
7698
7743
  async abort() {
7699
7744
  await this.remote.abort();
@@ -7806,6 +7851,9 @@ function createRemoteBashOperations(controller, excludeFromContext) {
7806
7851
  };
7807
7852
  }
7808
7853
 
7854
+ // packages/cli/src/commands/_spinner.ts
7855
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
7856
+
7809
7857
  // packages/cli/src/commands/_pi-worker-bridge-extension.ts
7810
7858
  function recordOf2(value) {
7811
7859
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -7823,6 +7871,50 @@ function asText(value) {
7823
7871
  return String(value);
7824
7872
  }
7825
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
+ }
7826
7918
  async function answerExtensionUiRequest(options, ctx, request) {
7827
7919
  const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
7828
7920
  const method = String(request.method ?? request.type ?? "input");
@@ -7849,7 +7941,7 @@ async function answerExtensionUiRequest(options, ctx, request) {
7849
7941
  ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
7850
7942
  }
7851
7943
  }
7852
- 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"]);
7853
7945
  function registerDaemonCommands(pi, options, ctx, commands, registered) {
7854
7946
  for (const command of commands) {
7855
7947
  const record = recordOf2(command);
@@ -7877,6 +7969,21 @@ function registerDaemonCommands(pi, options, ctx, commands, registered) {
7877
7969
  function createRigWorkerPiBridgeExtension(options) {
7878
7970
  return (pi) => {
7879
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
+ };
7880
7987
  pi.registerCommand("detach", {
7881
7988
  description: "Detach from this run; the worker keeps going",
7882
7989
  handler: async (_args, ctx) => {
@@ -7892,31 +7999,57 @@ function createRigWorkerPiBridgeExtension(options) {
7892
7999
  ctx.shutdown();
7893
8000
  }
7894
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
+ });
7895
8011
  pi.on("user_bash", (event) => ({
7896
8012
  operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
7897
8013
  }));
7898
8014
  pi.on("session_start", async (_event, ctx) => {
7899
- ctx.ui.setTitle("Rig \xB7 worker session");
7900
- ctx.ui.setStatus("rig-worker-pi", "connecting to worker session");
7901
- 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");
7902
8023
  if (options.initialMessageSent)
7903
8024
  ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
7904
8025
  const nativeUi = ctx.ui;
7905
8026
  options.controller.setUiHooks({
7906
- onStatusText: (text2) => ctx.ui.setStatus("rig-worker-pi", text2),
7907
- onActivity: (label, detail) => ctx.ui.setStatus("rig-worker-pi", [label, detail].filter(Boolean).join(" \u2014 ")),
7908
- 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
+ },
7909
8038
  onError: (message2) => ctx.ui.notify(message2, "error"),
7910
8039
  onExtensionUiRequest: (request) => {
7911
8040
  answerExtensionUiRequest(options, ctx, request);
7912
8041
  },
7913
- onCatchUp: (messages, commands) => {
8042
+ onCatchUp: (messages, commands, capabilities) => {
7914
8043
  if (nativeUi.appendSessionMessages)
7915
8044
  nativeUi.appendSessionMessages(messages);
7916
8045
  const cwd = options.controller.status.cwd;
7917
8046
  if (nativeUi.setDisplayCwd && cwd)
7918
8047
  nativeUi.setDisplayCwd(cwd);
7919
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
+ }
7920
8053
  }
7921
8054
  });
7922
8055
  options.controller.connect().catch((error) => {
@@ -7924,6 +8057,8 @@ function createRigWorkerPiBridgeExtension(options) {
7924
8057
  });
7925
8058
  });
7926
8059
  pi.on("session_shutdown", () => {
8060
+ if (spinnerTimer)
8061
+ clearInterval(spinnerTimer);
7927
8062
  options.controller.close();
7928
8063
  });
7929
8064
  };
@@ -7965,6 +8100,9 @@ async function attachRunBundledPiFrontend(context, input) {
7965
8100
  initialMessageSent: input.steered === true
7966
8101
  })
7967
8102
  ],
8103
+ noExtensions: true,
8104
+ noSkills: true,
8105
+ noPromptTemplates: true,
7968
8106
  noContextFiles: true
7969
8107
  }
7970
8108
  });
@@ -7999,7 +8137,7 @@ async function attachRunBundledPiFrontend(context, input) {
7999
8137
  timelineCursor: null,
8000
8138
  steered: input.steered === true,
8001
8139
  detached,
8002
- rendered: "native bundled Pi frontend with remote worker session runtime"
8140
+ rendered: "enriched bundled Pi frontend with remote worker session runtime"
8003
8141
  };
8004
8142
  }
8005
8143
 
@@ -8796,7 +8934,7 @@ var PRIMARY_GROUPS = [
8796
8934
  { command: "list", description: "List recent runs from the selected server or local state.", primary: true },
8797
8935
  { command: "status", description: "Render active and recent run groups.", primary: true },
8798
8936
  { command: "show <id>|--run <id> [--raw]", description: "Show a human run summary; --raw prints the full payload.", primary: true },
8799
- { command: "attach <run-id>|--run <id> [--follow]", description: "Attach to the run; --follow launches native bundled Pi for live Pi runs.", primary: true },
8937
+ { command: "attach <run-id>|--run <id> [--follow]", description: "Attach to the run; --follow opens the enriched bundled Pi (worker brain).", primary: true },
8800
8938
  { command: "stop [<run-id>|--run <id>]", description: "Request stop for one run or local active runs.", primary: true },
8801
8939
  { command: "steer <run-id> --message <text>", description: "Queue a steering message into a live worker without attaching." },
8802
8940
  { 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,15 +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
- await this.remote.sendPrompt(text, "steer");
825
+ const trimmed = text.trim();
826
+ if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
827
+ return;
828
+ const expanded = await this.expandLocalInput(trimmed);
829
+ if (expanded === null)
830
+ return;
831
+ await this.remote.sendPrompt(expanded, "steer");
793
832
  }
794
833
  async followUp(text) {
795
- await this.remote.sendPrompt(text, "followUp");
834
+ const trimmed = text.trim();
835
+ if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
836
+ return;
837
+ const expanded = await this.expandLocalInput(trimmed);
838
+ if (expanded === null)
839
+ return;
840
+ await this.remote.sendPrompt(expanded, "followUp");
796
841
  }
797
842
  async abort() {
798
843
  await this.remote.abort();
@@ -905,6 +950,9 @@ function createRemoteBashOperations(controller, excludeFromContext) {
905
950
  };
906
951
  }
907
952
 
953
+ // packages/cli/src/commands/_spinner.ts
954
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
955
+
908
956
  // packages/cli/src/commands/_pi-worker-bridge-extension.ts
909
957
  function recordOf2(value) {
910
958
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -922,6 +970,50 @@ function asText(value) {
922
970
  return String(value);
923
971
  }
924
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
+ }
925
1017
  async function answerExtensionUiRequest(options, ctx, request) {
926
1018
  const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
927
1019
  const method = String(request.method ?? request.type ?? "input");
@@ -948,7 +1040,7 @@ async function answerExtensionUiRequest(options, ctx, request) {
948
1040
  ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
949
1041
  }
950
1042
  }
951
- 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"]);
952
1044
  function registerDaemonCommands(pi, options, ctx, commands, registered) {
953
1045
  for (const command of commands) {
954
1046
  const record = recordOf2(command);
@@ -976,6 +1068,21 @@ function registerDaemonCommands(pi, options, ctx, commands, registered) {
976
1068
  function createRigWorkerPiBridgeExtension(options) {
977
1069
  return (pi) => {
978
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
+ };
979
1086
  pi.registerCommand("detach", {
980
1087
  description: "Detach from this run; the worker keeps going",
981
1088
  handler: async (_args, ctx) => {
@@ -991,31 +1098,57 @@ function createRigWorkerPiBridgeExtension(options) {
991
1098
  ctx.shutdown();
992
1099
  }
993
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
+ });
994
1110
  pi.on("user_bash", (event) => ({
995
1111
  operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
996
1112
  }));
997
1113
  pi.on("session_start", async (_event, ctx) => {
998
- ctx.ui.setTitle("Rig \xB7 worker session");
999
- ctx.ui.setStatus("rig-worker-pi", "connecting to worker session");
1000
- 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");
1001
1122
  if (options.initialMessageSent)
1002
1123
  ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
1003
1124
  const nativeUi = ctx.ui;
1004
1125
  options.controller.setUiHooks({
1005
- onStatusText: (text) => ctx.ui.setStatus("rig-worker-pi", text),
1006
- onActivity: (label, detail) => ctx.ui.setStatus("rig-worker-pi", [label, detail].filter(Boolean).join(" \u2014 ")),
1007
- 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
+ },
1008
1137
  onError: (message) => ctx.ui.notify(message, "error"),
1009
1138
  onExtensionUiRequest: (request) => {
1010
1139
  answerExtensionUiRequest(options, ctx, request);
1011
1140
  },
1012
- onCatchUp: (messages, commands) => {
1141
+ onCatchUp: (messages, commands, capabilities) => {
1013
1142
  if (nativeUi.appendSessionMessages)
1014
1143
  nativeUi.appendSessionMessages(messages);
1015
1144
  const cwd = options.controller.status.cwd;
1016
1145
  if (nativeUi.setDisplayCwd && cwd)
1017
1146
  nativeUi.setDisplayCwd(cwd);
1018
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
+ }
1019
1152
  }
1020
1153
  });
1021
1154
  options.controller.connect().catch((error) => {
@@ -1023,6 +1156,8 @@ function createRigWorkerPiBridgeExtension(options) {
1023
1156
  });
1024
1157
  });
1025
1158
  pi.on("session_shutdown", () => {
1159
+ if (spinnerTimer)
1160
+ clearInterval(spinnerTimer);
1026
1161
  options.controller.close();
1027
1162
  });
1028
1163
  };
@@ -1064,6 +1199,9 @@ async function attachRunBundledPiFrontend(context, input) {
1064
1199
  initialMessageSent: input.steered === true
1065
1200
  })
1066
1201
  ],
1202
+ noExtensions: true,
1203
+ noSkills: true,
1204
+ noPromptTemplates: true,
1067
1205
  noContextFiles: true
1068
1206
  }
1069
1207
  });
@@ -1098,7 +1236,7 @@ async function attachRunBundledPiFrontend(context, input) {
1098
1236
  timelineCursor: null,
1099
1237
  steered: input.steered === true,
1100
1238
  detached,
1101
- rendered: "native bundled Pi frontend with remote worker session runtime"
1239
+ rendered: "enriched bundled Pi frontend with remote worker session runtime"
1102
1240
  };
1103
1241
  }
1104
1242