@yusukeshib/pi-babysit 0.2.2 → 0.2.4

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 +35 -0
  2. package/index.ts +244 -16
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -50,6 +50,7 @@ programs** (installers, wizards, REPLs): type with `babysit_send`
50
50
  | ---- | ------------ |
51
51
  | `babysit_run` | Start a process (`command`, optional `name`/`pty`/`timeout`/`idleTimeout`) or a subagent (`profile: "subagent"`, `task`, optional `agent`/`model`/`tools`). Non-blocking; returns a session id |
52
52
  | `babysit_check` | List all sessions, or inspect one: process → state + log tail (or `screen: true` for TUIs); subagent → live progress (turns, recent tool calls, partial answer) |
53
+ | `babysit_analyze` | Run a local JavaScript, Python, or shell analyzer over a process's complete captured log; only its bounded report returns to the model |
53
54
  | `babysit_send` | Process: type `text` / press `keys` into the PTY. Subagent: steer mid-run, or send a follow-up task when idle (`mode: auto/steer/task`) |
54
55
  | `babysit_wait` | Block until done: process exit (or `expect: "regex"` readiness marker), subagent task completion. Multi-wait: `ids` + `mode: "any"\|"all"` |
55
56
  | `babysit_kill` | Terminate a session (suppresses the exit notification) |
@@ -67,6 +68,40 @@ A `tool_call` hook also blocks bash commands that background themselves
67
68
  A minimal widget above the editor shows live counts
68
69
  (`N processes · M subagents working · K idle`).
69
70
 
71
+ ## Log reports without context flooding
72
+
73
+ A process run can include a `report` program. On completion, babysit writes the
74
+ complete recorded log to a temporary file, runs the program in a separate local
75
+ child process, and sends **only the program's bounded stdout** in the automatic
76
+ completion notification. This avoids putting a full test/build log in the model
77
+ context while preserving the original log for `babysit_check`, `/babysit`, or
78
+ `babysit attach`.
79
+
80
+ ```ts
81
+ babysit_run({
82
+ name: "test",
83
+ command: "npm test",
84
+ report: {
85
+ language: "javascript",
86
+ code: `
87
+ const failures = FILE_CONTENT.split("\\n")
88
+ .filter(line => /FAIL|Error:|✗/.test(line));
89
+ console.log(`failures: ${failures.length}`);
90
+ console.log(failures.slice(-20).join("\\n"));
91
+ `,
92
+ },
93
+ });
94
+ ```
95
+
96
+ JavaScript and Python reports receive `FILE_CONTENT` and `INPUT`; shell reports
97
+ read the log path from `$BABYSIT_REPORT_INPUT`. `report.timeout` defaults to
98
+ 30 seconds. Input is capped at 64 MiB and returned report output at 12 KB.
99
+ Use `babysit_analyze` with the same `language`, `code`, and optional `timeout`
100
+ fields to analyze a running process's output so far or re-analyze a completed
101
+ process. Report code executes with your local user permissions, just like any
102
+ other extension tool command; it is isolated from the pi extension host but is
103
+ not an OS security sandbox.
104
+
70
105
  ## How completion detection works
71
106
 
72
107
  - **Process**: a 2.5s poller watches for running→exited transitions and injects
package/index.ts CHANGED
@@ -26,7 +26,7 @@
26
26
  * surface (babysit_run/check/send/wait/kill) covers both, and domain knowledge
27
27
  * (RPC bookkeeping, byte offsets, parked-turn detection) stays in code.
28
28
  *
29
- * Tools (LLM): babysit_run, babysit_check, babysit_send, babysit_wait, babysit_kill
29
+ * Tools (LLM): babysit_run, babysit_check, babysit_analyze, babysit_send, babysit_wait, babysit_kill
30
30
  * Commands: /babysit (arrow-key picker: attach/tail/inspect)
31
31
  * Widget: live counts (processes running · subagents working · idle)
