@yusukeshib/pi-babysit 0.4.0 → 0.5.0
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 +12 -4
- package/index.ts +165 -23
- package/package.json +1 -1
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_settled` in the RPC event stream (worker remains reusable during its idle grace) |
|
|
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 |
|
|
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,7 +56,7 @@ 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`
|
|
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 |
|
|
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"` |
|
|
@@ -117,7 +117,12 @@ Set `PI_BABYSIT_RPC_LOG_MODE=standard` only when legacy lifecycle payloads are
|
|
|
117
117
|
needed for debugging. Live `/babysit` and attach views render either format.
|
|
118
118
|
|
|
119
119
|
All shell commands, including `pwd` and Git, go through `babysit_run`. Bundle
|
|
120
|
-
closely related tiny observations when doing so safely reduces tool turns.
|
|
120
|
+
closely related tiny observations when doing so safely reduces tool turns. For
|
|
121
|
+
parallel checks, do not issue sibling `foreground: true` calls: start them with
|
|
122
|
+
`continueAfterStart: true` and collect them in one multi-session `babysit_wait`.
|
|
123
|
+
During fix loops, prefer targeted checks after each edit and one full validation
|
|
124
|
+
suite at the end. Background subagents must likewise be collected before the
|
|
125
|
+
parent task finishes so their answer and nested usage are not lost.
|
|
121
126
|
Set `PI_BABYSIT_ALLOW_BASH=1` only as an explicit emergency escape hatch.
|
|
122
127
|
|
|
123
128
|
## Unexpected worker loss
|
|
@@ -143,7 +148,10 @@ because blindly rerunning an arbitrary command can duplicate side effects.
|
|
|
143
148
|
their exits span multiple polls.
|
|
144
149
|
`babysit_kill` and an exit already reported by `babysit_wait` suppress the
|
|
145
150
|
notification.
|
|
146
|
-
- **Subagent**: `babysit_wait` blocks on
|
|
151
|
+
- **Subagent**: `foreground: true` or `babysit_wait` blocks on
|
|
152
|
+
`babysit expect '"type":"agent_settled"'`. A background task that settles or
|
|
153
|
+
exits before it is collected emits one ready-to-collect reminder; the parent
|
|
154
|
+
must still call `babysit_wait` so the answer and nested usage are persisted.
|
|
147
155
|
Unlike `agent_end`, `agent_settled` cannot precede an automatic retry,
|
|
148
156
|
compaction retry, or queued continuation. A settled run containing a
|
|
149
157
|
**parked** toolResult — a `babysit_run { command }` result carrying the
|
package/index.ts
CHANGED
|
@@ -418,6 +418,13 @@ interface Meta {
|
|
|
418
418
|
budgetKilled?: boolean;
|
|
419
419
|
/** Prompt offset whose nested usage has already been charged to the parent session. */
|
|
420
420
|
usageReportedOffset?: number;
|
|
421
|
+
/** Prompt offset explicitly collected by foreground mode or babysit_wait. */
|
|
422
|
+
subagentCollectedOffset?: number;
|
|
423
|
+
/** Prompt offset whose ready-to-collect reminder was sent to the parent. */
|
|
424
|
+
subagentNotifiedOffset?: number;
|
|
425
|
+
/** Current task completion first observed by the reminder poller. */
|
|
426
|
+
subagentCompletionObservedOffset?: number;
|
|
427
|
+
subagentCompletionObservedAt?: number;
|
|
421
428
|
}
|
|
422
429
|
|
|
423
430
|
const metaDir = () => path.join(ROOT, "meta");
|
|
@@ -731,6 +738,16 @@ export function shouldDeliverProcessCompletion(
|
|
|
731
738
|
return meta?.kind === "process" && !meta.notified && !meta.notificationPaused;
|
|
732
739
|
}
|
|
733
740
|
|
|
741
|
+
export function shouldDeliverSubagentCompletion(meta: Meta | null): meta is Meta & { kind: "subagent" } {
|
|
742
|
+
if (meta?.kind !== "subagent") return false;
|
|
743
|
+
const offset = meta.promptOffset ?? 0;
|
|
744
|
+
return (
|
|
745
|
+
meta.usageReportedOffset !== offset &&
|
|
746
|
+
meta.subagentCollectedOffset !== offset &&
|
|
747
|
+
meta.subagentNotifiedOffset !== offset
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
|
|
734
751
|
export function isNotificationGroupReady(
|
|
735
752
|
meta: Meta,
|
|
736
753
|
sessions: BsSession[],
|
|
@@ -752,10 +769,14 @@ export function shouldKeepPolling(
|
|
|
752
769
|
sessions: Array<{ id: string; state: string }>,
|
|
753
770
|
metaFor: (id: string) => Meta | null,
|
|
754
771
|
): boolean {
|
|
755
|
-
return sessions.some(
|
|
756
|
-
(session)
|
|
757
|
-
|
|
758
|
-
|
|
772
|
+
return sessions.some((session) => {
|
|
773
|
+
const meta = metaFor(session.id);
|
|
774
|
+
return (
|
|
775
|
+
session.state === "running" ||
|
|
776
|
+
shouldDeliverProcessCompletion(meta) ||
|
|
777
|
+
(session.state !== "running" && shouldDeliverSubagentCompletion(meta))
|
|
778
|
+
);
|
|
779
|
+
});
|
|
759
780
|
}
|
|
760
781
|
|
|
761
782
|
export function shouldKeepPollingAfterList(
|
|
@@ -2457,16 +2478,27 @@ async function waitForTask(
|
|
|
2457
2478
|
}
|
|
2458
2479
|
}
|
|
2459
2480
|
|
|
2460
|
-
// Mark a
|
|
2461
|
-
//
|
|
2481
|
+
// Mark a session as already reported so completion pollers do not send a
|
|
2482
|
+
// duplicate message for something the agent just observed.
|
|
2462
2483
|
function suppressNotify(id: string, reason: "observed" | "kill" = "observed"): void {
|
|
2463
2484
|
const meta = readMeta(id);
|
|
2464
|
-
if (meta
|
|
2485
|
+
if (!meta) return;
|
|
2486
|
+
if (meta.kind === "process") {
|
|
2465
2487
|
meta.notified = true;
|
|
2466
2488
|
if (reason === "kill") meta.killNotificationSuppressed = true;
|
|
2467
2489
|
delete meta.notificationPaused;
|
|
2468
|
-
|
|
2490
|
+
} else {
|
|
2491
|
+
meta.subagentCollectedOffset = meta.promptOffset ?? 0;
|
|
2469
2492
|
}
|
|
2493
|
+
writeMeta(id, meta);
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
function collectSubagentOutcome(outcome: WaitOutcome): void {
|
|
2497
|
+
if (outcome.kind !== "done" && outcome.kind !== "exited") return;
|
|
2498
|
+
const meta = readMeta(outcome.id);
|
|
2499
|
+
if (!meta || meta.kind !== "subagent") return;
|
|
2500
|
+
meta.subagentCollectedOffset = meta.promptOffset ?? 0;
|
|
2501
|
+
writeMeta(outcome.id, meta);
|
|
2470
2502
|
}
|
|
2471
2503
|
|
|
2472
2504
|
export interface WaitReservationState {
|
|
@@ -2574,10 +2606,23 @@ async function waitForExit(
|
|
|
2574
2606
|
// one concurrent wait timing out cannot re-enable notifications underneath
|
|
2575
2607
|
// another wait that is still pending.
|
|
2576
2608
|
updateWaitReservation(id, "reserve");
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2609
|
+
let w: Awaited<ReturnType<typeof bs>>;
|
|
2610
|
+
let attempt = 0;
|
|
2611
|
+
for (;;) {
|
|
2612
|
+
w = await bs(["wait", "-s", id, "--timeout", t], { signal });
|
|
2613
|
+
if (signal?.aborted) {
|
|
2614
|
+
updateWaitReservation(id, "abandon");
|
|
2615
|
+
return { id, kind: "interrupted", ok: false, text: `wait for ${id} was interrupted.` };
|
|
2616
|
+
}
|
|
2617
|
+
if (w.code === 0 || w.code === 124 || w.code === 130) break;
|
|
2618
|
+
|
|
2619
|
+
// A freshly spawned session can be visible in `list` before the backend's
|
|
2620
|
+
// wait endpoint is ready, especially when sibling foreground tools start
|
|
2621
|
+
// concurrently. Retry that transient startup race instead of reporting the
|
|
2622
|
+
// still-running child as "exited with code ?".
|
|
2623
|
+
const retryStatus = await statusOf(id);
|
|
2624
|
+
if (retryStatus?.state !== "running" || attempt++ >= 3) break;
|
|
2625
|
+
await new Promise((resolve) => setTimeout(resolve, 50 * attempt));
|
|
2581
2626
|
}
|
|
2582
2627
|
if (w.code === 130) {
|
|
2583
2628
|
const interruptedStatus = await statusOf(id);
|
|
@@ -2619,6 +2664,18 @@ async function waitForExit(
|
|
|
2619
2664
|
if (!expectPattern) updateWaitReservation(id, "claim");
|
|
2620
2665
|
return { id, kind: "exited", ok: false, text: `No such session: ${id}` };
|
|
2621
2666
|
}
|
|
2667
|
+
if (st.state === "running") {
|
|
2668
|
+
if (!expectPattern) updateWaitReservation(id, "abandon");
|
|
2669
|
+
return {
|
|
2670
|
+
id,
|
|
2671
|
+
kind: "interrupted",
|
|
2672
|
+
ok: false,
|
|
2673
|
+
text:
|
|
2674
|
+
`The wait backend returned before process ${id} exited; the process is still running. ` +
|
|
2675
|
+
`Use babysit_wait to continue waiting.\nLog: ${logPath(id)}`,
|
|
2676
|
+
status: st,
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2622
2679
|
if (expectPattern) suppressNotify(id);
|
|
2623
2680
|
else updateWaitReservation(id, "claim"); // the agent sees the exit here; don't notify again
|
|
2624
2681
|
const meta = readMeta(id);
|
|
@@ -2964,6 +3021,64 @@ export default function (pi: ExtensionAPI) {
|
|
|
2964
3021
|
);
|
|
2965
3022
|
}
|
|
2966
3023
|
|
|
3024
|
+
async function notifySettledSubagents(
|
|
3025
|
+
ctx: ExtensionContext,
|
|
3026
|
+
snapshot?: BsSession[],
|
|
3027
|
+
): Promise<void> {
|
|
3028
|
+
if (shouldDeferCompletionNotification(ctx.isIdle())) return;
|
|
3029
|
+
const sessions = snapshot ?? (await listSessions()).sessions;
|
|
3030
|
+
const ready: Array<{ id: string; offset: number; state: string; summary: string }> = [];
|
|
3031
|
+
for (const session of sessions) {
|
|
3032
|
+
const meta = readMeta(session.id);
|
|
3033
|
+
if (!shouldDeliverSubagentCompletion(meta)) continue;
|
|
3034
|
+
let progress: Progress;
|
|
3035
|
+
try {
|
|
3036
|
+
progress = taskProgressOf(session.id).progress;
|
|
3037
|
+
} catch {
|
|
3038
|
+
progress = emptyProgress();
|
|
3039
|
+
}
|
|
3040
|
+
if (session.state === "running" && !progress.done) continue;
|
|
3041
|
+
|
|
3042
|
+
const offset = meta.promptOffset ?? 0;
|
|
3043
|
+
if (meta.subagentCompletionObservedOffset !== offset) {
|
|
3044
|
+
meta.subagentCompletionObservedOffset = offset;
|
|
3045
|
+
meta.subagentCompletionObservedAt = Date.now();
|
|
3046
|
+
writeMeta(session.id, meta);
|
|
3047
|
+
continue;
|
|
3048
|
+
}
|
|
3049
|
+
if (Date.now() - (meta.subagentCompletionObservedAt ?? 0) < POLL_MS) continue;
|
|
3050
|
+
const summary = progress.done
|
|
3051
|
+
? `task settled; ${progress.turns} turns, ${progress.toolCallCount} tools, $${progress.cost.toFixed(4)}`
|
|
3052
|
+
: `worker ${session.state} with exit code ${session.exit_code ?? "?"}; partial usage $${progress.cost.toFixed(4)}`;
|
|
3053
|
+
ready.push({ id: session.id, offset, state: session.state, summary });
|
|
3054
|
+
}
|
|
3055
|
+
if (ready.length === 0 || shouldDeferCompletionNotification(ctx.isIdle())) return;
|
|
3056
|
+
|
|
3057
|
+
const deliverable = ready.filter(({ id, offset }) => {
|
|
3058
|
+
const meta = readMeta(id);
|
|
3059
|
+
return shouldDeliverSubagentCompletion(meta) && (meta.promptOffset ?? 0) === offset;
|
|
3060
|
+
});
|
|
3061
|
+
if (deliverable.length === 0) return;
|
|
3062
|
+
pi.sendMessage(
|
|
3063
|
+
{
|
|
3064
|
+
customType: "pi-babysit-subagent-ready",
|
|
3065
|
+
content:
|
|
3066
|
+
`${deliverable.length === 1 ? "A background subagent is" : `${deliverable.length} background subagents are`} ready to collect:\n` +
|
|
3067
|
+
deliverable.map(({ id, summary }) => `- ${id}: ${summary}`).join("\n") +
|
|
3068
|
+
"\nCall babysit_wait now to retrieve the answer and charge nested usage before finishing the parent task.",
|
|
3069
|
+
display: true,
|
|
3070
|
+
details: { subagents: deliverable.map(({ id, state }) => ({ id, state })) },
|
|
3071
|
+
},
|
|
3072
|
+
{ triggerTurn: true, deliverAs: "steer" },
|
|
3073
|
+
);
|
|
3074
|
+
for (const { id, offset } of deliverable) {
|
|
3075
|
+
const meta = readMeta(id);
|
|
3076
|
+
if (!meta || meta.kind !== "subagent" || (meta.promptOffset ?? 0) !== offset) continue;
|
|
3077
|
+
meta.subagentNotifiedOffset = offset;
|
|
3078
|
+
writeMeta(id, meta);
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
|
|
2967
3082
|
const refreshWidget = async (ctx: ExtensionContext, snapshot?: BsSession[]) => {
|
|
2968
3083
|
if (!ctx.hasUI) return;
|
|
2969
3084
|
const active = (snapshot ?? (await listSessions()).sessions).filter(
|
|
@@ -3157,6 +3272,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3157
3272
|
await enforceSubagentBudgets(snapshot);
|
|
3158
3273
|
await Promise.all([
|
|
3159
3274
|
notifyEndedProcesses(ctx, snapshot),
|
|
3275
|
+
notifySettledSubagents(ctx, snapshot),
|
|
3160
3276
|
refreshWidget(ctx, snapshot),
|
|
3161
3277
|
]);
|
|
3162
3278
|
pruneTerminalSessionCache(taskProgressCache, snapshot);
|
|
@@ -3228,12 +3344,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
3228
3344
|
"`returnPattern`/`returnLines` bound foreground output. Sessions support check, wait, send, and kill.",
|
|
3229
3345
|
promptSnippet: "Run supervised commands or bounded pi subagents with context-safe logs",
|
|
3230
3346
|
promptGuidelines: [
|
|
3231
|
-
"Use babysit_run for shell commands and give meaningful sessions a stable name; bundle tiny related observations.",
|
|
3232
|
-
"Use babysit_run foreground mode
|
|
3347
|
+
"Use babysit_run for shell commands and give meaningful sessions a stable name; bundle tiny related read-only observations into one command.",
|
|
3348
|
+
"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.",
|
|
3349
|
+
"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.",
|
|
3233
3350
|
"After a background process starts, stop the turn for its automatic notification; never poll or sleep. Use continueAfterStart only for specific non-polling work.",
|
|
3234
3351
|
"Inspect large logs with a narrow babysit_check pattern and maxBytes rather than broad tails.",
|
|
3235
3352
|
"Use retryOnWorkerDeath only once and only for idempotent commands; retries may duplicate side effects.",
|
|
3236
|
-
"Delegate independent work with bounded babysit_run subagents
|
|
3353
|
+
"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.",
|
|
3237
3354
|
"Subagent recursion defaults to depth 1; only a top-level caller may explicitly raise maxDepth.",
|
|
3238
3355
|
],
|
|
3239
3356
|
parameters: Type.Object({
|
|
@@ -3321,7 +3438,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3321
3438
|
),
|
|
3322
3439
|
foreground: Type.Optional(
|
|
3323
3440
|
Type.Boolean({
|
|
3324
|
-
description: "Process: wait for
|
|
3441
|
+
description: "Process or subagent: wait for completion and return the result in this tool call.",
|
|
3325
3442
|
}),
|
|
3326
3443
|
),
|
|
3327
3444
|
returnPattern: Type.Optional(
|
|
@@ -3396,16 +3513,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
3396
3513
|
details: {},
|
|
3397
3514
|
};
|
|
3398
3515
|
}
|
|
3399
|
-
if (isSubagent && params.
|
|
3516
|
+
if (isSubagent && (params.returnPattern || params.returnLines != null || params.maxBytes != null)) {
|
|
3400
3517
|
return {
|
|
3401
|
-
content: [{ type: "text", text: "`
|
|
3518
|
+
content: [{ type: "text", text: "`returnPattern`, `returnLines`, and `maxBytes` are process-output options." }],
|
|
3402
3519
|
isError: true,
|
|
3403
3520
|
details: {},
|
|
3404
3521
|
};
|
|
3405
3522
|
}
|
|
3406
|
-
if (isSubagent &&
|
|
3523
|
+
if (isSubagent && params.continueAfterStart != null) {
|
|
3407
3524
|
return {
|
|
3408
|
-
content: [{ type: "text", text: "`
|
|
3525
|
+
content: [{ type: "text", text: "`continueAfterStart` is available only in process mode." }],
|
|
3409
3526
|
isError: true,
|
|
3410
3527
|
details: {},
|
|
3411
3528
|
};
|
|
@@ -3625,15 +3742,37 @@ export default function (pi: ExtensionAPI) {
|
|
|
3625
3742
|
|
|
3626
3743
|
pollNeeded = true;
|
|
3627
3744
|
await refreshWidget(ctx);
|
|
3745
|
+
if (params.foreground || !ctx.hasUI) {
|
|
3746
|
+
const outcome = await waitForTask(res.id, null, _signal);
|
|
3747
|
+
collectSubagentOutcome(outcome);
|
|
3748
|
+
const usage = claimOutcomeUsage(outcome);
|
|
3749
|
+
if (ctx.hasUI) await refreshWidget(ctx);
|
|
3750
|
+
return {
|
|
3751
|
+
content: [{ type: "text", text: outcome.text }],
|
|
3752
|
+
isError: !outcome.ok,
|
|
3753
|
+
usage,
|
|
3754
|
+
details: {
|
|
3755
|
+
id: res.id,
|
|
3756
|
+
kind: "subagent",
|
|
3757
|
+
name: params.name ?? res.id,
|
|
3758
|
+
agent: agent?.name,
|
|
3759
|
+
model: res.model,
|
|
3760
|
+
task: params.task,
|
|
3761
|
+
depth: subagentNesting.childDepth,
|
|
3762
|
+
maxDepth: subagentNesting.maxDepth,
|
|
3763
|
+
status: outcomeStatus(outcome),
|
|
3764
|
+
},
|
|
3765
|
+
};
|
|
3766
|
+
}
|
|
3628
3767
|
return {
|
|
3629
3768
|
content: [
|
|
3630
3769
|
{
|
|
3631
3770
|
type: "text",
|
|
3632
3771
|
text:
|
|
3633
3772
|
`Subagent started (id: ${res.id})${agent ? ` [agent: ${agent.name}]` : ""}${res.model ? ` [model: ${res.model}]` : ""} [depth: ${subagentNesting.childDepth}/${subagentNesting.maxDepth}].\n` +
|
|
3634
|
-
`Task accepted — running in the background;
|
|
3635
|
-
`
|
|
3636
|
-
`
|
|
3773
|
+
`Task 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` +
|
|
3774
|
+
`Progress: babysit_check { id: "${res.id}" } (only when inspection is needed)\n` +
|
|
3775
|
+
`Collect: babysit_wait { id: "${res.id}" }\n` +
|
|
3637
3776
|
`Human can watch/steer: /babysit (pick ${res.id})`,
|
|
3638
3777
|
},
|
|
3639
3778
|
],
|
|
@@ -4257,6 +4396,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4257
4396
|
|
|
4258
4397
|
if (ids.length === 1) {
|
|
4259
4398
|
const r = await waitFor(ids[0], limitMs, signal, params.expect);
|
|
4399
|
+
collectSubagentOutcome(r);
|
|
4260
4400
|
const usage = claimOutcomeUsage(r);
|
|
4261
4401
|
return {
|
|
4262
4402
|
content: [{ type: "text", text: r.text }],
|
|
@@ -4277,6 +4417,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4277
4417
|
ids.map((i) => waitFor(i, limitMs, signal, params.expect)),
|
|
4278
4418
|
);
|
|
4279
4419
|
const ok = results.every((r) => r.ok);
|
|
4420
|
+
results.forEach(collectSubagentOutcome);
|
|
4280
4421
|
const usage = sumNestedUsage(results.map(claimOutcomeUsage));
|
|
4281
4422
|
return {
|
|
4282
4423
|
content: [
|
|
@@ -4306,6 +4447,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4306
4447
|
ids.map((i) => waitFor(i, limitMs, ctrl.signal, params.expect)),
|
|
4307
4448
|
);
|
|
4308
4449
|
const others = ids.filter((i) => i !== first.id);
|
|
4450
|
+
collectSubagentOutcome(first);
|
|
4309
4451
|
const usage = claimOutcomeUsage(first);
|
|
4310
4452
|
return {
|
|
4311
4453
|
content: [
|