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 +12 -0
- package/README.md +73 -1
- package/package.json +1 -1
- package/src/extension/index.ts +11 -0
- package/src/intercom/intercom-bridge.ts +25 -1
- package/src/profiles/profiles.ts +637 -0
- package/src/runs/background/async-resume.ts +11 -13
- package/src/runs/background/control-channel.ts +177 -0
- package/src/runs/background/subagent-runner.ts +7 -0
- package/src/runs/foreground/subagent-executor.ts +31 -9
- package/src/runs/shared/subagent-prompt-runtime.ts +1 -0
- package/src/shared/types.ts +1 -0
- package/src/slash/slash-commands.ts +609 -40
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
|
|
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
package/src/extension/index.ts
CHANGED
|
@@ -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
|
-
|
|
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 {
|