32
32
  */
@@ -225,11 +225,20 @@ async function statusOf(id: string): Promise<BsSession | null> {
225
225
  // kind=process: name/command + `notified` (exit notification dedup).
226
226
  // kind=subagent: task + the raw-log byte offset of the last prompt, which lets
227
227
  // check/wait analyze only the CURRENT task's events (important for follow-ups).
228
+ type ReportLanguage = "javascript" | "python" | "shell";
229
+
230
+ interface ReportSpec {
231
+ language: ReportLanguage;
232
+ code: string;
233
+ timeout?: string;
234
+ }
235
+
228
236
  interface Meta {
229
237
  kind: "process" | "subagent";
230
238
  // process
231
239
  name?: string;
232
240
  command?: string;
241
+ report?: ReportSpec;
233
242
  notified?: boolean;
234
243
  startedAt?: number;
235
244
  // subagent
@@ -366,6 +375,9 @@ function parseDurMs(s?: string): number | null {
366
375
 
367
376
  const TAIL_MAX_BYTES = 8_000; // log tails / screens
368
377
  const ANSWER_MAX_BYTES = 24_000; // subagent answers / error messages
378
+ const REPORT_MAX_INPUT_BYTES = 64 * 1024 * 1024; // analyzer input log
379
+ const REPORT_MAX_OUTPUT_BYTES = 12_000; // analyzer stdout/stderr returned to the agent
380
+ const REPORT_DEFAULT_TIMEOUT_MS = 30_000;
369
381
 
370
382
  function clip(s: string, maxBytes = TAIL_MAX_BYTES): string {
371
383
  const buf = Buffer.from(s, "utf8");
@@ -377,6 +389,132 @@ function clip(s: string, maxBytes = TAIL_MAX_BYTES): string {
377
389
  return `${head}\n… [${buf.length - maxBytes} bytes elided] …\n${tail}`;
378
390
  }
379
391
 
392
+ // ---------------------------------------------------------------------------
393
+ // Report analyzers — a report receives the complete captured process log, but
394
+ // only its bounded stdout is returned to the model. It intentionally runs in
395
+ // a child process: report code is arbitrary user/model-authored code and must
396
+ // never be evaluated inside pi's extension host.
397
+
398
+ interface ReportOutcome {
399
+ ok: boolean;
400
+ text: string;
401
+ }
402
+
403
+ async function saveSessionLog(id: string, destination: string, signal?: AbortSignal): Promise<string | null> {
404
+ return new Promise((resolve) => {
405
+ const child = spawn(BABYSIT_BIN, ["log", "-s", id], {
406
+ env: { ...process.env, BABYSIT_DIR: ROOT },
407
+ });
408
+ const output = fs.createWriteStream(destination);
409
+ let bytes = 0;
410
+ let finished = false;
411
+ const finish = (error: string | null) => {
412
+ if (finished) return;
413
+ finished = true;
414
+ signal?.removeEventListener("abort", abort);
415
+ output.end(() => resolve(error));
416
+ };
417
+ const abort = () => {
418
+ child.kill("SIGTERM");
419
+ finish("report analysis was cancelled");
420
+ };
421
+ if (signal?.aborted) return abort();
422
+ signal?.addEventListener("abort", abort, { once: true });
423
+ child.stdout?.on("data", (chunk: Buffer) => {
424
+ if (finished) return;
425
+ bytes += chunk.length;
426
+ if (bytes > REPORT_MAX_INPUT_BYTES) {
427
+ child.kill("SIGTERM");
428
+ finish(`process log exceeds the ${REPORT_MAX_INPUT_BYTES / 1024 / 1024} MiB report limit`);
429
+ return;
430
+ }
431
+ output.write(chunk);
432
+ });
433
+ child.on("error", (error) => finish(`could not read process log: ${error.message}`));
434
+ child.on("close", (code) => {
435
+ if (finished) return;
436
+ finish(code === 0 ? null : `could not read process log (babysit log exited ${code ?? 1})`);
437
+ });
438
+ });
439
+ }
440
+
441
+ async function executeReport(spec: ReportSpec, inputPath: string, signal?: AbortSignal): Promise<ReportOutcome> {
442
+ const dir = path.dirname(inputPath);
443
+ const scriptPath = path.join(dir, spec.language === "python" ? "report.py" : spec.language === "shell" ? "report.sh" : "report.cjs");
444
+ const script =
445
+ spec.language === "javascript"
446
+ ? `const fs = require("node:fs");\nconst FILE_CONTENT = fs.readFileSync(process.env.BABYSIT_REPORT_INPUT, "utf8");\nconst INPUT = FILE_CONTENT;\n(async () => {\n${spec.code}\n})().catch((error) => { console.error(error?.stack ?? String(error)); process.exitCode = 1; });\n`
447
+ : spec.language === "python"
448
+ ? `import os\nfrom pathlib import Path\nFILE_CONTENT = Path(os.environ["BABYSIT_REPORT_INPUT"]).read_text()\nINPUT = FILE_CONTENT\n${spec.code}\n`
449
+ : `# Read the complete log from $BABYSIT_REPORT_INPUT.\n${spec.code}\n`;
450
+ fs.writeFileSync(scriptPath, script, { mode: 0o700 });
451
+ const command = spec.language === "javascript" ? process.execPath : spec.language === "python" ? "python3" : SHELL;
452
+ const args = spec.language === "shell" ? [scriptPath] : [scriptPath];
453
+ const timeoutMs = parseDurMs(spec.timeout) ?? REPORT_DEFAULT_TIMEOUT_MS;
454
+
455
+ return new Promise((resolve) => {
456
+ const child = spawn(command, args, {
457
+ env: { ...process.env, BABYSIT_REPORT_INPUT: inputPath },
458
+ });
459
+ let stdout = "";
460
+ let stderr = "";
461
+ let outputTruncated = false;
462
+ let timedOut = false;
463
+ let settled = false;
464
+ const append = (current: string, chunk: Buffer) => {
465
+ const remaining = REPORT_MAX_OUTPUT_BYTES - Buffer.byteLength(current);
466
+ if (remaining <= 0) {
467
+ outputTruncated = true;
468
+ return current;
469
+ }
470
+ const text = chunk.subarray(0, remaining).toString("utf8");
471
+ if (text.length < chunk.length) outputTruncated = true;
472
+ return current + text;
473
+ };
474
+ const finish = (code: number | null) => {
475
+ if (settled) return;
476
+ settled = true;
477
+ clearTimeout(timer);
478
+ signal?.removeEventListener("abort", abort);
479
+ const body = [stdout.trim(), stderr.trim()].filter(Boolean).join("\n");
480
+ const suffix = outputTruncated ? "\n[report output truncated]" : "";
481
+ if (timedOut) {
482
+ resolve({ ok: false, text: `Report timed out after ${Math.ceil(timeoutMs / 1000)}s.${body ? `\n${clip(body, REPORT_MAX_OUTPUT_BYTES)}` : ""}${suffix}` });
483
+ } else if (code === 0) {
484
+ resolve({ ok: true, text: `${body || "(report produced no output)"}${suffix}` });
485
+ } else {
486
+ resolve({ ok: false, text: `Report failed (exit ${code ?? 1}).${body ? `\n${clip(body, REPORT_MAX_OUTPUT_BYTES)}` : ""}${suffix}` });
487
+ }
488
+ };
489
+ const abort = () => child.kill("SIGTERM");
490
+ const timer = setTimeout(() => {
491
+ timedOut = true;
492
+ child.kill("SIGTERM");
493
+ }, timeoutMs);
494
+ if (signal?.aborted) abort();
495
+ signal?.addEventListener("abort", abort, { once: true });
496
+ child.stdout?.on("data", (chunk: Buffer) => { stdout = append(stdout, chunk); });
497
+ child.stderr?.on("data", (chunk: Buffer) => { stderr = append(stderr, chunk); });
498
+ child.on("error", (error) => {
499
+ stderr = append(stderr, Buffer.from(error.message));
500
+ finish(1);
501
+ });
502
+ child.on("close", finish);
503
+ });
504
+ }
505
+
506
+ async function runReport(id: string, spec: ReportSpec, signal?: AbortSignal): Promise<ReportOutcome> {
507
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-babysit-report-"));
508
+ try {
509
+ const inputPath = path.join(dir, "process.log");
510
+ const error = await saveSessionLog(id, inputPath, signal);
511
+ if (error) return { ok: false, text: `Could not prepare report input: ${error}` };
512
+ return await executeReport(spec, inputPath, signal);
513
+ } finally {
514
+ fs.rmSync(dir, { recursive: true, force: true });
515
+ }
516
+ }
517
+
380
518
  // ---------------------------------------------------------------------------
381
519
  // parked-turn detection (shared rule with self-reap.ts)
382
520
  // ---------------------------------------------------------------------------
@@ -553,6 +691,7 @@ interface ProcOpts {
553
691
  timeout?: string; // default: none — dev servers may run indefinitely
554
692
  idleTimeout?: string;
555
693
  pty: boolean;
694
+ report?: ReportSpec;
556
695
  }
557
696
 
558
697
  async function spawnProcess(opts: ProcOpts): Promise<{ id: string } | { error: string }> {
@@ -582,6 +721,7 @@ async function spawnProcess(opts: ProcOpts): Promise<{ id: string } | { error: s
582
721
  kind: "process",
583
722
  name: opts.name ?? id,
584
723
  command: opts.command,
724
+ report: opts.report,
585
725
  notified: false,
586
726
  startedAt: Date.now(),
587
727
  });
@@ -1009,9 +1149,10 @@ async function waitForExit(
1009
1149
  return { id, kind: "exited", ok: false, text: `No such session: ${id}` };
1010
1150
  }
1011
1151
  suppressNotify(id); // the agent sees the exit here; don't notify again
1012
- const tail = clip((await bs(["log", "-s", id, "--tail", "20"])).stdout.trim());
1013
- const ok = st.exit_code === 0;
1014
1152
  const meta = readMeta(id);
1153
+ const report = meta?.report ? await runReport(id, meta.report, signal) : null;
1154
+ const tail = report ? "" : clip((await bs(["log", "-s", id, "--tail", "20"])).stdout.trim());
1155
+ const ok = st.exit_code === 0;
1015
1156
  return {
1016
1157
  id,
1017
1158
  kind: "exited",
@@ -1020,7 +1161,9 @@ async function waitForExit(
1020
1161
  `Process ${id}${meta?.command ? ` (${meta.command})` : ""} ` +
1021
1162
  `${ok ? "completed successfully" : `exited with code ${st.exit_code ?? "?"}`}` +
1022
1163
  `${expectPattern ? ` before /${expectPattern}/ appeared` : ""}.` +
1023
- (tail ? `\n\nLast output:\n${tail}` : ""),
1164
+ (report
1165
+ ? `\n\nReport${report.ok ? "" : " (failed)"}:\n${report.text}`
1166
+ : tail ? `\n\nLast output:\n${tail}` : ""),
1024
1167
  status: st,
1025
1168
  };
1026
1169
  }
@@ -1069,7 +1212,8 @@ export default function (pi: ExtensionAPI) {
1069
1212
  if (!meta || meta.kind !== "process" || meta.notified) continue;
1070
1213
  meta.notified = true;
1071
1214
  writeMeta(s.id, meta);
1072
- const tail = clip((await bs(["log", "-s", s.id, "--tail", "20"])).stdout.trim());
1215
+ const report = meta.report ? await runReport(s.id, meta.report) : null;
1216
+ const tail = report ? "" : clip((await bs(["log", "-s", s.id, "--tail", "20"])).stdout.trim());
1073
1217
  const ok = s.exit_code === 0;
1074
1218
  const runtime = meta.startedAt
1075
1219
  ? `${Math.round((Date.now() - meta.startedAt) / 1000)}s`
@@ -1084,10 +1228,12 @@ export default function (pi: ExtensionAPI) {
1084
1228
  customType: "pi-babysit-process-end",
1085
1229
  content:
1086
1230
  `${summary}\nCommand: ${meta.command ?? "?"}` +
1087
- (tail ? `\n\nRecent output:\n${tail}` : "") +
1231
+ (report
1232
+ ? `\n\nReport${report.ok ? "" : " (failed)"}:\n${report.text}`
1233
+ : tail ? `\n\nRecent output:\n${tail}` : "") +
1088
1234
  `\n\nThis is the automatic process-end notification. Do not call babysit_check just to re-verify; use it once only if you need more logs for debugging.`,
1089
1235
  display: true,
1090
- details: { id: s.id, exitCode: s.exit_code, success: ok, runtime },
1236
+ details: { id: s.id, exitCode: s.exit_code, success: ok, runtime, report },
1091
1237
  },
1092
1238
  { triggerTurn: true, deliverAs: "steer" },
1093
1239
  );
@@ -1201,7 +1347,8 @@ export default function (pi: ExtensionAPI) {
1201
1347
  "exits and returns its output inline — there is no notification loop to resume a parked turn. " +
1202
1348
  "Two modes: (1) `command` — run any long-lived or slow command (build, tests, dev server, " +
1203
1349
  "watcher, interactive TUI) in a PTY; you get an AUTOMATIC notification when it exits, and " +
1204
- "you can type into it with babysit_send and read its screen with babysit_check. " +
1350
+ "you can type into it with babysit_send and read its screen with babysit_check. Optional `report` " +
1351
+ "runs local code over the full captured log and returns only its concise output on completion. " +
1205
1352
  "(2) `profile: \"subagent\"` + `task` — spawn a pi subagent that works on the task in the " +
1206
1353
  "background; poll with babysit_check, steer with babysit_send, block with babysit_wait, " +
1207
1354
  "stop with babysit_kill.",
@@ -1209,7 +1356,8 @@ export default function (pi: ExtensionAPI) {
1209
1356
  "Run a command or a pi subagent as a supervised background session (non-blocking); returns a session id",
1210
1357
  promptGuidelines: [
1211
1358
  "Run every long-lived or slow command through babysit_run instead of bash: builds, test suites, dev servers, watchers, `tail -f`, anything expected to take more than a few seconds. Give it a clear stable `name` (e.g. cargo-build).",
1212
- "babysit_run { command } ENDS YOUR TURN by default — you are resumed by the automatic process-end notification, so just stop; NEVER poll with babysit_check or sleep. Set continueAfterStart: true only when you have immediate, specific, non-polling work to do next. Call babysit_wait when you must consume the result inside the current turn (optionally with `expect` to wait for a readiness line like 'listening on').",
1359
+ "After babysit_run { command } starts a process, end your response immediately so the automatic process-end notification can resume you; NEVER poll with babysit_check or sleep. Set continueAfterStart: true only when you have immediate, specific, non-polling work to do next. Call babysit_wait when you must consume the result inside the current turn (optionally with `expect` to wait for a readiness line like 'listening on').",
1360
+ "For a build/test/API command with potentially large output, give babysit_run a `report` program that extracts the needed facts from FILE_CONTENT; the automatic completion notification will contain only that report rather than a raw log tail.",
1213
1361
  "babysit_run gives full PTY control: drive interactive programs (installers, wizards, REPLs) with babysit_send (text or named keys) and read the rendered screen with babysit_check { screen: true }.",
1214
1362
  "Delegate self-contained tasks (codebase recon, a parallelizable subtask, work that would pollute your context) with babysit_run { profile: \"subagent\", task }. Launch several for independent subtasks; they run concurrently.",
1215
1363
  "After spawning subagents, do not idle-wait and do not end your turn to wait for them: keep making progress, then call babysit_wait (ids + mode any/all) when you need their results. Steer or send follow-up tasks with babysit_send; kill runaways with babysit_kill.",
@@ -1269,6 +1417,20 @@ export default function (pi: ExtensionAPI) {
1269
1417
  "Process mode only. Default false: starting a process ENDS the current turn (you are resumed by the exit notification). Set true only when you have immediate, specific, non-polling work to do after starting.",
1270
1418
  }),
1271
1419
  ),
1420
+ report: Type.Optional(
1421
+ Type.Object({
1422
+ language: StringEnum(["javascript", "python", "shell"] as const, {
1423
+ description: "Language for the local report program.",
1424
+ }),
1425
+ code: Type.String({
1426
+ description:
1427
+ "Program that analyzes the complete captured log. JavaScript/Python receive FILE_CONTENT and INPUT strings; shell receives $BABYSIT_REPORT_INPUT. Print only the concise report.",
1428
+ }),
1429
+ timeout: Type.Optional(
1430
+ Type.String({ description: "Report-program timeout (default 30s, e.g. '10s')." }),
1431
+ ),
1432
+ }),
1433
+ ),
1272
1434
  }),
1273
1435
  async execute(_id, params, _signal, _onUpdate, ctx) {
1274
1436
  await requireBabysit();
@@ -1294,6 +1456,13 @@ export default function (pi: ExtensionAPI) {
1294
1456
  details: {},
1295
1457
  };
1296
1458
  }
1459
+ if (isSubagent && params.report) {
1460
+ return {
1461
+ content: [{ type: "text", text: "`report` is available only for process sessions." }],
1462
+ isError: true,
1463
+ details: {},
1464
+ };
1465
+ }
1297
1466
 
1298
1467
  // --- process mode ---
1299
1468
  if (!isSubagent) {
@@ -1304,6 +1473,7 @@ export default function (pi: ExtensionAPI) {
1304
1473
  timeout: params.timeout,
1305
1474
  idleTimeout: params.idleTimeout,
1306
1475
  pty: params.pty ?? true,
1476
+ report: params.report,
1307
1477
  });
1308
1478
  if ("error" in res) {
1309
1479
  return {
@@ -1326,7 +1496,7 @@ export default function (pi: ExtensionAPI) {
1326
1496
  return {
1327
1497
  content: [{ type: "text", text: outcome.text }],
1328
1498
  isError: !outcome.ok,
1329
- details: { id: res.id, kind: "process", command: params.command },
1499
+ details: { id: res.id, kind: "process", command: params.command, report: params.report },
1330
1500
  };
1331
1501
  }
1332
1502
 
@@ -1345,12 +1515,14 @@ export default function (pi: ExtensionAPI) {
1345
1515
  `Human can watch/take over: /babysit`,
1346
1516
  },
1347
1517
  ],
1348
- details: { id: res.id, kind: "process", command: params.command },
1349
- // Same contract as the old `process` tool: end the turn so the
1350
- // exit notification can resume the agent. This also makes the
1351
- // parked-turn rule deterministic — the toolResult (with
1352
- // NOTIFY_MARKER) is guaranteed to be the turn's LAST message.
1353
- terminate: !continueAfter,
1518
+ details: { id: res.id, kind: "process", command: params.command, report: params.report },
1519
+ // Do not return `terminate: true` here. In RPC/subagent hosts that hint
1520
+ // can shut down the hosting pi worker, whose process-tree cleanup then
1521
+ // kills the otherwise detached babysit supervisor and closes its PTY
1522
+ // (observed as an immediate `^D`). The prompt contract tells the model
1523
+ // to stop after this result instead; the NOTIFY_MARKER still identifies
1524
+ // a parked turn to the parent/self-reaper logic.
1525
+ terminate: false,
1354
1526
  };
1355
1527
  }
1356
1528
 
@@ -1704,6 +1876,62 @@ export default function (pi: ExtensionAPI) {
1704
1876
  },
1705
1877
  });
