@yusukeshib/pi-babysit 0.6.21 → 0.6.23
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 +51 -18
- package/index.ts +137 -53
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,8 +32,8 @@ reachable from anywhere (`~/.pi-babysit/<pi-session-id>/`). Two kinds:
|
|
|
32
32
|
|
|
33
33
|
| kind | started by | completion | on completion |
|
|
34
34
|
| ---- | ---------- | ---------- | ------------- |
|
|
35
|
-
| **process** | `babysit_run { command }` | process **exit** | automatic notification message (`triggerTurn`), batched for all exits observed in the same poll — the agent may end its turn after starting and is resumed on exit, same contract as the old `process` tool |
|
|
36
|
-
| **subagent** | `babysit_run { profile: "subagent", task }` | `agent_settled` in the RPC event stream (worker remains reusable during its idle grace) | `foreground: true` returns the answer and nested usage in the same tool call; background workers must be collected with `babysit_wait` before the parent finishes, and remain reusable until self-reap |
|
|
35
|
+
| **process** | `babysit_run { description, command }` | process **exit** | automatic notification message (`triggerTurn`), batched for all exits observed in the same poll — the agent may end its turn after starting and is resumed on exit, same contract as the old `process` tool |
|
|
36
|
+
| **subagent** | `babysit_run { description, profile: "subagent", task }` | `agent_settled` in the RPC event stream (worker remains reusable during its idle grace) | `foreground: true` returns the answer and nested usage in the same tool call; background workers must be collected with `babysit_wait` before the parent finishes, and remain reusable until self-reap |
|
|
37
37
|
|
|
38
38
|
The **profile is a tool parameter, not a separate tool set**: domain knowledge
|
|
39
39
|
(RPC bookkeeping, per-task byte offsets, parked-turn detection, PTY-safe
|
|
@@ -44,19 +44,22 @@ Subagent recursion is **disabled by default**: a top-level pi may create workers
|
|
|
44
44
|
(depth 1), but those workers cannot create more workers. A top-level caller can
|
|
45
45
|
explicitly opt in for a specific tree with `maxDepth: 2` (or higher) when it
|
|
46
46
|
creates the first worker. Descendants inherit that ceiling and cannot raise it.
|
|
47
|
-
Normal `babysit_run { command }` process execution remains available
|
|
48
|
-
depth.
|
|
47
|
+
Normal `babysit_run { description, command }` process execution remains available
|
|
48
|
+
at every depth.
|
|
49
49
|
|
|
50
50
|
Because sessions are real PTYs, the agent can also **drive interactive
|
|
51
51
|
programs** (installers, wizards, REPLs): type with `babysit_send`
|
|
52
52
|
(text or named keys) and read the rendered screen with
|
|
53
|
-
`babysit_check { screen: true }` — a capability neither retired extension had.
|
|
53
|
+
`babysit_check { description, screen: true }` — a capability neither retired extension had.
|
|
54
54
|
|
|
55
55
|
## Tools (LLM-callable)
|
|
56
56
|
|
|
57
|
+
All five tools require a short, non-blank `description` explaining the purpose
|
|
58
|
+
of that call.
|
|
59
|
+
|
|
57
60
|
| Tool | What it does |
|
|
58
61
|
| ---- | ------------ |
|
|
59
|
-
| `babysit_run` | Run any command (`command`, optional `name`/`pty`/`timeout`/`idleTimeout`/`retryOnWorkerDeath`/`notificationGroup`/`lifecycle`). Set `foreground: true` for one process or subagent whose result is needed in the same tool call; use `returnPattern`/`returnLines`/`maxBytes` to keep noisy process output bounded. Foreground and non-interactive process waits default to lifecycle `attached`, which terminates the process tree if that owning tool call is interrupted; background starts default to `detached`. Or start a named background subagent (`profile: "subagent"`, `task`, optional `name`/`agent`/`model`/`tools`/`maxDepth` and budget fields), then always collect it with `babysit_wait`. `maxDepth` defaults to 1. Subagent `continueAfterStart: true` is accepted as a compatibility alias for this default background behavior. Quick commands return inline; longer process runs notify in the background |
|
|
62
|
+
| `babysit_run` | Run any command (`description` + `command`, optional `name`/`pty`/`timeout`/`idleTimeout`/`retryOnWorkerDeath`/`notificationGroup`/`lifecycle`). Set `foreground: true` for one process or subagent whose result is needed in the same tool call; use `returnPattern`/`returnLines`/`maxBytes` to keep noisy process output bounded. Foreground and non-interactive process waits default to lifecycle `attached`, which terminates the process tree if that owning tool call is interrupted; background starts default to `detached`. Or start a named background subagent (`description`, `profile: "subagent"`, `task`, optional `name`/`agent`/`model`/`tools`/`maxDepth` and budget fields), then always collect it with `babysit_wait`. `maxDepth` defaults to 1. Subagent `continueAfterStart: true` is accepted as a compatibility alias for this default background behavior. Quick commands return inline; longer process runs notify in the background |
|
|
60
63
|
| `babysit_check` | Without an id, list sessions with state/kind filters. With an id, inspect bounded output, search with `pattern`, or capture a TUI with `screen: true`; `maxBytes` overrides the 4 KB default up to 24 KB |
|
|
61
64
|
| `babysit_send` | Process: type `text` / press `keys` into the PTY. Subagent: steer mid-run, or send a follow-up task when confirmed settled (`mode: auto/steer/task`); explicit task mode rejects busy, parked, or unknown state |
|
|
62
65
|
| `babysit_wait` | Block until done: process exit (or `expect: "regex"` readiness marker), subagent task completion. Multi-wait: up to 32 unique `ids` + `mode: "any"\|"all"` |
|
|
@@ -71,6 +74,36 @@ calls if another extension or preset re-enables `bash`, including shell
|
|
|
71
74
|
backgrounding (`… &`, `nohup`, `setsid`, `disown`). Set
|
|
72
75
|
`PI_BABYSIT_ALLOW_BASH=1` to retain direct `bash` explicitly.
|
|
73
76
|
|
|
77
|
+
## Describe every operation
|
|
78
|
+
|
|
79
|
+
Every babysit tool call requires a non-blank `description`. Explain the purpose
|
|
80
|
+
briefly in language the user can understand. For `babysit_run`, `name` remains
|
|
81
|
+
a stable session identifier, while `command` / `task` contains the execution
|
|
82
|
+
instructions. Its description is trimmed and saved with the session. For
|
|
83
|
+
`babysit_check`, `babysit_send`, `babysit_wait`, and `babysit_kill`, the description
|
|
84
|
+
explains only that operation and never overwrites the session description.
|
|
85
|
+
Descriptions are display metadata, not shell code, stdin, or instructions sent
|
|
86
|
+
to the subagent.
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
babysit_run { description: "Run regression tests", command: "bun test", foreground: true }
|
|
90
|
+
babysit_run { description: "Review the authentication changes", profile: "subagent", task: "Review the authentication diff for security regressions", maxTurns: 5 }
|
|
91
|
+
babysit_check { description: "Inspect build errors", id: "build", pattern: "ERROR" }
|
|
92
|
+
babysit_wait { description: "Wait for the build to finish", id: "build" }
|
|
93
|
+
babysit_send { description: "Ask the reviewer to check authorization too", id: "review", text: "Also check authorization." }
|
|
94
|
+
babysit_kill { description: "Stop the unused development server", id: "dev-server" }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
In Pi's main transcript the description is the prominent yellow heading, with
|
|
98
|
+
the tool name and execution kind in muted gray. The full command or task (or
|
|
99
|
+
all other operation arguments, including target IDs and input) is always visible
|
|
100
|
+
in gray beneath it, even when tool results are collapsed. Session lists, the
|
|
101
|
+
widget, and completion notifications show the session's original description.
|
|
102
|
+
Older saved sessions and tool calls without descriptions remain readable,
|
|
103
|
+
falling back to their command, task, or other arguments. Callers must supply a
|
|
104
|
+
description for every new call; blank or missing values are rejected before
|
|
105
|
+
execution.
|
|
106
|
+
|
|
74
107
|
## Remote processes over SSH
|
|
75
108
|
|
|
76
109
|
Install a `babysit` CLI with `--host` support on both machines (tested with
|
|
@@ -79,11 +112,11 @@ SSH aliases, keys, known hosts, and ProxyJump are handled by babysit/SSH;
|
|
|
79
112
|
pi-babysit does not install remote software or copy credentials.
|
|
80
113
|
|
|
81
114
|
```text
|
|
82
|
-
babysit_run { host: "yusuke-t4", cwd: "/home/yusuke/project", command: "bun test", foreground: true, returnLines: 30 }
|
|
83
|
-
babysit_run { host: "user@server", command: "python3 -i", name: "remote-repl", continueAfterStart: true }
|
|
84
|
-
babysit_check { id: "remote-repl", screen: true }
|
|
85
|
-
babysit_send { id: "remote-repl", text: "print(42)" }
|
|
86
|
-
babysit_kill { id: "remote-repl" }
|
|
115
|
+
babysit_run { description: "Run tests on Linux", host: "yusuke-t4", cwd: "/home/yusuke/project", command: "bun test", foreground: true, returnLines: 30 }
|
|
116
|
+
babysit_run { description: "Open a remote Python REPL", host: "user@server", command: "python3 -i", name: "remote-repl", continueAfterStart: true }
|
|
117
|
+
babysit_check { description: "Read the remote REPL screen", id: "remote-repl", screen: true }
|
|
118
|
+
babysit_send { description: "Evaluate a value in the remote REPL", id: "remote-repl", text: "print(42)" }
|
|
119
|
+
babysit_kill { description: "Close the remote REPL", id: "remote-repl" }
|
|
87
120
|
```
|
|
88
121
|
|
|
89
122
|
`host` omitted (or `"local"`) preserves local behavior. `cwd` is process-only:
|
|
@@ -132,9 +165,9 @@ summary counts use `RUNNING` / `IDLE`, and every row is labeled
|
|
|
132
165
|
Click the header to toggle to `babysits(n all)`, which also shows finished and failed
|
|
133
166
|
sessions, and click again to return to running-only. Both modes show newest first,
|
|
134
167
|
ten per page; `< 1/4 >` shows the current/total page when needed. Click the
|
|
135
|
-
arrows to page. Rows occupy one line, previewing the
|
|
136
|
-
|
|
137
|
-
replace the list with a tall **log viewer in the same widget area**, not an
|
|
168
|
+
arrows to page. Rows occupy one line, previewing the description (or the command
|
|
169
|
+
for older processes and task for older agents), with `…` when clipped. Click an
|
|
170
|
+
AGENT or PROCESS row to replace the list with a tall **log viewer in the same widget area**, not an
|
|
138
171
|
overlay or separate screen. `Esc` or the right-hand **Close** button restores
|
|
139
172
|
the list, preserving its mode and page. The viewer uses Pi's `ScrollView` for
|
|
140
173
|
scroll position and follow behavior, with a widget-local scrollbar styled using
|
|
@@ -183,8 +216,8 @@ pending for the next poll. Larger output stays out of model context. Inspect it
|
|
|
183
216
|
through the session id without creating another shell session:
|
|
184
217
|
|
|
185
218
|
```text
|
|
186
|
-
babysit_check { id: "cargo-test", lines: 50 }
|
|
187
|
-
babysit_check { id: "cargo-test", pattern: "FAIL|ERROR", lines: 50 }
|
|
219
|
+
babysit_check { description: "Inspect the recent test output", id: "cargo-test", lines: 50 }
|
|
220
|
+
babysit_check { description: "Find test failures", id: "cargo-test", pattern: "FAIL|ERROR", lines: 50 }
|
|
188
221
|
```
|
|
189
222
|
|
|
190
223
|
Tail and search results are capped at 200 lines and default to a 4 KB total
|
|
@@ -269,7 +302,7 @@ because blindly rerunning an arbitrary command can duplicate side effects.
|
|
|
269
302
|
must still call `babysit_wait` so the answer and nested usage are persisted.
|
|
270
303
|
Unlike `agent_end`, `agent_settled` cannot precede an automatic retry,
|
|
271
304
|
compaction retry, or queued continuation. A settled run containing a
|
|
272
|
-
**parked** toolResult — a `babysit_run { command }` result carrying the
|
|
305
|
+
**parked** toolResult — a `babysit_run { description, command }` result carrying the
|
|
273
306
|
`[notify-on-exit]` marker (or the legacy `process` tool) — only means "turn
|
|
274
307
|
parked awaiting a process-exit notification; pi resumes on its own", so the
|
|
275
308
|
wait continues. The marker-bearing result may be followed by a short
|
|
@@ -280,7 +313,7 @@ because blindly rerunning an arbitrary command can duplicate side effects.
|
|
|
280
313
|
Subagents load `self-reap.ts`, which exits an idle finished subagent after a
|
|
281
314
|
grace window using the same parked-turn rule, so a subagent waiting on a long
|
|
282
315
|
build is never false-killed. Callers can set `reapAfter: "5m"` on
|
|
283
|
-
`babysit_run { profile: "subagent", task, reapAfter: "5m" }`. Omission uses
|
|
316
|
+
`babysit_run { description, profile: "subagent", task, reapAfter: "5m" }`. Omission uses
|
|
284
317
|
`PI_BABYSIT_REAP_AFTER`, then `PI_SUBAGENT_REAP_AFTER`, then 120s.
|
|
285
318
|
`reapAfter: "none"` (also `"off"` or `"0"`) disables idle reaping; the absolute
|
|
286
319
|
`timeout` still applies. Follow-up tasks cancel the idle timer. This differs
|
package/index.ts
CHANGED
|
@@ -51,6 +51,18 @@ import { Type, type TSchema } from "typebox";
|
|
|
51
51
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
52
52
|
import { type AgentConfig, type AgentScope, discoverAgents } from "./agents";
|
|
53
53
|
|
|
54
|
+
const descriptionSchema = Type.String({
|
|
55
|
+
minLength: 1,
|
|
56
|
+
pattern: "\\S",
|
|
57
|
+
description: "Required short human-readable purpose for this operation. Explain what it does and why; shown prominently to the user.",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
function requireDescription(value: unknown): void {
|
|
61
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
62
|
+
throw new Error("`description` is required and must be a non-blank string for every babysit tool call.");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
54
66
|
// Dedicated babysit state root so pi-managed sessions never collide with the
|
|
55
67
|
// user's own manual `babysit` sessions. The base is namespaced per pi session
|
|
56
68
|
// on session_start (BABYSIT_DIR=<base>/<session-id>), so each pi session only
|
|
@@ -127,7 +139,7 @@ export function planSubagentSpawn(
|
|
|
127
139
|
error:
|
|
128
140
|
`Nested subagent creation is disabled at depth ${currentDepth}: spawning would reach depth ${childDepth}, ` +
|
|
129
141
|
`but the inherited maxDepth is ${maxDepth}. Have the top-level parent explicitly opt in with ` +
|
|
130
|
-
`babysit_run { profile: "subagent", task, maxDepth: ${childDepth} } when creating the first worker.`,
|
|
142
|
+
`babysit_run { description, profile: "subagent", task, maxDepth: ${childDepth} } when creating the first worker.`,
|
|
131
143
|
};
|
|
132
144
|
}
|
|
133
145
|
return { allowed: true, childDepth, maxDepth };
|
|
@@ -160,7 +172,7 @@ export function subagentGuidance(
|
|
|
160
172
|
const shellGuidance = directBashAvailable
|
|
161
173
|
? "Direct bash is available, and babysit_run can supervise longer commands."
|
|
162
174
|
: babysitRunAvailable
|
|
163
|
-
? "Direct bash is unavailable; run shell commands with babysit_run { command }."
|
|
175
|
+
? "Direct bash is unavailable; run shell commands with babysit_run { description, command }."
|
|
164
176
|
: "No shell execution tool is available in this task's tool allowlist; do not attempt shell commands.";
|
|
165
177
|
const nestingGuidance = depth >= maxDepth
|
|
166
178
|
? `You are at the inherited subagent depth limit (${depth}/${maxDepth}); do not attempt to spawn another subagent.`
|
|
@@ -521,6 +533,8 @@ interface Meta {
|
|
|
521
533
|
remoteId?: string;
|
|
522
534
|
remoteTerminalState?: string;
|
|
523
535
|
kind: "process" | "subagent";
|
|
536
|
+
/** Human-facing purpose. Optional only for metadata written by older versions. */
|
|
537
|
+
description?: string;
|
|
524
538
|
// process
|
|
525
539
|
name?: string;
|
|
526
540
|
command?: string;
|
|
@@ -568,6 +582,11 @@ interface Meta {
|
|
|
568
582
|
}
|
|
569
583
|
|
|
570
584
|
const metaDir = () => path.join(ROOT, "meta");
|
|
585
|
+
|
|
586
|
+
/** Prefer the human-facing purpose while preserving previews for old sessions. */
|
|
587
|
+
export function sessionPreview(meta: Pick<Meta, "description" | "kind" | "task" | "command"> | undefined): string {
|
|
588
|
+
return meta?.description?.trim() || (meta?.kind === "subagent" ? meta.task : meta?.command) || "";
|
|
589
|
+
}
|
|
571
590
|
const localLogPath = (id: string) => path.join(ROOT, "sessions", id, "output.log");
|
|
572
591
|
const logPath = (id: string) => {
|
|
573
592
|
const meta = readMeta(id);
|
|
@@ -1366,6 +1385,17 @@ function escapeCommandForDisplay(command: string): string {
|
|
|
1366
1385
|
);
|
|
1367
1386
|
}
|
|
1368
1387
|
|
|
1388
|
+
function renderOperationCall(name: string, args: Record<string, unknown> | undefined, theme: Theme) {
|
|
1389
|
+
const { description, ...operationArgs } = args ?? {};
|
|
1390
|
+
const purpose = typeof description === "string" ? description.trim() : "";
|
|
1391
|
+
const title = theme.fg("muted", theme.bold(name));
|
|
1392
|
+
const heading = purpose
|
|
1393
|
+
? `${title} ${theme.fg("warning", theme.bold(escapeCommandForDisplay(purpose)))}`
|
|
1394
|
+
: title;
|
|
1395
|
+
const raw = Object.keys(operationArgs).length ? JSON.stringify(operationArgs) : "";
|
|
1396
|
+
return new Text(heading + (raw ? `\n${theme.fg("muted", escapeCommandForDisplay(raw))}` : ""), 0, 0);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1369
1399
|
export function readableLaunchCommand(command: string): string {
|
|
1370
1400
|
// Older sessions quoted every argv element, including safe paths and flags.
|
|
1371
1401
|
if (!command.startsWith("'") || !command.endsWith("'") || !command.includes("' '")) return command;
|
|
@@ -1397,6 +1427,7 @@ export interface ProcessCompletionNotice {
|
|
|
1397
1427
|
status: CompletionStatus;
|
|
1398
1428
|
runtime: string;
|
|
1399
1429
|
summary: string;
|
|
1430
|
+
description?: string;
|
|
1400
1431
|
command: string | undefined;
|
|
1401
1432
|
logPath: string;
|
|
1402
1433
|
output: string;
|
|
@@ -1414,6 +1445,7 @@ interface ProcessCompletionMessage {
|
|
|
1414
1445
|
runtime?: string;
|
|
1415
1446
|
logPath?: string;
|
|
1416
1447
|
command?: string;
|
|
1448
|
+
description?: string;
|
|
1417
1449
|
count: number;
|
|
1418
1450
|
totalCount: number;
|
|
1419
1451
|
remainingCount: number;
|
|
@@ -1424,6 +1456,7 @@ interface ProcessCompletionMessage {
|
|
|
1424
1456
|
status: CompletionStatus;
|
|
1425
1457
|
runtime: string;
|
|
1426
1458
|
command: string | undefined;
|
|
1459
|
+
description?: string;
|
|
1427
1460
|
logPath: string;
|
|
1428
1461
|
}>;
|
|
1429
1462
|
};
|
|
@@ -1465,9 +1498,11 @@ export function buildProcessCompletionMessage(
|
|
|
1465
1498
|
count < totalCount
|
|
1466
1499
|
? `\n\n${totalCount - count} completion${totalCount - count === 1 ? "" : "s"} deferred to the next poll.`
|
|
1467
1500
|
: "";
|
|
1501
|
+
const noticeSummary = (notice: ProcessCompletionNotice) =>
|
|
1502
|
+
notice.summary + (notice.description ? `\nDescription: ${summarizeNotificationCommand(notice.description)}` : "");
|
|
1468
1503
|
const renderCompact = (batch: ProcessCompletionNotice[]) =>
|
|
1469
1504
|
header(batch.length) +
|
|
1470
|
-
batch.map((notice) => `${notice
|
|
1505
|
+
batch.map((notice) => `${noticeSummary(notice)}\nLog: ${notice.logPath}`).join("\n\n") +
|
|
1471
1506
|
deferred(batch.length) +
|
|
1472
1507
|
footer;
|
|
1473
1508
|
|
|
@@ -1492,7 +1527,7 @@ export function buildProcessCompletionMessage(
|
|
|
1492
1527
|
}
|
|
1493
1528
|
|
|
1494
1529
|
const blockBase = (notice: ProcessCompletionNotice) =>
|
|
1495
|
-
`${notice
|
|
1530
|
+
`${noticeSummary(notice)}\nCommand: ${summarizeNotificationCommand(notice.command)}\nLog: ${notice.logPath}`;
|
|
1496
1531
|
const outputs = batch.map((notice) =>
|
|
1497
1532
|
notice.output.startsWith("\n\nOutput:") ? AGGREGATE_OUTPUT_OMISSION : notice.output,
|
|
1498
1533
|
);
|
|
@@ -1533,6 +1568,7 @@ export function buildProcessCompletionMessage(
|
|
|
1533
1568
|
status: notice.status,
|
|
1534
1569
|
runtime: notice.runtime,
|
|
1535
1570
|
command: notice.command,
|
|
1571
|
+
description: notice.description,
|
|
1536
1572
|
logPath: notice.logPath,
|
|
1537
1573
|
}));
|
|
1538
1574
|
const single = totalCount === 1 ? batch[0] : undefined;
|
|
@@ -1548,6 +1584,7 @@ export function buildProcessCompletionMessage(
|
|
|
1548
1584
|
runtime: single?.runtime,
|
|
1549
1585
|
logPath: single?.logPath,
|
|
1550
1586
|
command: single?.command,
|
|
1587
|
+
description: single?.description,
|
|
1551
1588
|
count: batch.length,
|
|
1552
1589
|
totalCount,
|
|
1553
1590
|
remainingCount: totalCount - batch.length,
|
|
@@ -2109,6 +2146,7 @@ async function reserveUniqueSessionId(name: string): Promise<string> {
|
|
|
2109
2146
|
}
|
|
2110
2147
|
|
|
2111
2148
|
interface ProcOpts {
|
|
2149
|
+
description: string;
|
|
2112
2150
|
name?: string;
|
|
2113
2151
|
command: string;
|
|
2114
2152
|
cwd?: string;
|
|
@@ -2161,7 +2199,7 @@ async function spawnProcess(opts: ProcOpts): Promise<{ id: string } | { error: s
|
|
|
2161
2199
|
const reservedId = remote ? await reserveUniqueSessionId(opts.name ?? "remote-proc") : opts.name ? await reserveUniqueSessionId(opts.name) : undefined;
|
|
2162
2200
|
const remoteId = remote ? remoteProcessId(ROOT) : undefined;
|
|
2163
2201
|
if (reservedId) bsArgs.push("--id", remoteId ?? reservedId);
|
|
2164
|
-
if (remote && !writeMeta(reservedId!, { kind: "process", host: opts.host, remoteId, name: opts.name ?? reservedId, command: opts.command, notificationGroup: opts.notificationGroup, notified: false, startedAt: Date.now() })) {
|
|
2202
|
+
if (remote && !writeMeta(reservedId!, { kind: "process", host: opts.host, remoteId, name: opts.name ?? reservedId, description: opts.description, command: opts.command, notificationGroup: opts.notificationGroup, notified: false, startedAt: Date.now() })) {
|
|
2165
2203
|
reservedSessionIds.delete(reservedId!);
|
|
2166
2204
|
return { error: "Cannot persist remote identity; process was not started." };
|
|
2167
2205
|
}
|
|
@@ -2196,6 +2234,7 @@ async function spawnProcess(opts: ProcOpts): Promise<{ id: string } | { error: s
|
|
|
2196
2234
|
writeMeta(id, {
|
|
2197
2235
|
kind: "process",
|
|
2198
2236
|
name: opts.name ?? id,
|
|
2237
|
+
description: opts.description,
|
|
2199
2238
|
command: opts.command,
|
|
2200
2239
|
notificationGroup: opts.notificationGroup,
|
|
2201
2240
|
notified: false,
|
|
@@ -2251,6 +2290,7 @@ function discardDelivery(delivery: DeliverableMessage): void {
|
|
|
2251
2290
|
}
|
|
2252
2291
|
|
|
2253
2292
|
interface SubagentOpts {
|
|
2293
|
+
description: string;
|
|
2254
2294
|
name?: string;
|
|
2255
2295
|
agent?: AgentConfig;
|
|
2256
2296
|
task: string;
|
|
@@ -2381,6 +2421,7 @@ async function spawnSubagent(
|
|
|
2381
2421
|
writeMeta(id, {
|
|
2382
2422
|
kind: "subagent",
|
|
2383
2423
|
name: opts.name ?? id,
|
|
2424
|
+
description: opts.description,
|
|
2384
2425
|
task: opts.task,
|
|
2385
2426
|
launchCommand,
|
|
2386
2427
|
notified: true,
|
|
@@ -2470,6 +2511,7 @@ async function spawnSubagent(
|
|
|
2470
2511
|
writeMeta(id, {
|
|
2471
2512
|
kind: "subagent",
|
|
2472
2513
|
name: opts.name ?? id,
|
|
2514
|
+
description: opts.description,
|
|
2473
2515
|
task: opts.task,
|
|
2474
2516
|
launchCommand,
|
|
2475
2517
|
promptOffset: resp.offset,
|
|
@@ -2566,6 +2608,7 @@ export interface WidgetSessionListState {
|
|
|
2566
2608
|
|
|
2567
2609
|
export interface WidgetSessionDisplay {
|
|
2568
2610
|
id: string;
|
|
2611
|
+
description?: string;
|
|
2569
2612
|
header: string;
|
|
2570
2613
|
tail: string[];
|
|
2571
2614
|
viewable: boolean;
|
|
@@ -2603,7 +2646,7 @@ export function createWidgetComponent(
|
|
|
2603
2646
|
const pushSession = (session: WidgetSessionDisplay) => {
|
|
2604
2647
|
const target = session.viewable ? { type: "session" as const, id: session.id } : undefined;
|
|
2605
2648
|
const detail = session.details?.[0];
|
|
2606
|
-
const preview = detail?.startsWith("task: ") ? detail.slice(6) : detail ?? session.tail.at(-1);
|
|
2649
|
+
const preview = session.description ?? (detail?.startsWith("task: ") ? detail.slice(6) : detail ?? session.tail.at(-1));
|
|
2607
2650
|
push(`${session.header}${preview ? ` ${preview}` : ""}`, target, "…");
|
|
2608
2651
|
};
|
|
2609
2652
|
|
|
@@ -2689,8 +2732,9 @@ export function createSessionWidget(options: {
|
|
|
2689
2732
|
const log = options.readLog(selectedId);
|
|
2690
2733
|
const session = displays.find((session) => session.id === selectedId);
|
|
2691
2734
|
const header = session?.header ?? selectedId;
|
|
2692
|
-
const
|
|
2693
|
-
|
|
2735
|
+
const context = (session?.details ?? [])
|
|
2736
|
+
.map((detail) => detail.startsWith("task: ") ? detail.slice(6) : detail)
|
|
2737
|
+
.join("\n");
|
|
2694
2738
|
if (log.file) {
|
|
2695
2739
|
if (!source || source.file !== log.file || source.isAgent !== log.isAgent)
|
|
2696
2740
|
source = new FileLogSource(log.file, log.isAgent);
|
|
@@ -3265,7 +3309,7 @@ async function waitForTask(
|
|
|
3265
3309
|
ok: completed.ok,
|
|
3266
3310
|
text:
|
|
3267
3311
|
`Subagent ${id} finished its task (${stats}).\n` +
|
|
3268
|
-
`${subagentReuseHint(readMeta(id)?.reapAfter)} Follow-up: babysit_send { id: "${id}" }, ` +
|
|
3312
|
+
`${subagentReuseHint(readMeta(id)?.reapAfter)} Follow-up: babysit_send { description: "Assign a follow-up task", id: "${id}" }, ` +
|
|
3269
3313
|
`or babysit_kill when done.\n\n${completed.body}`,
|
|
3270
3314
|
status: st,
|
|
3271
3315
|
progress: prog,
|
|
@@ -3540,7 +3584,7 @@ async function waitForExit(
|
|
|
3540
3584
|
kind: "exited",
|
|
3541
3585
|
ok,
|
|
3542
3586
|
text:
|
|
3543
|
-
`Process ${id}${meta
|
|
3587
|
+
`Process ${id}${sessionPreview(meta) ? ` (${summarizeNotificationCommand(sessionPreview(meta))})` : ""} ` +
|
|
3544
3588
|
(workerDead
|
|
3545
3589
|
? "worker-dead: the babysit supervisor disappeared without an exit status"
|
|
3546
3590
|
: ok ? "completed successfully" : `exited with code ${st.exit_code ?? "?"}`) +
|
|
@@ -3693,7 +3737,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
3693
3737
|
const execute = tool.execute;
|
|
3694
3738
|
pi.registerTool({
|
|
3695
3739
|
...tool,
|
|
3740
|
+
description: tool.description + " Always provide a short human-readable `description` explaining the purpose of this operation.",
|
|
3741
|
+
promptGuidelines: [
|
|
3742
|
+
"Every babysit tool call requires a short description explaining its purpose for the user. Operation descriptions for check/send/wait/kill do not change the session's original description.",
|
|
3743
|
+
...(tool.promptGuidelines ?? []),
|
|
3744
|
+
],
|
|
3745
|
+
renderCall: tool.renderCall ?? ((args, theme) => renderOperationCall(tool.name, args as Record<string, unknown>, theme)),
|
|
3696
3746
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
3747
|
+
requireDescription((params as { description?: unknown }).description);
|
|
3697
3748
|
const result = await execute(toolCallId, params, signal, onUpdate, ctx);
|
|
3698
3749
|
if ((result as typeof result & { isError?: boolean }).isError === true) {
|
|
3699
3750
|
declaredToolErrors.add(toolCallId);
|
|
@@ -3852,6 +3903,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3852
3903
|
runtime,
|
|
3853
3904
|
summary,
|
|
3854
3905
|
command: meta.command,
|
|
3906
|
+
description: meta.description,
|
|
3855
3907
|
logPath: logPath(session.id),
|
|
3856
3908
|
output,
|
|
3857
3909
|
});
|
|
@@ -3873,7 +3925,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3873
3925
|
if (!latest || latest.transportError || latest.state === "running") return [];
|
|
3874
3926
|
if (!isNotificationGroupReady(current, finalSessions, readMeta)) return [];
|
|
3875
3927
|
metadataById.set(notice.id, current);
|
|
3876
|
-
return [{ ...notice, command: current.command }];
|
|
3928
|
+
return [{ ...notice, command: current.command, description: current.description }];
|
|
3877
3929
|
});
|
|
3878
3930
|
// Output collection above yields to the event loop. Re-check idleness so a
|
|
3879
3931
|
// newly-started agent turn cannot receive a duplicate completion mid-turn.
|
|
@@ -3897,7 +3949,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3897
3949
|
): Promise<void> {
|
|
3898
3950
|
if (shouldDeferCompletionNotification(ctx.isIdle())) return;
|
|
3899
3951
|
const sessions = snapshot ?? (await listSessions()).sessions;
|
|
3900
|
-
const ready: Array<{ id: string; offset: number; state: string; summary: string }> = [];
|
|
3952
|
+
const ready: Array<{ id: string; offset: number; state: string; summary: string; description?: string }> = [];
|
|
3901
3953
|
for (const session of sessions) {
|
|
3902
3954
|
const meta = readMeta(session.id);
|
|
3903
3955
|
if (!shouldDeliverSubagentCompletion(meta)) continue;
|
|
@@ -3920,7 +3972,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3920
3972
|
const summary = progress.done
|
|
3921
3973
|
? `task settled; ${progress.turns} turns, ${progress.toolCallCount} tools, $${progress.cost.toFixed(4)}`
|
|
3922
3974
|
: `worker ${session.state} with exit code ${session.exit_code ?? "?"}; partial usage $${progress.cost.toFixed(4)}`;
|
|
3923
|
-
ready.push({ id: session.id, offset, state: session.state, summary });
|
|
3975
|
+
ready.push({ id: session.id, offset, state: session.state, summary, description: meta.description });
|
|
3924
3976
|
}
|
|
3925
3977
|
if (ready.length === 0 || shouldDeferCompletionNotification(ctx.isIdle())) return;
|
|
3926
3978
|
|
|
@@ -3934,10 +3986,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
3934
3986
|
customType: "pi-babysit-subagent-ready",
|
|
3935
3987
|
content:
|
|
3936
3988
|
`${deliverable.length === 1 ? "A background subagent is" : `${deliverable.length} background subagents are`} ready to collect:\n` +
|
|
3937
|
-
deliverable.map(({ id, summary }) => `- ${id}: ${summary}`).join("\n") +
|
|
3989
|
+
deliverable.map(({ id, summary, description }) => `- ${id}${description ? ` (${summarizeNotificationCommand(description)})` : ""}: ${summary}`).join("\n") +
|
|
3938
3990
|
"\nCall babysit_wait now to retrieve the answer and charge nested usage before finishing the parent task.",
|
|
3939
3991
|
display: true,
|
|
3940
|
-
details: { subagents: deliverable.map(({ id, state }) => ({ id, state })) },
|
|
3992
|
+
details: { subagents: deliverable.map(({ id, state, description }) => ({ id, state, description })) },
|
|
3941
3993
|
},
|
|
3942
3994
|
{ triggerTurn: true, deliverAs: "steer" },
|
|
3943
3995
|
);
|
|
@@ -3992,12 +4044,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
3992
4044
|
const id = session.id;
|
|
3993
4045
|
const meta = readMeta(id);
|
|
3994
4046
|
const isSubagent = meta?.kind === "subagent";
|
|
4047
|
+
const description = meta?.description?.trim()
|
|
4048
|
+
? theme.fg("warning", theme.bold(escapeCommandForDisplay(meta.description.trim())))
|
|
4049
|
+
: undefined;
|
|
3995
4050
|
const details = isSubagent
|
|
3996
4051
|
? [
|
|
3997
|
-
...(meta?.task ? [`task: ${escapeCommandForDisplay(meta.task)}`] : []),
|
|
3998
|
-
...(meta?.launchCommand ? [theme.
|
|
4052
|
+
...(meta?.task ? [theme.fg("muted", `task: ${escapeCommandForDisplay(meta.task)}`)] : []),
|
|
4053
|
+
...(meta?.launchCommand ? [theme.fg("muted", escapeCommandForDisplay(readableLaunchCommand(meta.launchCommand)))] : []),
|
|
3999
4054
|
]
|
|
4000
|
-
: meta?.command ? [theme.
|
|
4055
|
+
: meta?.command ? [theme.fg("muted", escapeCommandForDisplay(meta.command))] : [];
|
|
4056
|
+
if (description) details.unshift(description);
|
|
4001
4057
|
if (meta?.host) details.unshift(`host: ${meta.host}${session.transportError ? " (status unknown)" : ""} · ${attachCmd(id)}`);
|
|
4002
4058
|
if (session.state === "running") {
|
|
4003
4059
|
const state: WidgetSessionState = isSubagent && progressById.get(id)?.done ? "idle" : "running";
|
|
@@ -4005,6 +4061,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4005
4061
|
id,
|
|
4006
4062
|
header: renderWidgetSessionHeader(id, isSubagent ? "agent" : "process", state, elapsedOf(id), theme) + (session.transportError ? " · status unknown" : ""),
|
|
4007
4063
|
tail: activeTails.get(id) ?? [],
|
|
4064
|
+
description,
|
|
4008
4065
|
viewable: true,
|
|
4009
4066
|
active: true,
|
|
4010
4067
|
details,
|
|
@@ -4020,6 +4077,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4020
4077
|
theme,
|
|
4021
4078
|
),
|
|
4022
4079
|
details,
|
|
4080
|
+
description,
|
|
4023
4081
|
// Only read/parse visible sessions on demand.
|
|
4024
4082
|
get tail() {
|
|
4025
4083
|
try {
|
|
@@ -4115,25 +4173,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
4115
4173
|
success?: boolean;
|
|
4116
4174
|
exitCode?: number | null;
|
|
4117
4175
|
command?: string;
|
|
4176
|
+
description?: string;
|
|
4118
4177
|
count?: number;
|
|
4119
4178
|
};
|
|
4120
4179
|
const status =
|
|
4121
4180
|
d.status ??
|
|
4122
4181
|
(d.success ? "success" : d.exitCode == null ? "terminated" : "failed");
|
|
4123
|
-
const payload = d.
|
|
4124
|
-
?
|
|
4125
|
-
: d.
|
|
4126
|
-
?
|
|
4127
|
-
:
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
);
|
|
4182
|
+
const payload = d.description?.trim()
|
|
4183
|
+
? theme.fg("warning", theme.bold(summarizeNotificationCommand(d.description)))
|
|
4184
|
+
: d.command
|
|
4185
|
+
? theme.fg("muted", summarizeNotificationCommand(d.command))
|
|
4186
|
+
: d.count && d.count > 1
|
|
4187
|
+
? theme.fg("muted", `×${d.count}`)
|
|
4188
|
+
: "";
|
|
4189
|
+
const header = theme.fg("muted", theme.bold("babysit_run COMMAND")) + (payload ? ` ${payload}` : "");
|
|
4132
4190
|
const box = new Box(1, 1, (t) => theme.bg("toolSuccessBg", t));
|
|
4133
4191
|
box.addChild(new Text(header, 0, 0));
|
|
4134
4192
|
box.addChild(new Text(renderStatus(status, theme), 0, 0));
|
|
4135
4193
|
if (expanded && content) {
|
|
4136
|
-
box.addChild(new Text(
|
|
4194
|
+
box.addChild(new Text(content.split("\n").map((line) =>
|
|
4195
|
+
theme.fg(line.startsWith("Command: ") ? "muted" : "toolOutput", line),
|
|
4196
|
+
).join("\n"), 0, 0));
|
|
4137
4197
|
}
|
|
4138
4198
|
return box;
|
|
4139
4199
|
},
|
|
@@ -4281,7 +4341,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4281
4341
|
block: true,
|
|
4282
4342
|
reason:
|
|
4283
4343
|
`This bash command tries to run in the background. Use babysit_run instead, e.g. ` +
|
|
4284
|
-
`babysit_run({ name: "background-process", command: ${JSON.stringify(command.replace(/\s*&\s*$/, ""))} })`,
|
|
4344
|
+
`babysit_run({ description: "Describe this background process's purpose", name: "background-process", command: ${JSON.stringify(command.replace(/\s*&\s*$/, ""))} })`,
|
|
4285
4345
|
};
|
|
4286
4346
|
}
|
|
4287
4347
|
if (isAllowedDirectBash(command)) return;
|
|
@@ -4289,8 +4349,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
4289
4349
|
block: true,
|
|
4290
4350
|
reason:
|
|
4291
4351
|
"Use babysit_run for shell commands so output is supervised and captured outside model context. " +
|
|
4292
|
-
"Inspect an existing session log with babysit_check { id, lines, pattern? }. " +
|
|
4293
|
-
`Retry as babysit_run({ command: ${JSON.stringify(command)} }).`,
|
|
4352
|
+
"Inspect an existing session log with babysit_check { description, id, lines, pattern? }. " +
|
|
4353
|
+
`Retry with a short purpose description as babysit_run({ description: "Explain the purpose of this command", command: ${JSON.stringify(command)} }).`,
|
|
4294
4354
|
};
|
|
4295
4355
|
});
|
|
4296
4356
|
|
|
@@ -4318,6 +4378,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4318
4378
|
return prepareBabysitRunArguments(args) as never;
|
|
4319
4379
|
},
|
|
4320
4380
|
parameters: Type.Object({
|
|
4381
|
+
description: descriptionSchema,
|
|
4321
4382
|
host: Type.Optional(Type.String({ description: "Process SSH destination/config alias (default local). Remote subagents are unsupported." })),
|
|
4322
4383
|
cwd: Type.Optional(Type.String({ description: "Process working directory. Remote default is login home; never inferred from local cwd." })),
|
|
4323
4384
|
command: Type.Optional(
|
|
@@ -4448,6 +4509,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4448
4509
|
|
|
4449
4510
|
}),
|
|
4450
4511
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
4512
|
+
const description = params.description.trim();
|
|
4451
4513
|
await requireBabysit();
|
|
4452
4514
|
const isSubagent = params.profile === "subagent";
|
|
4453
4515
|
validateProcessHost(params.host);
|
|
@@ -4555,6 +4617,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4555
4617
|
? { pattern: params.returnPattern, lines: params.returnLines, maxBytes: params.maxBytes }
|
|
4556
4618
|
: undefined;
|
|
4557
4619
|
const spawnOpts: ProcOpts = {
|
|
4620
|
+
description,
|
|
4558
4621
|
name: params.name,
|
|
4559
4622
|
command: params.command as string,
|
|
4560
4623
|
host: params.host,
|
|
@@ -4605,6 +4668,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4605
4668
|
kind: "process",
|
|
4606
4669
|
command: params.command,
|
|
4607
4670
|
logPath: logPath(res.id),
|
|
4671
|
+
description,
|
|
4608
4672
|
retried,
|
|
4609
4673
|
status: outcomeStatus(outcome),
|
|
4610
4674
|
},
|
|
@@ -4641,6 +4705,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4641
4705
|
kind: "process",
|
|
4642
4706
|
command: params.command,
|
|
4643
4707
|
logPath: logPath(res.id),
|
|
4708
|
+
description,
|
|
4644
4709
|
retried,
|
|
4645
4710
|
status: outcomeStatus(outcome),
|
|
4646
4711
|
},
|
|
@@ -4657,9 +4722,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
4657
4722
|
type: "text",
|
|
4658
4723
|
text:
|
|
4659
4724
|
`${retried ? "Retried once after external worker death.\n" : ""}` +
|
|
4660
|
-
`Process started (id: ${res.id}). ${NOTIFY_MARKER}\nLog: ${logPath(res.id)}\n${nextStep}\n` +
|
|
4661
|
-
`Inspect: babysit_check { id: "${res.id}" } (screen: true for TUIs) · ` +
|
|
4662
|
-
`Wait: babysit_wait { id: "${res.id}" } · Kill: babysit_kill { id: "${res.id}" }\n` +
|
|
4725
|
+
`Process started (id: ${res.id}). ${NOTIFY_MARKER}\nDescription: ${escapeCommandForDisplay(description)}\nLog: ${logPath(res.id)}\n${nextStep}\n` +
|
|
4726
|
+
`Inspect: babysit_check { description: "Inspect session output", id: "${res.id}" } (screen: true for TUIs) · ` +
|
|
4727
|
+
`Wait: babysit_wait { description: "Wait for completion", id: "${res.id}" } · Kill: babysit_kill { description: "Stop this session", id: "${res.id}" }\n` +
|
|
4663
4728
|
`Human can watch/take over: /babysit`,
|
|
4664
4729
|
},
|
|
4665
4730
|
],
|
|
@@ -4667,6 +4732,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4667
4732
|
id: res.id,
|
|
4668
4733
|
kind: "process",
|
|
4669
4734
|
command: params.command,
|
|
4735
|
+
description,
|
|
4670
4736
|
logPath: logPath(res.id),
|
|
4671
4737
|
retried,
|
|
4672
4738
|
status: "started" satisfies DisplayStatus,
|
|
@@ -4702,6 +4768,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4702
4768
|
// The branch above guarantees a successful plan in subagent mode.
|
|
4703
4769
|
const subagentNesting = nesting as Extract<SubagentSpawnPlan, { allowed: true }>;
|
|
4704
4770
|
const res = await spawnSubagent({
|
|
4771
|
+
description,
|
|
4705
4772
|
name: params.name,
|
|
4706
4773
|
agent,
|
|
4707
4774
|
task: params.task as string,
|
|
@@ -4750,6 +4817,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4750
4817
|
agent: agent?.name,
|
|
4751
4818
|
model: res.model,
|
|
4752
4819
|
task: params.task,
|
|
4820
|
+
description,
|
|
4753
4821
|
depth: subagentNesting.childDepth,
|
|
4754
4822
|
maxDepth: subagentNesting.maxDepth,
|
|
4755
4823
|
status: outcomeStatus(outcome),
|
|
@@ -4762,9 +4830,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
4762
4830
|
type: "text",
|
|
4763
4831
|
text:
|
|
4764
4832
|
`Subagent started (id: ${res.id})${agent ? ` [agent: ${agent.name}]` : ""}${res.model ? ` [model: ${res.model}]` : ""} [depth: ${subagentNesting.childDepth}/${subagentNesting.maxDepth}].\n` +
|
|
4765
|
-
`
|
|
4766
|
-
`Progress: babysit_check { id: "${res.id}" } (only when inspection is needed)\n` +
|
|
4767
|
-
`Collect: babysit_wait { id: "${res.id}" }\n` +
|
|
4833
|
+
`Description: ${escapeCommandForDisplay(description)}\nTask accepted — running in the background. You MUST collect it with babysit_wait before finishing the parent task; use foreground: true next time when no independent work is available.\n` +
|
|
4834
|
+
`Progress: babysit_check { description: "Inspect agent progress", id: "${res.id}" } (only when inspection is needed)\n` +
|
|
4835
|
+
`Collect: babysit_wait { description: "Collect the agent result", id: "${res.id}" }\n` +
|
|
4768
4836
|
`Human can watch/steer: /babysit (pick ${res.id})`,
|
|
4769
4837
|
},
|
|
4770
4838
|
],
|
|
@@ -4775,25 +4843,30 @@ export default function (pi: ExtensionAPI) {
|
|
|
4775
4843
|
agent: agent?.name,
|
|
4776
4844
|
model: res.model,
|
|
4777
4845
|
task: params.task,
|
|
4846
|
+
description,
|
|
4778
4847
|
depth: subagentNesting.childDepth,
|
|
4779
4848
|
maxDepth: subagentNesting.maxDepth,
|
|
4780
4849
|
status: "started" satisfies DisplayStatus,
|
|
4781
4850
|
},
|
|
4782
4851
|
};
|
|
4783
4852
|
},
|
|
4784
|
-
//
|
|
4785
|
-
//
|
|
4853
|
+
// The human-readable purpose is primary; the executable payload is secondary.
|
|
4854
|
+
// Legacy calls without descriptions still render their original payload safely.
|
|
4786
4855
|
renderCall(args, theme) {
|
|
4787
4856
|
const isSubagent = args.profile === "subagent";
|
|
4788
4857
|
const kind = isSubagent ? "AGENT" : "COMMAND";
|
|
4789
4858
|
const payload = isSubagent ? args.task : args.command;
|
|
4790
|
-
const agent = isSubagent && args.agent ? ` [${args.agent}]` : "";
|
|
4791
|
-
const title = theme.bold(`babysit_run ${kind}`);
|
|
4792
|
-
const
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4859
|
+
const agent = isSubagent && args.agent ? ` [${escapeCommandForDisplay(args.agent)}]` : "";
|
|
4860
|
+
const title = theme.fg("muted", `${theme.bold(`babysit_run ${kind}`)}${agent}`);
|
|
4861
|
+
const description = typeof args.description === "string" ? args.description.trim() : "";
|
|
4862
|
+
const raw = typeof payload === "string" ? escapeCommandForDisplay(payload) : "";
|
|
4863
|
+
const heading = description
|
|
4864
|
+
? `${title} ${theme.fg("warning", theme.bold(escapeCommandForDisplay(description)))}`
|
|
4865
|
+
: `${title}${raw ? theme.fg("muted", ` ${raw}`) : ""}`;
|
|
4866
|
+
const detail = description && raw
|
|
4867
|
+
? `\n${theme.fg("muted", raw)}`
|
|
4868
|
+
: "";
|
|
4869
|
+
return new Text(heading + detail, 0, 0);
|
|
4797
4870
|
},
|
|
4798
4871
|
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
4799
4872
|
const details = (result.details ?? {}) as {
|
|
@@ -4843,6 +4916,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4843
4916
|
"Do NOT poll this while merely waiting for a process to end — the exit notification is automatic.",
|
|
4844
4917
|
promptSnippet: "Check status/progress of babysit sessions (processes and subagents)",
|
|
4845
4918
|
parameters: Type.Object({
|
|
4919
|
+
description: descriptionSchema,
|
|
4846
4920
|
id: Type.Optional(Type.String({ description: "Session id. Omit to list sessions." })),
|
|
4847
4921
|
state: Type.Optional(
|
|
4848
4922
|
StringEnum(["running", "terminal", "all"] as const, {
|
|
@@ -4932,7 +5006,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4932
5006
|
kind === "subagent" && meta?.depth != null
|
|
4933
5007
|
? ` depth=${meta.depth}/${meta.maxDepth ?? "?"}`
|
|
4934
5008
|
: "";
|
|
4935
|
-
const what = (
|
|
5009
|
+
const what = escapeCommandForDisplay(sessionPreview(meta));
|
|
4936
5010
|
const preview = what.length > 60 ? `${what.slice(0, 57)}…` : what;
|
|
4937
5011
|
return `${s.id} [${kind}${s.host ? ` host=${s.host}` : ""}] ${s.transportError ? "unknown" : s.state}${ec}${depth}${flag}${preview ? ` — ${preview}` : ""}`;
|
|
4938
5012
|
});
|
|
@@ -5000,6 +5074,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
5000
5074
|
if (el) header += ` elapsed=${el}`;
|
|
5001
5075
|
}
|
|
5002
5076
|
if (st.exit_code != null) header += ` exit_code=${st.exit_code}`;
|
|
5077
|
+
if (meta?.description) header += `\ndescription: ${escapeCommandForDisplay(meta.description)}`;
|
|
5003
5078
|
if (meta?.command) header += `\ncommand: ${summarizeNotificationCommand(meta.command)}`;
|
|
5004
5079
|
header += `\nlog: ${logPath(params.id)}`;
|
|
5005
5080
|
if (st.note) header += ` ⚑ ${st.note}`;
|
|
@@ -5045,6 +5120,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
5045
5120
|
if (prog.tokens != null) header += ` ctx=${prog.tokens}`;
|
|
5046
5121
|
if (prog.modelCalls > 0) header += ` usage=${prog.usageTokens} $${prog.cost.toFixed(4)}`;
|
|
5047
5122
|
if (st.note) header += ` ⚑ ${st.note}`;
|
|
5123
|
+
if (meta.description) header += `\ndescription: ${escapeCommandForDisplay(meta.description)}`;
|
|
5048
5124
|
parts.push(header);
|
|
5049
5125
|
|
|
5050
5126
|
if (prog.errorMsg) parts.push(`⚠ error: ${clip(prog.errorMsg, ANSWER_MAX_BYTES)}`);
|
|
@@ -5081,11 +5157,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
5081
5157
|
description:
|
|
5082
5158
|
"Send input to a babysit session. Process: `text` types a line into its stdin (PTY), " +
|
|
5083
5159
|
"`keys` presses named keys (Enter, Tab, Esc, Up/Down/Left/Right, C-c, F1…) — use with " +
|
|
5084
|
-
"babysit_check { screen: true } to drive interactive programs. Subagent: `text` is " +
|
|
5160
|
+
"babysit_check { description, screen: true } to drive interactive programs. Subagent: `text` is " +
|
|
5085
5161
|
"STEERING while it works, or a NEW TASK after the current task settles (mode: auto/steer/task) — this " +
|
|
5086
5162
|
"is how you resume a finished subagent with full context.",
|
|
5087
5163
|
promptSnippet: "Send text/keys to a process, or steering/follow-up tasks to a subagent",
|
|
5088
5164
|
parameters: Type.Object({
|
|
5165
|
+
description: descriptionSchema,
|
|
5089
5166
|
id: Type.String({ description: "Session id." }),
|
|
5090
5167
|
text: Type.Optional(
|
|
5091
5168
|
Type.String({ description: "Text to send (a line for processes; a message for subagents)." }),
|
|
@@ -5157,7 +5234,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
5157
5234
|
content: [
|
|
5158
5235
|
{
|
|
5159
5236
|
type: "text",
|
|
5160
|
-
text: `Sent to ${params.id}. Read the reaction with babysit_check { id: "${params.id}"${params.keys?.length ? ", screen: true" : ""} } — don't expect the echo of your own input.`,
|
|
5237
|
+
text: `Sent to ${params.id}. Read the reaction with babysit_check { description: "Inspect the response to input", id: "${params.id}"${params.keys?.length ? ", screen: true" : ""} } — don't expect the echo of your own input.`,
|
|
5161
5238
|
},
|
|
5162
5239
|
],
|
|
5163
5240
|
details: { kind: "process" },
|
|
@@ -5274,6 +5351,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
5274
5351
|
// New task → new bookkeeping window, so check/wait track THIS task.
|
|
5275
5352
|
writeMeta(params.id, {
|
|
5276
5353
|
kind: "subagent",
|
|
5354
|
+
name: meta?.name,
|
|
5355
|
+
description: meta?.description,
|
|
5356
|
+
launchCommand: meta?.launchCommand,
|
|
5357
|
+
reapAfter: meta?.reapAfter,
|
|
5358
|
+
startedAt: meta?.startedAt,
|
|
5277
5359
|
task: params.text,
|
|
5278
5360
|
// Start after the prompt-acceptance response. This excludes a
|
|
5279
5361
|
// preceding run that settled between `send` and RPC acceptance.
|
|
@@ -5335,6 +5417,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
5335
5417
|
"the exit notification will resume you.",
|
|
5336
5418
|
promptSnippet: "Block until session(s) finish — process exit / output pattern / subagent task done",
|
|
5337
5419
|
parameters: Type.Object({
|
|
5420
|
+
description: descriptionSchema,
|
|
5338
5421
|
id: Type.Optional(Type.String({ description: "Session id (single wait)." })),
|
|
5339
5422
|
ids: Type.Optional(
|
|
5340
5423
|
Type.Array(Type.String(), {
|
|
@@ -5481,7 +5564,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
5481
5564
|
label: "Babysit: kill",
|
|
5482
5565
|
description: "Terminate a babysit session (process or subagent).",
|
|
5483
5566
|
promptSnippet: "Terminate a babysit session",
|
|
5484
|
-
parameters: Type.Object({
|
|
5567
|
+
parameters: Type.Object({
|
|
5568
|
+
description: descriptionSchema,
|
|
5569
|
+
id: Type.String({ description: "Session id." }),
|
|
5570
|
+
}),
|
|
5485
5571
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
5486
5572
|
await requireBabysit();
|
|
5487
5573
|
// Prevent the exit poller racing a requested kill, but restore delivery
|
|
@@ -5593,9 +5679,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
5593
5679
|
|
|
5594
5680
|
const whatOf = (s: BsSession): string => {
|
|
5595
5681
|
const meta = readMeta(s.id);
|
|
5596
|
-
return ((meta
|
|
5597
|
-
.replace(/\s+/g, " ")
|
|
5598
|
-
.trim();
|
|
5682
|
+
return escapeCommandForDisplay(sessionPreview(meta).replace(/\s+/g, " ").trim());
|
|
5599
5683
|
};
|
|
5600
5684
|
|
|
5601
5685
|
// Labels must be unique for index mapping; the id makes them unique.
|