@d3ara1n/pi-subagent 1.4.0 → 1.5.1

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Role-based subagent orchestration for [pi](https://github.com/earendil-works/pi).
4
4
 
5
- Provides a `subagent_delegate` tool that lets the main model offload tasks to specialized pi child processes with configurable model roles, real-time TUI progress, and AI-generated summaries. Runs can be foreground (blocking) or background (asynchronous, collected later via `subagent_wait`/`subagent_check`, cancellable via `subagent_cancel`).
5
+ Provides a `subagent_delegate` tool that lets the main model offload tasks to specialized pi child processes with configurable model roles, real-time TUI progress, and AI-generated summaries. Runs can be foreground (blocking) or background (asynchronous, collected later via `subagent_wait`/`subagent_check`, cancellable via `subagent_cancel`). A centered live view (`/subagent:view`) shows every run's activity feed as it happens, and mid-run corrections can be queued into a running subagent from the view's input box or via `subagent_steer`.
6
6
 
7
7
  ## Design Philosophy
8
8
 
@@ -54,16 +54,18 @@ This means:
54
54
 
55
55
  | Command | Description |
56
56
  |---------|-------------|
57
- | `/subagent:view` | Open the live activity view: a continuous feed of every run's thinking, tool calls, and streamed output with an input box to steer a running subagent mid-flight |
57
+ | `/subagent:view` | Open the live view: a tabbed overlay showing one run's full activity feed at a time, with an input box to steer the focused run |
58
58
  | `/subagent:doctor` | Diagnose pi invocation, model-role resolution, configuration, and role references |
59
59
  | `/subagent:status` | List background runs (active + collected) and their current state |
60
60
  | `/subagent:cancel <id\|all> [reason]` | Cancel a live background run (or every live run); the optional reason is recorded with the run |
61
61
 
62
62
  ### Live view (`/subagent:view`)
63
63
 
64
- A continuous, append-only list: each entry is static text with a state icon; running entries carry an animated ellipsis (`.` `..` `...`) and freeze in place when they finish position never changes. Multiple runs stack (one header line per run); streamed assistant text grows in place as the run's last line and freezes at the turn boundary.
64
+ A centered overlay covering most of the screen. A tab row across the top lists every run (icon · id · role); `Tab` cycles the focused run, and the rest of the viewport belongs to it alone its complete activity feed, tail-capped to the visible area with a `⋮ earlier activity` marker when older entries roll off.
65
65
 
66
- The bottom of the panel has a steer input box: type a correction and press Enter to queue it into the focused running subagent (Tab cycles targets when several are running). The message is delivered after the child finishes its current tool batch, before its next LLM call the run keeps its progress. Esc closes the panel.
66
+ The feed itself is a continuous, append-only list: each entry is static text with a state icon, and the only animated thing is the ellipsis on a running entry (`.` `..` `...`). Finishing freezes an entry in place its position never changes, only the icon flips. Streamed assistant text grows in place as the run's last line and settles into plain terminal-colored text at the turn boundary. Both foreground and background runs appear here; a foreground run stays listed while its delegate call blocks the main agent.
67
+
68
+ Below the feed sits a steer input box: type a correction and press Enter to queue it into the focused run (only while it is running). The message appears immediately in the feed as an `↩ steer:` entry and is delivered to the child after its current tool batch, before its next LLM call — the run keeps its progress. `Esc` closes the overlay.
67
69
 
68
70
  ## Dependencies
69
71
 
@@ -189,7 +191,7 @@ Three execution properties, kept separate:
189
191
 
190
192
  - **Foreground** (default): the call blocks until the run finishes and returns the final output directly. (Under the hood foreground and background share one async run engine — foreground is simply background-but-blocking.)
191
193
  - **Parallel**: multiple `subagent_delegate` calls in one turn run concurrently — foreground and background alike, no special flag.
192
- - **Background** (`background: true`): non-blocking — `subagent_delegate` returns immediately with a run id. Use it when you have your own work to do (or a discussion with the user to continue) while the run executes; three companion tools manage the outcome:
194
+ - **Background** (`background: true`): non-blocking — `subagent_delegate` returns immediately with a run id. Use it when you have your own work to do (or a discussion with the user to continue) while the run executes; four companion tools manage the outcome:
193
195
 
194
196
  | Tool | Purpose | Returns to the model |
195
197
  |------|---------|---------------------|
@@ -228,6 +230,17 @@ Semantics worth knowing:
228
230
  - **Top-level only:** nested subagents cannot delegate in the background (a subagent process exits when its task finishes, which would orphan the run).
229
231
  - The run registry lives in the pi process: a `/reload` or restart orphans in-flight background runs (their ids stop resolving). `/subagent:status` lists every registered run (active + collected) and its current state.
230
232
 
233
+ ### Steering a running subagent
234
+
235
+ Steering queues a correction into a running subagent without killing it — the middle ground between waiting it out and cancelling. The message is delivered after the child finishes its current tool batch, before its next LLM call, so the run keeps its progress and can change course. It is a suggestion injected between turns, not an interrupt: the child may comply immediately, finish what it was doing first, or ignore poor instructions entirely — to actually stop a run, cancel it.
236
+
237
+ There are two channels into the same mechanism:
238
+
239
+ - **The model** calls `subagent_steer(id, message)` — typically right after a `subagent_check` snapshot revealed the run heading down a wrong path (check → steer → check again later).
240
+ - **The user** types into the input box of `/subagent:view`, targeting the focused run. Every accepted steer also appears in the run's activity feed as an `↩ steer:` entry, so whoever watches the view sees what was injected and when.
241
+
242
+ Queued steers are visible in neither wait nor check results — they shape the run's subsequent behavior, not its transcript.
243
+
231
244
  ### Background TUI display
232
245
 
233
246
  Each tool row renders one aspect of the same decomposition the foreground row shows all at once (input · process · result · usage):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d3ara1n/pi-subagent",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "type": "module",
5
5
  "description": "Role-based subagent orchestration for pi — delegates tasks to specialized pi child processes with configurable model roles",
6
6
  "main": "src/index.ts",
package/src/utils.test.ts CHANGED
@@ -250,21 +250,18 @@ describe("previewArgs", () => {
250
250
  test("command -> $ prefix", () => {
251
251
  assert.equal(previewArgs({ command: "ls -la" }), "$ ls -la");
252
252
  });
253
- test("command truncated at 60 chars", () => {
253
+ test("command is preserved for viewport-aware truncation", () => {
254
254
  const long = "x".repeat(70);
255
- const r = previewArgs({ command: long });
256
- assert.ok(r.startsWith("$ "));
257
- assert.ok(r.endsWith("..."));
258
- assert.ok(r.length < long.length);
255
+ assert.equal(previewArgs({ command: long }), `$ ${long}`);
259
256
  });
260
257
  test("file_path is shortened (home -> ~)", () => {
261
258
  const r = previewArgs({ file_path: "/home/user/foo.ts" });
262
259
  assert.ok(r.includes("foo.ts"));
263
260
  });
264
- test("url passthrough (truncated when long)", () => {
261
+ test("url is preserved for viewport-aware truncation", () => {
265
262
  assert.equal(previewArgs({ url: "https://example.com" }), "https://example.com");
266
263
  const longUrl = "https://" + "x".repeat(70);
267
- assert.ok(previewArgs({ url: longUrl }).endsWith("..."));
264
+ assert.equal(previewArgs({ url: longUrl }), longUrl);
268
265
  });
269
266
  test("query/pattern/regex/search -> /.../ form", () => {
270
267
  assert.equal(previewArgs({ query: "foo" }), "/foo/");
package/src/utils.ts CHANGED
@@ -149,8 +149,7 @@ export function formatToolCall(
149
149
  }
150
150
  case "bash": {
151
151
  const command = (args.command as string) || "...";
152
- const preview = command.length > 60 ? `${command.slice(0, 60)}...` : command;
153
- return fg("muted", "$ ") + fg("toolOutput", preview);
152
+ return fg("muted", "$ ") + fg("toolOutput", command);
154
153
  }
155
154
  case "read": {
156
155
  const rawPath = (args.file_path || args.path || "...") as string;
@@ -271,10 +270,8 @@ export function taskPreview(task: string): string {
271
270
  * Width-aware collapsed-view component: renders each line truncated with "…"
272
271
  * to the actual viewport width (never wraps), padded full-width like Text(0,0).
273
272
  *
274
- * The char caps in taskPreview/formatToolCall/etc. stay as they are — they are
275
- * content limits shared with the LLM-facing text (check output, error
276
- * messages), which has no viewport semantics. This component is the TUI-side
277
- * final guard, applied where the folding affordance exists.
273
+ * Content formatters leave tool-call arguments intact; this component is the
274
+ * TUI-side final guard, applied where the folding affordance exists.
278
275
  */
279
276
  export function collapsedText(text: string): Component {
280
277
  const lines = text.split("\n");
@@ -586,15 +583,14 @@ export function freezeFrame(r: SubagentResult): SubagentResult {
586
583
  */
587
584
  export function previewArgs(args: Record<string, unknown>): string {
588
585
  const command = args.command as string | undefined;
589
- if (command) return `$ ${command.length > 60 ? command.slice(0, 60) + "..." : command}`;
586
+ if (command) return `$ ${command}`;
590
587
  const fp = (args.file_path || args.path) as string | undefined;
591
588
  if (fp) return shortenPath(fp);
592
589
  const url = args.url as string | undefined;
593
- if (url) return url.length > 60 ? url.slice(0, 60) + "..." : url;
590
+ if (url) return url;
594
591
  const query = (args.query || args.pattern || args.regex || args.search) as string | undefined;
595
- if (query) return `/${query.length > 60 ? query.slice(0, 60) + "..." : query}/`;
596
- const argsStr = JSON.stringify(args);
597
- return argsStr.length > 50 ? argsStr.slice(0, 50) + "..." : argsStr;
592
+ if (query) return `/${query}/`;
593
+ return JSON.stringify(args);
598
594
  }
599
595
 
600
596
  // ── Numeric configuration ─────────────────────────────────────