@yusukeshib/pi-babysit 0.3.11 → 0.3.12
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 +17 -9
- package/index.ts +140 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,8 +62,11 @@ programs** (installers, wizards, REPLs): type with `babysit_send`
|
|
|
62
62
|
| `babysit_wait` | Block until done: process exit (or `expect: "regex"` readiness marker), subagent task completion. Multi-wait: `ids` + `mode: "any"\|"all"` |
|
|
63
63
|
| `babysit_kill` | Terminate a session, verify terminal state, then suppress the exit notification |
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
The built-in `bash` tool is removed from the active tool set so the model does
|
|
66
|
+
not waste a failed tool turn before choosing `babysit_run`. A fallback
|
|
67
|
+
`tool_call` hook still blocks direct shell calls if another extension or preset
|
|
68
|
+
re-enables `bash`, including shell backgrounding (`… &`, `nohup`, `setsid`,
|
|
69
|
+
`disown`). Set `PI_BABYSIT_ALLOW_BASH=1` to retain direct `bash` explicitly.
|
|
67
70
|
|
|
68
71
|
## Commands (human)
|
|
69
72
|
|
|
@@ -91,9 +94,10 @@ babysit_check { id: "cargo-test", lines: 50 }
|
|
|
91
94
|
babysit_check { id: "cargo-test", pattern: "FAIL|ERROR", lines: 50 }
|
|
92
95
|
```
|
|
93
96
|
|
|
94
|
-
Tail and search results are capped at 200 lines and
|
|
95
|
-
|
|
96
|
-
|
|
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.
|
|
97
101
|
|
|
98
102
|
Subagent logs also compact Pi's streaming `message_update` events before they
|
|
99
103
|
are recorded. Pi repeats the complete growing assistant message and partial
|
|
@@ -103,7 +107,8 @@ events untouched. This keeps long RPC sessions approximately linear in emitted
|
|
|
103
107
|
content without changing final answers, completion detection, or follow-up
|
|
104
108
|
behavior. Live `/babysit` and attach views render the retained deltas.
|
|
105
109
|
|
|
106
|
-
All shell commands, including `pwd` and Git,
|
|
110
|
+
All shell commands, including `pwd` and Git, go through `babysit_run`. Bundle
|
|
111
|
+
closely related tiny observations when doing so safely reduces tool turns.
|
|
107
112
|
Set `PI_BABYSIT_ALLOW_BASH=1` only as an explicit emergency escape hatch.
|
|
108
113
|
|
|
109
114
|
## Unexpected worker loss
|
|
@@ -117,9 +122,12 @@ because blindly rerunning an arbitrary command can duplicate side effects.
|
|
|
117
122
|
|
|
118
123
|
## How completion detection works
|
|
119
124
|
|
|
120
|
-
- **Process**: a 2.5s poller watches for running→exited transitions and
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
- **Process**: a 2.5s poller watches for running→exited transitions and, once the
|
|
126
|
+
parent agent is idle, injects one
|
|
127
|
+
`pi.sendMessage(…, { triggerTurn: true, deliverAs: "steer" })` containing every
|
|
128
|
+
deliverable exit observed in that poll (deduped via `meta/<id>.json`). Waiting
|
|
129
|
+
for idleness prevents an immediately-following `babysit_wait` from racing the
|
|
130
|
+
poller and receiving a duplicate completion.
|
|
123
131
|
`babysit_kill` and an exit already reported by `babysit_wait` suppress the
|
|
124
132
|
notification.
|
|
125
133
|
- **Subagent**: `babysit_wait` blocks on `babysit expect '"type":"agent_end"'`.
|
package/index.ts
CHANGED
|
@@ -36,10 +36,15 @@ 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 {
|
|
39
|
+
import type {
|
|
40
|
+
ExtensionAPI,
|
|
41
|
+
ExtensionContext,
|
|
42
|
+
Theme,
|
|
43
|
+
ToolDefinition,
|
|
44
|
+
} from "@earendil-works/pi-coding-agent";
|
|
40
45
|
import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
41
46
|
import { Box, Markdown, Text } from "@earendil-works/pi-tui";
|
|
42
|
-
import { Type } from "typebox";
|
|
47
|
+
import { Type, type TSchema } from "typebox";
|
|
43
48
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
44
49
|
import { type AgentConfig, type AgentScope, discoverAgents } from "./agents";
|
|
45
50
|
|
|
@@ -365,6 +370,10 @@ interface Meta {
|
|
|
365
370
|
// Temporary reservation while kill is in flight. Unlike `notified`, this
|
|
366
371
|
// must be cleared on failure so a real completion remains deliverable.
|
|
367
372
|
notificationPaused?: boolean;
|
|
373
|
+
// Concurrent explicit waits share a reference-counted notification claim.
|
|
374
|
+
// A timeout must not re-enable the poller while another wait still owns it.
|
|
375
|
+
waitReservations?: number;
|
|
376
|
+
waitCompletionClaimed?: boolean;
|
|
368
377
|
completionObservedAt?: number;
|
|
369
378
|
startedAt?: number;
|
|
370
379
|
// subagent
|
|
@@ -405,6 +414,10 @@ export function shouldDeliverProcessCompletion(
|
|
|
405
414
|
return meta?.kind === "process" && !meta.notified && !meta.notificationPaused;
|
|
406
415
|
}
|
|
407
416
|
|
|
417
|
+
export function shouldDeferCompletionNotification(agentIsIdle: boolean): boolean {
|
|
418
|
+
return !agentIsIdle;
|
|
419
|
+
}
|
|
420
|
+
|
|
408
421
|
const kindOf = (id: string): "process" | "subagent" => readMeta(id)?.kind ?? "process";
|
|
409
422
|
|
|
410
423
|
// Compact elapsed formatting: "42s", "3m12s", "1h04m".
|
|
@@ -528,14 +541,26 @@ const NOTIFY_COMMAND_MAX_BYTES = byteLimitFromEnv("PI_BABYSIT_NOTIFY_COMMAND_MAX
|
|
|
528
541
|
const NOTIFY_BATCH_MAX_BYTES = byteLimitFromEnv("PI_BABYSIT_NOTIFY_BATCH_MAX_BYTES", 8_000);
|
|
529
542
|
const ANSWER_MAX_BYTES = 24_000; // subagent answers / error messages
|
|
530
543
|
|
|
531
|
-
function clip(s: string, maxBytes = TAIL_MAX_BYTES): string {
|
|
544
|
+
export function clip(s: string, maxBytes = TAIL_MAX_BYTES): string {
|
|
545
|
+
if (maxBytes <= 0) return "";
|
|
532
546
|
const buf = Buffer.from(s, "utf8");
|
|
533
547
|
if (buf.length <= maxBytes) return s;
|
|
534
|
-
|
|
535
|
-
//
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
548
|
+
|
|
549
|
+
// The marker counts toward the limit. Recompute a few times because the
|
|
550
|
+
// omitted-byte count can change the marker's digit width.
|
|
551
|
+
let available = maxBytes;
|
|
552
|
+
let marker = "";
|
|
553
|
+
for (let i = 0; i < 3; i++) {
|
|
554
|
+
marker = `\n… [${buf.length - available} bytes elided] …\n`;
|
|
555
|
+
available = Math.max(0, maxBytes - Buffer.byteLength(marker, "utf8"));
|
|
556
|
+
}
|
|
557
|
+
if (Buffer.byteLength(marker, "utf8") > maxBytes) return truncateUtf8End(marker, maxBytes);
|
|
558
|
+
const headBytes = Math.floor(available / 2);
|
|
559
|
+
const tailBytes = available - headBytes;
|
|
560
|
+
// Strip replacement chars from a mid-codepoint cut at either boundary.
|
|
561
|
+
const head = buf.subarray(0, headBytes).toString("utf8").replace(/\uFFFD+$/, "");
|
|
562
|
+
const tail = buf.subarray(buf.length - tailBytes).toString("utf8").replace(/^\uFFFD+/, "");
|
|
563
|
+
return `${head}${marker}${tail}`;
|
|
539
564
|
}
|
|
540
565
|
|
|
541
566
|
async function searchLog(
|
|
@@ -1442,19 +1467,47 @@ function suppressNotify(id: string, reason: "observed" | "kill" = "observed"): v
|
|
|
1442
1467
|
}
|
|
1443
1468
|
}
|
|
1444
1469
|
|
|
1445
|
-
export
|
|
1470
|
+
export interface WaitReservationState {
|
|
1446
1471
|
notified?: boolean;
|
|
1447
1472
|
killNotificationSuppressed?: boolean;
|
|
1448
|
-
|
|
1449
|
-
|
|
1473
|
+
waitReservations?: number;
|
|
1474
|
+
waitCompletionClaimed?: boolean;
|
|
1450
1475
|
}
|
|
1451
1476
|
|
|
1452
|
-
function
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
meta.
|
|
1456
|
-
|
|
1477
|
+
export function canRestoreNotificationAfterWait(meta: WaitReservationState): boolean {
|
|
1478
|
+
return (
|
|
1479
|
+
meta.notified === true &&
|
|
1480
|
+
meta.killNotificationSuppressed !== true &&
|
|
1481
|
+
meta.waitCompletionClaimed !== true &&
|
|
1482
|
+
(meta.waitReservations ?? 0) === 0
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
export function transitionWaitReservation(
|
|
1487
|
+
state: WaitReservationState,
|
|
1488
|
+
action: "reserve" | "abandon" | "claim",
|
|
1489
|
+
): WaitReservationState {
|
|
1490
|
+
const next = { ...state };
|
|
1491
|
+
if (action === "reserve") {
|
|
1492
|
+
next.waitReservations = (next.waitReservations ?? 0) + 1;
|
|
1493
|
+
next.notified = true;
|
|
1494
|
+
return next;
|
|
1457
1495
|
}
|
|
1496
|
+
|
|
1497
|
+
next.waitReservations = Math.max(0, (next.waitReservations ?? 0) - 1);
|
|
1498
|
+
if (action === "claim") next.waitCompletionClaimed = true;
|
|
1499
|
+
if (action === "claim" || next.waitCompletionClaimed) {
|
|
1500
|
+
next.notified = true;
|
|
1501
|
+
} else if (canRestoreNotificationAfterWait(next)) {
|
|
1502
|
+
next.notified = false;
|
|
1503
|
+
}
|
|
1504
|
+
return next;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
function updateWaitReservation(id: string, action: "reserve" | "abandon" | "claim"): void {
|
|
1508
|
+
const meta = readMeta(id);
|
|
1509
|
+
if (!meta || meta.kind !== "process") return;
|
|
1510
|
+
writeMeta(id, { ...meta, ...transitionWaitReservation(meta, action) });
|
|
1458
1511
|
}
|
|
1459
1512
|
|
|
1460
1513
|
function pauseNotify(id: string): void {
|
|
@@ -1508,19 +1561,20 @@ async function waitForExit(
|
|
|
1508
1561
|
}
|
|
1509
1562
|
// fall through: session exited before the pattern appeared
|
|
1510
1563
|
} else {
|
|
1511
|
-
// An explicit wait owns completion delivery.
|
|
1512
|
-
//
|
|
1513
|
-
|
|
1564
|
+
// An explicit wait owns completion delivery. Reference-count the claim so
|
|
1565
|
+
// one concurrent wait timing out cannot re-enable notifications underneath
|
|
1566
|
+
// another wait that is still pending.
|
|
1567
|
+
updateWaitReservation(id, "reserve");
|
|
1514
1568
|
const w = await bs(["wait", "-s", id, "--timeout", t], { signal });
|
|
1515
1569
|
if (signal?.aborted || w.code === 130) {
|
|
1516
|
-
|
|
1570
|
+
updateWaitReservation(id, "abandon");
|
|
1517
1571
|
return { id, kind: "interrupted", ok: false, text: `wait for ${id} was interrupted.` };
|
|
1518
1572
|
}
|
|
1519
1573
|
if (w.code === 124) {
|
|
1520
1574
|
// 124 is ambiguous (timeout vs child exiting 124) — disambiguate.
|
|
1521
1575
|
const st0 = await statusOf(id);
|
|
1522
1576
|
if (st0?.state === "running") {
|
|
1523
|
-
|
|
1577
|
+
updateWaitReservation(id, "abandon");
|
|
1524
1578
|
return {
|
|
1525
1579
|
id,
|
|
1526
1580
|
kind: "timeout",
|
|
@@ -1534,9 +1588,15 @@ async function waitForExit(
|
|
|
1534
1588
|
|
|
1535
1589
|
const st = await statusOf(id);
|
|
1536
1590
|
if (!st) {
|
|
1591
|
+
// A non-pattern backend wait returned before this lookup, so preserve its
|
|
1592
|
+
// completion claim when status persistence is transiently unavailable.
|
|
1593
|
+
// Abandoning here can re-enable the poller and duplicate a completion the
|
|
1594
|
+
// explicit wait already consumed. This matches the old suppress-first rule.
|
|
1595
|
+
if (!expectPattern) updateWaitReservation(id, "claim");
|
|
1537
1596
|
return { id, kind: "exited", ok: false, text: `No such session: ${id}` };
|
|
1538
1597
|
}
|
|
1539
|
-
suppressNotify(id);
|
|
1598
|
+
if (expectPattern) suppressNotify(id);
|
|
1599
|
+
else updateWaitReservation(id, "claim"); // the agent sees the exit here; don't notify again
|
|
1540
1600
|
const meta = readMeta(id);
|
|
1541
1601
|
const workerDead = st.state === "dead" && st.exit_code == null;
|
|
1542
1602
|
const ok = st.exit_code === 0;
|
|
@@ -1588,12 +1648,41 @@ export function isAllowedDirectBash(_command: string): boolean {
|
|
|
1588
1648
|
return process.env.PI_BABYSIT_ALLOW_BASH === "1";
|
|
1589
1649
|
}
|
|
1590
1650
|
|
|
1651
|
+
export function activeToolsWithoutDirectBash(activeTools: string[], allowDirectBash: boolean): string[] {
|
|
1652
|
+
return allowDirectBash ? activeTools : activeTools.filter((name) => name !== "bash");
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1591
1655
|
// ---------------------------------------------------------------------------
|
|
1592
1656
|
// extension
|
|
1593
1657
|
// ---------------------------------------------------------------------------
|
|
1594
1658
|
|
|
1595
1659
|
export default function (pi: ExtensionAPI) {
|
|
1596
1660
|
let pollTimer: ReturnType<typeof setInterval> | undefined;
|
|
1661
|
+
const declaredToolErrors = new Set<string>();
|
|
1662
|
+
|
|
1663
|
+
// Pi only persists custom-tool failures when they are thrown or patched by a
|
|
1664
|
+
// tool_result hook; an `isError` property returned from execute() is ignored.
|
|
1665
|
+
// Keep the structured result (status, log path, diagnostics) while promoting
|
|
1666
|
+
// its declared error bit at the supported hook boundary.
|
|
1667
|
+
const registerTool = <TParams extends TSchema, TDetails, TState>(
|
|
1668
|
+
tool: ToolDefinition<TParams, TDetails, TState>,
|
|
1669
|
+
): void => {
|
|
1670
|
+
const execute = tool.execute;
|
|
1671
|
+
pi.registerTool({
|
|
1672
|
+
...tool,
|
|
1673
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
1674
|
+
const result = await execute(toolCallId, params, signal, onUpdate, ctx);
|
|
1675
|
+
if ((result as typeof result & { isError?: boolean }).isError === true) {
|
|
1676
|
+
declaredToolErrors.add(toolCallId);
|
|
1677
|
+
}
|
|
1678
|
+
return result;
|
|
1679
|
+
},
|
|
1680
|
+
});
|
|
1681
|
+
};
|
|
1682
|
+
|
|
1683
|
+
pi.on("tool_result", (event) => {
|
|
1684
|
+
if (declaredToolErrors.delete(event.toolCallId)) return { isError: true };
|
|
1685
|
+
});
|
|
1597
1686
|
|
|
1598
1687
|
// Exit notifications for kind=process sessions: the poller detects
|
|
1599
1688
|
// running→exited transitions and injects ONE message (triggerTurn) for all
|
|
@@ -1601,7 +1690,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
1601
1690
|
// that ended its turn after babysit_run without spending one turn per exit.
|
|
1602
1691
|
// Kills via babysit_kill and exits already reported by babysit_wait are
|
|
1603
1692
|
// suppressed via meta.notified.
|
|
1604
|
-
async function notifyEndedProcesses(): Promise<void> {
|
|
1693
|
+
async function notifyEndedProcesses(ctx: ExtensionContext): Promise<void> {
|
|
1694
|
+
// Never steer a completion into an active agent turn. In particular, this
|
|
1695
|
+
// lets an immediately-following babysit_wait reserve the completion first,
|
|
1696
|
+
// instead of racing the poller and receiving both wait + auto notification.
|
|
1697
|
+
if (shouldDeferCompletionNotification(ctx.isIdle())) return;
|
|
1605
1698
|
const { sessions } = await listSessions();
|
|
1606
1699
|
const ready: Array<{ session: BsSession; meta: Meta }> = [];
|
|
1607
1700
|
for (const session of sessions) {
|
|
@@ -1660,6 +1753,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
1660
1753
|
metadataById.set(notice.id, current);
|
|
1661
1754
|
return [{ ...notice, command: current.command }];
|
|
1662
1755
|
});
|
|
1756
|
+
// Output collection above yields to the event loop. Re-check idleness so a
|
|
1757
|
+
// newly-started agent turn cannot receive a duplicate completion mid-turn.
|
|
1758
|
+
if (shouldDeferCompletionNotification(ctx.isIdle())) return;
|
|
1663
1759
|
deliverProcessCompletionMessage(
|
|
1664
1760
|
notices,
|
|
1665
1761
|
(message, options) => pi.sendMessage(message, options),
|
|
@@ -1773,6 +1869,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
1773
1869
|
|
|
1774
1870
|
let polling = false;
|
|
1775
1871
|
pi.on("session_start", async (_event, ctx) => {
|
|
1872
|
+
// Do not expose the built-in bash tool only to reject it after the model has
|
|
1873
|
+
// already paid for a failed tool turn. The tool_call hook remains a fallback
|
|
1874
|
+
// if another extension/preset re-enables bash later in the session.
|
|
1875
|
+
const activeTools = pi.getActiveTools();
|
|
1876
|
+
const supervisedTools = activeToolsWithoutDirectBash(
|
|
1877
|
+
activeTools,
|
|
1878
|
+
isAllowedDirectBash(""),
|
|
1879
|
+
);
|
|
1880
|
+
if (supervisedTools.length !== activeTools.length) pi.setActiveTools(supervisedTools);
|
|
1881
|
+
|
|
1776
1882
|
// Session-local registry: scope the babysit root to this pi session so
|
|
1777
1883
|
// other sessions' processes/subagents are invisible here. Resuming a
|
|
1778
1884
|
// session keeps the same id, so its sessions come back with it.
|
|
@@ -1792,7 +1898,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1792
1898
|
// calls never stack up.
|
|
1793
1899
|
if (polling) return;
|
|
1794
1900
|
polling = true;
|
|
1795
|
-
Promise.all([notifyEndedProcesses(), refreshWidget(ctx)])
|
|
1901
|
+
Promise.all([notifyEndedProcesses(ctx), refreshWidget(ctx)])
|
|
1796
1902
|
.catch(() => {
|
|
1797
1903
|
/* ignore poll errors */
|
|
1798
1904
|
})
|
|
@@ -1829,7 +1935,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1829
1935
|
});
|
|
1830
1936
|
|
|
1831
1937
|
// ----- babysit_run --------------------------------------------------------
|
|
1832
|
-
|
|
1938
|
+
registerTool({
|
|
1833
1939
|
name: "babysit_run",
|
|
1834
1940
|
label: "Babysit: run",
|
|
1835
1941
|
description:
|
|
@@ -1850,7 +1956,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
1850
1956
|
"Run any shell command with context-safe captured output; quick commands return metadata, longer ones continue in background",
|
|
1851
1957
|
promptGuidelines: [
|
|
1852
1958
|
"Use babysit_run as the default for shell commands, not only long-running work. Small output is returned directly; large stdout/stderr stays out of model context in the returned log path. Give meaningful commands a clear stable `name`.",
|
|
1853
|
-
"
|
|
1959
|
+
"Bundle closely related tiny observations into one babysit_run command when that reduces tool turns without obscuring lifecycle or failure handling.",
|
|
1960
|
+
"Inspect a babysit log with babysit_check { id, lines, pattern? }; never read or cat a potentially large log file in full. Prefer a targeted `pattern` search over returning a broad tail.",
|
|
1854
1961
|
"After babysit_run { command } starts a process, end your response immediately so the automatic process-end notification can resume you; NEVER poll with babysit_check or sleep. Set continueAfterStart: true only when you have immediate, specific, non-polling work to do next. Call babysit_wait when you must consume the result inside the current turn (optionally with `expect` to wait for a readiness line like 'listening on').",
|
|
1855
1962
|
"If a babysit worker is killed externally, babysit_run reports it as worker-dead rather than hanging. Set retryOnWorkerDeath: true only for safe, idempotent commands; it retries at most once and may otherwise duplicate side effects.",
|
|
1856
1963
|
"babysit_run gives full PTY control: drive interactive programs (installers, wizards, REPLs) with babysit_send (text or named keys) and read the rendered screen with babysit_check { screen: true }.",
|
|
@@ -2199,7 +2306,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2199
2306
|
});
|
|
2200
2307
|
|
|
2201
2308
|
// ----- babysit_check ------------------------------------------------------
|
|
2202
|
-
|
|
2309
|
+
registerTool({
|
|
2203
2310
|
name: "babysit_check",
|
|
2204
2311
|
label: "Babysit: check",
|
|
2205
2312
|
description:
|
|
@@ -2257,7 +2364,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2257
2364
|
const preview = what.length > 60 ? `${what.slice(0, 57)}…` : what;
|
|
2258
2365
|
return `${s.id} [${kind}] ${s.state}${ec}${depth}${flag}${preview ? ` — ${preview}` : ""}`;
|
|
2259
2366
|
});
|
|
2260
|
-
return { content: [{ type: "text", text: lines.join("\n") }], details: { sessions } };
|
|
2367
|
+
return { content: [{ type: "text", text: clip(lines.join("\n")) }], details: { sessions } };
|
|
2261
2368
|
}
|
|
2262
2369
|
|
|
2263
2370
|
const st = await statusOf(params.id);
|
|
@@ -2299,7 +2406,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2299
2406
|
? `--- latest matches /${params.pattern}/ ---\n${result.text}`
|
|
2300
2407
|
: `(no output matching /${params.pattern}/)`;
|
|
2301
2408
|
return {
|
|
2302
|
-
content: [{ type: "text", text: `${header}\n${body}` }],
|
|
2409
|
+
content: [{ type: "text", text: clip(`${header}\n${body}`) }],
|
|
2303
2410
|
details: { status: st, kind, logPath: logPath(params.id), pattern: params.pattern },
|
|
2304
2411
|
};
|
|
2305
2412
|
}
|
|
@@ -2327,7 +2434,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2327
2434
|
parts.push(tail ? `--- recent output ---\n${tail}` : "(no output yet)");
|
|
2328
2435
|
}
|
|
2329
2436
|
return {
|
|
2330
|
-
content: [{ type: "text", text: parts.join("\n") }],
|
|
2437
|
+
content: [{ type: "text", text: clip(parts.join("\n")) }],
|
|
2331
2438
|
details: { status: st, kind: "process", logPath: logPath(params.id) },
|
|
2332
2439
|
};
|
|
2333
2440
|
}
|
|
@@ -2389,7 +2496,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2389
2496
|
});
|
|
2390
2497
|
|
|
2391
2498
|
// ----- babysit_send -------------------------------------------------------
|
|
2392
|
-
|
|
2499
|
+
registerTool({
|
|
2393
2500
|
name: "babysit_send",
|
|
2394
2501
|
label: "Babysit: send",
|
|
2395
2502
|
description:
|
|
@@ -2542,7 +2649,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2542
2649
|
});
|
|
2543
2650
|
|
|
2544
2651
|
// ----- babysit_wait -------------------------------------------------------
|
|
2545
|
-
|
|
2652
|
+
registerTool({
|
|
2546
2653
|
name: "babysit_wait",
|
|
2547
2654
|
label: "Babysit: wait",
|
|
2548
2655
|
description:
|
|
@@ -2653,7 +2760,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2653
2760
|
});
|
|
2654
2761
|
|
|
2655
2762
|
// ----- babysit_kill -------------------------------------------------------
|
|
2656
|
-
|
|
2763
|
+
registerTool({
|
|
2657
2764
|
name: "babysit_kill",
|
|
2658
2765
|
label: "Babysit: kill",
|
|
2659
2766
|
description: "Terminate a babysit session (process or subagent).",
|