@yusukeshib/pi-babysit 0.6.7 → 0.6.8
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 +12 -6
- package/index.ts +61 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -81,12 +81,18 @@ 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 occupy one line: processes show the command,
|
|
89
|
+
while agents show a task preview. Click an agent row for the full wrapped task,
|
|
90
|
+
its RPC worker launch argv (bold, readable text rather than a copy-paste shell
|
|
91
|
+
command), and up to 20 output lines. Opening another row closes the previous
|
|
92
|
+
one. A session with no available output shows an explicit placeholder when
|
|
93
|
+
expanded. Mouse clicks
|
|
94
|
+
require fullscreen mode; `/babysit` works in regular mode. The displayed `age`
|
|
95
|
+
is total session age, not time spent
|
|
90
96
|
in the current state. Transcript details
|
|
91
97
|
from tool results, snapshots, and completion notifications collapse or expand
|
|
92
98
|
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 {
|
|
@@ -2399,8 +2411,8 @@ export function createWidgetComponent(
|
|
|
2399
2411
|
render(width: number): string[] {
|
|
2400
2412
|
const lines: string[] = [];
|
|
2401
2413
|
targetByLine = [];
|
|
2402
|
-
const push = (text: string, target?: Target) => {
|
|
2403
|
-
lines.push(truncateToWidth(text, Math.max(0, width),
|
|
2414
|
+
const push = (text: string, target?: Target, ellipsis = "") => {
|
|
2415
|
+
lines.push(truncateToWidth(text, Math.max(0, width), ellipsis));
|
|
2404
2416
|
targetByLine.push(target);
|
|
2405
2417
|
};
|
|
2406
2418
|
const pushSession = (session: WidgetSessionDisplay) => {
|
|
@@ -2409,10 +2421,25 @@ export function createWidgetComponent(
|
|
|
2409
2421
|
expanded ? -WIDGET_EXPANDED_TAIL_LINES : -WIDGET_COLLAPSED_TAIL_LINES,
|
|
2410
2422
|
);
|
|
2411
2423
|
const target = session.expandable ? { type: "session" as const, id: session.id } : undefined;
|
|
2412
|
-
|
|
2424
|
+
const details = session.details ?? [];
|
|
2425
|
+
const pushDetail = (detail: string) => {
|
|
2426
|
+
const text = detail.startsWith("task: ") ? detail.slice(6) : detail;
|
|
2427
|
+
if (width > 6) {
|
|
2428
|
+
for (const line of wrapTextWithAnsi(text, width - 6)) push(` ${line}`, target);
|
|
2429
|
+
} else {
|
|
2430
|
+
push(` ${text}`, target);
|
|
2431
|
+
}
|
|
2432
|
+
};
|
|
2433
|
+
if (!expanded && details[0]?.startsWith("task: ")) {
|
|
2434
|
+
push(`${session.header} ${details[0].slice(6)}`, target, "…");
|
|
2435
|
+
} else if (!expanded && details.length) {
|
|
2436
|
+
push(`${session.header} ${details[0]}`, target);
|
|
2437
|
+
for (const detail of details.slice(1)) pushDetail(detail);
|
|
2438
|
+
} else if (!expanded && tail.length === 1) {
|
|
2413
2439
|
push(`${session.header} ${tail[0]}`, target);
|
|
2414
2440
|
} else {
|
|
2415
2441
|
push(session.header, target);
|
|
2442
|
+
for (const detail of details) pushDetail(detail);
|
|
2416
2443
|
for (const line of tail) push(` ${line}`, target);
|
|
2417
2444
|
if (expanded && tail.length === 0) push(" (no output available)", target);
|
|
2418
2445
|
}
|
|
@@ -2421,10 +2448,11 @@ export function createWidgetComponent(
|
|
|
2421
2448
|
for (const line of summaryLines.slice(0, -1)) push(line);
|
|
2422
2449
|
const summary = summaryLines.at(-1);
|
|
2423
2450
|
if (listState && sessions.length) {
|
|
2424
|
-
const
|
|
2451
|
+
const visible = listState.mode === "running" ? sessions.filter((session) => session.active) : sessions;
|
|
2452
|
+
const pageCount = Math.max(1, Math.ceil(visible.length / WIDGET_SESSION_PAGE_SIZE));
|
|
2425
2453
|
listState.page = Math.min(Math.max(0, listState.page), pageCount - 1);
|
|
2426
|
-
const label = `
|
|
2427
|
-
const showArrows =
|
|
2454
|
+
const label = ` babysits(${visible.length} ${listState.mode})`;
|
|
2455
|
+
const showArrows = pageCount > 1;
|
|
2428
2456
|
const suffix = `${label}${showArrows ? ` < ${listState.page + 1}/${pageCount} >` : ""}`;
|
|
2429
2457
|
// Keep the count clickable on narrow terminals by moving it to its own row.
|
|
2430
2458
|
const inline = summary !== undefined && visibleWidth(summary) + visibleWidth(suffix) <= width;
|
|
@@ -2439,11 +2467,9 @@ export function createWidgetComponent(
|
|
|
2439
2467
|
prevX: showArrows ? prefixWidth + suffix.indexOf("<") : undefined,
|
|
2440
2468
|
nextX: showArrows ? prefixWidth + suffix.indexOf(">") : undefined,
|
|
2441
2469
|
});
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
pushSession(session);
|
|
2446
|
-
}
|
|
2470
|
+
const start = listState.page * WIDGET_SESSION_PAGE_SIZE;
|
|
2471
|
+
for (const session of visible.slice(start, start + WIDGET_SESSION_PAGE_SIZE)) {
|
|
2472
|
+
pushSession(session);
|
|
2447
2473
|
}
|
|
2448
2474
|
} else {
|
|
2449
2475
|
if (summary !== undefined) push(summary);
|
|
@@ -2459,16 +2485,21 @@ export function createWidgetComponent(
|
|
|
2459
2485
|
// Toggle on press so a terminal that omits release still works.
|
|
2460
2486
|
if (event.type === "press") {
|
|
2461
2487
|
if (target.type === "session") {
|
|
2462
|
-
if (expandedSessionIds.has(target.id)) expandedSessionIds.
|
|
2463
|
-
else
|
|
2488
|
+
if (expandedSessionIds.has(target.id)) expandedSessionIds.clear();
|
|
2489
|
+
else {
|
|
2490
|
+
expandedSessionIds.clear();
|
|
2491
|
+
expandedSessionIds.add(target.id);
|
|
2492
|
+
}
|
|
2464
2493
|
} else if (listState) {
|
|
2465
|
-
const
|
|
2494
|
+
const count = listState.mode === "running" ? sessions.filter((session) => session.active).length : sessions.length;
|
|
2495
|
+
const pageCount = Math.max(1, Math.ceil(count / WIDGET_SESSION_PAGE_SIZE));
|
|
2466
2496
|
if (event.x === target.prevX && target.prevX !== undefined) {
|
|
2467
2497
|
listState.page = (listState.page - 1 + pageCount) % pageCount;
|
|
2468
2498
|
} else if (event.x === target.nextX && target.nextX !== undefined) {
|
|
2469
2499
|
listState.page = (listState.page + 1) % pageCount;
|
|
2470
2500
|
} else {
|
|
2471
|
-
listState.
|
|
2501
|
+
listState.mode = listState.mode === "running" ? "all" : "running";
|
|
2502
|
+
listState.page = 0;
|
|
2472
2503
|
}
|
|
2473
2504
|
}
|
|
2474
2505
|
return { handled: true, render: true };
|
|
@@ -3437,7 +3468,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3437
3468
|
}
|
|
3438
3469
|
|
|
3439
3470
|
const expandedWidgetSessions = new Set<string>();
|
|
3440
|
-
const widgetListState: WidgetSessionListState = {
|
|
3471
|
+
const widgetListState: WidgetSessionListState = { mode: "running", page: 0 };
|
|
3441
3472
|
|
|
3442
3473
|
const refreshWidget = async (ctx: ExtensionContext, snapshot?: BsSession[]) => {
|
|
3443
3474
|
if (!ctx.hasUI) return;
|
|
@@ -3475,7 +3506,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
3475
3506
|
const activeTails = new Map(active.map((session, index) => [session.id, tails[index]]));
|
|
3476
3507
|
const displays = ordered.map((session): WidgetSessionDisplay => {
|
|
3477
3508
|
const id = session.id;
|
|
3478
|
-
const
|
|
3509
|
+
const meta = readMeta(id);
|
|
3510
|
+
const isSubagent = meta?.kind === "subagent";
|
|
3511
|
+
const details = isSubagent
|
|
3512
|
+
? [
|
|
3513
|
+
...(meta?.task ? [`task: ${escapeCommandForDisplay(meta.task)}`] : []),
|
|
3514
|
+
...(meta?.launchCommand ? [theme.bold(escapeCommandForDisplay(readableLaunchCommand(meta.launchCommand)))] : []),
|
|
3515
|
+
]
|
|
3516
|
+
: meta?.command ? [theme.bold(summarizeNotificationCommand(meta.command))] : [];
|
|
3479
3517
|
if (session.state === "running") {
|
|
3480
3518
|
const state: WidgetSessionState = isSubagent && progressById.get(id)?.done ? "idle" : "running";
|
|
3481
3519
|
return {
|
|
@@ -3483,6 +3521,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
3483
3521
|
header: renderWidgetSessionHeader(id, isSubagent ? "agent" : "process", state, elapsedOf(id), theme),
|
|
3484
3522
|
tail: activeTails.get(id) ?? [],
|
|
3485
3523
|
expandable: true,
|
|
3524
|
+
active: true,
|
|
3525
|
+
details,
|
|
3486
3526
|
};
|
|
3487
3527
|
}
|
|
3488
3528
|
return {
|
|
@@ -3494,6 +3534,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3494
3534
|
"",
|
|
3495
3535
|
theme,
|
|
3496
3536
|
),
|
|
3537
|
+
details,
|
|
3497
3538
|
// Only read/parse the five visible sessions on demand.
|
|
3498
3539
|
get tail() {
|
|
3499
3540
|
try {
|