@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.
Files changed (2) hide show
  1. package/index.ts +149 -16
  2. 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: { id: s.id, exitCode: s.exit_code, success: ok, runtime, logPath: logPath(s.id) },
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
- // Render a subagent's final answer INLINE in the transcript as formatted
1202
- // markdown (agent output is markdown). Fed via pi.sendMessage below.
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 { title?: string; body?: string };
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("customMessageBg", t));
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 (plain text in a subtle box).
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 box = new Box(1, 0, (t) => theme.bg("customMessageBg", t));
1217
- box.addChild(new Text(content, 0, 0));
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: { id: res.id, kind: "process", command: params.command, logPath: logPath(res.id), retried },
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: { id: res.id, kind: "process", command: params.command, logPath: logPath(res.id), retried },
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: { id: res.id, kind: "process", command: params.command, logPath: logPath(res.id), retried },
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: { id: res.id, kind: "subagent", agent: agent?.name, model: res.model, task: params.task },
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yusukeshib/pi-babysit",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Run any shell command and pi subagents under babysit, with context-safe captured output.",
5
5
  "keywords": [
6
6
  "pi-package",