@yusukeshib/pi-babysit 0.6.22 → 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 +29 -17
  2. package/index.ts +51 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -50,10 +50,13 @@ at every depth.
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
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 |
@@ -71,26 +74,35 @@ 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
 
74
- ## Describe every run
77
+ ## Describe every operation
75
78
 
76
- Every `babysit_run` requires a non-blank `description`, for both commands and
77
- subagents. Explain the purpose briefly in language the user can understand;
78
- `name` remains a stable session identifier, while `command` / `task` contains
79
- the actual execution instructions. Descriptions are trimmed before saving and
80
- are display metadata, not shell code or instructions sent to the subagent.
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.
81
87
 
82
88
  ```text
83
89
  babysit_run { description: "Run regression tests", command: "bun test", foreground: true }
84
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" }
85
95
  ```
86
96
 
87
97
  In Pi's main transcript the description is the prominent yellow heading, with
88
- the execution kind in muted gray. The full command or task is always visible
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
89
100
  in gray beneath it, even when tool results are collapsed. Session lists, the
90
- widget, and completion notifications also show the description. Older saved sessions and tool calls
91
- without one remain readable, falling back to their command or task. Callers
92
- must supply a description for new runs; blank or missing values are rejected
93
- before launch.
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.
94
106
 
95
107
  ## Remote processes over SSH
96
108
 
@@ -102,9 +114,9 @@ pi-babysit does not install remote software or copy credentials.
102
114
  ```text
103
115
  babysit_run { description: "Run tests on Linux", host: "yusuke-t4", cwd: "/home/yusuke/project", command: "bun test", foreground: true, returnLines: 30 }
104
116
  babysit_run { description: "Open a remote Python REPL", host: "user@server", command: "python3 -i", name: "remote-repl", continueAfterStart: true }
105
- babysit_check { id: "remote-repl", screen: true }
106
- babysit_send { id: "remote-repl", text: "print(42)" }
107
- babysit_kill { id: "remote-repl" }
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" }
108
120
  ```
109
121
 
110
122
  `host` omitted (or `"local"`) preserves local behavior. `cwd` is process-only:
@@ -204,8 +216,8 @@ pending for the next poll. Larger output stays out of model context. Inspect it
204
216
  through the session id without creating another shell session:
205
217
 
206
218
  ```text
207
- babysit_check { id: "cargo-test", lines: 50 }
208
- 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 }
209
221
  ```
210
222
 
211
223
  Tail and search results are capped at 200 lines and default to a 4 KB total
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
@@ -1373,6 +1385,17 @@ function escapeCommandForDisplay(command: string): string {
1373
1385
  );
1374
1386
  }
