pi-subagents 0.31.0 → 0.31.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/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.31.1] - 2026-06-25
6
+
7
+ ### Added
8
+ - Added `/chain` inline parallel groups with per-step metadata, group options, and tab completion.
9
+ - Added subagent profile commands and provider model catalog generation for quota and quality model profiles.
10
+
11
+ ### Fixed
12
+ - Discover `pi-intercom` installations created by `--extension npm:pi-intercom` under Pi's temporary npm extension cache.
13
+ - Made async subagent interrupt, steer, and stop requests portable across platforms that do not support Unix signals.
14
+ - Hardened profile commands by probing models without tools, rejecting unsafe profile/provider path tokens, and resolving short model IDs and thinking suffixes against the current registry.
15
+ - Limited inline `/chain` acceptance values to levels expressible in slash syntax and kept completion disabled inside shared `--` tasks with literal parentheses.
16
+
5
17
  ## [0.31.0] - 2026-06-24
6
18
 
7
19
  ### Added
package/README.md CHANGED
@@ -329,13 +329,45 @@ Skip this section until you want exact syntax.
329
329
  |---------|-------------|
330
330
  | `/run <agent> [task]` | Run one agent; omit the task for self-contained agents |
331
331
  | `/chain agent1 "task1" -> agent2 "task2"` | Run agents in sequence |
332
+ | `/chain scout "scan" -> (reviewer "A" \| reviewer "B") -> writer "fix"` | Run a chain with a static parallel group inline |
332
333
  | `/parallel agent1 "task1" -> agent2 "task2"` | Run agents in parallel |
333
334
  | `/run-chain <chainName> -- <task>` | Launch a saved `.chain.md` or `.chain.json` workflow |
334
335
  | `/subagents-doctor` | Show read-only setup diagnostics |
335
336
  | `/subagents-models [agent]` | Show the runtime-loaded builtin model mapping, optionally filtered to one builtin |
337
+ | `/subagents-profiles` | List saved subagent profiles from `~/.pi/agent/profiles/pi-subagents/` |
338
+ | `/subagents-load-profile <name>` | Replace only `settings.subagents` with a saved profile and optionally switch this session to the profile worker model |
339
+ | `/subagents-refresh-provider-models <provider> [--force]` | Create or refresh the cached provider model catalog |
340
+ | `/subagents-generate-profiles <provider>` | Generate `<provider>.quota.json` and `<provider>.quality.json` profiles |
341
+ | `/subagents-check-profile <name>` | Check a saved profile against the current registry and live model probes |
336
342
 
337
343
  Commands validate agent names locally, support tab completion, and send results back into the conversation.
338
344
 
345
+ ### Profiles and provider model catalogs
346
+
347
+ Profiles are stored under:
348
+
349
+ ```text
350
+ ~/.pi/agent/profiles/pi-subagents/
351
+ ```
352
+
353
+ Provider model catalogs are cached under:
354
+
355
+ ```text
356
+ ~/.pi/agent/profiles/pi-subagents/providers/
357
+ ```
358
+
359
+ Use the profile workflow like this:
360
+
361
+ ```text
362
+ /subagents-refresh-provider-models openai-codex
363
+ /subagents-generate-profiles openai-codex
364
+ /subagents-load-profile openai-codex.quota
365
+ ```
366
+
367
+ `/subagents-refresh-provider-models` writes a serialized provider model catalog with observed registry data, simple role-oriented classification, and live probe results from tiny one-shot `pi -p --model ... --no-tools` checks. The cache refreshes when missing or stale; use `--force` to ignore freshness and probe again immediately.
368
+
369
+ `/subagents-generate-profiles` uses the provider catalog to produce quota and quality profiles. `/subagents-check-profile` re-checks each assigned model in a saved profile against the current registry and a live probe so you can detect model removals, auth problems, or stale assignments.
370
+
339
371
  ### Per-step tasks
340
372
 
341
373
  Use `->` to separate steps and give each step its own task:
@@ -353,6 +385,37 @@ Both double and single quotes work. You can also use `--` as a delimiter:
353
385
 
