@yusukeshib/pi-babysit 0.6.21 → 0.6.23

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.
Files changed (3) hide show
  1. package/README.md +51 -18
  2. package/index.ts +137 -53
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -32,8 +32,8 @@ reachable from anywhere (`~/.pi-babysit/<pi-session-id>/`). Two kinds:
32
32
 
33
33
  | kind | started by | completion | on completion |
34
34
  | ---- | ---------- | ---------- | ------------- |
35
- | **process** | `babysit_run { command }` | process **exit** | automatic notification message (`triggerTurn`), batched for all exits observed in the same poll — the agent may end its turn after starting and is resumed on exit, same contract as the old `process` tool |
36
- | **subagent** | `babysit_run { profile: "subagent", task }` | `agent_settled` in the RPC event stream (worker remains reusable during its idle grace) | `foreground: true` returns the answer and nested usage in the same tool call; background workers must be collected with `babysit_wait` before the parent finishes, and remain reusable until self-reap |
35
+ | **process** | `babysit_run { description, command }` | process **exit** | automatic notification message (`triggerTurn`), batched for all exits observed in the same poll — the agent may end its turn after starting and is resumed on exit, same contract as the old `process` tool |
36
+ | **subagent** | `babysit_run { description, profile: "subagent", task }` | `agent_settled` in the RPC event stream (worker remains reusable during its idle grace) | `foreground: true` returns the answer and nested usage in the same tool call; background workers must be collected with `babysit_wait` before the parent finishes, and remain reusable until self-reap |
37
37
 
38
38
  The **profile is a tool parameter, not a separate tool set**: domain knowledge