1375
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
+
1376
1399
  export function readableLaunchCommand(command: string): string {
1377
1400
  // Older sessions quoted every argv element, including safe paths and flags.
1378
1401
  if (!command.startsWith("'") || !command.endsWith("'") || !command.includes("' '")) return command;
@@ -3286,7 +3309,7 @@ async function waitForTask(
3286
3309
  ok: completed.ok,
3287
3310
  text:
3288
3311
  `Subagent ${id} finished its task (${stats}).\n` +
3289
- `${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}" }, ` +
3290
3313
  `or babysit_kill when done.\n\n${completed.body}`,
3291
3314
  status: st,
3292
3315
  progress: prog,
@@ -3714,7 +3737,14 @@ export default function (pi: ExtensionAPI) {
3714
3737
  const execute = tool.execute;
3715
3738
  pi.registerTool({
3716
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)),
3717
3746
  async execute(toolCallId, params, signal, onUpdate, ctx) {
3747
+ requireDescription((params as { description?: unknown }).description);
3718
3748
  const result = await execute(toolCallId, params, signal, onUpdate, ctx);
3719
3749
  if ((result as typeof result & { isError?: boolean }).isError === true) {
3720
3750
  declaredToolErrors.add(toolCallId);
@@ -4319,7 +4349,7 @@ export default function (pi: ExtensionAPI) {
4319
4349
  block: true,
4320
4350
  reason:
4321
4351
  "Use babysit_run for shell commands so output is supervised and captured outside model context. " +
4322
- "Inspect an existing session log with babysit_check { id, lines, pattern? }. " +
4352
+ "Inspect an existing session log with babysit_check { description, id, lines, pattern? }. " +
4323
4353
  `Retry with a short purpose description as babysit_run({ description: "Explain the purpose of this command", command: ${JSON.stringify(command)} }).`,
4324
4354
  };
4325
4355
  });
@@ -4330,13 +4360,11 @@ export default function (pi: ExtensionAPI) {
4330
4360
  label: "Babysit: run",
4331
4361
  description:
4332
4362
  "Run a supervised shell command, or start a reusable pi subagent with `profile: \"subagent\"`. " +
4333
- "Always provide a short human-readable `description` of what this run does and why. " +
4334
4363
  "Use `foreground` for results needed now; otherwise long commands notify on exit. Full logs stay on disk. " +
4335
4364
  "`returnPattern`/`returnLines` bound foreground output. Sessions support check, wait, send, and kill.",
4336
4365
  promptSnippet: "Run supervised commands or bounded pi subagents with context-safe logs",
4337
4366
  promptGuidelines: [
4338
4367
  "Use babysit_run for shell commands and give meaningful sessions a stable name; bundle tiny related read-only observations into one command.",
4339
- "Every babysit_run requires a short description explaining its purpose for the user, for both commands and subagents. Describe the actual work, not a generic 'run command' or 'start agent'.",
4340
4368
  "Use babysit_run foreground mode for one process or subagent whose result is needed now; never issue sibling foreground runs in parallel. For parallel process checks, start background runs with continueAfterStart and collect them with one multi-session babysit_wait.",
4341
4369
  "Use returnPattern/returnLines for noisy commands. During edit/fix loops run targeted checks first and one full validation suite at the end instead of repeating every full gate.",
4342
4370
  "After a background process starts, stop the turn for its automatic notification; never poll or sleep. Use continueAfterStart only for specific non-polling process work.",
@@ -4350,11 +4378,7 @@ export default function (pi: ExtensionAPI) {
4350
4378
  return prepareBabysitRunArguments(args) as never;
4351
4379
  },
4352
4380
  parameters: Type.Object({
4353
- description: Type.String({
4354
- minLength: 1,
4355
- pattern: "\\S",
4356
- description: "Required short human-readable purpose for this command or subagent. Explain what it does and why; shown prominently to the user.",
4357
- }),
4381
+ description: descriptionSchema,
4358
4382
  host: Type.Optional(Type.String({ description: "Process SSH destination/config alias (default local). Remote subagents are unsupported." })),
4359
4383
  cwd: Type.Optional(Type.String({ description: "Process working directory. Remote default is login home; never inferred from local cwd." })),
4360
4384
  command: Type.Optional(
@@ -4485,9 +4509,6 @@ export default function (pi: ExtensionAPI) {
4485
4509
 
4486
4510
  }),
4487
4511
  async execute(_id, params, _signal, _onUpdate, ctx) {
4488
- if (typeof params.description !== "string" || !params.description.trim()) {
4489
- throw new Error("`description` is required and must be a non-blank string for both commands and subagents.");
4490
- }
4491
4512
  const description = params.description.trim();
4492
4513
  await requireBabysit();
4493
4514
  const isSubagent = params.profile === "subagent";
@@ -4702,8 +4723,8 @@ export default function (pi: ExtensionAPI) {
4702
4723
  text:
4703
4724
  `${retried ? "Retried once after external worker death.\n" : ""}` +
4704
4725
  `Process started (id: ${res.id}). ${NOTIFY_MARKER}\nDescription: ${escapeCommandForDisplay(description)}\nLog: ${logPath(res.id)}\n${nextStep}\n` +
4705
- `Inspect: babysit_check { id: "${res.id}" } (screen: true for TUIs) · ` +
4706
- `Wait: babysit_wait { id: "${res.id}" } · Kill: babysit_kill { id: "${res.id}" }\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` +
4707
4728
  `Human can watch/take over: /babysit`,
4708
4729
  },
4709
4730
  ],
@@ -4810,8 +4831,8 @@ export default function (pi: ExtensionAPI) {
4810
4831
  text:
4811
4832
  `Subagent started (id: ${res.id})${agent ? ` [agent: ${agent.name}]` : ""}${res.model ? ` [model: ${res.model}]` : ""} [depth: ${subagentNesting.childDepth}/${subagentNesting.maxDepth}].\n` +
4812
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` +
4813
- `Progress: babysit_check { id: "${res.id}" } (only when inspection is needed)\n` +
4814
- `Collect: babysit_wait { id: "${res.id}" }\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` +
4815
4836
  `Human can watch/steer: /babysit (pick ${res.id})`,
4816
4837
  },
4817
4838
  ],
@@ -4895,6 +4916,7 @@ export default function (pi: ExtensionAPI) {
4895
4916
  "Do NOT poll this while merely waiting for a process to end — the exit notification is automatic.",
4896
4917
  promptSnippet: "Check status/progress of babysit sessions (processes and subagents)",
4897
4918
  parameters: Type.Object({
4919
+ description: descriptionSchema,
4898
4920
  id: Type.Optional(Type.String({ description: "Session id. Omit to list sessions." })),
4899
4921
  state: Type.Optional(
4900
4922
  StringEnum(["running", "terminal", "all"] as const, {
@@ -5135,11 +5157,12 @@ export default function (pi: ExtensionAPI) {
5135
5157
  description:
5136
5158
  "Send input to a babysit session. Process: `text` types a line into its stdin (PTY), " +
5137
5159
  "`keys` presses named keys (Enter, Tab, Esc, Up/Down/Left/Right, C-c, F1…) — use with " +
5138
- "babysit_check { screen: true } to drive interactive programs. Subagent: `text` is " +
5160
+ "babysit_check { description, screen: true } to drive interactive programs. Subagent: `text` is " +
5139
5161
  "STEERING while it works, or a NEW TASK after the current task settles (mode: auto/steer/task) — this " +
5140
5162
  "is how you resume a finished subagent with full context.",
5141
5163
  promptSnippet: "Send text/keys to a process, or steering/follow-up tasks to a subagent",
5142
5164
  parameters: Type.Object({
5165
+ description: descriptionSchema,
5143
5166
  id: Type.String({ description: "Session id." }),
5144
5167
  text: Type.Optional(
5145
5168
  Type.String({ description: "Text to send (a line for processes; a message for subagents)." }),
@@ -5211,7 +5234,7 @@ export default function (pi: ExtensionAPI) {
5211
5234
  content: [
5212
5235
  {
5213
5236
  type: "text",
5214
- 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.`,
5215
5238
  },
5216
5239
  ],
5217
5240
  details: { kind: "process" },
@@ -5328,6 +5351,11 @@ export default function (pi: ExtensionAPI) {
5328
5351
  // New task → new bookkeeping window, so check/wait track THIS task.
5329
5352
  writeMeta(params.id, {
5330
5353
  kind: "subagent",
5354
+ name: meta?.name,
5355
+ description: meta?.description,
5356
+ launchCommand: meta?.launchCommand,
5357
+ reapAfter: meta?.reapAfter,
5358
+ startedAt: meta?.startedAt,
5331
5359
  task: params.text,
5332
5360
  // Start after the prompt-acceptance response. This excludes a
5333
5361
  // preceding run that settled between `send` and RPC acceptance.
@@ -5389,6 +5417,7 @@ export default function (pi: ExtensionAPI) {
5389
5417
  "the exit notification will resume you.",
5390
5418
  promptSnippet: "Block until session(s) finish — process exit / output pattern / subagent task done",
5391
5419
  parameters: Type.Object({
5420
+ description: descriptionSchema,
5392
5421
  id: Type.Optional(Type.String({ description: "Session id (single wait)." })),
5393
5422
  ids: Type.Optional(
5394
5423
  Type.Array(Type.String(), {
@@ -5535,7 +5564,10 @@ export default function (pi: ExtensionAPI) {
5535
5564
  label: "Babysit: kill",
5536
5565
  description: "Terminate a babysit session (process or subagent).",
5537
5566
  promptSnippet: "Terminate a babysit session",
5538
- parameters: Type.Object({ id: Type.String({ description: "Session id." }) }),
5567
+ parameters: Type.Object({
5568
+ description: descriptionSchema,
5569
+ id: Type.String({ description: "Session id." }),
5570
+ }),
5539
5571
  async execute(_id, params, _signal, _onUpdate, ctx) {
5540
5572
  await requireBabysit();
5541
5573
  // Prevent the exit poller racing a requested kill, but restore delivery
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yusukeshib/pi-babysit",
3
- "version": "0.6.22",
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",