@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 +4 -2
- package/index.ts +61 -11
- package/package.json +1 -1
- package/self-reap.ts +6 -3
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
|
|
79
|
-
|
|
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
|
-
|
|
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
|
|
2194
|
-
|
|
2195
|
-
if (
|
|
2196
|
-
if (
|
|
2197
|
-
|
|
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
|
|
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
|
|
3112
|
-
?
|
|
3113
|
-
: "
|
|
3155
|
+
const state: WidgetSessionState = isSubagent && progressById.get(session.id)?.done
|
|
3156
|
+
? "idle"
|
|
3157
|
+
: "running";
|
|
3114
3158
|
const elapsed = elapsedOf(session.id);
|
|
3115
|
-
const header =
|
|
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
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", (
|
|
103
|
+
pi.on("agent_settled", () => {
|
|
104
104
|
cancel();
|
|
105
105
|
if (lastEndWasParked) return;
|
|
106
106
|
timer = setTimeout(() => {
|
|
107
|
-
//
|
|
108
|
-
|
|
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
|
});
|