1706
1878
 
1879
+ // ----- babysit_analyze ----------------------------------------------------
1880
+ pi.registerTool({
1881
+ name: "babysit_analyze",
1882
+ label: "Babysit: analyze",
1883
+ description:
1884
+ "Run a local analyzer over a babysit process's complete captured log. The raw log remains outside " +
1885
+ "the model context; only the analyzer's bounded stdout is returned. JavaScript/Python receive " +
1886
+ "FILE_CONTENT and INPUT strings; shell receives $BABYSIT_REPORT_INPUT. It can inspect a running " +
1887
+ "session's output so far or a completed session.",
1888
+ promptSnippet: "Analyze a babysit process log locally and return only a concise report",
1889
+ promptGuidelines: [
1890
+ "Use babysit_analyze to extract failures, counts, or other concise facts from a large babysit process log instead of calling babysit_check repeatedly or returning raw logs.",
1891
+ ],
1892
+ parameters: Type.Object({
1893
+ id: Type.String({ description: "Process session id whose captured log should be analyzed." }),
1894
+ language: StringEnum(["javascript", "python", "shell"] as const, {
1895
+ description: "Language for the local analyzer program.",
1896
+ }),
1897
+ code: Type.String({
1898
+ description:
1899
+ "Analyzer program. JavaScript/Python receive FILE_CONTENT and INPUT strings; shell reads $BABYSIT_REPORT_INPUT. Print only the concise result.",
1900
+ }),
1901
+ timeout: Type.Optional(
1902
+ Type.String({ description: "Analyzer timeout (default 30s, e.g. '10s')." }),
1903
+ ),
1904
+ }),
1905
+ async execute(_id, params, signal) {
1906
+ await requireBabysit();
1907
+ const status = await statusOf(params.id);
1908
+ if (!status) {
1909
+ return {
1910
+ content: [{ type: "text", text: `No such session: ${params.id}` }],
1911
+ isError: true,
1912
+ details: {},
1913
+ };
1914
+ }
1915
+ if (kindOf(params.id) === "subagent") {
1916
+ return {
1917
+ content: [{ type: "text", text: "babysit_analyze supports process sessions only; use babysit_check for subagent progress." }],
1918
+ isError: true,
1919
+ details: {},
1920
+ };
1921
+ }
1922
+ const report = await runReport(params.id, {
1923
+ language: params.language,
1924
+ code: params.code,
1925
+ timeout: params.timeout,
1926
+ }, signal);
1927
+ return {
1928
+ content: [{ type: "text", text: report.text }],
1929
+ isError: !report.ok,
1930
+ details: {},
1931
+ };
1932
+ },
1933
+ });
1934
+
1707
1935
  // ----- babysit_wait -------------------------------------------------------
1708
1936
  pi.registerTool({
1709
1937
  name: "babysit_wait",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yusukeshib/pi-babysit",
3
- "version": "0.2.2",
4
- "description": "Run anything long-lived under babysit — one supervision substrate for processes and pi subagents, surfaced to the agent as babysit_run/check/send/wait/kill.",
3
+ "version": "0.2.4",
4
+ "description": "Run long-lived processes and pi subagents under babysit, with bounded local reports over captured logs.",
5
5
  "keywords": [
6
6
  "pi-package",
7
7
  "pi-extension",
@@ -27,6 +27,7 @@
27
27
  "LICENSE"
28
28
  ],
29
29
  "scripts": {
30
+ "test": "bun test",
30
31
  "release": "npm publish --access public"
31
32
  },
32
33
  "pi": {