@yusukeshib/pi-babysit 0.6.0 → 0.6.2
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 +26 -11
- package/index.ts +134 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,24 +56,27 @@ 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`/`notificationGroup`). 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. 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. Quick commands return inline; longer process runs notify in the background |
|
|
59
|
+
| `babysit_run` | Run any command (`command`, optional `name`/`pty`/`timeout`/`idleTimeout`/`retryOnWorkerDeath`/`notificationGroup`). 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. 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 |
|
|
60
60
|
| `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
61
|
| `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
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
|
|
66
|
-
not waste a failed tool turn before choosing `babysit_run`.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
`
|
|
66
|
+
not waste a failed tool turn before choosing `babysit_run`. Process commands
|
|
67
|
+
retain the built-in Bash tool's dynamic Pi metadata environment:
|
|
68
|
+
`PI_SESSION_ID`, `PI_SESSION_FILE`, `PI_PROVIDER`, `PI_MODEL`, and
|
|
69
|
+
`PI_REASONING_LEVEL`. A fallback `tool_call` hook still blocks direct shell
|
|
70
|
+
calls if another extension or preset re-enables `bash`, including shell
|
|
71
|
+
backgrounding (`… &`, `nohup`, `setsid`, `disown`). Set
|
|
72
|
+
`PI_BABYSIT_ALLOW_BASH=1` to retain direct `bash` explicitly.
|
|
70
73
|
|
|
71
74
|
## Commands (human)
|
|
72
75
|
|
|
73
76
|
| Command | What it does |
|
|
74
77
|
| ------- | ------------ |
|
|
75
78
|
| `/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;
|
|
79
|
+
| `/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; old empty roots are eligible once their lease is gone. Deletion uses a GC lock and atomic rename |
|
|
77
80
|
|
|
78
81
|
A widget below the editor separates session **kind** from task **state** at a glance:
|
|
79
82
|
summary counts use `RUNNING` / `IDLE`, and every row is labeled
|
|
@@ -127,6 +130,14 @@ suite at the end. Background subagents must likewise be collected before the
|
|
|
127
130
|
parent task finishes so their answer and nested usage are not lost.
|
|
128
131
|
Set `PI_BABYSIT_ALLOW_BASH=1` only as an explicit emergency escape hatch.
|
|
129
132
|
|
|
133
|
+
## Pi shutdown and reload
|
|
134
|
+
|
|
135
|
+
A real Pi quit terminates every running process and subagent in the current Pi
|
|
136
|
+
session namespace. The workers are killed concurrently, so shutdown time does
|
|
137
|
+
not grow linearly when many sessions are active. Lifecycle transitions that keep
|
|
138
|
+
Pi running—`/reload`, `/new`, `/resume`, and `/fork`—leave detached workers
|
|
139
|
+
alone. Reloading or resuming the same Pi session reconnects to its namespace.
|
|
140
|
+
|
|
130
141
|
## Unexpected worker loss
|
|
131
142
|
|
|
132
143
|
If the babysit supervisor disappears without recording an exit, pi-babysit
|
|
@@ -149,7 +160,9 @@ because blindly rerunning an arbitrary command can duplicate side effects.
|
|
|
149
160
|
currently running member to stop and then share one notification even when
|
|
150
161
|
their exits span multiple polls.
|
|
151
162
|
`babysit_kill` and an exit already reported by `babysit_wait` suppress the
|
|
152
|
-
notification.
|
|
163
|
+
notification. Kill results are reconciled with persisted terminal state, so
|
|
164
|
+
a backend escalation warning that arrives after the child has already exited
|
|
165
|
+
does not produce a false failure followed by a redundant notification.
|
|
153
166
|
- **Subagent**: `foreground: true` or `babysit_wait` blocks on
|
|
154
167
|
`babysit expect '"type":"agent_settled"'`. A background task that settles or
|
|
155
168
|
exits before it is collected emits one ready-to-collect reminder; the parent
|
|
@@ -169,10 +182,12 @@ grace window (`PI_BABYSIT_REAP_AFTER`, default 120s) using the same parked-turn
|
|
|
169
182
|
rule, so a subagent waiting on a long build is never false-killed. Give bounded
|
|
170
183
|
recon/review tasks at least one cost, turn, tool-call, or token budget; omit
|
|
171
184
|
budgets only for intentionally open-ended work. Optional task budgets are
|
|
172
|
-
observed by the parent poller.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
185
|
+
observed by the parent poller. `maxUsageTokens` counts cumulative input, output,
|
|
186
|
+
and cache-token usage, so size it above the worker's initial context and
|
|
187
|
+
expected calls. At 80% of a limit the worker is steered to wrap up; reaching the
|
|
188
|
+
configured limit starts the hard grace immediately, even if a wedged worker
|
|
189
|
+
cannot accept steering. An in-flight model call or parallel tool batch can
|
|
190
|
+
still overshoot before the next poll. If the worker remains active
|
|
176
191
|
after `PI_BABYSIT_BUDGET_GRACE`, termination is verified before it is marked
|
|
177
192
|
budget-killed. Usage shown by check/wait is cumulative, and the first terminal
|
|
178
193
|
wait for each task charges that nested usage exactly once to the parent Pi
|
package/index.ts
CHANGED
|
@@ -220,9 +220,15 @@ function bs(
|
|
|
220
220
|
resolve({ stdout: "", stderr: "aborted", code: 130 });
|
|
221
221
|
return;
|
|
222
222
|
}
|
|
223
|
+
const env: NodeJS.ProcessEnv = { ...process.env };
|
|
224
|
+
for (const [name, value] of Object.entries(opts.env ?? {})) {
|
|
225
|
+
if (value === undefined) delete env[name];
|
|
226
|
+
else env[name] = value;
|
|
227
|
+
}
|
|
228
|
+
env.BABYSIT_DIR = ROOT;
|
|
223
229
|
const child = spawn(BABYSIT_BIN, args, {
|
|
224
230
|
cwd: opts.cwd,
|
|
225
|
-
env
|
|
231
|
+
env,
|
|
226
232
|
});
|
|
227
233
|
let stdout = "";
|
|
228
234
|
let stderr = "";
|
|
@@ -236,7 +242,12 @@ function bs(
|
|
|
236
242
|
});
|
|
237
243
|
child.on("error", (e) => {
|
|
238
244
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
239
|
-
|
|
245
|
+
const installHint = babysitSpawnInstallHint(e);
|
|
246
|
+
if (installHint) {
|
|
247
|
+
babysitPreflightError = installHint;
|
|
248
|
+
babysitPreflightCheckedAt = Date.now();
|
|
249
|
+
}
|
|
250
|
+
resolve({ stdout, stderr: installHint ?? stderr + String(e), code: 1 });
|
|
240
251
|
});
|
|
241
252
|
child.on("close", (code) => {
|
|
242
253
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
@@ -261,6 +272,10 @@ const INSTALL_HINT =
|
|
|
261
272
|
`The \`babysit\` binary was not found (tried "${BABYSIT_BIN}").\n` + INSTALL_STEPS;
|
|
262
273
|
const MIN_BABYSIT_VERSION = [0, 13, 0] as const;
|
|
263
274
|
|
|
275
|
+
export function babysitSpawnInstallHint(error: unknown): string | null {
|
|
276
|
+
return (error as NodeJS.ErrnoException | undefined)?.code === "ENOENT" ? INSTALL_HINT : null;
|
|
277
|
+
}
|
|
278
|
+
|
|
264
279
|
export function isSupportedBabysitVersion(output: string): boolean {
|
|
265
280
|
const match = /\b(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?\b/.exec(output);
|
|
266
281
|
if (!match) return false;
|
|
@@ -271,7 +286,8 @@ export function isSupportedBabysitVersion(output: string): boolean {
|
|
|
271
286
|
return match[4] === undefined;
|
|
272
287
|
}
|
|
273
288
|
|
|
274
|
-
// Cached preflight
|
|
289
|
+
// Cached preflight. A supported binary stays cached until a later spawn reports
|
|
290
|
+
// ENOENT; failures are retried after a short delay so installation can recover.
|
|
275
291
|
// undefined = not probed, null = supported, string = actionable error.
|
|
276
292
|
let babysitPreflightError: string | null | undefined;
|
|
277
293
|
let babysitPreflightCheckedAt = 0;
|
|
@@ -355,6 +371,22 @@ export function validateKillResponse(stdout: string): string | null {
|
|
|
355
371
|
}
|
|
356
372
|
}
|
|
357
373
|
|
|
374
|
+
export function resolveKillConfirmation(
|
|
375
|
+
id: string,
|
|
376
|
+
backendError: string | null,
|
|
377
|
+
state: string | undefined,
|
|
378
|
+
): { confirmed: true; warning?: string } | { confirmed: false; error: string } {
|
|
379
|
+
if (state && isConfirmedTerminalState(state)) {
|
|
380
|
+
return backendError ? { confirmed: true, warning: backendError } : { confirmed: true };
|
|
381
|
+
}
|
|
382
|
+
if (backendError) return { confirmed: false, error: backendError };
|
|
383
|
+
if (!state) return { confirmed: false, error: `Kill could not be verified: session ${id} disappeared.` };
|
|
384
|
+
return {
|
|
385
|
+
confirmed: false,
|
|
386
|
+
error: `Kill was acknowledged but ${id} is still ${state}; completion notifications were restored.`,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
358
390
|
async function awaitConfirmedTermination(id: string): Promise<BsSession | null> {
|
|
359
391
|
const initial = await statusOf(id);
|
|
360
392
|
if (!initial || isConfirmedTerminalState(initial.state) || initial.state === "dead") {
|
|
@@ -367,6 +399,35 @@ async function awaitConfirmedTermination(id: string): Promise<BsSession | null>
|
|
|
367
399
|
return statusOf(id);
|
|
368
400
|
}
|
|
369
401
|
|
|
402
|
+
export function shouldTerminateSessionsOnShutdown(reason: string | undefined): boolean {
|
|
403
|
+
return reason === "quit";
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async function terminateRunningSessions(): Promise<void> {
|
|
407
|
+
let listed: Awaited<ReturnType<typeof listSessions>>;
|
|
408
|
+
try {
|
|
409
|
+
listed = await listSessions();
|
|
410
|
+
} catch {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (listed.error) return;
|
|
414
|
+
|
|
415
|
+
// A namespace may contain many independent commands and agents. Terminate
|
|
416
|
+
// them concurrently so quitting Pi does not wait for serial kill timeouts.
|
|
417
|
+
// allSettled ensures one unreachable worker cannot skip cleanup for others.
|
|
418
|
+
await Promise.allSettled(
|
|
419
|
+
listed.sessions
|
|
420
|
+
.filter((session) => session.state === "running")
|
|
421
|
+
.map(async (session) => {
|
|
422
|
+
const result = await bs(["kill", "-s", session.id, "--json"]);
|
|
423
|
+
if (result.code !== 0 || validateKillResponse(result.stdout)) return;
|
|
424
|
+
// A later resume of this Pi session must not turn intentional shutdown
|
|
425
|
+
// cleanup into a process-completion notification.
|
|
426
|
+
suppressNotify(session.id, "kill");
|
|
427
|
+
}),
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
370
431
|
// ---------------------------------------------------------------------------
|
|
371
432
|
// per-session metadata
|
|
372
433
|
// ---------------------------------------------------------------------------
|
|
@@ -562,9 +623,12 @@ function gcRootIsSafe(root: string): boolean {
|
|
|
562
623
|
let sessionDirs: fs.Dirent[];
|
|
563
624
|
try {
|
|
564
625
|
sessionDirs = fs.readdirSync(sessionsDir, { withFileTypes: true });
|
|
565
|
-
} catch {
|
|
566
|
-
|
|
626
|
+
} catch (error) {
|
|
627
|
+
// session_start acquires a lease before the first worker exists. Once that
|
|
628
|
+
// lease is gone, an old root with no sessions is safe to collect.
|
|
629
|
+
return (error as NodeJS.ErrnoException).code === "ENOENT";
|
|
567
630
|
}
|
|
631
|
+
if (!sessionDirs.some((entry) => entry.isDirectory())) return true;
|
|
568
632
|
let sawStatus = false;
|
|
569
633
|
for (const sessionEntry of sessionDirs) {
|
|
570
634
|
if (!sessionEntry.isDirectory()) continue;
|
|
@@ -1864,12 +1928,26 @@ interface ProcOpts {
|
|
|
1864
1928
|
name?: string;
|
|
1865
1929
|
command: string;
|
|
1866
1930
|
cwd: string;
|
|
1931
|
+
env?: Record<string, string | undefined>;
|
|
1867
1932
|
timeout?: string; // default: none — dev servers may run indefinitely
|
|
1868
1933
|
idleTimeout?: string;
|
|
1869
1934
|
pty: boolean;
|
|
1870
1935
|
notificationGroup?: string;
|
|
1871
1936
|
}
|
|
1872
1937
|
|
|
1938
|
+
export function processSessionEnvironment(
|
|
1939
|
+
ctx: ExtensionContext,
|
|
1940
|
+
reasoningLevel = (ctx as ExtensionContext & { thinkingLevel?: string }).thinkingLevel,
|
|
1941
|
+
): Record<string, string | undefined> {
|
|
1942
|
+
return {
|
|
1943
|
+
PI_SESSION_ID: ctx.sessionManager.getSessionId(),
|
|
1944
|
+
PI_SESSION_FILE: ctx.sessionManager.getSessionFile(),
|
|
1945
|
+
PI_PROVIDER: ctx.model?.provider,
|
|
1946
|
+
PI_MODEL: ctx.model?.id,
|
|
1947
|
+
PI_REASONING_LEVEL: reasoningLevel,
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1873
1951
|
async function spawnProcess(opts: ProcOpts): Promise<{ id: string } | { error: string }> {
|
|
1874
1952
|
const bsArgs = ["run", "-d", "--json", "--size", "120x40"];
|
|
1875
1953
|
if (!opts.pty) bsArgs.push("--no-tty");
|
|
@@ -1882,7 +1960,7 @@ async function spawnProcess(opts: ProcOpts): Promise<{ id: string } | { error: s
|
|
|
1882
1960
|
|
|
1883
1961
|
let r: Awaited<ReturnType<typeof bs>>;
|
|
1884
1962
|
try {
|
|
1885
|
-
r = await bs(bsArgs, { cwd: opts.cwd });
|
|
1963
|
+
r = await bs(bsArgs, { cwd: opts.cwd, env: opts.env });
|
|
1886
1964
|
} finally {
|
|
1887
1965
|
if (reservedId) reservedSessionIds.delete(reservedId);
|
|
1888
1966
|
}
|
|
@@ -2814,6 +2892,21 @@ export function automaticNotificationGroup(entry: unknown): string | undefined {
|
|
|
2814
2892
|
return runs.length >= 2 ? group : undefined;
|
|
2815
2893
|
}
|
|
2816
2894
|
|
|
2895
|
+
export function prepareBabysitRunArguments(args: unknown): unknown {
|
|
2896
|
+
if (!args || typeof args !== "object") return args;
|
|
2897
|
+
const input = args as Record<string, unknown>;
|
|
2898
|
+
if (
|
|
2899
|
+
input.profile === "subagent" &&
|
|
2900
|
+
input.continueAfterStart === true &&
|
|
2901
|
+
input.foreground !== true
|
|
2902
|
+
) {
|
|
2903
|
+
const prepared = { ...input };
|
|
2904
|
+
delete prepared.continueAfterStart;
|
|
2905
|
+
return prepared;
|
|
2906
|
+
}
|
|
2907
|
+
return args;
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2817
2910
|
export function resolveSubagentSendMode(
|
|
2818
2911
|
requested: "auto" | "steer" | "task",
|
|
2819
2912
|
streaming?: boolean,
|
|
@@ -3338,9 +3431,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
3338
3431
|
}, POLL_MS);
|
|
3339
3432
|
});
|
|
3340
3433
|
|
|
3341
|
-
pi.on("session_shutdown", async () => {
|
|
3434
|
+
pi.on("session_shutdown", async (event) => {
|
|
3342
3435
|
if (pollTimer) clearInterval(pollTimer);
|
|
3343
3436
|
pollTimer = undefined;
|
|
3437
|
+
// Pi also emits session_shutdown while hot-reloading and switching sessions.
|
|
3438
|
+
// Preserve detached work for those lifecycle transitions; only a real Pi
|
|
3439
|
+
// quit owns and terminates every worker in the current session namespace.
|
|
3440
|
+
if (shouldTerminateSessionsOnShutdown(event?.reason)) {
|
|
3441
|
+
await terminateRunningSessions();
|
|
3442
|
+
}
|
|
3344
3443
|
releaseRootLease(rootLeasePath);
|
|
3345
3444
|
rootLeasePath = undefined;
|
|
3346
3445
|
});
|
|
@@ -3395,14 +3494,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
3395
3494
|
promptSnippet: "Run supervised commands or bounded pi subagents with context-safe logs",
|
|
3396
3495
|
promptGuidelines: [
|
|
3397
3496
|
"Use babysit_run for shell commands and give meaningful sessions a stable name; bundle tiny related read-only observations into one command.",
|
|
3398
|
-
"Use babysit_run foreground mode for one process or subagent whose result is needed now; never issue sibling foreground runs in parallel. For parallel checks, start background runs with continueAfterStart and collect them with one multi-session babysit_wait.",
|
|
3497
|
+
"Use babysit_run foreground mode for one process or subagent whose result is needed now; never issue sibling foreground runs in parallel. For parallel process checks, start background runs with continueAfterStart and collect them with one multi-session babysit_wait.",
|
|
3399
3498
|
"Use returnPattern/returnLines for noisy commands. During edit/fix loops run targeted checks first and one full validation suite at the end instead of repeating every full gate.",
|
|
3400
|
-
"After a background process starts, stop the turn for its automatic notification; never poll or sleep. Use continueAfterStart only for specific non-polling work.",
|
|
3499
|
+
"After a background process starts, stop the turn for its automatic notification; never poll or sleep. Use continueAfterStart only for specific non-polling process work.",
|
|
3401
3500
|
"Inspect large logs with a narrow babysit_check pattern and maxBytes rather than broad tails.",
|
|
3402
3501
|
"Use retryOnWorkerDeath only once and only for idempotent commands; retries may duplicate side effects.",
|
|
3403
|
-
"Delegate independent work with bounded babysit_run subagents. Prefer foreground for one result needed now; every background subagent must be collected with babysit_wait before the parent task finishes. Size budgets above the worker's initial context and expected tool count.",
|
|
3502
|
+
"Delegate independent work with bounded babysit_run subagents. Prefer foreground for one result needed now; every background subagent must be collected with babysit_wait before the parent task finishes. Size budgets above the worker's initial context and expected tool count; maxUsageTokens counts cumulative input/cache tokens and can overshoot by one in-flight model call.",
|
|
3503
|
+
"Omit babysit_run.agent unless you know a named agent definition exists in the selected agentScope.",
|
|
3404
3504
|
"Subagent recursion defaults to depth 1; only a top-level caller may explicitly raise maxDepth.",
|
|
3405
3505
|
],
|
|
3506
|
+
prepareArguments(args) {
|
|
3507
|
+
return prepareBabysitRunArguments(args) as never;
|
|
3508
|
+
},
|
|
3406
3509
|
parameters: Type.Object({
|
|
3407
3510
|
command: Type.Optional(
|
|
3408
3511
|
Type.String({
|
|
@@ -3509,7 +3612,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3509
3612
|
continueAfterStart: Type.Optional(
|
|
3510
3613
|
Type.Boolean({
|
|
3511
3614
|
description:
|
|
3512
|
-
"Process mode
|
|
3615
|
+
"Process mode. Default false: starting a process ENDS the current turn (you are resumed by the exit notification). Set true only for immediate, specific, non-polling process work. In subagent mode true is accepted as a compatibility alias for the default background behavior.",
|
|
3513
3616
|
}),
|
|
3514
3617
|
),
|
|
3515
3618
|
retryOnWorkerDeath: Type.Optional(
|
|
@@ -3570,14 +3673,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3570
3673
|
details: {},
|
|
3571
3674
|
};
|
|
3572
3675
|
}
|
|
3573
|
-
if (
|
|
3574
|
-
return {
|
|
3575
|
-
content: [{ type: "text", text: "`continueAfterStart` is available only in process mode." }],
|
|
3576
|
-
isError: true,
|
|
3577
|
-
details: {},
|
|
3578
|
-
};
|
|
3579
|
-
}
|
|
3580
|
-
if (!isSubagent && params.foreground && params.continueAfterStart) {
|
|
3676
|
+
if (params.foreground && params.continueAfterStart) {
|
|
3581
3677
|
return {
|
|
3582
3678
|
content: [{ type: "text", text: "`foreground` and `continueAfterStart` are mutually exclusive." }],
|
|
3583
3679
|
isError: true,
|
|
@@ -3622,6 +3718,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3622
3718
|
timeout: params.timeout,
|
|
3623
3719
|
idleTimeout: params.idleTimeout,
|
|
3624
3720
|
pty: params.pty ?? true,
|
|
3721
|
+
env: processSessionEnvironment(ctx, pi.getThinkingLevel()),
|
|
3625
3722
|
notificationGroup: params.notificationGroup?.trim() || undefined,
|
|
3626
3723
|
};
|
|
3627
3724
|
let res = await spawnProcess(spawnOpts);
|
|
@@ -3750,7 +3847,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3750
3847
|
const avail = agents.map((a) => a.name).join(", ") || "none";
|
|
3751
3848
|
return {
|
|
3752
3849
|
content: [
|
|
3753
|
-
{ type: "text", text: `Unknown agent "${params.agent}". Available: ${avail}.` },
|
|
3850
|
+
{ type: "text", text: `Unknown agent "${params.agent}". Available: ${avail}. Omit \`agent\` to use the default subagent configuration.` },
|
|
3754
3851
|
],
|
|
3755
3852
|
isError: true,
|
|
3756
3853
|
details: {},
|
|
@@ -4546,29 +4643,34 @@ export default function (pi: ExtensionAPI) {
|
|
|
4546
4643
|
};
|
|
4547
4644
|
|
|
4548
4645
|
const r = await bs(["kill", "-s", params.id, "--json"]);
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4646
|
+
const backendError = r.code !== 0
|
|
4647
|
+
? (r.stderr || r.stdout || "kill failed").trim()
|
|
4648
|
+
: validateKillResponse(r.stdout);
|
|
4649
|
+
// A backend can report an escalation error after the child has already
|
|
4650
|
+
// reached a persisted terminal state. Reconcile against authoritative
|
|
4651
|
+
// state before deciding whether to restore completion notifications.
|
|
4554
4652
|
const status = await awaitConfirmedTermination(params.id);
|
|
4653
|
+
const confirmation = resolveKillConfirmation(params.id, backendError, status?.state);
|
|
4654
|
+
if (!confirmation.confirmed) return fail(confirmation.error, status);
|
|
4555
4655
|
if (!status) return fail(`Kill could not be verified: session ${params.id} disappeared.`);
|
|
4556
|
-
if (!isConfirmedTerminalState(status.state)) {
|
|
4557
|
-
return fail(
|
|
4558
|
-
`Kill was acknowledged but ${params.id} is still ${status.state}; completion notifications were restored.`,
|
|
4559
|
-
status,
|
|
4560
|
-
);
|
|
4561
|
-
}
|
|
4562
4656
|
|
|
4563
4657
|
suppressNotify(params.id, "kill");
|
|
4564
4658
|
await refreshWidget(ctx);
|
|
4565
4659
|
return {
|
|
4566
|
-
content: [
|
|
4660
|
+
content: [
|
|
4661
|
+
{
|
|
4662
|
+
type: "text",
|
|
4663
|
+
text:
|
|
4664
|
+
`Killed ${params.id} (confirmed ${status.state}).` +
|
|
4665
|
+
(confirmation.warning ? ` Backend warning after termination: ${confirmation.warning}` : ""),
|
|
4666
|
+
},
|
|
4667
|
+
],
|
|
4567
4668
|
details: {
|
|
4568
4669
|
id: params.id,
|
|
4569
4670
|
status: status.state,
|
|
4570
4671
|
exitCode: status.exit_code,
|
|
4571
4672
|
logPath: logPath(params.id),
|
|
4673
|
+
backendWarning: confirmation.warning,
|
|
4572
4674
|
},
|
|
4573
4675
|
};
|
|
4574
4676
|
},
|