@yusukeshib/pi-babysit 0.2.0 → 0.2.2

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 (2) hide show
  1. package/index.ts +76 -3
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -746,6 +746,50 @@ function renderWidgetLines(procs: number, busy: number, idle: number): string[]
746
746
  return [`\x1b[44;97m ${parts.join(" \u00b7 ")} \x1b[0m`];
747
747
  }
748
748
 
749
+ // How many trailing output lines to show per running session in the widget.
750
+ const WIDGET_TAIL_LINES = 1;
751
+ const WIDGET_TAIL_WIDTH = 100;
752
+
753
+ // Strip ANSI/control escapes and clamp width so raw PTY output can't wrap or
754
+ // corrupt the widget area.
755
+ function sanitizeTailLine(s: string): string {
756
+ const clean = s
757
+ .replace(/\r/g, "")
758
+ // CSI / OSC / other escape sequences
759
+ .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "")
760
+ .replace(/\x1b[@-Z\\-_]|\x1b\[[0-?]*[ -/]*[@-~]/g, "")
761
+ // remaining non-printable control chars (keep tab)
762
+ .replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "");
763
+ return clean.length > WIDGET_TAIL_WIDTH ? `${clean.slice(0, WIDGET_TAIL_WIDTH - 1)}…` : clean;
764
+ }
765
+
766
+ // Trailing lines to show for a running session (sanitized, unprefixed).
767
+ // process → raw log tail; subagent → derived activity (recent tool calls /
768
+ // partial answer), since its log is RPC JSONL, not human-readable.
769
+ async function widgetTail(id: string, isSub: boolean): Promise<string[]> {
770
+ let raw: string[];
771
+ if (!isSub) {
772
+ const out = (await bs(["log", "-s", id, "--tail", String(WIDGET_TAIL_LINES)])).stdout;
773
+ raw = out.split("\n");
774
+ } else {
775
+ const meta = readMeta(id);
776
+ const logArgs = ["log", "-s", id];
777
+ if (meta?.promptOffset) logArgs.push("--since", String(meta.promptOffset));
778
+ const prog = parseEvents((await bs(logArgs)).stdout);
779
+ if (prog.finalText.trim()) {
780
+ raw = prog.finalText.trim().split("\n");
781
+ } else if (prog.toolCalls.length > 0) {
782
+ raw = prog.toolCalls.map((t) => t.summary);
783
+ } else {
784
+ raw = prog.errorMsg ? [`⚠ ${prog.errorMsg}`] : [];
785
+ }
786
+ }
787
+ return raw
788
+ .map(sanitizeTailLine)
789
+ .filter((l) => l.trim().length > 0)
790
+ .slice(-WIDGET_TAIL_LINES);
791
+ }
792
+
749
793
  // Classify a running subagent as busy or idle by analyzing the current task's
750
794
  // log slice (same logic babysit_check uses).
751
795
  async function isTaskDone(id: string): Promise<boolean> {
@@ -1060,12 +1104,22 @@ export default function (pi: ExtensionAPI) {
1060
1104
  const lines = renderWidgetLines(procs, subs.length - idle, idle);
1061
1105
  // Per-session elapsed time (live: refreshed every poll tick).
1062
1106
  const doneById = new Map(subs.map((s, i) => [s.id, doneFlags[i]]));
1063
- for (const s of active) {
1107
+ const tails = await Promise.all(
1108
+ active.map((s) => widgetTail(s.id, kindOf(s.id) === "subagent")),
1109
+ );
1110
+ active.forEach((s, i) => {
1064
1111
  const isSub = kindOf(s.id) === "subagent";
1065
1112
  const tag = isSub ? (doneById.get(s.id) ? "sub idle" : "sub") : "proc";
1066
1113
  const el = elapsedOf(s.id);
1067
- lines.push(` ⏳ ${s.id} [${tag}]${el ? ` ${el}` : ""}`);
1068
- }
1114
+ const header = ` ⏳ ${s.id} [${tag}]${el ? ` ${el}` : ""}`;
1115
+ if (tails[i].length === 1) {
1116
+ // Single tail line: keep it on the same row as the header.
1117
+ lines.push(`${header} │ ${tails[i][0]}`);
1118
+ } else {
1119
+ lines.push(header);
1120
+ for (const t of tails[i]) lines.push(` │ ${t}`);
1121
+ }
1122
+ });
1069
1123
  ctx.ui.setWidget("pi-babysit", lines, { placement: "belowEditor" });
1070
1124
  };
1071
1125
 
@@ -1143,6 +1197,8 @@ export default function (pi: ExtensionAPI) {
1143
1197
  label: "Babysit: run",
1144
1198
  description:
1145
1199
  "Start a supervised background session under babysit (NON-BLOCKING; returns a session id). " +
1200
+ "In non-interactive mode (`pi -p`, no UI) process mode instead BLOCKS until the command " +
1201
+ "exits and returns its output inline — there is no notification loop to resume a parked turn. " +
1146
1202
  "Two modes: (1) `command` — run any long-lived or slow command (build, tests, dev server, " +
1147
1203
  "watcher, interactive TUI) in a PTY; you get an AUTOMATIC notification when it exits, and " +
1148
1204
  "you can type into it with babysit_send and read its screen with babysit_check. " +
@@ -1257,6 +1313,23 @@ export default function (pi: ExtensionAPI) {
1257
1313
  };
1258
1314
  }
1259
1315
  await refreshWidget(ctx);
1316
+
1317
+ // One-shot / non-interactive mode (e.g. `pi -p`): there is no live
1318
+ // event loop to deliver the exit notification, and ending the turn
1319
+ // would exit pi entirely — orphaning the babysat process and losing
1320
+ // the session (the agent "settles" the moment it is told the turn will
1321
+ // stop). So block inline like a normal command and return the full
1322
+ // outcome in THIS turn. The process still runs under babysit (logged,
1323
+ // killable), we just wait for it here instead of fire-and-forget.
1324
+ if (!ctx.hasUI) {
1325
+ const outcome = await waitForExit(res.id, parseDurMs(params.timeout), _signal);
1326
+ return {
1327
+ content: [{ type: "text", text: outcome.text }],
1328
+ isError: !outcome.ok,
1329
+ details: { id: res.id, kind: "process", command: params.command },
1330
+ };
1331
+ }
1332
+
1260
1333
  const continueAfter = params.continueAfterStart === true;
1261
1334
  const nextStep = continueAfter
1262
1335
  ? "Continue with specific non-polling work now; the exit notification will arrive on its own."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yusukeshib/pi-babysit",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
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.",
5
5
  "keywords": [
6
6
  "pi-package",