@yusukeshib/pi-babysit 0.3.12 → 0.3.14

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 CHANGED
@@ -33,7 +33,7 @@ reachable from anywhere (`~/.pi-babysit/<pi-session-id>/`). Two kinds:
33
33
  | kind | started by | completion | on completion |
34
34
  | ---- | ---------- | ---------- | ------------- |
35
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_end` in the RPC event stream (process stays alive) | none — the agent polls `babysit_check` or blocks on `babysit_wait`; the idle session accepts follow-up tasks |
36
+ | **subagent** | `babysit_run { profile: "subagent", task }` | `agent_settled` in the RPC event stream (process stays alive) | none — the agent polls `babysit_check` or blocks on `babysit_wait`; the idle session accepts follow-up tasks |
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
@@ -56,10 +56,10 @@ programs** (installers, wizards, REPLs): type with `babysit_send`
56
56
 
57
57
  | Tool | What it does |
58
58
  | ---- | ------------ |
59
- | `babysit_run` | Run any command (`command`, optional `name`/`pty`/`timeout`/`idleTimeout`/`retryOnWorkerDeath`) or start a subagent (`profile: "subagent"`, `task`, optional `agent`/`model`/`tools`/`maxDepth`). `maxDepth` defaults to 1 and can only be set by the top-level caller. Quick commands return inline; longer ones continue in the background |
59
+ | `babysit_run` | Run any command (`command`, optional `name`/`pty`/`timeout`/`idleTimeout`/`retryOnWorkerDeath`/`notificationGroup`) or start a subagent (`profile: "subagent"`, `task`, optional `agent`/`model`/`tools`/`maxDepth` and `maxCost`/`maxTurns`/`maxToolCalls`/`maxUsageTokens` budgets). `maxDepth` defaults to 1 and can only be set by the top-level caller. Quick commands return inline; longer ones continue in the background |
60
60
  | `babysit_check` | List all sessions, inspect one, tail its bounded recent output, or search its raw log with `pattern`; `screen: true` captures TUIs and subagents otherwise show structured live progress |
61
61
  | `babysit_send` | Process: type `text` / press `keys` into the PTY. Subagent: steer mid-run, or send a follow-up task when idle (`mode: auto/steer/task`) |
62
- | `babysit_wait` | Block until done: process exit (or `expect: "regex"` readiness marker), subagent task completion. Multi-wait: `ids` + `mode: "any"\|"all"` |
62
+ | `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"` |
63
63
  | `babysit_kill` | Terminate a session, verify terminal state, then suppress the exit notification |
64
64
 
65
65
  The built-in `bash` tool is removed from the active tool set so the model does
