@yusukeshib/pi-babysit 0.5.0 → 0.6.0

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.
package/README.md CHANGED
@@ -75,8 +75,10 @@ re-enables `bash`, including shell backgrounding (`… &`, `nohup`, `setsid`,
75
75
  | `/babysit` | Arrow-key picker over all sessions. Renders an **inline snapshot** (no tmux): running **process** → current rendered screen + recent output + a copy-paste `babysit attach` take-over hint (detach `Ctrl-\ Ctrl-\`); running **subagent** → read-only progress (RPC stdin stays untouchable); finished → summary. Re-run `/babysit` to refresh |
76
76
  | `/babysit gc [days]` | Preview and confirm deletion of old Pi-session roots (default 14 days). Active leases, live supervisor/child PIDs, unknown states, the current root, and recent roots are retained; deletion uses a GC lock and atomic rename |
77
77
 
78
- A minimal widget above the editor shows live counts
79
- (`N processes · M subagents working · K idle`).
78
+ A widget below the editor separates session **kind** from task **state** at a glance:
79
+ summary counts use `RUNNING` / `IDLE`, and every row is labeled
80
+ `[PROCESS] [RUNNING]`, `[AGENT] [RUNNING]`, or `[AGENT] [IDLE]`. The displayed
81
+ `age` is total session age, not time spent in the current state.
80
82
 
81
83
  ## Logs without context flooding
82
84
 
package/index.ts CHANGED
@@ -2188,13 +2188,56 @@ async function spawnSubagent(
2188
2188
  // A subagent whose task is done stays alive as an idle RPC worker (for
2189
2189
  // follow-ups), so "running" in babysit does NOT mean "working" — count
2190
2190
  // processes / busy subagents / idle subagents separately.
2191
- function renderWidgetLines(procs: number, busy: number, idle: number): string[] {
2191
+ export type WidgetSessionKind = "process" | "agent";
2192
+ export type WidgetSessionState = "running" | "idle";
2193
+
2194
+ export function widgetSummaryText(procs: number, busy: number, idle: number): string {
2195
+ const sections: string[] = [];
2196
+ const running: string[] = [];
2197
+ if (procs > 0) running.push(`${procs} process${procs > 1 ? "es" : ""}`);
2198
+ if (busy > 0) running.push(`${busy} agent${busy > 1 ? "s" : ""}`);
2199
+ if (running.length > 0) sections.push(`RUNNING ${running.join(" · ")}`);
2200
+ if (idle > 0) sections.push(`IDLE ${idle} agent${idle > 1 ? "s" : ""}`);
2201
+ return sections.join(" │ ");
2202
+ }
2203
+
2204
+ export function widgetSessionHeader(
2205
+ id: string,
2206
+ kind: WidgetSessionKind,
2207
+ state: WidgetSessionState,
2208
+ elapsed?: string,
2209
+ ): string {
2210
+ const icon = state === "running" ? "▶" : "○";
2211
+ return ` ${icon} ${id} [${kind.toUpperCase()}] [${state.toUpperCase()}]${elapsed ? ` · age ${elapsed}` : ""}`;
2212
+ }
2213
+
2214
+ function renderWidgetLines(procs: number, busy: number, idle: number, theme: Theme): string[] {
2192
2215
  if (procs === 0 && busy === 0 && idle === 0) return [];
2193
- const parts: string[] = [];
2194
- if (procs > 0) parts.push(`${procs} process${procs > 1 ? "es" : ""}`);
2195
- if (busy > 0) parts.push(`${busy} subagent${busy > 1 ? "s" : ""} working`);
2196
- if (idle > 0) parts.push(`${idle} idle`);
2197
- return [`\x1b[44;97m ${parts.join(" \u00b7 ")} \x1b[0m`];
2216
+ const sections: string[] = [];
2217
+ const running: string[] = [];
2218
+ if (procs > 0) running.push(`${procs} process${procs > 1 ? "es" : ""}`);
2219
+ if (busy > 0) running.push(`${busy} agent${busy > 1 ? "s" : ""}`);
2220
+ if (running.length > 0) {
2221
+ sections.push(`${theme.fg("success", theme.bold("RUNNING"))} ${running.join(" · ")}`);
2222
+ }
2223
+ if (idle > 0) {
2224
+ sections.push(`${theme.fg("muted", theme.bold("IDLE"))} ${idle} agent${idle > 1 ? "s" : ""}`);
2225
+ }
2226
+ return [theme.bg("toolPendingBg", ` ${sections.join(" │ ")} `)];
2227
+ }
2228
+
2229
+ function renderWidgetSessionHeader(
2230
+ id: string,
2231
+ kind: WidgetSessionKind,
2232
+ state: WidgetSessionState,
2233
+ elapsed: string,
2234
+ theme: Theme,
2235
+ ): string {
2236
+ const running = state === "running";
2237
+ const icon = theme.fg(running ? "success" : "muted", running ? "▶" : "○");
2238
+ const kindLabel = theme.fg(kind === "process" ? "accent" : "warning", theme.bold(`[${kind.toUpperCase()}]`));
2239
+ const stateLabel = theme.fg(running ? "success" : "muted", theme.bold(`[${state.toUpperCase()}]`));
2240
+ return ` ${icon} ${id} ${kindLabel} ${stateLabel}${elapsed ? theme.fg("dim", ` · age ${elapsed}`) : ""}`;
2198
2241
  }
2199
2242
 
2200
2243
  // How many trailing output lines to show per running session in the widget.
@@ -3095,7 +3138,8 @@ export default function (pi: ExtensionAPI) {
3095
3138
  }
3096
3139
  }
3097
3140
  const idle = subs.filter((session) => progressById.get(session.id)?.done).length;
3098
- const lines = renderWidgetLines(procs, subs.length - idle, idle);
3141
+ const theme = ctx.ui.theme;
3142
+ const lines = renderWidgetLines(procs, subs.length - idle, idle, theme);
3099
3143
  const tails = await Promise.all(
3100
3144
  active.map((session) => {
3101
3145
  const isSubagent = kindOf(session.id) === "subagent";
@@ -3108,11 +3152,17 @@ export default function (pi: ExtensionAPI) {
3108
3152
  );
3109
3153
  active.forEach((session, index) => {
3110
3154
  const isSubagent = kindOf(session.id) === "subagent";
3111
- const tag = isSubagent
3112
- ? progressById.get(session.id)?.done ? "sub idle" : "sub"
3113
- : "proc";
3155
+ const state: WidgetSessionState = isSubagent && progressById.get(session.id)?.done
3156
+ ? "idle"
3157
+ : "running";
3114
3158
  const elapsed = elapsedOf(session.id);
3115
- const header = ` ⏳ ${session.id} [${tag}]${elapsed ? ` ${elapsed}` : ""}`;
3159
+ const header = renderWidgetSessionHeader(
3160
+ session.id,
3161
+ isSubagent ? "agent" : "process",
3162
+ state,
3163
+ elapsed,
3164
+ theme,
3165
+ );
3116
3166
  if (tails[index].length === 1) {
3117
3167
  lines.push(`${header} │ ${tails[index][0]}`);
3118
3168
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yusukeshib/pi-babysit",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Run any shell command and pi subagents under babysit, with context-safe captured output.",
5
5
  "keywords": [
6
6
  "pi-package",
package/self-reap.ts CHANGED
@@ -100,12 +100,15 @@ export default function (pi: ExtensionAPI) {
100
100
 
101
101
  // agent_end can precede automatic retries, compaction retries, and queued
102
102
  // continuations. Start the grace timer only once Pi is truly settled.
103
- pi.on("agent_settled", (_event, ctx) => {
103
+ pi.on("agent_settled", () => {
104
104
  cancel();
105
105
  if (lastEndWasParked) return;
106
106
  timer = setTimeout(() => {
107
- // Graceful shutdown emits session_shutdown for every loaded extension.
108
- ctx.shutdown();
107
+ // In RPC mode ctx.shutdown() only sets a flag that is checked on the next
108
+ // command or agent_settled event. An already-idle worker receives neither,
109
+ // so it would remain alive indefinitely. At this point the grace elapsed,
110
+ // the task is settled, and parked turns were excluded above; exit cleanly.
111
+ process.exit(0);
109
112
  }, graceMs);
110
113
  timer.unref?.();
111
114
  });