39
39
  (RPC bookkeeping, per-task byte offsets, parked-turn detection, PTY-safe
@@ -44,19 +44,22 @@ Subagent recursion is **disabled by default**: a top-level pi may create workers
44
44
  (depth 1), but those workers cannot create more workers. A top-level caller can
45
45
  explicitly opt in for a specific tree with `maxDepth: 2` (or higher) when it
46
46
  creates the first worker. Descendants inherit that ceiling and cannot raise it.
47
- Normal `babysit_run { command }` process execution remains available at every
48
- depth.
47
+ Normal `babysit_run { description, command }` process execution remains available
48
+ at every depth.
49
49
 
50
50
  Because sessions are real PTYs, the agent can also **drive interactive
51
51
  programs** (installers, wizards, REPLs): type with `babysit_send`
52
52
  (text or named keys) and read the rendered screen with
53
- `babysit_check { screen: true }` — a capability neither retired extension had.
53
+ `babysit_check { description, screen: true }` — a capability neither retired extension had.
54
54
 
55
55
  ## Tools (LLM-callable)
56
56
 
57
+ All five tools require a short, non-blank `description` explaining the purpose
58
+ of that call.
59
+
57
60
  | Tool | What it does |
58
61
  | ---- | ------------ |
59
- | `babysit_run` | Run any command (`command`, optional `name`/`pty`/`timeout`/`idleTimeout`/`retryOnWorkerDeath`/`notificationGroup`/`lifecycle`). Set `foreground: true` for one process or subagent whose result is needed in the same tool call; use `returnPattern`/`returnLines`/`maxBytes` to keep noisy process output bounded. Foreground and non-interactive process waits default to lifecycle `attached`, which terminates the process tree if that owning tool call is interrupted; background starts default to `detached`. Or start a named background subagent (`profile: "subagent"`, `task`, optional `name`/`agent`/`model`/`tools`/`maxDepth` and budget fields), then always collect it with `babysit_wait`. `maxDepth` defaults to 1. Subagent `continueAfterStart: true` is accepted as a compatibility alias for this default background behavior. Quick commands return inline; longer process runs notify in the background |
62
+ | `babysit_run` | Run any command (`description` + `command`, optional `name`/`pty`/`timeout`/`idleTimeout`/`retryOnWorkerDeath`/`notificationGroup`/`lifecycle`). Set `foreground: true` for one process or subagent whose result is needed in the same tool call; use `returnPattern`/`returnLines`/`maxBytes` to keep noisy process output bounded. Foreground and non-interactive process waits default to lifecycle `attached`, which terminates the process tree if that owning tool call is interrupted; background starts default to `detached`. Or start a named background subagent (`description`, `profile: "subagent"`, `task`, optional `name`/`agent`/`model`/`tools`/`maxDepth` and budget fields), then always collect it with `babysit_wait`. `maxDepth` defaults to 1. Subagent `continueAfterStart: true` is accepted as a compatibility alias for this default background behavior. Quick commands return inline; longer process runs notify in the background |
60
63
  | `babysit_check` | Without an id, list sessions with state/kind filters. With an id, inspect bounded output, search with `pattern`, or capture a TUI with `screen: true`; `maxBytes` overrides the 4 KB default up to 24 KB |
61
64
  | `babysit_send` | Process: type `text` / press `keys` into the PTY. Subagent: steer mid-run, or send a follow-up task when confirmed settled (`mode: auto/steer/task`); explicit task mode rejects busy, parked, or unknown state |
62
65
  | `babysit_wait` | Block until done: process exit (or `expect: "regex"` readiness marker), subagent task completion. Multi-wait: up to 32 unique `ids` + `mode: "any"\|"all"` |
@@ -71,6 +74,36 @@ calls if another extension or preset re-enables `bash`, including shell
71
74
  backgrounding (`… &`, `nohup`, `setsid`, `disown`). Set
72
75
  `PI_BABYSIT_ALLOW_BASH=1` to retain direct `bash` explicitly.
73
76
 
77
+ ## Describe every operation
78
+
79
+ Every babysit tool call requires a non-blank `description`. Explain the purpose
80
+ briefly in language the user can understand. For `babysit_run`, `name` remains
81
+ a stable session identifier, while `command` / `task` contains the execution
82
+ instructions. Its description is trimmed and saved with the session. For
83
+ `babysit_check`, `babysit_send`, `babysit_wait`, and `babysit_kill`, the description
84
+ explains only that operation and never overwrites the session description.
85
+ Descriptions are display metadata, not shell code, stdin, or instructions sent
86
+ to the subagent.
87
+
88
+ ```text
89
+ babysit_run { description: "Run regression tests", command: "bun test", foreground: true }
90
+ babysit_run { description: "Review the authentication changes", profile: "subagent", task: "Review the authentication diff for security regressions", maxTurns: 5 }
91
+ babysit_check { description: "Inspect build errors", id: "build", pattern: "ERROR" }
92
+ babysit_wait { description: "Wait for the build to finish", id: "build" }
93
+ babysit_send { description: "Ask the reviewer to check authorization too", id: "review", text: "Also check authorization." }
94
+ babysit_kill { description: "Stop the unused development server", id: "dev-server" }
95
+ ```
96
+
97
+ In Pi's main transcript the description is the prominent yellow heading, with
98
+ the tool name and execution kind in muted gray. The full command or task (or
99
+ all other operation arguments, including target IDs and input) is always visible
100
+ in gray beneath it, even when tool results are collapsed. Session lists, the
101
+ widget, and completion notifications show the session's original description.
102
+ Older saved sessions and tool calls without descriptions remain readable,
103
+ falling back to their command, task, or other arguments. Callers must supply a
104
+ description for every new call; blank or missing values are rejected before
105
+ execution.
106
+
74
107
  ## Remote processes over SSH
75
108
 
76
109
  Install a `babysit` CLI with `--host` support on both machines (tested with
@@ -79,11 +112,11 @@ SSH aliases, keys, known hosts, and ProxyJump are handled by babysit/SSH;
79
112
  pi-babysit does not install remote software or copy credentials.
80
113
 
81
114
  ```text
82
- babysit_run { host: "yusuke-t4", cwd: "/home/yusuke/project", command: "bun test", foreground: true, returnLines: 30 }
83
- babysit_run { host: "user@server", command: "python3 -i", name: "remote-repl", continueAfterStart: true }
84
- babysit_check { id: "remote-repl", screen: true }
85
- babysit_send { id: "remote-repl", text: "print(42)" }
86
- babysit_kill { id: "remote-repl" }
115
+ babysit_run { description: "Run tests on Linux", host: "yusuke-t4", cwd: "/home/yusuke/project", command: "bun test", foreground: true, returnLines: 30 }
116
+ babysit_run { description: "Open a remote Python REPL", host: "user@server", command: "python3 -i", name: "remote-repl", continueAfterStart: true }
117
+ babysit_check { description: "Read the remote REPL screen", id: "remote-repl", screen: true }
118
+ babysit_send { description: "Evaluate a value in the remote REPL", id: "remote-repl", text: "print(42)" }
119
+ babysit_kill { description: "Close the remote REPL", id: "remote-repl" }
87
120
  ```
88
121
 
89
122
  `host` omitted (or `"local"`) preserves local behavior. `cwd` is process-only:
@@ -132,9 +165,9 @@ summary counts use `RUNNING` / `IDLE`, and every row is labeled
132
165
  Click the header to toggle to `babysits(n all)`, which also shows finished and failed
133
166
  sessions, and click again to return to running-only. Both modes show newest first,
134
167
  ten per page; `< 1/4 >` shows the current/total page when needed. Click the
135
- arrows to page. Rows occupy one line, previewing the command for processes and
136
- the task for agents (with `…` when clipped). Click an AGENT or PROCESS row to
137
- replace the list with a tall **log viewer in the same widget area**, not an
168
+ arrows to page. Rows occupy one line, previewing the description (or the command
169
+ for older processes and task for older agents), with `…` when clipped. Click an
170
+ AGENT or PROCESS row to replace the list with a tall **log viewer in the same widget area**, not an
138
171
  overlay or separate screen. `Esc` or the right-hand **Close** button restores
139
172
  the list, preserving its mode and page. The viewer uses Pi's `ScrollView` for
140
173
  scroll position and follow behavior, with a widget-local scrollbar styled using
@@ -183,8 +216,8 @@ pending for the next poll. Larger output stays out of model context. Inspect it
183
216
  through the session id without creating another shell session:
184
217
 
185
218
  ```text
186
- babysit_check { id: "cargo-test", lines: 50 }
187
- babysit_check { id: "cargo-test", pattern: "FAIL|ERROR", lines: 50 }
219
+ babysit_check { description: "Inspect the recent test output", id: "cargo-test", lines: 50 }
220
+ babysit_check { description: "Find test failures", id: "cargo-test", pattern: "FAIL|ERROR", lines: 50 }
188
221
  ```
189
222
 
190
223
  Tail and search results are capped at 200 lines and default to a 4 KB total
@@ -269,7 +302,7 @@ because blindly rerunning an arbitrary command can duplicate side effects.
269
302
  must still call `babysit_wait` so the answer and nested usage are persisted.
270
303
  Unlike `agent_end`, `agent_settled` cannot precede an automatic retry,
271
304
  compaction retry, or queued continuation. A settled run containing a
272
- **parked** toolResult — a `babysit_run { command }` result carrying the
305
+ **parked** toolResult — a `babysit_run { description, command }` result carrying the
273
306
  `[notify-on-exit]` marker (or the legacy `process` tool) — only means "turn
274
307
  parked awaiting a process-exit notification; pi resumes on its own", so the
275
308
  wait continues. The marker-bearing result may be followed by a short
@@ -280,7 +313,7 @@ because blindly rerunning an arbitrary command can duplicate side effects.
280
313
  Subagents load `self-reap.ts`, which exits an idle finished subagent after a
281
314
  grace window using the same parked-turn rule, so a subagent waiting on a long
282
315
  build is never false-killed. Callers can set `reapAfter: "5m"` on
283
- `babysit_run { profile: "subagent", task, reapAfter: "5m" }`. Omission uses
316
+ `babysit_run { description, profile: "subagent", task, reapAfter: "5m" }`. Omission uses
284
317
  `PI_BABYSIT_REAP_AFTER`, then `PI_SUBAGENT_REAP_AFTER`, then 120s.
285
318
  `reapAfter: "none"` (also `"off"` or `"0"`) disables idle reaping; the absolute
286
319
  `timeout` still applies. Follow-up tasks cancel the idle timer. This differs
package/index.ts CHANGED
@@ -51,6 +51,18 @@ import { Type, type TSchema } from "typebox";
51
51
  import { StringEnum } from "@earendil-works/pi-ai";
52
52
  import { type AgentConfig, type AgentScope, discoverAgents } from "./agents";
53
53
 
54
+ const descriptionSchema = Type.String({
55
+ minLength: 1,
56
+ pattern: "\\S",
57
+ description: "Required short human-readable purpose for this operation. Explain what it does and why; shown prominently to the user.",
58
+ });
59
+
60
+ function requireDescription(value: unknown): void {
61
+ if (typeof value !== "string" || !value.trim()) {
62
+ throw new Error("`description` is required and must be a non-blank string for every babysit tool call.");
63
+ }
64
+ }
65
+
54
66
  // Dedicated babysit state root so pi-managed sessions never collide with the
55
67
  // user's own manual `babysit` sessions. The base is namespaced per pi session
56
68
  // on session_start (BABYSIT_DIR=<base>/<session-id>), so each pi session only
@@ -127,7 +139,7 @@ export function planSubagentSpawn(
127
139
  error:
128
140
  `Nested subagent creation is disabled at depth ${currentDepth}: spawning would reach depth ${childDepth}, ` +
129
141
  `but the inherited maxDepth is ${maxDepth}. Have the top-level parent explicitly opt in with ` +
130
- `babysit_run { profile: "subagent", task, maxDepth: ${childDepth} } when creating the first worker.`,
142
+ `babysit_run { description, profile: "subagent", task, maxDepth: ${childDepth} } when creating the first worker.`,
131
143
  };
132
144
  }
133
145
  return { allowed: true, childDepth, maxDepth };
@@ -160,7 +172,7 @@ export function subagentGuidance(
160
172
  const shellGuidance = directBashAvailable
161
173
  ? "Direct bash is available, and babysit_run can supervise longer commands."
162
174
  : babysitRunAvailable
163
- ? "Direct bash is unavailable; run shell commands with babysit_run { command }."
175
+ ? "Direct bash is unavailable; run shell commands with babysit_run { description, command }."
164
176
  : "No shell execution tool is available in this task's tool allowlist; do not attempt shell commands.";
165
177
  const nestingGuidance = depth >= maxDepth
166
178
  ? `You are at the inherited subagent depth limit (${depth}/${maxDepth}); do not attempt to spawn another subagent.`
@@ -521,6 +533,8 @@ interface Meta {
521
533
  remoteId?: string;
522
534
  remoteTerminalState?: string;
523
535
  kind: "process" | "subagent";
536
+ /** Human-facing purpose. Optional only for metadata written by older versions. */
537
+ description?: string;
524
538
  // process
525
539
  name?: string;
526
540
  command?: string;
@@ -568,6 +582,11 @@ interface Meta {
568
582
  }
569
583
 
570
584
  const metaDir = () => path.join(ROOT, "meta");
585
+
586
+ /** Prefer the human-facing purpose while preserving previews for old sessions. */
587
+ export function sessionPreview(meta: Pick<Meta, "description" | "kind" | "task" | "command"> | undefined): string {
588
+ return meta?.description?.trim() || (meta?.kind === "subagent" ? meta.task : meta?.command) || "";
589
+ }
571
590
  const localLogPath = (id: string) => path.join(ROOT, "sessions", id, "output.log");
572
591
  const logPath = (id: string) => {
573
592
  const meta = readMeta(id);
@@ -1366,6 +1385,17 @@ function escapeCommandForDisplay(command: string): string {
1366
1385
  );
1367
1386
  }
1368
1387
 
1388
+ function renderOperationCall(name: string, args: Record<string, unknown> | undefined, theme: Theme) {
1389
+ const { description, ...operationArgs } = args ?? {};
1390
+ const purpose = typeof description === "string" ? description.trim() : "";
1391
+ const title = theme.fg("muted", theme.bold(name));
1392
+ const heading = purpose
1393
+ ? `${title} ${theme.fg("warning", theme.bold(escapeCommandForDisplay(purpose)))}`
1394
+ : title;
1395
+ const raw = Object.keys(operationArgs).length ? JSON.stringify(operationArgs) : "";
1396
+ return new Text(heading + (raw ? `\n${theme.fg("muted", escapeCommandForDisplay(raw))}` : ""), 0, 0);
1397
+ }
1398
+
1369
1399
  export function readableLaunchCommand(command: string): string {
1370
1400
  // Older sessions quoted every argv element, including safe paths and flags.
1371
1401
  if (!command.startsWith("'") || !command.endsWith("'") || !command.includes("' '")) return command;
@@ -1397,6 +1427,7 @@ export interface ProcessCompletionNotice {
1397
1427
  status: CompletionStatus;
1398
1428
  runtime: string;
1399
1429
  summary: string;
1430
+ description?: string;
1400
1431
  command: string | undefined;
1401
1432
  logPath: string;
1402
1433
  output: string;
@@ -1414,6 +1445,7 @@ interface ProcessCompletionMessage {
1414
1445
  runtime?: string;
1415
1446
  logPath?: string;
1416
1447
  command?: string;
1448
+ description?: string;
1417
1449
  count: number;
1418
1450
  totalCount: number;
1419
1451
  remainingCount: number;
@@ -1424,6 +1456,7 @@ interface ProcessCompletionMessage {
1424
1456
  status: CompletionStatus;
1425
1457
  runtime: string;
1426
1458
  command: string | undefined;
1459
+ description?: string;
1427
1460
  logPath: string;
1428
1461
  }>;
1429
1462
  };
@@ -1465,9 +1498,11 @@ export function buildProcessCompletionMessage(
1465
1498
  count < totalCount
1466
1499
  ? `\n\n${totalCount - count} completion${totalCount - count === 1 ? "" : "s"} deferred to the next poll.`
1467
1500
  : "";
1501
+ const noticeSummary = (notice: ProcessCompletionNotice) =>
1502
+ notice.summary + (notice.description ? `\nDescription: ${summarizeNotificationCommand(notice.description)}` : "");
1468
1503
  const renderCompact = (batch: ProcessCompletionNotice[]) =>
1469
1504
  header(batch.length) +
1470
- batch.map((notice) => `${notice.summary}\nLog: ${notice.logPath}`).join("\n\n") +
1505
+ batch.map((notice) => `${noticeSummary(notice)}\nLog: ${notice.logPath}`).join("\n\n") +
1471
1506
  deferred(batch.length) +
1472
1507
  footer;
1473
1508
 
@@ -1492,7 +1527,7 @@ export function buildProcessCompletionMessage(
1492
1527
  }
1493
1528
 
1494
1529
  const blockBase = (notice: ProcessCompletionNotice) =>
1495
- `${notice.summary}\nCommand: ${summarizeNotificationCommand(notice.command)}\nLog: ${notice.logPath}`;
1530
+ `${noticeSummary(notice)}\nCommand: ${summarizeNotificationCommand(notice.command)}\nLog: ${notice.logPath}`;
1496
1531
  const outputs = batch.map((notice) =>
1497
1532
  notice.output.startsWith("\n\nOutput:") ? AGGREGATE_OUTPUT_OMISSION : notice.output,
1498
1533
  );
@@ -1533,6 +1568,7 @@ export function buildProcessCompletionMessage(
1533
1568
  status: notice.status,
1534
1569
  runtime: notice.runtime,
1535
1570
  command: notice.command,
1571
+ description: notice.description,
1536
1572
  logPath: notice.logPath,
1537
1573
  }));
1538
1574
  const single = totalCount === 1 ? batch[0] : undefined;
@@ -1548,6 +1584,7 @@ export function buildProcessCompletionMessage(
1548
1584
  runtime: single?.runtime,
1549
1585
  logPath: single?.logPath,
1550
1586
  command: single?.command,
1587
+ description: single?.description,
1551
1588
  count: batch.length,
1552
1589
  totalCount,
1553
1590
  remainingCount: totalCount - batch.length,
@@ -2109,6 +2146,7 @@ async function reserveUniqueSessionId(name: string): Promise<string> {
2109
2146
  }
2110
2147
 
2111
2148
  interface ProcOpts {
2149
+ description: string;
2112
2150
  name?: string;
2113
2151
  command: string;
2114
2152
  cwd?: string;
@@ -2161,7 +2199,7 @@ async function spawnProcess(opts: ProcOpts): Promise<{ id: string } | { error: s
2161
2199
  const reservedId = remote ? await reserveUniqueSessionId(opts.name ?? "remote-proc") : opts.name ? await reserveUniqueSessionId(opts.name) : undefined;
2162
2200
  const remoteId = remote ? remoteProcessId(ROOT) : undefined;
2163
2201
  if (reservedId) bsArgs.push("--id", remoteId ?? reservedId);
2164
- if (remote && !writeMeta(reservedId!, { kind: "process", host: opts.host, remoteId, name: opts.name ?? reservedId, command: opts.command, notificationGroup: opts.notificationGroup, notified: false, startedAt: Date.now() })) {
2202
+ if (remote && !writeMeta(reservedId!, { kind: "process", host: opts.host, remoteId, name: opts.name ?? reservedId, description: opts.description, command: opts.command, notificationGroup: opts.notificationGroup, notified: false, startedAt: Date.now() })) {
2165
2203
  reservedSessionIds.delete(reservedId!);
2166
2204
  return { error: "Cannot persist remote identity; process was not started." };
2167
2205
  }
@@ -2196,6 +2234,7 @@ async function spawnProcess(opts: ProcOpts): Promise<{ id: string } | { error: s
2196
2234
  writeMeta(id, {
2197
2235
  kind: "process",
2198
2236
  name: opts.name ?? id,
2237
+ description: opts.description,
2199
2238
  command: opts.command,
2200
2239
  notificationGroup: opts.notificationGroup,
2201
2240
  notified: false,
@@ -2251,6 +2290,7 @@ function discardDelivery(delivery: DeliverableMessage): void {
2251
2290
  }
2252
2291
 
2253
2292
  interface SubagentOpts {
2293
+ description: string;
2254
2294
  name?: string;
2255
2295
  agent?: AgentConfig;
2256
2296
  task: string;
@@ -2381,6 +2421,7 @@ async function spawnSubagent(
2381
2421
  writeMeta(id, {
2382
2422
  kind: "subagent",
2383
2423
  name: opts.name ?? id,
2424
+ description: opts.description,
2384
2425
  task: opts.task,
2385
2426
  launchCommand,
2386
2427
  notified: true,
@@ -2470,6 +2511,7 @@ async function spawnSubagent(
2470
2511
  writeMeta(id, {
2471
2512
  kind: "subagent",
2472
2513
  name: opts.name ?? id,
2514
+ description: opts.description,
2473
2515
  task: opts.task,
2474
2516
  launchCommand,
2475
2517
  promptOffset: resp.offset,
@@ -2566,6 +2608,7 @@ export interface WidgetSessionListState {
2566
2608
 
2567
2609
  export interface WidgetSessionDisplay {
2568
2610
  id: string;
2611
+ description?: string;
2569
2612
  header: string;
2570
2613
  tail: string[];
2571
2614
  viewable: boolean;
@@ -2603,7 +2646,7 @@ export function createWidgetComponent(
2603
2646
  const pushSession = (session: WidgetSessionDisplay) => {
2604
2647
  const target = session.viewable ? { type: "session" as const, id: session.id } : undefined;
2605
2648
  const detail = session.details?.[0];
2606
- const preview = detail?.startsWith("task: ") ? detail.slice(6) : detail ?? session.tail.at(-1);
2649
+ const preview = session.description ?? (detail?.startsWith("task: ") ? detail.slice(6) : detail ?? session.tail.at(-1));
2607
2650
  push(`${session.header}${preview ? ` ${preview}` : ""}`, target, "…");
2608
2651
  };
2609
2652
 
@@ -2689,8 +2732,9 @@ export function createSessionWidget(options: {
2689
2732
  const log = options.readLog(selectedId);
2690
2733
  const session = displays.find((session) => session.id === selectedId);
2691
2734
  const header = session?.header ?? selectedId;
2692
- const detail = session?.details?.[0] ?? "";
2693
- const context = detail.startsWith("task: ") ? detail.slice(6) : detail;
2735
+ const context = (session?.details ?? [])
2736
+ .map((detail) => detail.startsWith("task: ") ? detail.slice(6) : detail)
2737
+ .join("\n");
2694
2738
  if (log.file) {
2695
2739
  if (!source || source.file !== log.file || source.isAgent !== log.isAgent)
2696
2740
  source = new FileLogSource(log.file, log.isAgent);
@@ -3265,7 +3309,7 @@ async function waitForTask(
3265
3309
  ok: completed.ok,
3266
3310
  text:
3267
3311
  `Subagent ${id} finished its task (${stats}).\n` +
3268
- `${subagentReuseHint(readMeta(id)?.reapAfter)} Follow-up: babysit_send { id: "${id}" }, ` +
3312
+ `${subagentReuseHint(readMeta(id)?.reapAfter)} Follow-up: babysit_send { description: "Assign a follow-up task", id: "${id}" }, ` +
3269
3313
  `or babysit_kill when done.\n\n${completed.body}`,
3270
3314
  status: st,
3271
3315
  progress: prog,
@@ -3540,7 +3584,7 @@ async function waitForExit(
3540
3584
  kind: "exited",
3541
3585
  ok,
3542
3586
  text:
3543
- `Process ${id}${meta?.command ? ` (${summarizeNotificationCommand(meta.command)})` : ""} ` +
3587
+ `Process ${id}${sessionPreview(meta) ? ` (${summarizeNotificationCommand(sessionPreview(meta))})` : ""} ` +
3544
3588
  (workerDead
3545
3589
  ? "worker-dead: the babysit supervisor disappeared without an exit status"
3546
3590
  : ok ? "completed successfully" : `exited with code ${st.exit_code ?? "?"}`) +
@@ -3693,7 +3737,14 @@ export default function (pi: ExtensionAPI) {
3693
3737
  const execute = tool.execute;
3694
3738
  pi.registerTool({
3695
3739
  ...tool,
3740
+ description: tool.description + " Always provide a short human-readable `description` explaining the purpose of this operation.",
3741
+ promptGuidelines: [
3742
+ "Every babysit tool call requires a short description explaining its purpose for the user. Operation descriptions for check/send/wait/kill do not change the session's original description.",
3743
+ ...(tool.promptGuidelines ?? []),
3744
+ ],
3745
+ renderCall: tool.renderCall ?? ((args, theme) => renderOperationCall(tool.name, args as Record<string, unknown>, theme)),
3696
3746
  async execute(toolCallId, params, signal, onUpdate, ctx) {
3747
+ requireDescription((params as { description?: unknown }).description);
3697
3748
  const result = await execute(toolCallId, params, signal, onUpdate, ctx);
3698
3749
  if ((result as typeof result & { isError?: boolean }).isError === true) {
3699
3750
  declaredToolErrors.add(toolCallId);
@@ -3852,6 +3903,7 @@ export default function (pi: ExtensionAPI) {
3852
3903
  runtime,
3853
3904
  summary,
3854
3905
  command: meta.command,
3906
+ description: meta.description,
3855
3907
  logPath: logPath(session.id),
3856
3908
  output,
3857
3909
  });
@@ -3873,7 +3925,7 @@ export default function (pi: ExtensionAPI) {
3873
3925
  if (!latest || latest.transportError || latest.state === "running") return [];
3874
3926
  if (!isNotificationGroupReady(current, finalSessions, readMeta)) return [];
3875
3927
  metadataById.set(notice.id, current);
3876
- return [{ ...notice, command: current.command }];
3928
+ return [{ ...notice, command: current.command, description: current.description }];
3877
3929
  });
3878
3930
  // Output collection above yields to the event loop. Re-check idleness so a
3879
3931
  // newly-started agent turn cannot receive a duplicate completion mid-turn.
@@ -3897,7 +3949,7 @@ export default function (pi: ExtensionAPI) {
3897
3949
  ): Promise<void> {
3898
3950
  if (shouldDeferCompletionNotification(ctx.isIdle())) return;
3899
3951
  const sessions = snapshot ?? (await listSessions()).sessions;
3900
- const ready: Array<{ id: string; offset: number; state: string; summary: string }> = [];
3952
+ const ready: Array<{ id: string; offset: number; state: string; summary: string; description?: string }> = [];
3901
3953
  for (const session of sessions) {
3902
3954
  const meta = readMeta(session.id);
3903
3955
  if (!shouldDeliverSubagentCompletion(meta)) continue;
@@ -3920,7 +3972,7 @@ export default function (pi: ExtensionAPI) {
3920
3972
  const summary = progress.done
3921
3973
  ? `task settled; ${progress.turns} turns, ${progress.toolCallCount} tools, $${progress.cost.toFixed(4)}`
3922
3974
  : `worker ${session.state} with exit code ${session.exit_code ?? "?"}; partial usage $${progress.cost.toFixed(4)}`;
3923
- ready.push({ id: session.id, offset, state: session.state, summary });
3975
+ ready.push({ id: session.id, offset, state: session.state, summary, description: meta.description });
3924
3976
  }
3925
3977
  if (ready.length === 0 || shouldDeferCompletionNotification(ctx.isIdle())) return;
3926
3978
 
@@ -3934,10 +3986,10 @@ export default function (pi: ExtensionAPI) {
3934
3986
  customType: "pi-babysit-subagent-ready",
3935
3987
  content:
3936
3988
  `${deliverable.length === 1 ? "A background subagent is" : `${deliverable.length} background subagents are`} ready to collect:\n` +
3937
- deliverable.map(({ id, summary }) => `- ${id}: ${summary}`).join("\n") +
3989
+ deliverable.map(({ id, summary, description }) => `- ${id}${description ? ` (${summarizeNotificationCommand(description)})` : ""}: ${summary}`).join("\n") +
3938
3990
  "\nCall babysit_wait now to retrieve the answer and charge nested usage before finishing the parent task.",
3939
3991
  display: true,
3940
- details: { subagents: deliverable.map(({ id, state }) => ({ id, state })) },
3992
+ details: { subagents: deliverable.map(({ id, state, description }) => ({ id, state, description })) },
3941
3993
  },
3942
3994
  { triggerTurn: true, deliverAs: "steer" },
3943
3995
  );
@@ -3992,12 +4044,16 @@ export default function (pi: ExtensionAPI) {
3992
4044
  const id = session.id;
3993
4045
  const meta = readMeta(id);
3994
4046
  const isSubagent = meta?.kind === "subagent";
4047
+ const description = meta?.description?.trim()
4048
+ ? theme.fg("warning", theme.bold(escapeCommandForDisplay(meta.description.trim())))
4049
+ : undefined;
3995
4050
  const details = isSubagent
3996
4051
  ? [
3997
- ...(meta?.task ? [`task: ${escapeCommandForDisplay(meta.task)}`] : []),
3998
- ...(meta?.launchCommand ? [theme.bold(escapeCommandForDisplay(readableLaunchCommand(meta.launchCommand)))] : []),
4052
+ ...(meta?.task ? [theme.fg("muted", `task: ${escapeCommandForDisplay(meta.task)}`)] : []),
4053
+ ...(meta?.launchCommand ? [theme.fg("muted", escapeCommandForDisplay(readableLaunchCommand(meta.launchCommand)))] : []),
3999
4054
  ]
4000
- : meta?.command ? [theme.bold(escapeCommandForDisplay(meta.command))] : [];
4055
+ : meta?.command ? [theme.fg("muted", escapeCommandForDisplay(meta.command))] : [];
4056
+ if (description) details.unshift(description);
4001
4057
  if (meta?.host) details.unshift(`host: ${meta.host}${session.transportError ? " (status unknown)" : ""} · ${attachCmd(id)}`);
4002
4058
  if (session.state === "running") {
4003
4059
  const state: WidgetSessionState = isSubagent && progressById.get(id)?.done ? "idle" : "running";
@@ -4005,6 +4061,7 @@ export default function (pi: ExtensionAPI) {
4005
4061
  id,
4006
4062
  header: renderWidgetSessionHeader(id, isSubagent ? "agent" : "process", state, elapsedOf(id), theme) + (session.transportError ? " · status unknown" : ""),
4007
4063
  tail: activeTails.get(id) ?? [],
4064
+ description,
4008
4065
  viewable: true,
4009
4066
  active: true,
4010
4067
  details,
@@ -4020,6 +4077,7 @@ export default function (pi: ExtensionAPI) {
4020
4077
  theme,
4021
4078
  ),
4022
4079
  details,
4080
+ description,
4023
4081
  // Only read/parse visible sessions on demand.
4024
4082
  get tail() {
4025
4083
  try {
@@ -4115,25 +4173,27 @@ export default function (pi: ExtensionAPI) {
4115
4173
  success?: boolean;
4116
4174
  exitCode?: number | null;
4117
4175
  command?: string;
4176
+ description?: string;
4118
4177
  count?: number;
4119
4178
  };
4120
4179
  const status =
4121
4180
  d.status ??
4122
4181
  (d.success ? "success" : d.exitCode == null ? "terminated" : "failed");
4123
- const payload = d.command
4124
- ? ` ${summarizeNotificationCommand(d.command)}`
4125
- : d.count && d.count > 1
4126
- ? ` ×${d.count}`
4127
- : "";
4128
- const header = theme.fg(
4129
- "warning",
4130
- theme.bold(`babysit_run COMMAND${payload}`),
4131
- );
4182
+ const payload = d.description?.trim()
4183
+ ? theme.fg("warning", theme.bold(summarizeNotificationCommand(d.description)))
4184
+ : d.command
4185
+ ? theme.fg("muted", summarizeNotificationCommand(d.command))
4186
+ : d.count && d.count > 1
4187
+ ? theme.fg("muted", `×${d.count}`)
4188
+ : "";
4189
+ const header = theme.fg("muted", theme.bold("babysit_run COMMAND")) + (payload ? ` ${payload}` : "");
4132
4190
  const box = new Box(1, 1, (t) => theme.bg("toolSuccessBg", t));
4133
4191
  box.addChild(new Text(header, 0, 0));
4134
4192
  box.addChild(new Text(renderStatus(status, theme), 0, 0));
4135
4193
  if (expanded && content) {
4136
- box.addChild(new Text(theme.fg("toolOutput", content), 0, 0));
4194
+ box.addChild(new Text(content.split("\n").map((line) =>
4195
+ theme.fg(line.startsWith("Command: ") ? "muted" : "toolOutput", line),
4196
+ ).join("\n"), 0, 0));
4137
4197
  }
4138
4198
  return box;
4139
4199
  },
@@ -4281,7 +4341,7 @@ export default function (pi: ExtensionAPI) {
4281
4341
  block: true,
4282
4342
  reason:
4283
4343
  `This bash command tries to run in the background. Use babysit_run instead, e.g. ` +
4284
- `babysit_run({ name: "background-process", command: ${JSON.stringify(command.replace(/\s*&\s*$/, ""))} })`,
4344
+ `babysit_run({ description: "Describe this background process's purpose", name: "background-process", command: ${JSON.stringify(command.replace(/\s*&\s*$/, ""))} })`,
4285
4345
  };
4286
4346
  }
4287
4347
  if (isAllowedDirectBash(command)) return;
@@ -4289,8 +4349,8 @@ export default function (pi: ExtensionAPI) {
4289
4349
  block: true,
4290
4350
  reason:
4291
4351
  "Use babysit_run for shell commands so output is supervised and captured outside model context. " +
4292
- "Inspect an existing session log with babysit_check { id, lines, pattern? }. " +
4293
- `Retry as babysit_run({ command: ${JSON.stringify(command)} }).`,
4352
+ "Inspect an existing session log with babysit_check { description, id, lines, pattern? }. " +
4353
+ `Retry with a short purpose description as babysit_run({ description: "Explain the purpose of this command", command: ${JSON.stringify(command)} }).`,
4294
4354
  };
4295
4355
  });
4296
4356
 
@@ -4318,6 +4378,7 @@ export default function (pi: ExtensionAPI) {
4318
4378
  return prepareBabysitRunArguments(args) as never;
4319
4379
  },
4320
4380
  parameters: Type.Object({
4381
+ description: descriptionSchema,
4321
4382
  host: Type.Optional(Type.String({ description: "Process SSH destination/config alias (default local). Remote subagents are unsupported." })),
4322
4383
  cwd: Type.Optional(Type.String({ description: "Process working directory. Remote default is login home; never inferred from local cwd." })),
4323
4384
  command: Type.Optional(
@@ -4448,6 +4509,7 @@ export default function (pi: ExtensionAPI) {
4448
4509
 
4449
4510
  }),
4450
4511
  async execute(_id, params, _signal, _onUpdate, ctx) {
4512
+ const description = params.description.trim();
4451
4513
  await requireBabysit();
4452
4514
  const isSubagent = params.profile === "subagent";
4453
4515
  validateProcessHost(params.host);
@@ -4555,6 +4617,7 @@ export default function (pi: ExtensionAPI) {
4555
4617
  ? { pattern: params.returnPattern, lines: params.returnLines, maxBytes: params.maxBytes }
4556
4618
  : undefined;
4557
4619
  const spawnOpts: ProcOpts = {
4620
+ description,
4558
4621
  name: params.name,
4559
4622
  command: params.command as string,
4560
4623
  host: params.host,
@@ -4605,6 +4668,7 @@ export default function (pi: ExtensionAPI) {
4605
4668
  kind: "process",
4606
4669
  command: params.command,
4607
4670
  logPath: logPath(res.id),
4671
+ description,
4608
4672
  retried,
4609
4673
  status: outcomeStatus(outcome),
4610
4674
  },
@@ -4641,6 +4705,7 @@ export default function (pi: ExtensionAPI) {
4641
4705
  kind: "process",
4642
4706
  command: params.command,
4643
4707
  logPath: logPath(res.id),
4708
+ description,
4644
4709
  retried,
4645
4710
  status: outcomeStatus(outcome),
4646
4711
  },
@@ -4657,9 +4722,9 @@ export default function (pi: ExtensionAPI) {
4657
4722
  type: "text",
4658
4723
  text:
4659
4724
  `${retried ? "Retried once after external worker death.\n" : ""}` +
4660
- `Process started (id: ${res.id}). ${NOTIFY_MARKER}\nLog: ${logPath(res.id)}\n${nextStep}\n` +
4661
- `Inspect: babysit_check { id: "${res.id}" } (screen: true for TUIs) · ` +
4662
- `Wait: babysit_wait { id: "${res.id}" } · Kill: babysit_kill { id: "${res.id}" }\n` +
4725
+ `Process started (id: ${res.id}). ${NOTIFY_MARKER}\nDescription: ${escapeCommandForDisplay(description)}\nLog: ${logPath(res.id)}\n${nextStep}\n` +
4726
+ `Inspect: babysit_check { description: "Inspect session output", id: "${res.id}" } (screen: true for TUIs) · ` +
4727
+ `Wait: babysit_wait { description: "Wait for completion", id: "${res.id}" } · Kill: babysit_kill { description: "Stop this session", id: "${res.id}" }\n` +
4663
4728
  `Human can watch/take over: /babysit`,
4664
4729
  },
4665
4730
  ],
@@ -4667,6 +4732,7 @@ export default function (pi: ExtensionAPI) {
4667
4732
  id: res.id,
4668
4733
  kind: "process",
4669
4734
  command: params.command,
4735
+ description,
4670
4736
  logPath: logPath(res.id),
4671
4737
  retried,
4672
4738
  status: "started" satisfies DisplayStatus,
@@ -4702,6 +4768,7 @@ export default function (pi: ExtensionAPI) {
4702
4768
  // The branch above guarantees a successful plan in subagent mode.
4703
4769
  const subagentNesting = nesting as Extract<SubagentSpawnPlan, { allowed: true }>;
4704
4770
  const res = await spawnSubagent({
4771
+ description,
4705
4772
  name: params.name,
4706
4773
  agent,
4707
4774
  task: params.task as string,
@@ -4750,6 +4817,7 @@ export default function (pi: ExtensionAPI) {
4750
4817
  agent: agent?.name,
4751
4818
  model: res.model,
4752
4819
  task: params.task,
4820
+ description,
4753
4821
  depth: subagentNesting.childDepth,
4754
4822
  maxDepth: subagentNesting.maxDepth,
4755
4823
  status: outcomeStatus(outcome),
@@ -4762,9 +4830,9 @@ export default function (pi: ExtensionAPI) {
4762
4830
  type: "text",
4763
4831
  text:
4764
4832
  `Subagent started (id: ${res.id})${agent ? ` [agent: ${agent.name}]` : ""}${res.model ? ` [model: ${res.model}]` : ""} [depth: ${subagentNesting.childDepth}/${subagentNesting.maxDepth}].\n` +
4765
- `Task accepted — running in the background. You MUST collect it with babysit_wait before finishing the parent task; use foreground: true next time when no independent work is available.\n` +
4766
- `Progress: babysit_check { id: "${res.id}" } (only when inspection is needed)\n` +
4767
- `Collect: babysit_wait { id: "${res.id}" }\n` +
4833
+ `Description: ${escapeCommandForDisplay(description)}\nTask accepted — running in the background. You MUST collect it with babysit_wait before finishing the parent task; use foreground: true next time when no independent work is available.\n` +
4834
+ `Progress: babysit_check { description: "Inspect agent progress", id: "${res.id}" } (only when inspection is needed)\n` +
4835
+ `Collect: babysit_wait { description: "Collect the agent result", id: "${res.id}" }\n` +
4768
4836
  `Human can watch/steer: /babysit (pick ${res.id})`,
4769
4837
  },
4770
4838
  ],
@@ -4775,25 +4843,30 @@ export default function (pi: ExtensionAPI) {
4775
4843
  agent: agent?.name,
4776
4844
  model: res.model,
4777
4845
  task: params.task,
4846
+ description,
4778
4847
  depth: subagentNesting.childDepth,
4779
4848
  maxDepth: subagentNesting.maxDepth,
4780
4849
  status: "started" satisfies DisplayStatus,
4781
4850
  },
4782
4851
  };
4783
4852
  },
4784
- // Make the execution kind and payload identifiable before the result arrives.
4785
- // Use semantic theme colors rather than the fallback gray tool-call text.
4853
+ // The human-readable purpose is primary; the executable payload is secondary.
4854
+ // Legacy calls without descriptions still render their original payload safely.
4786
4855
  renderCall(args, theme) {
4787
4856
  const isSubagent = args.profile === "subagent";
4788
4857
  const kind = isSubagent ? "AGENT" : "COMMAND";
4789
4858
  const payload = isSubagent ? args.task : args.command;
4790
- const agent = isSubagent && args.agent ? ` [${args.agent}]` : "";
4791
- const title = theme.bold(`babysit_run ${kind}`);
4792
- const detail =
4793
- typeof payload === "string" && payload.length > 0
4794
- ? ` ${escapeCommandForDisplay(payload)}`
4795
- : "";
4796
- return new Text(theme.fg("warning", `${title}${agent}${detail}`), 0, 0);
4859
+ const agent = isSubagent && args.agent ? ` [${escapeCommandForDisplay(args.agent)}]` : "";
4860
+ const title = theme.fg("muted", `${theme.bold(`babysit_run ${kind}`)}${agent}`);
4861
+ const description = typeof args.description === "string" ? args.description.trim() : "";
4862
+ const raw = typeof payload === "string" ? escapeCommandForDisplay(payload) : "";
4863
+ const heading = description
4864
+ ? `${title} ${theme.fg("warning", theme.bold(escapeCommandForDisplay(description)))}`
4865
+ : `${title}${raw ? theme.fg("muted", ` ${raw}`) : ""}`;
4866
+ const detail = description && raw
4867
+ ? `\n${theme.fg("muted", raw)}`
4868
+ : "";
4869
+ return new Text(heading + detail, 0, 0);
4797
4870
  },
4798
4871
  renderResult(result, { expanded, isPartial }, theme, context) {
4799
4872
  const details = (result.details ?? {}) as {
@@ -4843,6 +4916,7 @@ export default function (pi: ExtensionAPI) {
4843
4916
  "Do NOT poll this while merely waiting for a process to end — the exit notification is automatic.",
4844
4917
  promptSnippet: "Check status/progress of babysit sessions (processes and subagents)",
4845
4918
  parameters: Type.Object({
4919
+ description: descriptionSchema,
4846
4920
  id: Type.Optional(Type.String({ description: "Session id. Omit to list sessions." })),
4847
4921
  state: Type.Optional(
4848
4922
  StringEnum(["running", "terminal", "all"] as const, {
@@ -4932,7 +5006,7 @@ export default function (pi: ExtensionAPI) {
4932
5006
  kind === "subagent" && meta?.depth != null
4933
5007
  ? ` depth=${meta.depth}/${meta.maxDepth ?? "?"}`
4934
5008
  : "";
4935
- const what = (kind === "subagent" ? meta?.task : meta?.command) ?? "";
5009
+ const what = escapeCommandForDisplay(sessionPreview(meta));
4936
5010
  const preview = what.length > 60 ? `${what.slice(0, 57)}…` : what;
4937
5011
  return `${s.id} [${kind}${s.host ? ` host=${s.host}` : ""}] ${s.transportError ? "unknown" : s.state}${ec}${depth}${flag}${preview ? ` — ${preview}` : ""}`;
4938
5012
  });
@@ -5000,6 +5074,7 @@ export default function (pi: ExtensionAPI) {
5000
5074
  if (el) header += ` elapsed=${el}`;
5001
5075
  }
5002
5076
  if (st.exit_code != null) header += ` exit_code=${st.exit_code}`;
5077
+ if (meta?.description) header += `\ndescription: ${escapeCommandForDisplay(meta.description)}`;
5003
5078
  if (meta?.command) header += `\ncommand: ${summarizeNotificationCommand(meta.command)}`;
5004
5079
  header += `\nlog: ${logPath(params.id)}`;
5005
5080
  if (st.note) header += ` ⚑ ${st.note}`;
@@ -5045,6 +5120,7 @@ export default function (pi: ExtensionAPI) {
5045
5120
  if (prog.tokens != null) header += ` ctx=${prog.tokens}`;
5046
5121
  if (prog.modelCalls > 0) header += ` usage=${prog.usageTokens} $${prog.cost.toFixed(4)}`;
5047
5122
  if (st.note) header += ` ⚑ ${st.note}`;
5123
+ if (meta.description) header += `\ndescription: ${escapeCommandForDisplay(meta.description)}`;
5048
5124
  parts.push(header);
5049
5125
 
5050
5126
  if (prog.errorMsg) parts.push(`⚠ error: ${clip(prog.errorMsg, ANSWER_MAX_BYTES)}`);
@@ -5081,11 +5157,12 @@ export default function (pi: ExtensionAPI) {
5081
5157
  description:
5082
5158
  "Send input to a babysit session. Process: `text` types a line into its stdin (PTY), " +
5083
5159
  "`keys` presses named keys (Enter, Tab, Esc, Up/Down/Left/Right, C-c, F1…) — use with " +
5084
- "babysit_check { screen: true } to drive interactive programs. Subagent: `text` is " +
5160
+ "babysit_check { description, screen: true } to drive interactive programs. Subagent: `text` is " +
5085
5161
  "STEERING while it works, or a NEW TASK after the current task settles (mode: auto/steer/task) — this " +
5086
5162
  "is how you resume a finished subagent with full context.",
5087
5163
  promptSnippet: "Send text/keys to a process, or steering/follow-up tasks to a subagent",
5088
5164
  parameters: Type.Object({
5165
+ description: descriptionSchema,
5089
5166
  id: Type.String({ description: "Session id." }),
5090
5167
  text: Type.Optional(
5091
5168
  Type.String({ description: "Text to send (a line for processes; a message for subagents)." }),
@@ -5157,7 +5234,7 @@ export default function (pi: ExtensionAPI) {
5157
5234
  content: [
5158
5235
  {
5159
5236
  type: "text",
5160
- text: `Sent to ${params.id}. Read the reaction with babysit_check { id: "${params.id}"${params.keys?.length ? ", screen: true" : ""} } — don't expect the echo of your own input.`,
5237
+ text: `Sent to ${params.id}. Read the reaction with babysit_check { description: "Inspect the response to input", id: "${params.id}"${params.keys?.length ? ", screen: true" : ""} } — don't expect the echo of your own input.`,
5161
5238
  },
5162
5239
  ],
5163
5240
  details: { kind: "process" },
@@ -5274,6 +5351,11 @@ export default function (pi: ExtensionAPI) {
5274
5351
  // New task → new bookkeeping window, so check/wait track THIS task.
5275
5352
  writeMeta(params.id, {
5276
5353
  kind: "subagent",
5354
+ name: meta?.name,
5355
+ description: meta?.description,
5356
+ launchCommand: meta?.launchCommand,
5357
+ reapAfter: meta?.reapAfter,
5358
+ startedAt: meta?.startedAt,
5277
5359
  task: params.text,
5278
5360
  // Start after the prompt-acceptance response. This excludes a
5279
5361
  // preceding run that settled between `send` and RPC acceptance.
@@ -5335,6 +5417,7 @@ export default function (pi: ExtensionAPI) {
5335
5417
  "the exit notification will resume you.",
5336
5418
  promptSnippet: "Block until session(s) finish — process exit / output pattern / subagent task done",
5337
5419
  parameters: Type.Object({
5420
+ description: descriptionSchema,
5338
5421
  id: Type.Optional(Type.String({ description: "Session id (single wait)." })),
5339
5422
  ids: Type.Optional(
5340
5423
  Type.Array(Type.String(), {
@@ -5481,7 +5564,10 @@ export default function (pi: ExtensionAPI) {
5481
5564
  label: "Babysit: kill",
5482
5565
  description: "Terminate a babysit session (process or subagent).",
5483
5566
  promptSnippet: "Terminate a babysit session",
5484
- parameters: Type.Object({ id: Type.String({ description: "Session id." }) }),
5567
+ parameters: Type.Object({
5568
+ description: descriptionSchema,
5569
+ id: Type.String({ description: "Session id." }),
5570
+ }),
5485
5571
  async execute(_id, params, _signal, _onUpdate, ctx) {
5486
5572
  await requireBabysit();
5487
5573
  // Prevent the exit poller racing a requested kill, but restore delivery
@@ -5593,9 +5679,7 @@ export default function (pi: ExtensionAPI) {
5593
5679
 
5594
5680
  const whatOf = (s: BsSession): string => {
5595
5681
  const meta = readMeta(s.id);
5596
- return ((meta?.kind === "subagent" ? meta.task : meta?.command) ?? "")
5597
- .replace(/\s+/g, " ")
5598
- .trim();
5682
+ return escapeCommandForDisplay(sessionPreview(meta).replace(/\s+/g, " ").trim());
5599
5683
  };
5600
5684
 
5601
5685
  // Labels must be unique for index mapping; the id makes them unique.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yusukeshib/pi-babysit",
3
- "version": "0.6.21",
3
+ "version": "0.6.23",
4
4
  "description": "Run any shell command and pi subagents under babysit, with context-safe captured output.",
5
5
  "keywords": [
6
6
  "pi-package",