@yusukeshib/pi-babysit 0.6.7 → 0.6.9
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 +13 -6
- package/index.ts +62 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -81,12 +81,19 @@ backgrounding (`… &`, `nohup`, `setsid`, `disown`). Set
|
|
|
81
81
|
A widget below the editor separates session **kind** from task **state** at a glance:
|
|
82
82
|
summary counts use `RUNNING` / `IDLE`, and every row is labeled
|
|
83
83
|
`PROCESS RUNNING`, `AGENT RUNNING`, or `AGENT IDLE`. In fullscreen mode, the
|
|
84
|
-
`
|
|
85
|
-
|
|
86
|
-
and
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
`babysits(n running)` list shows live sessions (including idle agents) by default.
|
|
85
|
+
Click the header to toggle to `babysits(n all)`, which also shows finished and failed
|
|
86
|
+
sessions, and click again to return to running-only. Both modes show newest first,
|
|
87
|
+
ten per page; `< 1/4 >` shows the current/total page when needed. Click the
|
|
88
|
+
arrows to page. Collapsed rows show the complete command for processes and the
|
|
89
|
+
complete task for agents, wrapping long text across lines rather than abbreviating
|
|
90
|
+
it. Click an agent row for the task,
|
|
91
|
+
its RPC worker launch argv (bold, readable text rather than a copy-paste shell
|
|
92
|
+
command), and up to 20 output lines. Opening another row closes the previous
|
|
93
|
+
one. A session with no available output shows an explicit placeholder when
|
|
94
|
+
expanded. Mouse clicks
|
|
95
|
+
require fullscreen mode; `/babysit` works in regular mode. The displayed `age`
|
|
96
|
+
is total session age, not time spent
|
|
90
97
|
in the current state. Transcript details
|
|
91
98
|
from tool results, snapshots, and completion notifications collapse or expand
|
|
92
99
|
with Pi's tool-output toggle (`Ctrl+O` by default).
|
package/index.ts
CHANGED
|
@@ -43,7 +43,7 @@ import type {
|
|
|
43
43
|
ToolDefinition,
|
|
44
44
|
} from "@earendil-works/pi-coding-agent";
|
|
45
45
|
import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
46
|
-
import { Box, Markdown, Text, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
46
|
+
import { Box, Markdown, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
47
47
|
import { Type, type TSchema } from "typebox";
|
|
48
48
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
49
49
|
import { type AgentConfig, type AgentScope, discoverAgents } from "./agents";
|
|
@@ -463,6 +463,7 @@ interface Meta {
|
|
|
463
463
|
startedAt?: number;
|
|
464
464
|
// subagent
|
|
465
465
|
task?: string;
|
|
466
|
+
launchCommand?: string;
|
|
466
467
|
promptOffset?: number;
|
|
467
468
|
model?: string;
|
|
468
469
|
tools?: string[];
|
|
@@ -1239,6 +1240,12 @@ function escapeCommandForDisplay(command: string): string {
|
|
|
1239
1240
|
);
|
|
1240
1241
|
}
|
|
1241
1242
|
|
|
1243
|
+
export function readableLaunchCommand(command: string): string {
|
|
1244
|
+
// Older sessions quoted every argv element, including safe paths and flags.
|
|
1245
|
+
if (!command.startsWith("'") || !command.endsWith("'") || !command.includes("' '")) return command;
|
|
1246
|
+
return command.slice(1, -1).split("' '").map((arg) => arg.replace(/'\\''/g, "'")).join(" ");
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1242
1249
|
export function summarizeNotificationCommand(command: string | undefined): string {
|
|
1243
1250
|
const preview = escapeCommandForDisplay((command ?? "?").trim()) || "?";
|
|
1244
1251
|
const bytes = Buffer.from(preview, "utf8");
|
|
@@ -2154,6 +2161,7 @@ async function spawnSubagent(
|
|
|
2154
2161
|
PI_BIN,
|
|
2155
2162
|
...piArgs,
|
|
2156
2163
|
);
|
|
2164
|
+
const launchCommand = bsArgs.slice(bsArgs.indexOf("--") + 1).join(" ");
|
|
2157
2165
|
|
|
2158
2166
|
let r: Awaited<ReturnType<typeof bs>>;
|
|
2159
2167
|
try {
|
|
@@ -2187,6 +2195,7 @@ async function spawnSubagent(
|
|
|
2187
2195
|
kind: "subagent",
|
|
2188
2196
|
name: opts.name ?? id,
|
|
2189
2197
|
task: opts.task,
|
|
2198
|
+
launchCommand,
|
|
2190
2199
|
notified: true,
|
|
2191
2200
|
depth: opts.depth,
|
|
2192
2201
|
maxDepth: opts.maxDepth,
|
|
@@ -2274,6 +2283,7 @@ async function spawnSubagent(
|
|
|
2274
2283
|
kind: "subagent",
|
|
2275
2284
|
name: opts.name ?? id,
|
|
2276
2285
|
task: opts.task,
|
|
2286
|
+
launchCommand,
|
|
2277
2287
|
promptOffset: resp.offset,
|
|
2278
2288
|
model: resolvedModel,
|
|
2279
2289
|
tools,
|
|
@@ -2360,10 +2370,10 @@ function renderWidgetSessionHeader(
|
|
|
2360
2370
|
const WIDGET_COLLAPSED_TAIL_LINES = 1;
|
|
2361
2371
|
const WIDGET_EXPANDED_TAIL_LINES = 20;
|
|
2362
2372
|
const WIDGET_TAIL_WIDTH = 100;
|
|
2363
|
-
const WIDGET_SESSION_PAGE_SIZE =
|
|
2373
|
+
const WIDGET_SESSION_PAGE_SIZE = 10;
|
|
2364
2374
|
|
|
2365
2375
|
export interface WidgetSessionListState {
|
|
2366
|
-
|
|
2376
|
+
mode: "running" | "all";
|
|
2367
2377
|
page: number;
|
|
2368
2378
|
}
|
|
2369
2379
|
|
|
@@ -2372,6 +2382,8 @@ export interface WidgetSessionDisplay {
|
|
|
2372
2382
|
header: string;
|
|
2373
2383
|
tail: string[];
|
|
2374
2384
|
expandable: boolean;
|
|
2385
|
+
active?: boolean;
|
|
2386
|
+
details?: string[];
|
|
2375
2387
|
}
|
|
2376
2388
|
|
|
2377
2389
|
interface WidgetMouseEvent {
|
|
@@ -2403,16 +2415,34 @@ export function createWidgetComponent(
|
|
|
2403
2415
|
lines.push(truncateToWidth(text, Math.max(0, width), ""));
|
|
2404
2416
|
targetByLine.push(target);
|
|
2405
2417
|
};
|
|
2418
|
+
const pushWrapped = (text: string, target?: Target) => {
|
|
2419
|
+
for (const line of wrapTextWithAnsi(text, Math.max(1, width))) push(line, target);
|
|
2420
|
+
};
|
|
2406
2421
|
const pushSession = (session: WidgetSessionDisplay) => {
|
|
2407
2422
|
const expanded = session.expandable && expandedSessionIds.has(session.id);
|
|
2408
2423
|
const tail = session.tail.slice(
|
|
2409
2424
|
expanded ? -WIDGET_EXPANDED_TAIL_LINES : -WIDGET_COLLAPSED_TAIL_LINES,
|
|
2410
2425
|
);
|
|
2411
2426
|
const target = session.expandable ? { type: "session" as const, id: session.id } : undefined;
|
|
2412
|
-
|
|
2427
|
+
const details = session.details ?? [];
|
|
2428
|
+
const pushDetail = (detail: string) => {
|
|
2429
|
+
const text = detail.startsWith("task: ") ? detail.slice(6) : detail;
|
|
2430
|
+
if (width > 6) {
|
|
2431
|
+
for (const line of wrapTextWithAnsi(text, width - 6)) push(` ${line}`, target);
|
|
2432
|
+
} else {
|
|
2433
|
+
push(` ${text}`, target);
|
|
2434
|
+
}
|
|
2435
|
+
};
|
|
2436
|
+
if (!expanded && details[0]?.startsWith("task: ")) {
|
|
2437
|
+
pushWrapped(`${session.header} ${details[0].slice(6)}`, target);
|
|
2438
|
+
} else if (!expanded && details.length) {
|
|
2439
|
+
pushWrapped(`${session.header} ${details[0]}`, target);
|
|
2440
|
+
for (const detail of details.slice(1)) pushDetail(detail);
|
|
2441
|
+
} else if (!expanded && tail.length === 1) {
|
|
2413
2442
|
push(`${session.header} ${tail[0]}`, target);
|
|
2414
2443
|
} else {
|
|
2415
2444
|
push(session.header, target);
|
|
2445
|
+
for (const detail of details) pushDetail(detail);
|
|
2416
2446
|
for (const line of tail) push(` ${line}`, target);
|
|
2417
2447
|
if (expanded && tail.length === 0) push(" (no output available)", target);
|
|
2418
2448
|
}
|
|
@@ -2421,10 +2451,11 @@ export function createWidgetComponent(
|
|
|
2421
2451
|
for (const line of summaryLines.slice(0, -1)) push(line);
|
|
2422
2452
|
const summary = summaryLines.at(-1);
|
|
2423
2453
|
if (listState && sessions.length) {
|
|
2424
|
-
const
|
|
2454
|
+
const visible = listState.mode === "running" ? sessions.filter((session) => session.active) : sessions;
|
|
2455
|
+
const pageCount = Math.max(1, Math.ceil(visible.length / WIDGET_SESSION_PAGE_SIZE));
|
|
2425
2456
|
listState.page = Math.min(Math.max(0, listState.page), pageCount - 1);
|
|
2426
|
-
const label = `
|
|
2427
|
-
const showArrows =
|
|
2457
|
+
const label = ` babysits(${visible.length} ${listState.mode})`;
|
|
2458
|
+
const showArrows = pageCount > 1;
|
|
2428
2459
|
const suffix = `${label}${showArrows ? ` < ${listState.page + 1}/${pageCount} >` : ""}`;
|
|
2429
2460
|
// Keep the count clickable on narrow terminals by moving it to its own row.
|
|
2430
2461
|
const inline = summary !== undefined && visibleWidth(summary) + visibleWidth(suffix) <= width;
|
|
@@ -2439,11 +2470,9 @@ export function createWidgetComponent(
|
|
|
2439
2470
|
prevX: showArrows ? prefixWidth + suffix.indexOf("<") : undefined,
|
|
2440
2471
|
nextX: showArrows ? prefixWidth + suffix.indexOf(">") : undefined,
|
|
2441
2472
|
});
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
pushSession(session);
|
|
2446
|
-
}
|
|
2473
|
+
const start = listState.page * WIDGET_SESSION_PAGE_SIZE;
|
|
2474
|
+
for (const session of visible.slice(start, start + WIDGET_SESSION_PAGE_SIZE)) {
|
|
2475
|
+
pushSession(session);
|
|
2447
2476
|
}
|
|
2448
2477
|
} else {
|
|
2449
2478
|
if (summary !== undefined) push(summary);
|
|
@@ -2459,16 +2488,21 @@ export function createWidgetComponent(
|
|
|
2459
2488
|
// Toggle on press so a terminal that omits release still works.
|
|
2460
2489
|
if (event.type === "press") {
|
|
2461
2490
|
if (target.type === "session") {
|
|
2462
|
-
if (expandedSessionIds.has(target.id)) expandedSessionIds.
|
|
2463
|
-
else
|
|
2491
|
+
if (expandedSessionIds.has(target.id)) expandedSessionIds.clear();
|
|
2492
|
+
else {
|
|
2493
|
+
expandedSessionIds.clear();
|
|
2494
|
+
expandedSessionIds.add(target.id);
|
|
2495
|
+
}
|
|
2464
2496
|
} else if (listState) {
|
|
2465
|
-
const
|
|
2497
|
+
const count = listState.mode === "running" ? sessions.filter((session) => session.active).length : sessions.length;
|
|
2498
|
+
const pageCount = Math.max(1, Math.ceil(count / WIDGET_SESSION_PAGE_SIZE));
|
|
2466
2499
|
if (event.x === target.prevX && target.prevX !== undefined) {
|
|
2467
2500
|
listState.page = (listState.page - 1 + pageCount) % pageCount;
|
|
2468
2501
|
} else if (event.x === target.nextX && target.nextX !== undefined) {
|
|
2469
2502
|
listState.page = (listState.page + 1) % pageCount;
|
|
2470
2503
|
} else {
|
|
2471
|
-
listState.
|
|
2504
|
+
listState.mode = listState.mode === "running" ? "all" : "running";
|
|
2505
|
+
listState.page = 0;
|
|
2472
2506
|
}
|
|
2473
2507
|
}
|
|
2474
2508
|
return { handled: true, render: true };
|
|
@@ -3437,7 +3471,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3437
3471
|
}
|
|
3438
3472
|
|
|
3439
3473
|
const expandedWidgetSessions = new Set<string>();
|
|
3440
|
-
const widgetListState: WidgetSessionListState = {
|
|
3474
|
+
const widgetListState: WidgetSessionListState = { mode: "running", page: 0 };
|
|
3441
3475
|
|
|
3442
3476
|
const refreshWidget = async (ctx: ExtensionContext, snapshot?: BsSession[]) => {
|
|
3443
3477
|
if (!ctx.hasUI) return;
|
|
@@ -3475,7 +3509,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
3475
3509
|
const activeTails = new Map(active.map((session, index) => [session.id, tails[index]]));
|
|
3476
3510
|
const displays = ordered.map((session): WidgetSessionDisplay => {
|
|
3477
3511
|
const id = session.id;
|
|
3478
|
-
const
|
|
3512
|
+
const meta = readMeta(id);
|
|
3513
|
+
const isSubagent = meta?.kind === "subagent";
|
|
3514
|
+
const details = isSubagent
|
|
3515
|
+
? [
|
|
3516
|
+
...(meta?.task ? [`task: ${escapeCommandForDisplay(meta.task)}`] : []),
|
|
3517
|
+
...(meta?.launchCommand ? [theme.bold(escapeCommandForDisplay(readableLaunchCommand(meta.launchCommand)))] : []),
|
|
3518
|
+
]
|
|
3519
|
+
: meta?.command ? [theme.bold(escapeCommandForDisplay(meta.command))] : [];
|
|
3479
3520
|
if (session.state === "running") {
|
|
3480
3521
|
const state: WidgetSessionState = isSubagent && progressById.get(id)?.done ? "idle" : "running";
|
|
3481
3522
|
return {
|
|
@@ -3483,6 +3524,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
3483
3524
|
header: renderWidgetSessionHeader(id, isSubagent ? "agent" : "process", state, elapsedOf(id), theme),
|
|
3484
3525
|
tail: activeTails.get(id) ?? [],
|
|
3485
3526
|
expandable: true,
|
|
3527
|
+
active: true,
|
|
3528
|
+
details,
|
|
3486
3529
|
};
|
|
3487
3530
|
}
|
|
3488
3531
|
return {
|
|
@@ -3494,6 +3537,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3494
3537
|
"",
|
|
3495
3538
|
theme,
|
|
3496
3539
|
),
|
|
3540
|
+
details,
|
|
3497
3541
|
// Only read/parse the five visible sessions on demand.
|
|
3498
3542
|
get tail() {
|
|
3499
3543
|
try {
|