354
386
  Steps without a task inherit behavior from the execution mode. Chain steps get `{previous}`, the prior step’s output. Parallel steps use the first available task as a fallback.
355
387
 
388
+ ### Inline parallel groups in `/chain`
389
+
390
+ Wrap a group of agents in parentheses and separate them with `|` to fan them out within a single chain step. The group runs all of its tasks concurrently, then the next `->` step continues once they finish:
391
+
392
+ ```text
393
+ /chain scout "scan" -> (reviewer "review A" | reviewer "review B") -> writer "fix"
394
+ ```
395
+
396
+ Notes:
397
+
398
+ - Groups must contain at least two tasks separated by ` | `, each with its own task.
399
+ - Group syntax is only valid between ` -> ` separators, and the group must appear as a complete step.
400
+ - Only a step that *opens* with `(` is a group. Parentheses inside a shared `--` task (e.g. `/chain scout -- inspect auth (backend)`) stay literal text and keep the legacy single-agent behavior.
401
+ - A group is treated as the prior step’s output for the next sequential step.
402
+ - Tab completion suggests agents inside groups — after `(`, after `|`, and on each new `->` step.
403
+
404
+ Add a `[...]` suffix right after the closing `)` to set step-level options on the group:
405
+
406
+ ```text
407
+ /chain scout "scan" -> (reviewer "A" | reviewer "B")[concurrency=2,failFast,worktree] -> writer "fix"
408
+ ```
409
+
410
+ | Group option | Description |
411
+ |--------------|-------------|
412
+ | `concurrency=N` | Max tasks running at once within the group. |
413
+ | `failFast` | Stop the group as soon as one task fails. |
414
+ | `worktree` | Run each group task in its own git worktree. |
415
+
416
+ Dynamic fanout (`expand` / `collect`) is intentionally not available inline — use the
417
+ `subagent({ chain: [...] })` tool API or a saved `.chain.json` for data-driven fan-out.
418
+
356
419
  ```text
357
420
  /chain scout "analyze auth" -> planner -> worker
358
421
  # scout gets "analyze auth"; planner gets scout output; worker gets planner output
@@ -367,7 +430,7 @@ For a shared task, list agents and place one `--` before the task:
367
430
 
368
431
  ### Inline per-step config
369
432
 
370
- Append `[key=value,...]` to an agent name to override defaults for that step:
433
+ Append `[key=value,...]` to an agent name to override defaults. `/chain` applies every key below; `/run` and `/parallel` use the execution-behavior keys (`output`, `outputMode`, `reads`, `model`, `skills`, `progress`) and ignore chain-only metadata such as `as`, `label`, `phase`, `count`, `outputSchema`, and `acceptance`.
371
434
 
372
435
  ```text
373
436
  /chain scout[output=context.md] "scan code" -> planner[reads=context.md] "analyze auth"
@@ -383,9 +446,18 @@ Append `[key=value,...]` to an agent name to override defaults for that step:
383
446
  | `model` | `model=anthropic/claude-sonnet-4` | Override model for this step. |
384
447
  | `skills` | `skills=planning+review` | Override available skills. `+` separates multiple skills. |
385
448
  | `progress` | `progress` | Enable progress tracking. |
449
+ | `as` | `as=context` | Name this step’s output so later steps can reference it. |
450
+ | `label` | `label=Recon` | Human-readable label for the step. |
451
+ | `phase` | `phase=analysis` | Group steps into a named phase. |
452
+ | `cwd` | `cwd=packages/api` | Run the step in a subdirectory. |
453
+ | `count` | `count=3` | Fan a group task into N copies (only inside a `( ... )` group). |
454
+ | `outputSchema` | `outputSchema=schema.json` | Validate structured output against a JSON Schema file (path resolved against the session cwd, not an inline step `cwd`). |
455
+ | `acceptance` | `acceptance=checked` | Inline acceptance level: `auto`, `attested`, or `checked`. Use the tool API or saved `.chain.json` for object contracts such as `none`, `verified`, or `reviewed`. |
386
456
 
387
457
  Set `output=false`, `reads=false`, or `skills=false` to disable that behavior explicitly. Do not use `output=false` for file-only returns; use `outputMode=file-only` with an `output` path.
