@yusukeshib/pi-babysit 0.3.3 → 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 +146 -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
|
|
|
@@ -1462,7 +1521,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
1462
1521
|
return {
|
|
1463
1522
|
content: [{ type: "text", text: `${retried ? "Retried once after external worker death.\n" : ""}${outcome.text}` }],
|
|
1464
1523
|
isError: !outcome.ok,
|
|
1465
|
-
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
|
+
},
|
|
1466
1532
|
};
|
|
1467
1533
|
}
|
|
1468
1534
|
|
|
@@ -1482,7 +1548,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
1482
1548
|
`Human can watch/take over: /babysit`,
|
|
1483
1549
|
},
|
|
1484
1550
|
],
|
|
1485
|
-
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
|
+
},
|
|
1486
1559
|
// Do not return `terminate: true` here. In RPC/subagent hosts that hint
|
|
1487
1560
|
// can shut down the hosting pi worker, whose process-tree cleanup then
|
|
1488
1561
|
// kills the otherwise detached babysit supervisor and closes its PTY
|
|
@@ -1542,8 +1615,49 @@ export default function (pi: ExtensionAPI) {
|
|
|
1542
1615
|
`Human can watch/steer: /babysit (pick ${res.id})`,
|
|
1543
1616
|
},
|
|
1544
1617
|
],
|
|
1545
|
-
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;
|
|
1546
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);
|
|
1547
1661
|
},
|
|
1548
1662
|
});
|
|
1549
1663
|
|
|
@@ -2044,12 +2158,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
2044
2158
|
`${picked.id} ${picked.state}${elapsedSuffix}` +
|
|
2045
2159
|
(picked.exit_code != null ? ` (exit=${picked.exit_code})` : "") +
|
|
2046
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";
|
|
2047
2170
|
if (ctx.hasUI) {
|
|
2048
2171
|
pi.sendMessage({
|
|
2049
2172
|
customType: "pi-babysit-result",
|
|
2050
2173
|
content: title,
|
|
2051
2174
|
display: true,
|
|
2052
|
-
details: { title, body },
|
|
2175
|
+
details: { title, body, status },
|
|
2053
2176
|
});
|
|
2054
2177
|
} else {
|
|
2055
2178
|
ctx.ui.notify(`${title}\n\n${body}`, "info");
|
|
@@ -2078,12 +2201,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
2078
2201
|
(running
|
|
2079
2202
|
? `\n\n_Take over in your own terminal:_ \`${attachCmd(picked.id)}\` _(detach: Ctrl-\\ Ctrl-\\)._ Re-run \`/babysit\` to refresh this snapshot.`
|
|
2080
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";
|
|
2081
2211
|
if (ctx.hasUI) {
|
|
2082
2212
|
pi.sendMessage({
|
|
2083
2213
|
customType: "pi-babysit-result",
|
|
2084
2214
|
content: title,
|
|
2085
2215
|
display: true,
|
|
2086
|
-
details: { title, body },
|
|
2216
|
+
details: { title, body, status },
|
|
2087
2217
|
});
|
|
2088
2218
|
} else {
|
|
2089
2219
|
ctx.ui.notify(`${title}\n\n${body}`, "info");
|