@yusukeshib/pi-babysit 0.3.2 → 0.3.4
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/index.ts +149 -16
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -36,9 +36,9 @@ import * as fs from "node:fs";
|
|
|
36
36
|
import * as os from "node:os";
|
|
37
37
|
import * as path from "node:path";
|
|
38
38
|
import { fileURLToPath } from "node:url";
|
|
39
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
39
|
+
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
40
40
|
import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
41
|
-
import { Box, Markdown, Text } from "@earendil-works/pi-tui";
|
|
41
|
+
import { Box, Container, Markdown, Text } from "@earendil-works/pi-tui";
|
|
42
42
|
import { Type } from "typebox";
|
|
43
43
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
44
44
|
import { type AgentConfig, type AgentScope, discoverAgents } from "./agents";
|
|
@@ -1146,6 +1146,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
1146
1146
|
meta.notified = true;
|
|
1147
1147
|
writeMeta(s.id, meta);
|
|
1148
1148
|
const ok = s.exit_code === 0;
|
|
1149
|
+
const status: DisplayStatus = ok
|
|
1150
|
+
? "success"
|
|
1151
|
+
: s.state === "dead" || s.exit_code == null
|
|
1152
|
+
? "terminated"
|
|
1153
|
+
: "failed";
|
|
1149
1154
|
const output = await inlineOutput(s.id, s);
|
|
1150
1155
|
const runtime = meta.startedAt
|
|
1151
1156
|
? `${Math.round((Date.now() - meta.startedAt) / 1000)}s`
|
|
@@ -1162,7 +1167,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
1162
1167
|
`${summary}\nCommand: ${meta.command ?? "?"}\nLog: ${logPath(s.id)}${output}` +
|
|
1163
1168
|
`\n\nThis is the automatic process-end notification. Do not call babysit_check just to re-verify. Inspect the log only when needed, using bounded commands such as tail or rg; never read it in full.`,
|
|
1164
1169
|
display: true,
|
|
1165
|
-
details: {
|
|
1170
|
+
details: {
|
|
1171
|
+
id: s.id,
|
|
1172
|
+
exitCode: s.exit_code,
|
|
1173
|
+
success: ok,
|
|
1174
|
+
status,
|
|
1175
|
+
runtime,
|
|
1176
|
+
logPath: logPath(s.id),
|
|
1177
|
+
},
|
|
1166
1178
|
},
|
|
1167
1179
|
{ triggerTurn: true, deliverAs: "steer" },
|
|
1168
1180
|
);
|
|
@@ -1198,23 +1210,63 @@ export default function (pi: ExtensionAPI) {
|
|
|
1198
1210
|
ctx.ui.setWidget("pi-babysit", lines, { placement: "belowEditor" });
|
|
1199
1211
|
};
|
|
1200
1212
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1213
|
+
type DisplayStatus = "started" | "running" | "idle" | "success" | "failed" | "terminated";
|
|
1214
|
+
const renderStatus = (status: DisplayStatus, theme: Theme, prefix?: string): string => {
|
|
1215
|
+
const labels: Record<
|
|
1216
|
+
DisplayStatus,
|
|
1217
|
+
{ icon: string; text: string; color: "accent" | "warning" | "success" | "error" }
|
|
1218
|
+
> = {
|
|
1219
|
+
started: { icon: "", text: "STARTED", color: "accent" },
|
|
1220
|
+
running: { icon: "", text: "RUNNING", color: "accent" },
|
|
1221
|
+
idle: { icon: "", text: "IDLE", color: "warning" },
|
|
1222
|
+
success: { icon: "", text: "SUCCESS", color: "success" },
|
|
1223
|
+
failed: { icon: "", text: "FAILED", color: "error" },
|
|
1224
|
+
terminated: { icon: "", text: "TERMINATED", color: "error" },
|
|
1225
|
+
};
|
|
1226
|
+
const label = labels[status];
|
|
1227
|
+
const text = prefix ? `${prefix} ${label.text}` : label.text;
|
|
1228
|
+
const decorated = label.icon ? `${label.icon} ${text}` : text;
|
|
1229
|
+
return theme.fg(label.color, theme.bold(decorated));
|
|
1230
|
+
};
|
|
1231
|
+
const outcomeStatus = (outcome: WaitOutcome): DisplayStatus =>
|
|
1232
|
+
outcome.ok
|
|
1233
|
+
? "success"
|
|
1234
|
+
: outcome.status &&
|
|
1235
|
+
(outcome.status.state === "dead" || outcome.status.exit_code == null)
|
|
1236
|
+
? "terminated"
|
|
1237
|
+
: "failed";
|
|
1238
|
+
|
|
1239
|
+
// Render snapshots and subagent answers INLINE in the transcript as formatted
|
|
1240
|
+
// markdown, with a semantic status label that remains readable on any theme.
|
|
1203
1241
|
pi.registerMessageRenderer("pi-babysit-result", (message, _opts, theme) => {
|
|
1204
|
-
const d = (message.details ?? {}) as {
|
|
1242
|
+
const d = (message.details ?? {}) as {
|
|
1243
|
+
title?: string;
|
|
1244
|
+
body?: string;
|
|
1245
|
+
status?: DisplayStatus;
|
|
1246
|
+
};
|
|
1205
1247
|
const body =
|
|
1206
1248
|
d.body ?? (typeof message.content === "string" ? message.content : "");
|
|
1207
|
-
const box = new Box(1, 0, (t) => theme.bg("
|
|
1249
|
+
const box = new Box(1, 0, (t) => theme.bg("toolSuccessBg", t));
|
|
1250
|
+
if (d.status) box.addChild(new Text(renderStatus(d.status, theme), 0, 0));
|
|
1208
1251
|
if (d.title) box.addChild(new Text(theme.fg("accent", d.title), 0, 0));
|
|
1209
1252
|
box.addChild(new Markdown(body, 0, 0, getMarkdownTheme()));
|
|
1210
1253
|
return box;
|
|
1211
1254
|
});
|
|
1212
1255
|
|
|
1213
|
-
// Process-end notification rendering
|
|
1256
|
+
// Process-end notification rendering with a colored lifecycle label. Keep the
|
|
1257
|
+
// box background subtle: coloring a potentially large log excerpt is noisy.
|
|
1214
1258
|
pi.registerMessageRenderer("pi-babysit-process-end", (message, _opts, theme) => {
|
|
1215
1259
|
const content = typeof message.content === "string" ? message.content : "";
|
|
1216
|
-
const
|
|
1217
|
-
|
|
1260
|
+
const d = (message.details ?? {}) as {
|
|
1261
|
+
status?: DisplayStatus;
|
|
1262
|
+
success?: boolean;
|
|
1263
|
+
exitCode?: number | null;
|
|
1264
|
+
};
|
|
1265
|
+
const status =
|
|
1266
|
+
d.status ?? (d.success ? "success" : d.exitCode == null ? "terminated" : "failed");
|
|
1267
|
+
const box = new Box(1, 1, (t) => theme.bg("toolSuccessBg", t));
|
|
1268
|
+
box.addChild(new Text(renderStatus(status, theme, "babysit_run"), 0, 0));
|
|
1269
|
+
box.addChild(new Text(theme.fg("toolOutput", content), 0, 0));
|
|
1218
1270
|
return box;
|
|
1219
1271
|
});
|
|
1220
1272
|
|
|
@@ -1433,7 +1485,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
1433
1485
|
return {
|
|
1434
1486
|
content: [{ type: "text", text: `${retried ? "Retried once after external worker death.\n" : ""}${outcome.text}` }],
|
|
1435
1487
|
isError: !outcome.ok,
|
|
1436
|
-
details: {
|
|
1488
|
+
details: {
|
|
1489
|
+
id: res.id,
|
|
1490
|
+
kind: "process",
|
|
1491
|
+
command: params.command,
|
|
1492
|
+
logPath: logPath(res.id),
|
|
1493
|
+
retried,
|
|
1494
|
+
status: outcomeStatus(outcome),
|
|
1495
|
+
},
|
|
1437
1496
|
};
|
|
1438
1497
|
}
|
|
1439
1498
|
|
|
@@ -1445,6 +1504,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
1445
1504
|
let quickStatus = await statusOf(res.id);
|
|
1446
1505
|
let retried = false;
|
|
1447
1506
|
if (params.retryOnWorkerDeath && quickStatus?.state === "dead" && quickStatus.exit_code == null) {
|
|
1507
|
+
// This attempt is already represented by the retrying tool result; do
|
|
1508
|
+
// not let the exit poller emit a second, stale completion message.
|
|
1509
|
+
suppressNotify(res.id);
|
|
1448
1510
|
const retry = await spawnProcess(spawnOpts);
|
|
1449
1511
|
if (!("error" in retry)) {
|
|
1450
1512
|
res = retry;
|
|
@@ -1459,7 +1521,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
1459
1521
|
return {
|
|
1460
1522
|
content: [{ type: "text", text: `${retried ? "Retried once after external worker death.\n" : ""}${outcome.text}` }],
|
|
1461
1523
|
isError: !outcome.ok,
|
|
1462
|
-
details: {
|
|
1524
|
+
details: {
|
|
1525
|
+
id: res.id,
|
|
1526
|
+
kind: "process",
|
|
1527
|
+
command: params.command,
|
|
1528
|
+
logPath: logPath(res.id),
|
|
1529
|
+
retried,
|
|
1530
|
+
status: outcomeStatus(outcome),
|
|
1531
|
+
},
|
|
1463
1532
|
};
|
|
1464
1533
|
}
|
|
1465
1534
|
|
|
@@ -1479,7 +1548,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
1479
1548
|
`Human can watch/take over: /babysit`,
|
|
1480
1549
|
},
|
|
1481
1550
|
],
|
|
1482
|
-
details: {
|
|
1551
|
+
details: {
|
|
1552
|
+
id: res.id,
|
|
1553
|
+
kind: "process",
|
|
1554
|
+
command: params.command,
|
|
1555
|
+
logPath: logPath(res.id),
|
|
1556
|
+
retried,
|
|
1557
|
+
status: "started" satisfies DisplayStatus,
|
|
1558
|
+
},
|
|
1483
1559
|
// Do not return `terminate: true` here. In RPC/subagent hosts that hint
|
|
1484
1560
|
// can shut down the hosting pi worker, whose process-tree cleanup then
|
|
1485
1561
|
// kills the otherwise detached babysit supervisor and closes its PTY
|
|
@@ -1539,8 +1615,49 @@ export default function (pi: ExtensionAPI) {
|
|
|
1539
1615
|
`Human can watch/steer: /babysit (pick ${res.id})`,
|
|
1540
1616
|
},
|
|
1541
1617
|
],
|
|
1542
|
-
details: {
|
|
1618
|
+
details: {
|
|
1619
|
+
id: res.id,
|
|
1620
|
+
kind: "subagent",
|
|
1621
|
+
agent: agent?.name,
|
|
1622
|
+
model: res.model,
|
|
1623
|
+
task: params.task,
|
|
1624
|
+
status: "started" satisfies DisplayStatus,
|
|
1625
|
+
},
|
|
1626
|
+
};
|
|
1627
|
+
},
|
|
1628
|
+
// The result label already includes "babysit_run"; suppress the default
|
|
1629
|
+
// call header so the tool name is not shown twice in adjacent lines.
|
|
1630
|
+
renderCall() {
|
|
1631
|
+
return new Container();
|
|
1632
|
+
},
|
|
1633
|
+
renderResult(result, { isPartial }, theme, context) {
|
|
1634
|
+
const details = (result.details ?? {}) as {
|
|
1635
|
+
kind?: "process" | "subagent";
|
|
1636
|
+
status?: DisplayStatus;
|
|
1543
1637
|
};
|
|
1638
|
+
const content = result.content
|
|
1639
|
+
.filter((item): item is { type: "text"; text: string } => item.type === "text")
|
|
1640
|
+
.map((item) => item.text)
|
|
1641
|
+
.join("\n");
|
|
1642
|
+
// A vanished supervisor must never be presented as success, even if an
|
|
1643
|
+
// older/stale result omitted status details or the host did not preserve
|
|
1644
|
+
// the custom isError field. The textual diagnosis is part of our stable
|
|
1645
|
+
// tool contract, so give it precedence over all fallback classification.
|
|
1646
|
+
const workerDead =
|
|
1647
|
+
content.includes("worker-dead") ||
|
|
1648
|
+
content.includes("babysit supervisor disappeared");
|
|
1649
|
+
const status: DisplayStatus = isPartial
|
|
1650
|
+
? "running"
|
|
1651
|
+
: workerDead
|
|
1652
|
+
? "terminated"
|
|
1653
|
+
: details.status ??
|
|
1654
|
+
(context.isError
|
|
1655
|
+
? "failed"
|
|
1656
|
+
: details.kind === "subagent" || content.includes(NOTIFY_MARKER)
|
|
1657
|
+
? "started"
|
|
1658
|
+
: "success");
|
|
1659
|
+
const label = renderStatus(status, theme, "babysit_run");
|
|
1660
|
+
return new Text(content ? `${label}\n${theme.fg("toolOutput", content)}` : label, 0, 0);
|
|
1544
1661
|
},
|
|
1545
1662
|
});
|
|
1546
1663
|
|
|
@@ -2041,12 +2158,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
2041
2158
|
`${picked.id} ${picked.state}${elapsedSuffix}` +
|
|
2042
2159
|
(picked.exit_code != null ? ` (exit=${picked.exit_code})` : "") +
|
|
2043
2160
|
` ${stats}`;
|
|
2161
|
+
const status: DisplayStatus = prog.errorMsg
|
|
2162
|
+
? "failed"
|
|
2163
|
+
: prog.running || prog.waitingOnProcess
|
|
2164
|
+
? "running"
|
|
2165
|
+
: prog.done
|
|
2166
|
+
? "idle"
|
|
2167
|
+
: picked.exit_code === 0
|
|
2168
|
+
? "success"
|
|
2169
|
+
: "terminated";
|
|
2044
2170
|
if (ctx.hasUI) {
|
|
2045
2171
|
pi.sendMessage({
|
|
2046
2172
|
customType: "pi-babysit-result",
|
|
2047
2173
|
content: title,
|
|
2048
2174
|
display: true,
|
|
2049
|
-
details: { title, body },
|
|
2175
|
+
details: { title, body, status },
|
|
2050
2176
|
});
|
|
2051
2177
|
} else {
|
|
2052
2178
|
ctx.ui.notify(`${title}\n\n${body}`, "info");
|
|
@@ -2075,12 +2201,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
2075
2201
|
(running
|
|
2076
2202
|
? `\n\n_Take over in your own terminal:_ \`${attachCmd(picked.id)}\` _(detach: Ctrl-\\ Ctrl-\\)._ Re-run \`/babysit\` to refresh this snapshot.`
|
|
2077
2203
|
: "");
|
|
2204
|
+
const status: DisplayStatus = running
|
|
2205
|
+
? "running"
|
|
2206
|
+
: picked.exit_code === 0
|
|
2207
|
+
? "success"
|
|
2208
|
+
: picked.state === "dead" || picked.exit_code == null
|
|
2209
|
+
? "terminated"
|
|
2210
|
+
: "failed";
|
|
2078
2211
|
if (ctx.hasUI) {
|
|
2079
2212
|
pi.sendMessage({
|
|
2080
2213
|
customType: "pi-babysit-result",
|
|
2081
2214
|
content: title,
|
|
2082
2215
|
display: true,
|
|
2083
|
-
details: { title, body },
|
|
2216
|
+
details: { title, body, status },
|
|
2084
2217
|
});
|
|
2085
2218
|
} else {
|
|
2086
2219
|
ctx.ui.notify(`${title}\n\n${body}`, "info");
|