@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.
@@ -217,6 +217,10 @@ async function getRunPiCommandsViaServer(context, runId) {
217
217
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
218
218
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
219
219
  }
220
+ async function getRunPiCapabilitiesViaServer(context, runId) {
221
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
222
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
223
+ }
220
224
  async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
221
225
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
222
226
  method: "POST",
@@ -263,7 +267,7 @@ async function buildRunPiEventsWebSocketUrl(context, runId) {
263
267
  }
264
268
 
265
269
  // packages/cli/src/commands/_pi-remote-session.ts
266
- import { AgentSession as PiAgentSession } from "@earendil-works/pi-coding-agent";
270
+ import { AgentSession as PiAgentSession, expandPromptTemplate } from "@earendil-works/pi-coding-agent";
267
271
  var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
268
272
  function defaultTransport() {
269
273
  return {
@@ -271,6 +275,7 @@ function defaultTransport() {
271
275
  getMessages: getRunPiMessagesViaServer,
272
276
  getStatus: getRunPiStatusViaServer,
273
277
  getCommands: getRunPiCommandsViaServer,
278
+ getCapabilities: getRunPiCapabilitiesViaServer,
274
279
  sendPrompt: sendRunPiPromptViaServer,
275
280
  sendShell: sendRunPiShellViaServer,
276
281
  runCommand: runRunPiCommandViaServer,
@@ -358,16 +363,18 @@ class RigRemoteSessionController {
358
363
  this.hooks.onStatusText?.("worker Pi WebSocket disconnected");
359
364
  };
360
365
  try {
361
- const [messagesPayload, statusPayload, commandsPayload] = await Promise.all([
366
+ const [messagesPayload, statusPayload, commandsPayload, capabilitiesPayload] = await Promise.all([
362
367
  this.transport.getMessages(this.context, this.runId),
363
368
  this.transport.getStatus(this.context, this.runId),
364
- this.transport.getCommands(this.context, this.runId)
369
+ this.transport.getCommands(this.context, this.runId),
370
+ this.transport.getCapabilities(this.context, this.runId).catch(() => ({ capabilities: null }))
365
371
  ]);
366
372
  const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
367
373
  this.applyStatusPayload(statusPayload);
368
374
  this.session?.replaceRemoteMessages(messages);
369
375
  const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
370
- this.hooks.onCatchUp?.(messages, commands);
376
+ const capabilities = capabilitiesPayload.capabilities && typeof capabilitiesPayload.capabilities === "object" && !Array.isArray(capabilitiesPayload.capabilities) ? capabilitiesPayload.capabilities : null;
377
+ this.hooks.onCatchUp?.(messages, commands, capabilities);
371
378
  catchupDone = true;
372
379
  for (const payload of buffered.splice(0))
373
380
  this.applyEnvelope(payload);
@@ -544,6 +551,20 @@ class RigRemoteAgentSession extends PiAgentSession {
544
551
  replaceRemoteMessages(messages) {
545
552
  this.agent.state.messages = messages;
546
553
  }
554
+ async expandLocalInput(text) {
555
+ let current = text;
556
+ const runner = this.extensionRunner;
557
+ if (runner.hasHandlers("input")) {
558
+ const result = await runner.emitInput(current, undefined, "interactive");
559
+ if (result.action === "handled")
560
+ return null;
561
+ if (result.action === "transform")
562
+ current = result.text;
563
+ }
564
+ current = this._expandSkillCommand(current);
565
+ current = expandPromptTemplate(current, [...this.promptTemplates]);
566
+ return current;
567
+ }
547
568
  async prompt(text, options) {
548
569
  const trimmed = text.trim();
549
570
  if (!trimmed)
@@ -551,6 +572,15 @@ class RigRemoteAgentSession extends PiAgentSession {
551
572
  if (trimmed.startsWith("/")) {
552
573
  if (await this._tryExecuteExtensionCommand(trimmed))
553
574
  return;
575
+ const expanded2 = await this.expandLocalInput(trimmed);
576
+ if (expanded2 === null)
577
+ return;
578
+ if (expanded2 !== trimmed) {
579
+ const behavior2 = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
580
+ options?.preflightResult?.(true);
581
+ await this.remote.sendPrompt(expanded2, behavior2);
582
+ return;
583
+ }
554
584
  await this.remote.sendCommand(trimmed);
555
585
  return;
556
586
  }
@@ -558,21 +588,30 @@ class RigRemoteAgentSession extends PiAgentSession {
558
588
  await this.remote.sendShell(trimmed);
559
589
  return;
560
590
  }
591
+ const expanded = await this.expandLocalInput(trimmed);
592
+ if (expanded === null)
593
+ return;
561
594
  const behavior = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
562
595
  options?.preflightResult?.(true);
563
- await this.remote.sendPrompt(trimmed, behavior);
596
+ await this.remote.sendPrompt(expanded, behavior);
564
597
  }
565
598
  async steer(text) {
566
599
  const trimmed = text.trim();
567
600
  if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
568
601
  return;
569
- await this.remote.sendPrompt(trimmed, "steer");
602
+ const expanded = await this.expandLocalInput(trimmed);
603
+ if (expanded === null)
604
+ return;
605
+ await this.remote.sendPrompt(expanded, "steer");
570
606
  }
571
607
  async followUp(text) {
572
608
  const trimmed = text.trim();
573
609
  if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
574
610
  return;
575
- await this.remote.sendPrompt(trimmed, "followUp");
611
+ const expanded = await this.expandLocalInput(trimmed);
612
+ if (expanded === null)
613
+ return;
614
+ await this.remote.sendPrompt(expanded, "followUp");
576
615
  }
577
616
  async abort() {
578
617
  await this.remote.abort();
@@ -685,6 +724,9 @@ function createRemoteBashOperations(controller, excludeFromContext) {
685
724
  };
686
725
  }
687
726
 
727
+ // packages/cli/src/commands/_spinner.ts
728
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
729
+
688
730
  // packages/cli/src/commands/_pi-worker-bridge-extension.ts
689
731
  function recordOf2(value) {
690
732
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -702,6 +744,50 @@ function asText(value) {
702
744
  return String(value);
703
745
  }
704
746
  }
747
+ function names(value, key = "name") {
748
+ if (!Array.isArray(value))
749
+ return [];
750
+ return value.flatMap((entry) => {
751
+ if (typeof entry === "string")
752
+ return [entry];
753
+ const record = recordOf2(entry);
754
+ const name = record?.[key];
755
+ return typeof name === "string" ? [name] : [];
756
+ });
757
+ }
758
+ function renderWorkerCapabilities(capabilities) {
759
+ const lines = ["Worker session capabilities (in effect for this run)"];
760
+ const tools = Array.isArray(capabilities.tools) ? capabilities.tools : [];
761
+ const activeTools = tools.flatMap((tool) => {
762
+ const record = recordOf2(tool);
763
+ return record && record.active === true && typeof record.name === "string" ? [record.name] : [];
764
+ });
765
+ const inactiveCount = tools.length - activeTools.length;
766
+ lines.push(` tools ${activeTools.join(", ") || "(none)"}${inactiveCount > 0 ? ` (+${inactiveCount} inactive)` : ""}`);
767
+ const extensions = Array.isArray(capabilities.extensions) ? capabilities.extensions : [];
768
+ const extensionLabels = extensions.flatMap((entry) => {
769
+ const record = recordOf2(entry);
770
+ if (!record)
771
+ return [];
772
+ const path = typeof record.path === "string" ? record.path : "";
773
+ const short = path.split("/").filter(Boolean).slice(-2).join("/") || path || "extension";
774
+ return [short];
775
+ });
776
+ lines.push(` extensions ${extensionLabels.join(", ") || "(none)"}`);
777
+ const hookEvents = names(capabilities.hookEvents);
778
+ const hookList = Array.isArray(capabilities.hookEvents) ? capabilities.hookEvents.map(asText).filter(Boolean) : hookEvents;
779
+ lines.push(` hooks ${hookList.join(", ") || "(none)"}`);
780
+ lines.push(` skills ${names(capabilities.skills).join(", ") || "(none)"}`);
781
+ lines.push(` prompts ${names(capabilities.prompts).join(", ") || "(none)"}`);
782
+ const model = typeof capabilities.model === "string" ? capabilities.model : "(unknown)";
783
+ const thinking = typeof capabilities.thinkingLevel === "string" ? capabilities.thinkingLevel : "";
784
+ const cwd = typeof capabilities.cwd === "string" ? capabilities.cwd : "";
785
+ lines.push(` model ${model}${thinking ? ` \xB7 ${thinking}` : ""}`);
786
+ if (cwd)
787
+ lines.push(` cwd ${cwd}`);
788
+ lines.push(" (worker commands are in your palette as [worker \u2026] \xB7 /worker shows this again)");
789
+ return lines;
790
+ }
705
791
  async function answerExtensionUiRequest(options, ctx, request) {
706
792
  const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
707
793
  const method = String(request.method ?? request.type ?? "input");
@@ -728,7 +814,7 @@ async function answerExtensionUiRequest(options, ctx, request) {
728
814
  ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
729
815
  }
730
816
  }
731
- var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
817
+ var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "worker", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
732
818
  function registerDaemonCommands(pi, options, ctx, commands, registered) {
733
819
  for (const command of commands) {
734
820
  const record = recordOf2(command);
@@ -756,6 +842,21 @@ function registerDaemonCommands(pi, options, ctx, commands, registered) {
756
842
  function createRigWorkerPiBridgeExtension(options) {
757
843
  return (pi) => {
758
844
  const registeredDaemonCommands = new Set;
845
+ let capabilityLines = null;
846
+ let statusText = "connecting to worker session";
847
+ let busy = true;
848
+ let connected = false;
849
+ let frame = 0;
850
+ let spinnerTimer = null;
851
+ const renderStatus = (ctx) => {
852
+ const prefix = busy ? `${SPINNER_FRAMES[frame]} ` : connected ? "\u25CF " : "\u25CB ";
853
+ ctx.ui.setStatus("rig-worker-pi", `${prefix}${statusText}`);
854
+ };
855
+ const setStatus = (ctx, text, isBusy) => {
856
+ statusText = text;
857
+ busy = isBusy;
858
+ renderStatus(ctx);
859
+ };
759
860
  pi.registerCommand("detach", {
760
861
  description: "Detach from this run; the worker keeps going",
761
862
  handler: async (_args, ctx) => {
@@ -771,31 +872,57 @@ function createRigWorkerPiBridgeExtension(options) {
771
872
  ctx.shutdown();
772
873
  }
773
874
  });
875
+ pi.registerCommand("worker", {
876
+ description: "Show the worker session's real capabilities (tools, extensions, hooks, skills)",
877
+ handler: async (_args, ctx) => {
878
+ if (capabilityLines)
879
+ ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
880
+ else
881
+ ctx.ui.notify("Worker capabilities are not available yet (still connecting).", "info");
882
+ }
883
+ });
774
884
  pi.on("user_bash", (event) => ({
775
885
  operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
776
886
  }));
777
887
  pi.on("session_start", async (_event, ctx) => {
778
- ctx.ui.setTitle("Rig \xB7 worker session");
779
- ctx.ui.setStatus("rig-worker-pi", "connecting to worker session");
780
- ctx.ui.notify(`Attached to Rig run ${options.runId}. Native Pi, remote brain \u2014 /detach exits, /stop cancels the run.`, "info");
888
+ ctx.ui.setTitle("Rig \xB7 enriched bundled Pi");
889
+ setStatus(ctx, "waiting for worker Pi daemon", true);
890
+ spinnerTimer = setInterval(() => {
891
+ frame = (frame + 1) % SPINNER_FRAMES.length;
892
+ if (busy)
893
+ renderStatus(ctx);
894
+ }, 150);
895
+ 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");
781
896
  if (options.initialMessageSent)
782
897
  ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
783
898
  const nativeUi = ctx.ui;
784
899
  options.controller.setUiHooks({
785
- onStatusText: (text) => ctx.ui.setStatus("rig-worker-pi", text),
786
- onActivity: (label, detail) => ctx.ui.setStatus("rig-worker-pi", [label, detail].filter(Boolean).join(" \u2014 ")),
787
- onConnectionChange: (connected) => ctx.ui.setStatus("rig-worker-pi", connected ? "worker session live" : "worker session disconnected"),
900
+ onStatusText: (text) => setStatus(ctx, text, !connected),
901
+ onActivity: (label, detail) => {
902
+ const active = label !== "idle" && !/complete|ready/i.test(label);
903
+ setStatus(ctx, [label, detail].filter(Boolean).join(" \u2014 "), active);
904
+ if (/agent running|tool:/.test(label))
905
+ ctx.ui.setWidget("rig-worker-capabilities", undefined);
906
+ },
907
+ onConnectionChange: (nextConnected) => {
908
+ connected = nextConnected;
909
+ setStatus(ctx, nextConnected ? "worker session live" : "worker session disconnected", false);
910
+ },
788
911
  onError: (message) => ctx.ui.notify(message, "error"),
789
912
  onExtensionUiRequest: (request) => {
790
913
  answerExtensionUiRequest(options, ctx, request);
791
914
  },
792
- onCatchUp: (messages, commands) => {
915
+ onCatchUp: (messages, commands, capabilities) => {
793
916
  if (nativeUi.appendSessionMessages)
794
917
  nativeUi.appendSessionMessages(messages);
795
918
  const cwd = options.controller.status.cwd;
796
919
  if (nativeUi.setDisplayCwd && cwd)
797
920
  nativeUi.setDisplayCwd(cwd);
798
921
  registerDaemonCommands(pi, options, ctx, commands, registeredDaemonCommands);
922
+ if (capabilities) {
923
+ capabilityLines = renderWorkerCapabilities(capabilities);
924
+ ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
925
+ }
799
926
  }
800
927
  });
801
928
  options.controller.connect().catch((error) => {
@@ -803,6 +930,8 @@ function createRigWorkerPiBridgeExtension(options) {
803
930
  });
804
931
  });
805
932
  pi.on("session_shutdown", () => {
933
+ if (spinnerTimer)
934
+ clearInterval(spinnerTimer);
806
935
  options.controller.close();
807
936
  });
808
937
  };
@@ -844,6 +973,9 @@ async function attachRunBundledPiFrontend(context, input) {
844
973
  initialMessageSent: input.steered === true
845
974
  })
846
975
  ],
976
+ noExtensions: true,
977
+ noSkills: true,
978
+ noPromptTemplates: true,
847
979
  noContextFiles: true
848
980
  }
849
981
  });
@@ -878,7 +1010,7 @@ async function attachRunBundledPiFrontend(context, input) {
878
1010
  timelineCursor: null,
879
1011
  steered: input.steered === true,
880
1012
  detached,
881
- rendered: "native bundled Pi frontend with remote worker session runtime"
1013
+ rendered: "enriched bundled Pi frontend with remote worker session runtime"
882
1014
  };
883
1015
  }
884
1016
  export {
@@ -1,6 +1,6 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/_pi-remote-session.ts
3
- import { AgentSession as PiAgentSession } from "@earendil-works/pi-coding-agent";
3
+ import { AgentSession as PiAgentSession, expandPromptTemplate } from "@earendil-works/pi-coding-agent";
4
4
 
5
5
  // packages/cli/src/commands/_server-client.ts
6
6
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
@@ -206,6 +206,10 @@ async function getRunPiCommandsViaServer(context, runId) {
206
206
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
207
207
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
208
208
  }
209
+ async function getRunPiCapabilitiesViaServer(context, runId) {
210
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
211
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
212
+ }
209
213
  async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
210
214
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
211
215
  method: "POST",
@@ -251,6 +255,7 @@ function defaultTransport() {
251
255
  getMessages: getRunPiMessagesViaServer,
252
256
  getStatus: getRunPiStatusViaServer,
253
257
  getCommands: getRunPiCommandsViaServer,
258
+ getCapabilities: getRunPiCapabilitiesViaServer,
254
259
  sendPrompt: sendRunPiPromptViaServer,
255
260
  sendShell: sendRunPiShellViaServer,
256
261
  runCommand: runRunPiCommandViaServer,
@@ -338,16 +343,18 @@ class RigRemoteSessionController {
338
343
  this.hooks.onStatusText?.("worker Pi WebSocket disconnected");
339
344
  };
340
345
  try {
341
- const [messagesPayload, statusPayload, commandsPayload] = await Promise.all([
346
+ const [messagesPayload, statusPayload, commandsPayload, capabilitiesPayload] = await Promise.all([
342
347
  this.transport.getMessages(this.context, this.runId),
343
348
  this.transport.getStatus(this.context, this.runId),
344
- this.transport.getCommands(this.context, this.runId)
349
+ this.transport.getCommands(this.context, this.runId),
350
+ this.transport.getCapabilities(this.context, this.runId).catch(() => ({ capabilities: null }))
345
351
  ]);
346
352
  const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
347
353
  this.applyStatusPayload(statusPayload);
348
354
  this.session?.replaceRemoteMessages(messages);
349
355
  const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
350
- this.hooks.onCatchUp?.(messages, commands);
356
+ const capabilities = capabilitiesPayload.capabilities && typeof capabilitiesPayload.capabilities === "object" && !Array.isArray(capabilitiesPayload.capabilities) ? capabilitiesPayload.capabilities : null;
357
+ this.hooks.onCatchUp?.(messages, commands, capabilities);
351
358
  catchupDone = true;
352
359
  for (const payload of buffered.splice(0))
353
360
  this.applyEnvelope(payload);
@@ -524,6 +531,20 @@ class RigRemoteAgentSession extends PiAgentSession {
524
531
  replaceRemoteMessages(messages) {
525
532
  this.agent.state.messages = messages;
526
533
  }
534
+ async expandLocalInput(text) {
535
+ let current = text;
536
+ const runner = this.extensionRunner;
537
+ if (runner.hasHandlers("input")) {
538
+ const result = await runner.emitInput(current, undefined, "interactive");
539
+ if (result.action === "handled")
540
+ return null;
541
+ if (result.action === "transform")
542
+ current = result.text;
543
+ }
544
+ current = this._expandSkillCommand(current);
545
+ current = expandPromptTemplate(current, [...this.promptTemplates]);
546
+ return current;
547
+ }
527
548
  async prompt(text, options) {
528
549
  const trimmed = text.trim();
529
550
  if (!trimmed)
@@ -531,6 +552,15 @@ class RigRemoteAgentSession extends PiAgentSession {
531
552
  if (trimmed.startsWith("/")) {
532
553
  if (await this._tryExecuteExtensionCommand(trimmed))
533
554
  return;
555
+ const expanded2 = await this.expandLocalInput(trimmed);
556
+ if (expanded2 === null)
557
+ return;
558
+ if (expanded2 !== trimmed) {
559
+ const behavior2 = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
560
+ options?.preflightResult?.(true);
561
+ await this.remote.sendPrompt(expanded2, behavior2);
562
+ return;
563
+ }
534
564
  await this.remote.sendCommand(trimmed);
535
565
  return;
536
566
  }
@@ -538,21 +568,30 @@ class RigRemoteAgentSession extends PiAgentSession {
538
568
  await this.remote.sendShell(trimmed);
539
569
  return;
540
570
  }
571
+ const expanded = await this.expandLocalInput(trimmed);
572
+ if (expanded === null)
573
+ return;
541
574
  const behavior = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
542
575
  options?.preflightResult?.(true);
543
- await this.remote.sendPrompt(trimmed, behavior);
576
+ await this.remote.sendPrompt(expanded, behavior);
544
577
  }
545
578
  async steer(text) {
546
579
  const trimmed = text.trim();
547
580
  if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
548
581
  return;
549
- await this.remote.sendPrompt(trimmed, "steer");
582
+ const expanded = await this.expandLocalInput(trimmed);
583
+ if (expanded === null)
584
+ return;
585
+ await this.remote.sendPrompt(expanded, "steer");
550
586
  }
551
587
  async followUp(text) {
552
588
  const trimmed = text.trim();
553
589
  if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
554
590
  return;
555
- await this.remote.sendPrompt(trimmed, "followUp");
591
+ const expanded = await this.expandLocalInput(trimmed);
592
+ if (expanded === null)
593
+ return;
594
+ await this.remote.sendPrompt(expanded, "followUp");
556
595
  }
557
596
  async abort() {
558
597
  await this.remote.abort();
@@ -203,6 +203,10 @@ async function getRunPiCommandsViaServer(context, runId) {
203
203
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
204
204
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
205
205
  }
206
+ async function getRunPiCapabilitiesViaServer(context, runId) {
207
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
208
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
209
+ }
206
210
  async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
207
211
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
208
212
  method: "POST",
@@ -249,7 +253,7 @@ async function buildRunPiEventsWebSocketUrl(context, runId) {
249
253
  }
250
254
 
251
255
  // packages/cli/src/commands/_pi-remote-session.ts
252
- import { AgentSession as PiAgentSession } from "@earendil-works/pi-coding-agent";
256
+ import { AgentSession as PiAgentSession, expandPromptTemplate } from "@earendil-works/pi-coding-agent";
253
257
  var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
254
258
  function defaultTransport() {
255
259
  return {
@@ -257,6 +261,7 @@ function defaultTransport() {
257
261
  getMessages: getRunPiMessagesViaServer,
258
262
  getStatus: getRunPiStatusViaServer,
259
263
  getCommands: getRunPiCommandsViaServer,
264
+ getCapabilities: getRunPiCapabilitiesViaServer,
260
265
  sendPrompt: sendRunPiPromptViaServer,
261
266
  sendShell: sendRunPiShellViaServer,
262
267
  runCommand: runRunPiCommandViaServer,
@@ -344,16 +349,18 @@ class RigRemoteSessionController {
344
349
  this.hooks.onStatusText?.("worker Pi WebSocket disconnected");
345
350
  };
346
351
  try {
347
- const [messagesPayload, statusPayload, commandsPayload] = await Promise.all([
352
+ const [messagesPayload, statusPayload, commandsPayload, capabilitiesPayload] = await Promise.all([
348
353
  this.transport.getMessages(this.context, this.runId),
349
354
  this.transport.getStatus(this.context, this.runId),
350
- this.transport.getCommands(this.context, this.runId)
355
+ this.transport.getCommands(this.context, this.runId),
356
+ this.transport.getCapabilities(this.context, this.runId).catch(() => ({ capabilities: null }))
351
357
  ]);
352
358
  const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
353
359
  this.applyStatusPayload(statusPayload);
354
360
  this.session?.replaceRemoteMessages(messages);
355
361
  const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
356
- this.hooks.onCatchUp?.(messages, commands);
362
+ const capabilities = capabilitiesPayload.capabilities && typeof capabilitiesPayload.capabilities === "object" && !Array.isArray(capabilitiesPayload.capabilities) ? capabilitiesPayload.capabilities : null;
363
+ this.hooks.onCatchUp?.(messages, commands, capabilities);
357
364
  catchupDone = true;
358
365
  for (const payload of buffered.splice(0))
359
366
  this.applyEnvelope(payload);
@@ -549,6 +556,9 @@ function createRemoteBashOperations(controller, excludeFromContext) {
549
556
  };
550
557
  }
551
558
 
559
+ // packages/cli/src/commands/_spinner.ts
560
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
561
+
552
562
  // packages/cli/src/commands/_pi-worker-bridge-extension.ts
553
563
  function recordOf2(value) {
554
564
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -566,6 +576,50 @@ function asText(value) {
566
576
  return String(value);
567
577
  }
568
578
  }
579
+ function names(value, key = "name") {
580
+ if (!Array.isArray(value))
581
+ return [];
582
+ return value.flatMap((entry) => {
583
+ if (typeof entry === "string")
584
+ return [entry];
585
+ const record = recordOf2(entry);
586
+ const name = record?.[key];
587
+ return typeof name === "string" ? [name] : [];
588
+ });
589
+ }
590
+ function renderWorkerCapabilities(capabilities) {
591
+ const lines = ["Worker session capabilities (in effect for this run)"];
592
+ const tools = Array.isArray(capabilities.tools) ? capabilities.tools : [];
593
+ const activeTools = tools.flatMap((tool) => {
594
+ const record = recordOf2(tool);
595
+ return record && record.active === true && typeof record.name === "string" ? [record.name] : [];
596
+ });
597
+ const inactiveCount = tools.length - activeTools.length;
598
+ lines.push(` tools ${activeTools.join(", ") || "(none)"}${inactiveCount > 0 ? ` (+${inactiveCount} inactive)` : ""}`);
599
+ const extensions = Array.isArray(capabilities.extensions) ? capabilities.extensions : [];
600
+ const extensionLabels = extensions.flatMap((entry) => {
601
+ const record = recordOf2(entry);
602
+ if (!record)
603
+ return [];
604
+ const path = typeof record.path === "string" ? record.path : "";
605
+ const short = path.split("/").filter(Boolean).slice(-2).join("/") || path || "extension";
606
+ return [short];
607
+ });
608
+ lines.push(` extensions ${extensionLabels.join(", ") || "(none)"}`);
609
+ const hookEvents = names(capabilities.hookEvents);
610
+ const hookList = Array.isArray(capabilities.hookEvents) ? capabilities.hookEvents.map(asText).filter(Boolean) : hookEvents;
611
+ lines.push(` hooks ${hookList.join(", ") || "(none)"}`);
612
+ lines.push(` skills ${names(capabilities.skills).join(", ") || "(none)"}`);
613
+ lines.push(` prompts ${names(capabilities.prompts).join(", ") || "(none)"}`);
614
+ const model = typeof capabilities.model === "string" ? capabilities.model : "(unknown)";
615
+ const thinking = typeof capabilities.thinkingLevel === "string" ? capabilities.thinkingLevel : "";
616
+ const cwd = typeof capabilities.cwd === "string" ? capabilities.cwd : "";
617
+ lines.push(` model ${model}${thinking ? ` \xB7 ${thinking}` : ""}`);
618
+ if (cwd)
619
+ lines.push(` cwd ${cwd}`);
620
+ lines.push(" (worker commands are in your palette as [worker \u2026] \xB7 /worker shows this again)");
621
+ return lines;
622
+ }
569
623
  async function answerExtensionUiRequest(options, ctx, request) {
570
624
  const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
571
625
  const method = String(request.method ?? request.type ?? "input");
@@ -592,7 +646,7 @@ async function answerExtensionUiRequest(options, ctx, request) {
592
646
  ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
593
647
  }
594
648
  }
595
- var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
649
+ var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "worker", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
596
650
  function registerDaemonCommands(pi, options, ctx, commands, registered) {
597
651
  for (const command of commands) {
598
652
  const record = recordOf2(command);
@@ -620,6 +674,21 @@ function registerDaemonCommands(pi, options, ctx, commands, registered) {
620
674
  function createRigWorkerPiBridgeExtension(options) {
621
675
  return (pi) => {
622
676
  const registeredDaemonCommands = new Set;
677
+ let capabilityLines = null;
678
+ let statusText = "connecting to worker session";
679
+ let busy = true;
680
+ let connected = false;
681
+ let frame = 0;
682
+ let spinnerTimer = null;
683
+ const renderStatus = (ctx) => {
684
+ const prefix = busy ? `${SPINNER_FRAMES[frame]} ` : connected ? "\u25CF " : "\u25CB ";
685
+ ctx.ui.setStatus("rig-worker-pi", `${prefix}${statusText}`);
686
+ };
687
+ const setStatus = (ctx, text, isBusy) => {
688
+ statusText = text;
689
+ busy = isBusy;
690
+ renderStatus(ctx);
691
+ };
623
692
  pi.registerCommand("detach", {
624
693
  description: "Detach from this run; the worker keeps going",
625
694
  handler: async (_args, ctx) => {
@@ -635,31 +704,57 @@ function createRigWorkerPiBridgeExtension(options) {
635
704
  ctx.shutdown();
636
705
  }
637
706
  });
707
+ pi.registerCommand("worker", {
708
+ description: "Show the worker session's real capabilities (tools, extensions, hooks, skills)",
709
+ handler: async (_args, ctx) => {
710
+ if (capabilityLines)
711
+ ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
712
+ else
713
+ ctx.ui.notify("Worker capabilities are not available yet (still connecting).", "info");
714
+ }
715
+ });
638
716
  pi.on("user_bash", (event) => ({
639
717
  operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
640
718
  }));
641
719
  pi.on("session_start", async (_event, ctx) => {
642
- ctx.ui.setTitle("Rig \xB7 worker session");
643
- ctx.ui.setStatus("rig-worker-pi", "connecting to worker session");
644
- ctx.ui.notify(`Attached to Rig run ${options.runId}. Native Pi, remote brain \u2014 /detach exits, /stop cancels the run.`, "info");
720
+ ctx.ui.setTitle("Rig \xB7 enriched bundled Pi");
721
+ setStatus(ctx, "waiting for worker Pi daemon", true);
722
+ spinnerTimer = setInterval(() => {
723
+ frame = (frame + 1) % SPINNER_FRAMES.length;
724
+ if (busy)
725
+ renderStatus(ctx);
726
+ }, 150);
727
+ 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");
645
728
  if (options.initialMessageSent)
646
729
  ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
647
730
  const nativeUi = ctx.ui;
648
731
  options.controller.setUiHooks({
649
- onStatusText: (text) => ctx.ui.setStatus("rig-worker-pi", text),
650
- onActivity: (label, detail) => ctx.ui.setStatus("rig-worker-pi", [label, detail].filter(Boolean).join(" \u2014 ")),
651
- onConnectionChange: (connected) => ctx.ui.setStatus("rig-worker-pi", connected ? "worker session live" : "worker session disconnected"),
732
+ onStatusText: (text) => setStatus(ctx, text, !connected),
733
+ onActivity: (label, detail) => {
734
+ const active = label !== "idle" && !/complete|ready/i.test(label);
735
+ setStatus(ctx, [label, detail].filter(Boolean).join(" \u2014 "), active);
736
+ if (/agent running|tool:/.test(label))
737
+ ctx.ui.setWidget("rig-worker-capabilities", undefined);
738
+ },
739
+ onConnectionChange: (nextConnected) => {
740
+ connected = nextConnected;
741
+ setStatus(ctx, nextConnected ? "worker session live" : "worker session disconnected", false);
742
+ },
652
743
  onError: (message) => ctx.ui.notify(message, "error"),
653
744
  onExtensionUiRequest: (request) => {
654
745
  answerExtensionUiRequest(options, ctx, request);
655
746
  },
656
- onCatchUp: (messages, commands) => {
747
+ onCatchUp: (messages, commands, capabilities) => {
657
748
  if (nativeUi.appendSessionMessages)
658
749
  nativeUi.appendSessionMessages(messages);
659
750
  const cwd = options.controller.status.cwd;
660
751
  if (nativeUi.setDisplayCwd && cwd)
661
752
  nativeUi.setDisplayCwd(cwd);
662
753
  registerDaemonCommands(pi, options, ctx, commands, registeredDaemonCommands);
754
+ if (capabilities) {
755
+ capabilityLines = renderWorkerCapabilities(capabilities);
756
+ ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
757
+ }
663
758
  }
664
759
  });
665
760
  options.controller.connect().catch((error) => {
@@ -667,6 +762,8 @@ function createRigWorkerPiBridgeExtension(options) {
667
762
  });
668
763
  });
669
764
  pi.on("session_shutdown", () => {
765
+ if (spinnerTimer)
766
+ clearInterval(spinnerTimer);
670
767
  options.controller.close();
671
768
  });
672
769
  };
@@ -392,6 +392,10 @@ async function getRunPiCommandsViaServer(context, runId) {
392
392
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
393
393
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
394
394
  }
395
+ async function getRunPiCapabilitiesViaServer(context, runId) {
396
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
397
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
398
+ }
395
399
  async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
396
400
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
397
401
  method: "POST",
@@ -492,6 +496,7 @@ export {
492
496
  getRunPiSessionViaServer,
493
497
  getRunPiMessagesViaServer,
494
498
  getRunPiCommandsViaServer,
499
+ getRunPiCapabilitiesViaServer,
495
500
  getRunLogsViaServer,
496
501
  getRunDetailsViaServer,
497
502
  getGitHubProjectStatusFieldViaServer,