388
458
 
459
+ Inline `[...]` values must not contain spaces or commas — keep `label`/`phase` to single tokens.
460
+
389
461
  ### Background and forked runs
390
462
 
391
463
  Add `--bg` to run in the background:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-subagents",
3
- "version": "0.31.0",
3
+ "version": "0.31.1",
4
4
  "description": "Pi extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification",
5
5
  "author": "Nico Bailon",
6
6
  "license": "MIT",
@@ -43,6 +43,7 @@ import {
43
43
  DEFAULT_ARTIFACT_CONFIG,
44
44
  RESULTS_DIR,
45
45
  SLASH_RESULT_TYPE,
46
+ SLASH_TEXT_RESULT_TYPE,
46
47
  SUBAGENT_ASYNC_COMPLETE_EVENT,
47
48
  SUBAGENT_ASYNC_STARTED_EVENT,
48
49
  SUBAGENT_CONTROL_EVENT,
@@ -312,6 +313,16 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
312
313
  return createSlashResultComponent(details, options, theme);
313
314
  });
314
315
 
316
+ pi.registerMessageRenderer<undefined>(SLASH_TEXT_RESULT_TYPE, (message, _options, _theme) => {
317
+ const content = typeof message.content === "string"
318
+ ? message.content
319
+ : message.content
320
+ .filter((entry) => entry.type === "text")
321
+ .map((entry) => entry.text)
322
+ .join("\n");
323
+ return new Text(content, 0, 0);
324
+ });
325
+
315
326
  pi.registerMessageRenderer<SubagentNotifyDetails>("subagent-notify", (message, options, theme) => {
316
327
  const content = typeof message.content === "string" ? message.content : "";
317
328
  const details = (message.details as SubagentNotifyDetails | undefined) ?? parseSubagentNotifyContent(content);
@@ -199,6 +199,23 @@ function getGlobalNpmRoot(): string | null {
199
199
  }
200
200
  }
201
201
 
202
+ function tmpNpmIntercomPackageDir(agentDir: string): string | undefined {
203
+ const tmpNpmDir = path.join(agentDir, "tmp", "extensions", "npm");
204
+ try {
205
+ const entries = fs.readdirSync(tmpNpmDir, { withFileTypes: true });
206
+ for (const entry of entries) {
207
+ if (!entry.isDirectory()) continue;
208
+ const pkgDir = path.join(tmpNpmDir, entry.name, "node_modules", PI_INTERCOM_PACKAGE_NAME);
209
+ if (fs.existsSync(pkgDir) && packageHasPiExtension(pkgDir)) {
210
+ return path.resolve(pkgDir);
211
+ }
212
+ }
213
+ } catch {
214
+ // ignore ENOTDIR, ENOENT, permission errors
215
+ }
216
+ return undefined;
217
+ }
218
+
202
219
  function configuredPiIntercomPackageDir(input: ResolveIntercomBridgeInput, agentDir: string): string | undefined {
203
220
  const projectConfigDir = input.cwd ? findNearestProjectConfigDir(path.resolve(input.cwd)) : undefined;
204
221
  const settingsFiles = [
@@ -235,7 +252,14 @@ function configuredPiIntercomPackageDir(input: ResolveIntercomBridgeInput, agent
235
252
  function resolveIntercomExtensionDir(input: ResolveIntercomBridgeInput, agentDir: string): string {
236
253
  const legacyDir = path.resolve(input.extensionDir ?? envIntercomExtensionDir() ?? defaultIntercomExtensionDir(agentDir));
237
254
  if (fs.existsSync(legacyDir)) return legacyDir;
238
- return configuredPiIntercomPackageDir(input, agentDir) ?? legacyDir;
255
+
256
+ const configured = configuredPiIntercomPackageDir(input, agentDir);
257
+ if (configured) return configured;
258
+
259
+ const tmpDir = tmpNpmIntercomPackageDir(agentDir);
260
+ if (tmpDir) return tmpDir;
261
+
262
+ return legacyDir;
239
263
  }
240
264
 
241
265
  function extensionSandboxAllowsIntercom(extensions: string[] | undefined, extensionDir: string): boolean {