@yusukeshib/pi-babysit 0.6.12 → 0.6.14
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 -4
- package/index.ts +50 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,8 +88,11 @@ ten per page; `< 1/4 >` shows the current/total page when needed. Click the
|
|
|
88
88
|
arrows to page. Collapsed rows occupy one line, previewing the command for
|
|
89
89
|
processes and the task for agents (with `…` when clipped). Click a row for the
|
|
90
90
|
complete wrapped command or task; agent rows also show their RPC worker launch
|
|
91
|
-
argv (bold, readable text rather than a copy-paste shell command), and up to
|
|
92
|
-
output lines.
|
|
91
|
+
argv (bold, readable text rather than a copy-paste shell command), and up to 50
|
|
92
|
+
output lines. Agent output retains recent RPC text, tool calls, and tool results
|
|
93
|
+
in chronological order instead of showing only the latest message. Blank lines
|
|
94
|
+
and surrounding whitespace are preserved. Expanded output lines wrap without a
|
|
95
|
+
100-character cutoff.
|
|
93
96
|
Opening another row closes the previous
|
|
94
97
|
one. A session with no available output shows an explicit placeholder when
|
|
95
98
|
expanded. Mouse clicks
|
|
@@ -207,8 +210,13 @@ because blindly rerunning an arbitrary command can duplicate side effects.
|
|
|
207
210
|
incrementally, which keeps follow-up tasks efficient.
|
|
208
211
|
|
|
209
212
|
Subagents load `self-reap.ts`, which exits an idle finished subagent after a
|
|
210
|
-
grace window
|
|
211
|
-
|
|
213
|
+
grace window using the same parked-turn rule, so a subagent waiting on a long
|
|
214
|
+
build is never false-killed. Callers can set `reapAfter: "5m"` on
|
|
215
|
+
`babysit_run { profile: "subagent", task, reapAfter: "5m" }`. Omission uses
|
|
216
|
+
`PI_BABYSIT_REAP_AFTER`, then `PI_SUBAGENT_REAP_AFTER`, then 120s.
|
|
217
|
+
`reapAfter: "none"` (also `"off"` or `"0"`) disables idle reaping; the absolute
|
|
218
|
+
`timeout` still applies. Follow-up tasks cancel the idle timer. This differs
|
|
219
|
+
from `idleTimeout`, which kills based on lack of output, even during work. Give bounded
|
|
212
220
|
recon/review tasks at least one cost, turn, tool-call, or token budget; omit
|
|
213
221
|
budgets only for intentionally open-ended work. Optional task budgets are
|
|
214
222
|
observed by the parent poller. `maxUsageTokens` counts cumulative input, output,
|
package/index.ts
CHANGED
|
@@ -464,6 +464,7 @@ interface Meta {
|
|
|
464
464
|
// subagent
|
|
465
465
|
task?: string;
|
|
466
466
|
launchCommand?: string;
|
|
467
|
+
reapAfter?: string;
|
|
467
468
|
promptOffset?: number;
|
|
468
469
|
model?: string;
|
|
469
470
|
tools?: string[];
|
|
@@ -1065,9 +1066,11 @@ const SUBAGENT_BUDGET_GRACE_MS =
|
|
|
1065
1066
|
parseDurMs(process.env.PI_BABYSIT_BUDGET_GRACE ?? "90s") ?? 90_000;
|
|
1066
1067
|
const SUBAGENT_REAP_AFTER =
|
|
1067
1068
|
process.env.PI_BABYSIT_REAP_AFTER ?? process.env.PI_SUBAGENT_REAP_AFTER ?? "120s";
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1069
|
+
export function subagentReuseHint(reapAfter = SUBAGENT_REAP_AFTER): string {
|
|
1070
|
+
return ["0", "off", "none"].includes(reapAfter)
|
|
1071
|
+
? "Session remains available until its absolute timeout."
|
|
1072
|
+
: `Session remains available for follow-ups during the ${reapAfter} idle grace.`;
|
|
1073
|
+
}
|
|
1071
1074
|
|
|
1072
1075
|
export function clip(s: string, maxBytes = TAIL_MAX_BYTES): string {
|
|
1073
1076
|
if (maxBytes <= 0) return "";
|
|
@@ -1556,6 +1559,8 @@ export interface Progress {
|
|
|
1556
1559
|
finalText: string;
|
|
1557
1560
|
/** Best-effort text from the currently streaming assistant message. */
|
|
1558
1561
|
streamingText: string;
|
|
1562
|
+
/** Recent readable RPC output, independent of the latest assistant message. */
|
|
1563
|
+
logText: string;
|
|
1559
1564
|
/** Context size reported by the most recent assistant response. */
|
|
1560
1565
|
tokens?: number;
|
|
1561
1566
|
/** Cumulative model usage for the current subagent task. */
|
|
@@ -1616,6 +1621,7 @@ function emptyProgress(): Progress {
|
|
|
1616
1621
|
toolCallCount: 0,
|
|
1617
1622
|
finalText: "",
|
|
1618
1623
|
streamingText: "",
|
|
1624
|
+
logText: "",
|
|
1619
1625
|
modelCalls: 0,
|
|
1620
1626
|
usageTokens: 0,
|
|
1621
1627
|
inputTokens: 0,
|
|
@@ -1713,6 +1719,12 @@ function updateProgressState(progress: Progress): Progress {
|
|
|
1713
1719
|
return progress;
|
|
1714
1720
|
}
|
|
1715
1721
|
|
|
1722
|
+
function appendProgressLog(progress: Progress, text: string, separate = false): void {
|
|
1723
|
+
if (!text) return;
|
|
1724
|
+
const separator = separate && progress.logText && !progress.logText.endsWith("\n") ? "\n" : "";
|
|
1725
|
+
progress.logText = (progress.logText + separator + text).split("\n").slice(-50).join("\n");
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1716
1728
|
function parseEventLine(progress: Progress, raw: string): void {
|
|
1717
1729
|
const line = raw.replace(/\r$/, "").trim();
|
|
1718
1730
|
if (!line.startsWith("{")) return;
|
|
@@ -1732,12 +1744,14 @@ function parseEventLine(progress: Progress, raw: string): void {
|
|
|
1732
1744
|
| { type?: string; delta?: string }
|
|
1733
1745
|
| undefined;
|
|
1734
1746
|
if (update?.type === "text_delta" && typeof update.delta === "string") {
|
|
1747
|
+
appendProgressLog(progress, update.delta, !progress.streamingText);
|
|
1735
1748
|
progress.streamingText = clip(progress.streamingText + update.delta, ANSWER_MAX_BYTES);
|
|
1736
1749
|
}
|
|
1737
1750
|
break;
|
|
1738
1751
|
}
|
|
1739
1752
|
case "tool_execution_start": {
|
|
1740
1753
|
const name = String(event.toolName ?? "tool");
|
|
1754
|
+
appendProgressLog(progress, summarizeToolCall(name, (event.args as Record<string, unknown>) ?? {}), true);
|
|
1741
1755
|
progress.toolCallCount++;
|
|
1742
1756
|
progress.toolCalls.push({
|
|
1743
1757
|
name,
|
|
@@ -1771,12 +1785,22 @@ function parseEventLine(progress: Progress, raw: string): void {
|
|
|
1771
1785
|
};
|
|
1772
1786
|
}
|
|
1773
1787
|
| undefined;
|
|
1788
|
+
// Compact RPC logs omit successful tool_execution_end payloads, but
|
|
1789
|
+
// retain the corresponding toolResult message once.
|
|
1790
|
+
if (message?.role === "toolResult") {
|
|
1791
|
+
for (const block of message.content ?? []) {
|
|
1792
|
+
if (block.type === "text" && block.text) appendProgressLog(progress, block.text, true);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1774
1795
|
if (message?.role === "assistant") {
|
|
1775
1796
|
const text = (message.content ?? [])
|
|
1776
1797
|
.filter((content) => content.type === "text" && content.text)
|
|
1777
1798
|
.map((content) => content.text)
|
|
1778
1799
|
.join("");
|
|
1779
|
-
if (text.trim())
|
|
1800
|
+
if (text.trim()) {
|
|
1801
|
+
if (!progress.streamingText) appendProgressLog(progress, text, true);
|
|
1802
|
+
progress.finalText = clip(text, ANSWER_MAX_BYTES);
|
|
1803
|
+
}
|
|
1780
1804
|
if (message.stopReason === "error") {
|
|
1781
1805
|
progress.errorMsg = message.errorMessage || "subagent model request failed";
|
|
1782
1806
|
}
|
|
@@ -2080,6 +2104,7 @@ interface SubagentOpts {
|
|
|
2080
2104
|
depth: number;
|
|
2081
2105
|
maxDepth: number;
|
|
2082
2106
|
budget?: SubagentBudget;
|
|
2107
|
+
reapAfter?: string;
|
|
2083
2108
|
// Idle-timeout is OFF by default: an RPC-mode pi is silent while it works,
|
|
2084
2109
|
// so idle detection would false-kill a busy subagent. The absolute timeout
|
|
2085
2110
|
// is the safety valve instead.
|
|
@@ -2090,6 +2115,11 @@ interface SubagentOpts {
|
|
|
2090
2115
|
async function spawnSubagent(
|
|
2091
2116
|
opts: SubagentOpts,
|
|
2092
2117
|
): Promise<{ id: string; model?: string } | { error: string }> {
|
|
2118
|
+
const reapAfter = opts.reapAfter?.trim() ?? SUBAGENT_REAP_AFTER;
|
|
2119
|
+
if (opts.reapAfter !== undefined && !["0", "off", "none"].includes(reapAfter) &&
|
|
2120
|
+
(!/^\d+(ms|s|m|h)?$/.test(reapAfter) || !Number.isFinite(parseDurMs(reapAfter)))) {
|
|
2121
|
+
return { error: "Invalid reapAfter: use a duration such as 30s or 5m, or none/off/0 to disable." };
|
|
2122
|
+
}
|
|
2093
2123
|
// Long-lived RPC worker: the task is NOT passed as argv — it is injected
|
|
2094
2124
|
// below as an RPC `prompt` command, whose response we validate so spawn
|
|
2095
2125
|
// failures (bad model, missing API key) are loud instead of a silent exit=1.
|
|
@@ -2168,6 +2198,7 @@ async function spawnSubagent(
|
|
|
2168
2198
|
r = await bs(bsArgs, {
|
|
2169
2199
|
cwd: opts.cwd,
|
|
2170
2200
|
env: {
|
|
2201
|
+
PI_BABYSIT_REAP_AFTER: reapAfter,
|
|
2171
2202
|
[SUBAGENT_DEPTH_ENV]: String(opts.depth),
|
|
2172
2203
|
[SUBAGENT_MAX_DEPTH_ENV]: String(opts.maxDepth),
|
|
2173
2204
|
},
|
|
@@ -2200,6 +2231,7 @@ async function spawnSubagent(
|
|
|
2200
2231
|
depth: opts.depth,
|
|
2201
2232
|
maxDepth: opts.maxDepth,
|
|
2202
2233
|
budget: opts.budget,
|
|
2234
|
+
reapAfter,
|
|
2203
2235
|
});
|
|
2204
2236
|
|
|
2205
2237
|
// Wait for pi to boot (first JSON event in the log), then inject the task.
|
|
@@ -2293,6 +2325,7 @@ async function spawnSubagent(
|
|
|
2293
2325
|
depth: opts.depth,
|
|
2294
2326
|
maxDepth: opts.maxDepth,
|
|
2295
2327
|
budget: opts.budget,
|
|
2328
|
+
reapAfter,
|
|
2296
2329
|
startedAt: Date.now(),
|
|
2297
2330
|
});
|
|
2298
2331
|
return { id, model: resolvedModel };
|
|
@@ -2368,7 +2401,7 @@ function renderWidgetSessionHeader(
|
|
|
2368
2401
|
|
|
2369
2402
|
// How many trailing output lines to show per session in the widget.
|
|
2370
2403
|
const WIDGET_COLLAPSED_TAIL_LINES = 1;
|
|
2371
|
-
const WIDGET_EXPANDED_TAIL_LINES =
|
|
2404
|
+
const WIDGET_EXPANDED_TAIL_LINES = 50;
|
|
2372
2405
|
const WIDGET_SESSION_PAGE_SIZE = 10;
|
|
2373
2406
|
|
|
2374
2407
|
export interface WidgetSessionListState {
|
|
@@ -2565,10 +2598,12 @@ export function widgetTail(
|
|
|
2565
2598
|
raw = readTailLines(logPath(id), lines);
|
|
2566
2599
|
} else {
|
|
2567
2600
|
const progress = subagentProgress ?? taskProgressOf(id).progress;
|
|
2568
|
-
if (progress.
|
|
2569
|
-
raw = progress.
|
|
2601
|
+
if (progress.logText) {
|
|
2602
|
+
raw = progress.logText.split("\n");
|
|
2603
|
+
} else if (progress.streamingText.trim()) {
|
|
2604
|
+
raw = progress.streamingText.split("\n");
|
|
2570
2605
|
} else if (progress.finalText.trim()) {
|
|
2571
|
-
raw = progress.finalText.
|
|
2606
|
+
raw = progress.finalText.split("\n");
|
|
2572
2607
|
} else if (progress.toolCalls.length > 0) {
|
|
2573
2608
|
raw = progress.toolCalls.map((tool) => tool.summary);
|
|
2574
2609
|
} else {
|
|
@@ -2577,7 +2612,6 @@ export function widgetTail(
|
|
|
2577
2612
|
}
|
|
2578
2613
|
return raw
|
|
2579
2614
|
.map(sanitizeTailLine)
|
|
2580
|
-
.filter((line) => line.trim().length > 0)
|
|
2581
2615
|
.slice(-lines);
|
|
2582
2616
|
}
|
|
2583
2617
|
|
|
@@ -2782,7 +2816,7 @@ async function waitForTask(
|
|
|
2782
2816
|
ok: completed.ok,
|
|
2783
2817
|
text:
|
|
2784
2818
|
`Subagent ${id} finished its task (${stats}).\n` +
|
|
2785
|
-
`${
|
|
2819
|
+
`${subagentReuseHint(readMeta(id)?.reapAfter)} Follow-up: babysit_send { id: "${id}" }, ` +
|
|
2786
2820
|
`or babysit_kill when done.\n\n${completed.body}`,
|
|
2787
2821
|
status: st,
|
|
2788
2822
|
progress: prog,
|
|
@@ -3879,6 +3913,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
3879
3913
|
"Absolute auto-kill after this long (e.g. 30m). Default: none for processes (dev servers may run forever), 15m for subagents. 'none' disables.",
|
|
3880
3914
|
}),
|
|
3881
3915
|
),
|
|
3916
|
+
reapAfter: Type.Optional(
|
|
3917
|
+
Type.String({
|
|
3918
|
+
description: "Subagent only: stop the worker after this long in task-complete IDLE (e.g. 30s, 5m). Defaults to PI_BABYSIT_REAP_AFTER, then PI_SUBAGENT_REAP_AFTER, then 120s. none/off/0 disables; absolute timeout still applies. Follow-up tasks cancel the idle timer.",
|
|
3919
|
+
}),
|
|
3920
|
+
),
|
|
3882
3921
|
idleTimeout: Type.Optional(
|
|
3883
3922
|
Type.String({
|
|
3884
3923
|
description:
|
|
@@ -4199,6 +4238,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4199
4238
|
maxUsageTokens: params.maxUsageTokens,
|
|
4200
4239
|
}
|
|
4201
4240
|
: undefined,
|
|
4241
|
+
reapAfter: params.reapAfter,
|
|
4202
4242
|
timeout: params.timeout ?? "15m",
|
|
4203
4243
|
idleTimeout: params.idleTimeout,
|
|
4204
4244
|
});
|