@ferris1225/pi-subagents 4.1.20 → 4.1.21

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
@@ -184,13 +184,12 @@ that delivers without another gate.
184
184
  where they are in `~/.pi/agent/pi-subagents-recovery.json`. Every later session
185
185
  start repeats that notice until you remove the artifacts.
186
186
 
187
- ## Threads: wait, resume, stop
187
+ ## Threads: resume, stop
188
188
 
189
189
  Every dispatch returns a stable `#id`, which is the handle for the thread tools:
190
190
 
191
191
  | Tool | What it does |
192
192
  | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
193
- | `subagent_wait` | Block in-turn until already-dispatched run(s) settle and return their results (`id` or prefix; omit for all active runs). |
194
193
  | `subagent_control` | `resume` a parked or settled thread with its full retained context, optionally appending a new `objective`. |
195
194
  | `subagent_stop` | Destructively cancel, deliver the partial output, and retire the thread. Steering and follow-up messages still queued in the child are dropped so nothing can revive it later. |
196
195
 
@@ -198,14 +197,14 @@ Every dispatch returns a stable `#id`, which is the handle for the thread tools:
198
197
  subagent_control({ action: "resume", id: 7, objective: "Finish the tests." });
199
198
  ```
200
199
 
201
- There is deliberately no status or polling tool. Every result delivers itself,
202
- the TUI widget shows what is live, and blocking is event-driven only: `wait:
203
- true` on a dispatch holds the turn for that call's results — the escape hatch
204
- for one-shot `pi -p` parents, which exit at end of turn and would otherwise
205
- never see them and `subagent_wait` does the same for a run that is already
206
- in flight when the turn cannot proceed without it. Neither wait runs on a
207
- timer or a timeout the model picks: a waiter resolves the instant its run
208
- settles, a parked run answers immediately with its resume handle, and
200
+ There is deliberately no status, polling, or wait tool. Every result delivers
201
+ itself as a completion that wakes the main model, so a turn never blocks on a
202
+ running subagent keep working or end the turn, and the completion continues
203
+ it. The one in-turn block is `wait: true` on a dispatch, which holds that call
204
+ until the runs it started settle: the escape hatch for one-shot `pi -p`
205
+ parents, which exit at end of turn and would otherwise never see them. That
206
+ wait runs on no timer and no timeout the model picks: it resolves the instant
207
+ its run settles, a parked run answers immediately with its resume handle, and
209
208
  aborting the turn is the escape hatch. Control operations are all bounded, so
210
209
  they never hang on a generation that is still settling.
211
210
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ferris1225/pi-subagents",
3
- "version": "4.1.20",
3
+ "version": "4.1.21",
4
4
  "description": "A managed sub-agent team for pi: specialized roles, pre-commit documentation sync, retained threads, auto-fix chains, model fallback, and Git worktree isolation.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/dispatch.ts CHANGED
@@ -151,12 +151,14 @@ function withReviewerFixStageAgent(agent: AgentConfig): AgentConfig {
151
151
  };
152
152
  }
153
153
 
154
- /** Event-driven in-turn wait shared by dispatch `wait: true` and
155
- * subagent_wait: hold the turn until every listed run settles, then hand back
156
- * their result blocks. No timer: a waiter resolves the moment its run's result
157
- * registers (children are bounded by the idle watchdog), an already-parked run
158
- * answers immediately with its resume handle, and the turn's abort signal
159
- * remains the escape hatch. */
154
+ /** In-turn wait behind dispatch `wait: true` — the escape hatch for one-shot
155
+ * `pi -p` parents that exit at end of turn: hold the call until every run it
156
+ * started settles, then hand back their result blocks. Interactive sessions
157
+ * never take this path; their results arrive as completion wake-ups. No
158
+ * timer: a waiter resolves the moment its run's result registers (children
159
+ * are bounded by the idle watchdog), an already-parked run answers
160
+ * immediately with its resume handle, and the turn's abort signal remains the
161
+ * escape hatch. */
160
162
  export async function awaitRunResults(
161
163
  runtime: SubagentRuntime,
162
164
  runIds: number[],
package/src/tools.ts CHANGED
@@ -1,10 +1,9 @@
1
1
  /**
2
- * Thread controls around the subagent runtime: subagent_control (resume),
3
- * subagent_wait (event-driven in-turn wait), and destructive subagent_stop.
4
- * There is no status/poll tool completions carry each result (with an
5
- * on-disk artifact when truncated); `wait: true` on dispatch blocks for runs
6
- * it starts, and subagent_wait blocks for runs already dispatched. Both waits
7
- * resolve the moment a run settles, never on a timer.
2
+ * Thread controls around the subagent runtime: subagent_control (resume) and
3
+ * destructive subagent_stop. There is no status/poll tool — completions carry
4
+ * each result (with an on-disk artifact when truncated) and wake the main
5
+ * model, so waiting is never a tool call; the only in-turn block is `wait:
6
+ * true` on a dispatch, for one-shot parents that exit at end of turn.
8
7
  */
9
8
 
10
9
  import { StringEnum } from "@earendil-works/pi-ai";
@@ -13,11 +12,10 @@ import { Text } from "@earendil-works/pi-tui";
13
12
  import { existsSync } from "node:fs";
14
13
  import { Type } from "typebox";
15
14
  import { DEFAULT_MAX_RESULT_LINES, loadConfig } from "./config.ts";
16
- import { awaitRunResults } from "./dispatch.ts";
17
15
  import { removeThreadRecord } from "./durable.ts";
18
16
  import { formatCompletionBlock, matchRunIds } from "./format.ts";
19
17
  import { emptyUsage } from "./rpc-run.ts";
20
- import { formatTaskSummary, isRunActiveStatus, monitor } from "./monitor.ts";
18
+ import { formatTaskSummary, monitor } from "./monitor.ts";
21
19
  import { persistRecoveryRecords, recoveryRecordFromFinalization } from "./recovery.ts";
22
20
  import type { SubagentRuntime, SubagentThread } from "./runtime.ts";
23
21
  import { CONTROL_QUIESCE_TIMEOUT_MS, projectResultsRoot, quiesced } from "./thread-lifecycle.ts";
@@ -112,56 +110,6 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
112
110
  },
113
111
  });
114
112
 
115
- const SubagentWaitParams = Type.Object({
116
- id: Type.Optional(
117
- Type.String({ description: "Run id or prefix to wait for (see dispatch output). Omit to wait for every active run." }),
118
- ),
119
- });
120
-
121
- pi.registerTool({
122
- name: "subagent_wait",
123
- label: "Subagent Wait",
124
- description:
125
- "Block until dispatched background run(s) settle, then return their results — resolves the moment a run settles, never on a timer.",
126
- promptSnippet: "Block until dispatched runs settle; returns results the moment they finish.",
127
- parameters: SubagentWaitParams,
128
-
129
- async execute(_toolCallId, params, signal, _onUpdate, ctx) {
130
- await runtime.durableRestore;
131
- const config = await loadConfig(runtime.configPath);
132
- const active = monitor.getRuns()
133
- .filter((run) => isRunActiveStatus(run.status) || run.status === "parked")
134
- .map((run) => run.id);
135
- const requested = params.id?.trim();
136
- // Settled runs stay addressable: a wait issued just after completion
137
- // returns that result immediately instead of missing the run.
138
- const targets = requested
139
- ? matchRunIds([...new Set([...active, ...runtime.settledRuns.keys()])], requested)
140
- : active;
141
- if (targets.length === 0) {
142
- const activeList = active.map((id) => `#${id}`).join(", ");
143
- return {
144
- content: [{
145
- type: "text",
146
- text: requested
147
- ? `No subagent run matches "${requested}".${activeList ? ` Active runs: ${activeList}.` : ""}`
148
- : "No active subagent runs to wait for.",
149
- }],
150
- details: {},
151
- };
152
- }
153
- const blocks = await awaitRunResults(runtime, targets, signal, config.maxResultLines, ctx.cwd);
154
- return { content: [{ type: "text", text: blocks }], details: {} };
155
- },
156
-
157
- renderCall(args, theme) {
158
- return new Text(`${theme.fg("toolTitle", theme.bold("subagent_wait "))}${theme.fg("accent", args.id ? `#${args.id}` : "all")}`, 0, 0);
159
- },
160
- renderResult(result, _options, theme) {
161
- return renderFirstLine(result, "subagent_wait ", theme);
162
- },
163
- });
164
-
165
113
  // Cancel one or more active runs: aborts the queue controller, which
166
114
  // terminates the child and delivers an aborted result (with whatever partial
167
115
  // output it produced) so the main agent always knows the run stopped.