@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.
Files changed (2) hide show
  1. package/index.ts +146 -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
 
@@ -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: { 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
+ },
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: { 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
+ },
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: { 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;
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yusukeshib/pi-babysit",
3
- "version": "0.3.3",
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",