@@ -73,6 +73,7 @@ re-enables `bash`, including shell backgrounding (`… &`, `nohup`, `setsid`,
73
73
  | Command | What it does |
74
74
  | ------- | ------------ |
75
75
  | `/babysit` | Arrow-key picker over all sessions. Renders an **inline snapshot** (no tmux): running **process** → current rendered screen + recent output + a copy-paste `babysit attach` take-over hint (detach `Ctrl-\ Ctrl-\`); running **subagent** → read-only progress (RPC stdin stays untouchable); finished → summary. Re-run `/babysit` to refresh |
76
+ | `/babysit gc [days]` | Preview and confirm deletion of old Pi-session roots (default 14 days). Active leases, live supervisor/child PIDs, unknown states, the current root, and recent roots are retained; deletion uses a GC lock and atomic rename |
76
77
 
77
78
  A minimal widget above the editor shows live counts
78
79
  (`N processes · M subagents working · K idle`).
@@ -94,18 +95,24 @@ babysit_check { id: "cargo-test", lines: 50 }
94
95
  babysit_check { id: "cargo-test", pattern: "FAIL|ERROR", lines: 50 }
95
96
  ```
96
97
 
97
- Tail and search results are capped at 200 lines, and the complete returned tool
98
- result (including lifecycle headers) is clipped to 8 KB. Pattern search returns
99
- the latest matching lines with line numbers. Prefer a targeted pattern over a
100
- broad tail, and do not read a potentially large log file in full.
101
-
102
- Subagent logs also compact Pi's streaming `message_update` events before they
103
- are recorded. Pi repeats the complete growing assistant message and partial
104
- snapshot on every token; pi-babysit retains only the incremental delta while
105
- leaving authoritative `message_end`, tool, lifecycle, response, and error
106
- events untouched. This keeps long RPC sessions approximately linear in emitted
107
- content without changing final answers, completion detection, or follow-up
108
- behavior. Live `/babysit` and attach views render the retained deltas.
98
+ Tail and search results are capped at 200 lines, and ordinary returned tool
99
+ results (including lifecycle headers) are clipped to 8 KB. A single explicitly
100
+ waited-for subagent answer may use up to 24 KB; multi-session wait results default
101
+ to the 8 KB inline-output limit and can opt into a larger cap with `maxBytes`.
102
+ Pattern search returns the latest matching lines with line numbers. Prefer a
103
+ targeted pattern over a broad tail, and do not read a potentially large log file
104
+ in full. Subagent crashes return structured errors plus the full log path, never
105
+ a raw RPC JSON tail.
106
+
107
+ Subagent logs compact Pi's streaming `message_update` events before they are
108
+ recorded. Pi repeats the complete growing assistant message and partial snapshot
109
+ on every token; pi-babysit retains only the incremental delta. The opt-in
110
+ `compact` RPC log mode also removes duplicate payloads from `message_start`,
111
+ `turn_end`, successful `tool_execution_end`, and `agent_end`, while preserving
112
+ authoritative `message_end`, failures, responses, and errors. Parked-process
113
+ state is materialized as a small boolean so completion detection is unchanged.
114
+ Set `PI_BABYSIT_RPC_LOG_MODE=standard` for the legacy lifecycle payloads. Live
115
+ `/babysit` and attach views render either format.
109
116
 
110
117
  All shell commands, including `pwd` and Git, go through `babysit_run`. Bundle
111
118
  closely related tiny observations when doing so safely reduces tool turns.
@@ -127,20 +134,29 @@ because blindly rerunning an arbitrary command can duplicate side effects.
127
134
  `pi.sendMessage(…, { triggerTurn: true, deliverAs: "steer" })` containing every
128
135
  deliverable exit observed in that poll (deduped via `meta/<id>.json`). Waiting
129
136
  for idleness prevents an immediately-following `babysit_wait` from racing the
130
- poller and receiving a duplicate completion.
137
+ poller and receiving a duplicate completion. Processes with the same
138
+ `notificationGroup` wait for every currently running group member to stop and
139
+ then share one notification even when their exits span multiple polls.
131
140
  `babysit_kill` and an exit already reported by `babysit_wait` suppress the
132
141
  notification.
133
- - **Subagent**: `babysit_wait` blocks on `babysit expect '"type":"agent_end"'`.
134
- An `agent_end` whose last message is a **parked** toolResult — a
135
- `babysit_run { command }` result carrying the `[notify-on-exit]` marker (or
136
- the legacy `process` tool) — only means "turn parked awaiting a process-exit
137
- notification; pi resumes on its own", so the wait continues. Any other
138
- `agent_end` is real completion. Per-task byte offsets scope check/wait to the
139
- CURRENT task, which is what makes follow-up tasks work.
142
+ - **Subagent**: `babysit_wait` blocks on `babysit expect '"type":"agent_settled"'`.
143
+ Unlike `agent_end`, `agent_settled` cannot precede an automatic retry,
144
+ compaction retry, or queued continuation. A settled run containing a
145
+ **parked** toolResult — a `babysit_run { command }` result carrying the
146
+ `[notify-on-exit]` marker (or the legacy `process` tool) — only means "turn
147
+ parked awaiting a process-exit notification; pi resumes on its own", so the
148
+ wait continues. The marker-bearing result may be followed by a short
149
+ assistant note without defeating parked detection. Per-task byte offsets
150
+ scope check/wait to the CURRENT task, and appended log bytes are parsed
151
+ incrementally, which keeps follow-up tasks efficient.
140
152
 
141
153
  Subagents load `self-reap.ts`, which exits an idle finished subagent after a
142
154
  grace window (`PI_BABYSIT_REAP_AFTER`, default 120s) using the same parked-turn
143
- rule, so a subagent waiting on a long build is never false-killed.
155
+ rule, so a subagent waiting on a long build is never false-killed. Optional task
156
+ budgets are checked by the parent poller. On the first exceeded limit the worker
157
+ is steered to stop using tools and return its best answer; if it remains active
158
+ after `PI_BABYSIT_BUDGET_GRACE`, termination is verified before the task is
159
+ marked budget-killed. Usage shown by check/wait is cumulative for the task.
144
160
 
145
161
  ## Environment overrides
146
162
 
@@ -151,8 +167,11 @@ rule, so a subagent waiting on a long build is never false-killed.
151
167
  | `PI_BABYSIT_CLI` | `babysit` | babysit binary |
152
168
  | `PI_BABYSIT_VIEW_CMD` | bundled `format-stream.mjs` | live-attach pretty printer for subagent JSONL (`""` disables) |
153
169
  | `PI_BABYSIT_REAP_AFTER` | `120s` | idle grace before a finished subagent self-exits (`off`/`none`/`0` disables) |
170
+ | `PI_BABYSIT_BUDGET_GRACE` | `30s` | grace after a subagent budget is exceeded before verified termination |
171
+ | `PI_BABYSIT_RPC_LOG_MODE` | `standard` | `compact` removes duplicate RPC lifecycle payloads; `standard` retains legacy payloads |
172
+ | `PI_BABYSIT_RETENTION_DAYS` | unset | when set to a positive number, remove safe terminal roots older than this at session startup |
154
173
  | `PI_BABYSIT_TAIL_MAX_BYTES` | `8000` | cap for explicit log tails/screens returned by `babysit_check` |
155
- | `PI_BABYSIT_INLINE_OUTPUT_MAX_BYTES` | `8000` | cap for complete output in explicitly requested run/wait results |
174
+ | `PI_BABYSIT_INLINE_OUTPUT_MAX_BYTES` | `8000` | cap for complete process output and aggregate multi-wait results |
156
175
  | `PI_BABYSIT_NOTIFY_OUTPUT_MAX_BYTES` | `2000` | per-process output cap for unsolicited completion notifications (`0` omits all output) |
157
176
  | `PI_BABYSIT_NOTIFY_COMMAND_MAX_BYTES` | `240` | cap for each command preview in completion notifications |
158
177
  | `PI_BABYSIT_NOTIFY_BATCH_MAX_BYTES` | `8000` | hard cap for one aggregated completion notification |
package/agents.ts CHANGED
@@ -56,20 +56,34 @@ function loadAgentsFromDir(
56
56
  continue;
57
57
  }
58
58
 
59
- const { frontmatter, body } =
60
- parseFrontmatter<Record<string, string>>(content);
61
- if (!frontmatter.name || !frontmatter.description) continue;
59
+ let parsed: ReturnType<typeof parseFrontmatter<Record<string, unknown>>>;
60
+ try {
61
+ parsed = parseFrontmatter<Record<string, unknown>>(content);
62
+ } catch {
63
+ // One malformed definition must not prevent every other named agent
64
+ // from being discovered.
65
+ continue;
66
+ }
67
+ const { frontmatter, body } = parsed;
68
+ if (
69
+ typeof frontmatter.name !== "string" ||
70
+ typeof frontmatter.description !== "string"
71
+ ) {
72
+ continue;
73
+ }
62
74
 
63
- const tools = frontmatter.tools
64
- ?.split(",")
65
- .map((t) => t.trim())
66
- .filter(Boolean);
75
+ const tools = Array.isArray(frontmatter.tools)
76
+ ? frontmatter.tools.filter((tool): tool is string => typeof tool === "string")
77
+ : typeof frontmatter.tools === "string"
78
+ ? frontmatter.tools.split(",")
79
+ : [];
80
+ const normalizedTools = tools.map((tool) => tool.trim()).filter(Boolean);
67
81
 
68
82
  agents.push({
69
83
  name: frontmatter.name,
70
84
  description: frontmatter.description,
71
- tools: tools && tools.length > 0 ? tools : undefined,
72
- model: frontmatter.model,
85
+ tools: normalizedTools.length > 0 ? normalizedTools : undefined,
86
+ model: typeof frontmatter.model === "string" ? frontmatter.model : undefined,
73
87
  systemPrompt: body,
74
88
  source,
75
89
  filePath,