@howarewoo/pi-subagents 0.1.1 → 0.2.1

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.
@@ -0,0 +1,111 @@
1
+ import type {
2
+ AgentSession,
3
+ AgentSessionEvent,
4
+ ExtensionToolContext,
5
+ Skill,
6
+ ToolDefinition,
7
+ } from "@earendil-works/pi-coding-agent";
8
+ import type { CapabilityDenial, ToolMetadata } from "./capabilities.ts";
9
+ import type { StructuredOutcome } from "./outcomes.ts";
10
+ import type { ModelSelection } from "./model-policy.ts";
11
+ import type { ExecutionLimits } from "./scheduling.ts";
12
+ import type { BuiltinTool, Profile } from "./profiles.ts";
13
+
14
+ export type DelegationMessage = {
15
+ id: string;
16
+ sender: ChildRef | "root";
17
+ recipient: ChildRef | "root";
18
+ text: string;
19
+ mode: "inbox" | "steer" | "followUp";
20
+ status: "accepted" | "queued" | "handled" | "delivered" | "not-running" | "error";
21
+ error?: string;
22
+ };
23
+ export type CoordinationWait =
24
+ | { status: "timeout" | "cancelled" | "shutdown" }
25
+ | { status: "messages"; messages: DelegationMessage[] }
26
+ | { status: "message"; message: DelegationMessage }
27
+ | {
28
+ status: "outcome";
29
+ outcome: Pick<ChildSnapshot, "id" | "generation" | "status" | "limitReason" | "pauseReason">;
30
+ };
31
+ export type ChildRef = Readonly<{ id: string; generation: number }>;
32
+ export type ChildSnapshot = Readonly<
33
+ ChildRef & {
34
+ parentId: string;
35
+ parentGeneration?: number;
36
+ depth: number;
37
+ writerLease: boolean;
38
+ limits: ExecutionLimits;
39
+ limitReason?: string;
40
+ toolCalls: number;
41
+ spawned?: number;
42
+ pauseReason?: string;
43
+ retainedContext?: boolean;
44
+ window?: number;
45
+ status:
46
+ | "queued"
47
+ | "setup"
48
+ | "running"
49
+ | "pausing"
50
+ | "paused"
51
+ | "draining"
52
+ | "settled"
53
+ | "failed"
54
+ | "cancelled";
55
+ profile: string;
56
+ tools: readonly BuiltinTool[];
57
+ leafTools?: readonly string[];
58
+ skills?: readonly string[];
59
+ capabilityDenials?: readonly CapabilityDenial[];
60
+ cwd?: string;
61
+ assignment: string;
62
+ activity: string;
63
+ progress: string;
64
+ result: string;
65
+ model: { provider: string; id: string };
66
+ effort: string | null;
67
+ prewalk?: {
68
+ target: ModelSelection;
69
+ status: "armed" | "handed-off" | "suppressed-exact" | "no-op";
70
+ };
71
+ telemetry: {
72
+ requests: number | null;
73
+ tokens: ReturnType<AgentSession["getSessionStats"]>["tokens"] | null;
74
+ cost: number | null;
75
+ context: ReturnType<AgentSession["getContextUsage"]> | null;
76
+ };
77
+ outcome?: StructuredOutcome;
78
+ artifacts?: { text?: string; structured?: string; error?: string };
79
+ error?: string;
80
+ transcript?: string;
81
+ disposed: boolean;
82
+ createdAt: number;
83
+ endedAt: number | null;
84
+ }
85
+ >;
86
+ export type RegistryEvent = Readonly<{
87
+ type: "changed" | "sdk";
88
+ child: ChildSnapshot;
89
+ event?: AgentSessionEvent;
90
+ }>;
91
+ export type StartTask = {
92
+ cwd?: string;
93
+ model?: ModelSelection;
94
+ resultSchema?: unknown;
95
+ assignment: string;
96
+ profile: Profile;
97
+ skills: Skill[];
98
+ context: ExtensionToolContext;
99
+ assertTools: (names: readonly string[]) => void;
100
+ toolMetadata?: () => readonly ToolMetadata[];
101
+ /** Live parent codemode availability; never a grant to forward its dispatcher. */
102
+ codemodeEnabled?: () => boolean;
103
+ availableSkills?: () => readonly Skill[];
104
+ capabilityDenials?: CapabilityDenial[];
105
+ leafProvenance?: Readonly<Record<string, string>>;
106
+ callerId: string;
107
+ parent?: ChildRef;
108
+ limits?: Partial<ExecutionLimits>;
109
+ coordinationTools?: (actor: ChildRef) => ToolDefinition[];
110
+ nestedTool?: (parent: ChildRef) => ToolDefinition;
111
+ };
package/src/hub.ts CHANGED
@@ -3,7 +3,6 @@ import {
3
3
  Input,
4
4
  matchesKey,
5
5
  oklchColor,
6
- sliceByColumn,
7
6
  truncateToWidth,
8
7
  visibleWidth,
9
8
  wrapTextWithAnsi,
@@ -15,229 +14,24 @@ import type { Theme } from "@earendil-works/pi-coding-agent";
15
14
  import type { ChildRef, ChildSnapshot, DelegationCoordinator } from "./coordinator.ts";
16
15
  import { createNativeThread, safe, type LiveTool, type ThreadDependencies } from "./thread.ts";
17
16
  export { safe } from "./thread.ts";
17
+ export { createChildWidget } from "./subagent-widget.ts";
18
+ export {
19
+ agentLabel,
20
+ fleetSummary,
21
+ taskCard,
22
+ transcriptLines,
23
+ writerWarning,
24
+ } from "./task-presentation.ts";
25
+ import {
26
+ agentLabel,
27
+ assignmentHeadline,
28
+ lineText,
29
+ taskDetails,
30
+ taskTree,
31
+ transcriptLines,
32
+ } from "./task-presentation.ts";
18
33
 
19
34
  type Palette = Pick<Theme, "fg" | "style"> & { colors: Pick<Theme["colors"], "customMessageBg"> };
20
- const lineText = (text: string) => safe(text).replaceAll("\n", " ");
21
- const assignmentHeadline = (child: ChildSnapshot) =>
22
- safe(child.assignment)
23
- .split("\n")
24
- .map((line) => line.trim())
25
- .find(Boolean)
26
- ?.replace(/^#{1,6}\s+/, "") || "Assignment";
27
- export const agentLabel = (child: ChildSnapshot) =>
28
- `${assignmentHeadline(child)} · ${lineText(child.profile)}`;
29
- const active = (child: ChildSnapshot) => !child.disposed;
30
- const activity = (child: ChildSnapshot) =>
31
- child.error ||
32
- (active(child) ? child.activity || child.progress : child.result) ||
33
- child.progress ||
34
- "Waiting for SDK activity";
35
- export const writerWarning = "Model-tool writer only: no conflicting manual !/!! or external edits";
36
- const statuses = [
37
- "queued",
38
- "setup",
39
- "running",
40
- "draining",
41
- "settled",
42
- "failed",
43
- "cancelled",
44
- ] as const;
45
- export function fleetSummary(children: ChildSnapshot[]): string {
46
- return children.length
47
- ? statuses
48
- .map((status) => `${status} ${children.filter((child) => child.status === status).length}`)
49
- .join(" · ")
50
- : "No tasks · ask the model to delegate_task";
51
- }
52
-
53
- /** Event-driven, bounded current-agent tree; history and full metrics remain in the Hub. */
54
- export function createChildWidget(
55
- snapshot: () => ChildSnapshot[],
56
- theme: Pick<Theme, "fg"> & Partial<Pick<Theme, "bold">>,
57
- availableRows = () => 30,
58
- ): Component {
59
- return {
60
- render(width) {
61
- width = Math.max(0, width);
62
- const children = snapshot();
63
- if (!children.length) return [];
64
- const queued = children.filter((child) => child.status === "queued").length;
65
- const priority = (child: ChildSnapshot) =>
66
- child.status === "draining"
67
- ? 0
68
- : child.status === "running"
69
- ? 1
70
- : child.status === "setup"
71
- ? 2
72
- : 3;
73
- const live = children
74
- .filter(
75
- (child) =>
76
- !child.disposed &&
77
- (child.status === "setup" || child.status === "running" || child.status === "draining"),
78
- )
79
- .sort((a, b) => priority(a) - priority(b));
80
- const writer = children.some((child) => child.writerLease);
81
- const budget = Math.max(1, Math.min(5, Math.floor(availableRows() / 4)));
82
- const counts =
83
- width < 60
84
- ? `${live.length} active · ${queued} queued`
85
- : statuses
86
- .flatMap((status) => {
87
- const count = children.filter((child) => child.status === status).length;
88
- return count ? [`${count} ${status}`] : [];
89
- })
90
- .join(" · ");
91
- const rows = [
92
- theme.fg("accent", `● Subagents ${children.length}`) + theme.fg("muted", ` · ${counts}`),
93
- ];
94
- if (writer && budget === 1)
95
- rows[0] = theme.fg(
96
- "warning",
97
- `● Subagents ${children.length} · Model writer: !/!! + external unsafe · ${queued} queued`,
98
- );
99
- const room = Math.max(0, budget - 1 - (writer && budget > 1 ? 1 : 0));
100
- const activityRows = live.length && room >= (live.length > 1 ? 3 : 2) ? 1 : 0;
101
- const slots = room - activityRows;
102
- const overflow = live.length > slots;
103
- const shown = live.slice(0, Math.max(0, slots - (overflow ? 1 : 0)));
104
- shown.forEach((child, index) => {
105
- const last = index === shown.length - 1 && shown.length === live.length;
106
- const color = child.status === "draining" ? "warning" : "accent";
107
- const role = lineText(child.profile);
108
- const indent = " ".repeat(Math.min(3, Math.max(0, child.depth - 1)));
109
- const prefix =
110
- theme.fg("muted", `${indent}${last ? "└─" : "├─"} `) +
111
- theme.fg(color, "● ") +
112
- (theme.bold?.(role) ?? role) +
113
- " ";
114
- const elapsed = Math.max(
115
- 0,
116
- Math.floor(((child.endedAt ?? Date.now()) - child.createdAt) / 1000),
117
- );
118
- const stats = [
119
- child.status,
120
- `${elapsed}s`,
121
- `${child.toolCalls} tools`,
122
- ...(child.telemetry.requests === null ? [] : [`${child.telemetry.requests} req`]),
123
- ...(child.telemetry.tokens ? [`${child.telemetry.tokens.total} tok`] : []),
124
- ...(child.telemetry.cost === null ? [] : [`$${child.telemetry.cost.toFixed(3)}`]),
125
- ].join(" · ");
126
- const metric = truncateToWidth(stats, Math.max(8, Math.floor(width / 2)));
127
- const headline = assignmentHeadline(child);
128
- rows.push(
129
- prefix +
130
- theme.fg(
131
- "text",
132
- truncateToWidth(
133
- headline,
134
- Math.max(4, width - visibleWidth(prefix) - visibleWidth(metric) - 3),
135
- ),
136
- ) +
137
- theme.fg("muted", " · " + metric),
138
- );
139
- if (!index && activityRows) {
140
- const prefix = `${indent}${last ? " " : "│ "} └ `;
141
- const text = lineText(activity(child));
142
- const columns = Math.max(0, width - visibleWidth(prefix));
143
- const length = visibleWidth(text);
144
- // Progress is an accumulated tail; keep its newest text, not a frozen opening paragraph.
145
- const preview =
146
- length > columns
147
- ? "…" +
148
- sliceByColumn(text, length - Math.max(0, columns - 1), Math.max(0, columns - 1))
149
- : text;
150
- rows.push(theme.fg("muted", prefix + preview));
151
- }
152
- });
153
- if (shown.length < live.length && room)
154
- rows.push(theme.fg("muted", `└─ ${live.length - shown.length} more · /subagent-hub`));
155
- if (writer && budget > 1)
156
- rows.push(
157
- theme.fg("warning", width < 75 ? "Model writer: !/!! + external unsafe" : writerWarning),
158
- );
159
- return rows.slice(0, budget).map((line) => truncateToWidth(line, width));
160
- },
161
- invalidate() {},
162
- };
163
- }
164
-
165
- export function taskCard(child: ChildSnapshot | undefined, expanded = false): string[] {
166
- if (!child)
167
- return [
168
- "Task identity unavailable",
169
- "See tool error; /subagent-hub lists current runtime tasks",
170
- ];
171
- return [
172
- `${truncateToWidth(agentLabel(child), 240, "… [preview truncated]")} #${child.generation} · ${child.status}`,
173
- `Identity ${child.id} #${child.generation}`,
174
- `${child.model.provider}/${child.model.id} · effort ${child.effort ?? "unavailable"}`,
175
- truncateToWidth(lineText(activity(child)), 240, "… [preview truncated]"),
176
- `Prewalk ${child.prewalk ? JSON.stringify(child.prewalk) : "off"} · advisor unavailable`,
177
- `Parent ${child.parentId}${child.parentGeneration ? ` #${child.parentGeneration}` : ""} · depth ${child.depth} · writer lease ${child.writerLease}`,
178
- ...(child.outcome
179
- ? [
180
- `Structured ${child.outcome.validation} · ${child.outcome.finalized ? "finalized" : "provisional"} · attempts ${child.outcome.attempts}`,
181
- ]
182
- : []),
183
- ...(child.limitReason ? [`Limit: ${child.limitReason}`] : []),
184
- ...(child.artifacts ? [`Artifacts: ${JSON.stringify(child.artifacts)}`] : []),
185
- ...(expanded
186
- ? [
187
- "Assignment: " +
188
- truncateToWidth(lineText(child.assignment), 480, "… [preview truncated]"),
189
- "Result: " +
190
- truncateToWidth(
191
- lineText(child.result || child.error || "Not available yet"),
192
- 480,
193
- "… [preview truncated]",
194
- ),
195
- ]
196
- : []),
197
- child.transcript
198
- ? `Full transcript: ${child.transcript} · /subagent-hub`
199
- : "Transcript unavailable (setup) · /subagent-hub",
200
- ].map(lineText);
201
- }
202
-
203
- /** Read-only content projection: never dumps provider/auth objects, image payloads or opaque signatures. */
204
- export function transcriptLines(
205
- messages: ReturnType<DelegationCoordinator["messages"]>,
206
- toolsOnly = false,
207
- ): string[] {
208
- const lines: string[] = [];
209
- for (const message of messages) {
210
- if (message.role === "system") continue;
211
- const start = lines.length;
212
- const blocks = "content" in message ? message.content : undefined;
213
- if (!toolsOnly || message.role === "toolResult") {
214
- lines.push(
215
- `[${message.role}${message.role === "toolResult" ? ` ${message.toolName} ${message.toolCallId} ${message.isError ? "ERROR" : "result"}` : ""}]`,
216
- );
217
- if (typeof blocks === "string") lines.push(safe(blocks));
218
- }
219
- if (Array.isArray(blocks))
220
- for (const block of blocks) {
221
- if (block.type === "toolCall")
222
- lines.push(
223
- `Tool ${block.name} ${block.id}\nArguments: ${safe(JSON.stringify(block.arguments, null, 2))}`,
224
- );
225
- else if (!toolsOnly || message.role === "toolResult") {
226
- if (block.type === "text") lines.push(safe(block.text));
227
- else if (block.type === "thinking") lines.push("Thinking: " + safe(block.thinking));
228
- else if (block.type === "image")
229
- lines.push(`[image ${safe(block.mimeType)}; payload omitted]`);
230
- }
231
- }
232
- if (message.role === "assistant" && message.errorMessage)
233
- lines.push("Error: " + safe(message.errorMessage));
234
- if (start && lines.length > start) lines.splice(start, 0, "");
235
- }
236
- return lines.length
237
- ? lines.map(safe)
238
- : [toolsOnly ? "No tool calls yet" : "Waiting for SDK messages"];
239
- }
240
-
241
35
  /** Observation only: closing or disposing never touches child execution. */
242
36
  export function createHub(
243
37
  coordinator: DelegationCoordinator,
@@ -289,26 +83,28 @@ export function createHub(
289
83
  });
290
84
  const identity = (ref: ChildRef) => `${ref.id}/${ref.generation}`;
291
85
  const roster = () =>
292
- coordinator
293
- .list()
294
- .flatMap((child) => coordinator.generations(child.id))
295
- .filter((child) =>
296
- lineText(
297
- [
298
- child.id,
299
- child.parentId,
300
- child.profile,
301
- child.status,
302
- child.assignment,
303
- child.progress,
304
- child.result,
305
- child.error,
306
- child.model.id,
307
- ].join(" "),
308
- )
309
- .toLowerCase()
310
- .includes(query.toLowerCase()),
311
- );
86
+ taskTree(
87
+ coordinator
88
+ .list()
89
+ .flatMap((child) => coordinator.generations(child.id))
90
+ .filter((child) =>
91
+ lineText(
92
+ [
93
+ child.id,
94
+ child.parentId,
95
+ child.profile,
96
+ child.status,
97
+ child.assignment,
98
+ child.progress,
99
+ child.result,
100
+ child.error,
101
+ child.model.id,
102
+ ].join(" "),
103
+ )
104
+ .toLowerCase()
105
+ .includes(query.toLowerCase()),
106
+ ),
107
+ ).map(({ child }) => child);
312
108
  const selection = () => {
313
109
  const children = roster();
314
110
  if (!children.some((child) => identity(child) === selected)) {
@@ -337,6 +133,7 @@ export function createHub(
337
133
  !child ||
338
134
  child.disposed ||
339
135
  child.status === "draining" ||
136
+ child.status === "pausing" ||
340
137
  (text !== undefined && child.status !== "running")
341
138
  )
342
139
  throw new Error(
@@ -452,8 +249,6 @@ export function createHub(
452
249
  footer.push(
453
250
  theme.fg("muted", `Filter: ${lineText(query)} · ${children.length} generations`),
454
251
  );
455
- if (current.some((item) => item.writerLease))
456
- footer.push(theme.fg("warning", "WRITER: model tools only; no manual/external writes"));
457
252
  if (notice) {
458
253
  const lines = wrapTextWithAnsi(lineText(notice), Math.max(1, inner));
459
254
  footer.push(
@@ -517,40 +312,13 @@ export function createHub(
517
312
  let nativeLines: string[] | undefined;
518
313
  if (child) {
519
314
  if (tab === "details") {
520
- const metric = (value: unknown) =>
521
- value === null || value === undefined ? "unavailable" : JSON.stringify(value);
522
- detail = [
523
- `Identity ${child.id} #${child.generation}`,
524
- "",
525
- "Assignment",
526
- safe(child.assignment),
527
- "",
528
- "Result",
529
- safe(child.result || "Not available yet"),
530
- "",
531
- ...(child.error ? ["Error", safe(child.error), ""] : []),
532
- ...(child.outcome
533
- ? ["Structured outcome", JSON.stringify(child.outcome, null, 2), ""]
534
- : []),
535
- ...(child.artifacts
536
- ? ["Saved artifacts", JSON.stringify(child.artifacts, null, 2), ""]
537
- : []),
538
- `Workspace: ${safe(child.cwd ?? "parent workspace")}`,
539
- `Effective builtin tools: ${child.tools.join(", ")}`,
540
- `${child.model.provider}/${child.model.id} · effort ${child.effort ?? "unavailable"}`,
541
- `Prewalk ${child.prewalk ? JSON.stringify(child.prewalk) : "off"} · advisor unavailable`,
542
- `Presence ${coordinator.agents().find((agent) => agent.id === child.id && agent.generation === child.generation)?.presence ?? "historical"} · resources ${child.disposed ? "disposed" : "live"}`,
543
- `Elapsed ${Math.max(0, Math.floor(((child.endedAt ?? Date.now()) - child.createdAt) / 1000))}s`,
544
- `Direct requests ${metric(child.telemetry.requests)} · cost ${metric(child.telemetry.cost)}`,
545
- `Direct tokens ${metric(child.telemetry.tokens)}`,
546
- `Context ${metric(child.telemetry.context)}`,
547
- `Parent ${child.parentId} #${child.parentGeneration ?? "root"} · depth ${child.depth} · direct generation only`,
548
- `Limits ${JSON.stringify(child.limits)} · calls ${child.toolCalls} · reason ${child.limitReason ?? "none"}`,
549
- "Activity: " + safe(activity(child)),
550
- "Full transcript: " + safe(child.transcript ?? "unavailable (queued/setup)"),
551
- "Followup/revive: native delegate_coordinate only (Hub has no tool dispatcher)",
552
- ...(child.writerLease ? [writerWarning] : []),
553
- ];
315
+ detail = taskDetails(
316
+ child,
317
+ coordinator
318
+ .agents()
319
+ .find((agent) => agent.id === child.id && agent.generation === child.generation)
320
+ ?.presence,
321
+ );
554
322
  } else {
555
323
  try {
556
324
  const messages = coordinator.messages(child);
@@ -626,26 +394,33 @@ export function createHub(
626
394
  ? "success"
627
395
  : "accent";
628
396
  const selectedLabel = child
629
- ? `${agentLabel(child)} #${child.generation} · ${child.status}${historical(child) ? " · history" : ""}`
397
+ ? `${agentLabel(child)} · ${child.status}${historical(child) ? " · history" : ""}`
630
398
  : "No selection";
631
399
  const viewLabel = `${tab.toUpperCase()} · Tab: ${tab === "thread" ? "details" : tab === "details" ? "tools" : "thread"}`;
632
400
  const position = `${tab === "thread" ? (following ? "LIVE · " : "READING · ") : ""}${offset + 1}–${Math.min(wrapped.length, offset + viewport)}/${wrapped.length} lines${tab === "thread" ? (following ? " · k/PgUp read" : " · End: live") : offset < maxOffset ? " · more ↓" : ""}`;
633
401
  const pane: string[] = [];
634
402
  if (wide) {
635
- const slots = Math.max(1, Math.floor((paneHeight - 2) / 3));
403
+ const slots = Math.max(1, Math.floor((paneHeight - 2) / 2));
636
404
  const start = Math.max(0, index - slots + 1);
637
405
  const left = [
638
406
  theme.fg("accent", `SUBAGENTS · ${children.length} generations`),
639
- ...children
407
+ ...taskTree(children)
640
408
  .slice(start, start + slots)
641
- .flatMap((item) => [
642
- theme.fg(
643
- identity(item) === selected ? "accent" : "text",
644
- `${identity(item) === selected ? ">" : " "} ${" ".repeat(Math.min(4, Math.max(0, item.depth - 1)))}└ ${agentLabel(item)} #${item.generation}${historical(item) ? " · history" : ""}`,
645
- ),
646
- theme.fg(statusColor(item), ` ${item.profile} · ${item.status}`),
647
- theme.fg("muted", ` #${item.generation}${historical(item) ? " · history" : ""}`),
648
- ]),
409
+ .flatMap(({ child: item, indent, last, continuation }) => {
410
+ const prefix = `${identity(item) === selected ? ">" : " "} ${indent}${last ? "└" : "├"} `;
411
+ return [
412
+ theme.fg(identity(item) === selected ? "accent" : "text", prefix[0]) +
413
+ theme.fg("text", prefix.slice(1)) +
414
+ theme.fg(statusColor(item), "● ") +
415
+ theme.fg(
416
+ identity(item) === selected ? "accent" : "text",
417
+ `${assignmentHeadline(item)}${historical(item) ? " · history" : ""}`,
418
+ ),
419
+ theme.fg("text", ` ${continuation}`) +
420
+ theme.fg(statusColor(item), lineText(item.profile)) +
421
+ theme.fg("text", ` · ${item.status}`),
422
+ ];
423
+ }),
649
424
  ];
650
425
  const right = [
651
426
  theme.fg("accent", lineText(selectedLabel)),
@@ -674,21 +449,25 @@ export function createHub(
674
449
  ),
675
450
  );
676
451
  } else {
677
- const suffix = child ? ` #${child.generation} · ${index + 1}/${children.length}` : "";
452
+ const suffix = child ? ` · ${index + 1}/${children.length}` : "";
678
453
  pane.push(
679
- theme.fg(
680
- "accent",
681
- child
682
- ? `> └ ${truncateToWidth(agentLabel(child), Math.max(0, inner - 4 - visibleWidth(suffix)))}${suffix}`
683
- : "No selection",
684
- ),
454
+ child
455
+ ? theme.fg("accent", ">") +
456
+ theme.fg("text", " └ ") +
457
+ theme.fg(statusColor(child), "● ") +
458
+ theme.fg(
459
+ "accent",
460
+ `${truncateToWidth(agentLabel(child), Math.max(0, inner - 6 - visibleWidth(suffix)))}${suffix}`,
461
+ )
462
+ : theme.fg("accent", "No selection"),
685
463
  );
686
464
  if (paneHeight > 1)
687
465
  pane.push(
688
- theme.fg(
689
- child ? statusColor(child) : "muted",
690
- `${tab.toUpperCase()}${child ? ` · ${child.profile} · ${child.status}` : ""}`,
691
- ),
466
+ child
467
+ ? theme.fg("text", `${tab.toUpperCase()} · `) +
468
+ theme.fg(statusColor(child), lineText(child.profile)) +
469
+ theme.fg("text", ` · ${child.status}`)
470
+ : theme.fg("muted", tab.toUpperCase()),
692
471
  );
693
472
  if (paneHeight > 2)
694
473
  pane.push(
@@ -798,6 +577,7 @@ export function createHub(
798
577
  child &&
799
578
  !child.disposed &&
800
579
  child.status !== "draining" &&
580
+ child.status !== "pausing" &&
801
581
  (data === "x" || child.status === "running")
802
582
  ) {
803
583
  mode = data === "s" ? "steer" : data === "m" ? "message" : "stop";