@herbertgao/pi-subagents 0.16.1 → 0.17.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
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#132](https://github.com/HerbertGao/pi-extensions/pull/132) [`e873d36`](https://github.com/HerbertGao/pi-extensions/commit/e873d36513deb902028faa0bf095641a57cb2ae4) Thanks [@HerbertGao](https://github.com/HerbertGao)! - Review pi-subagents 0.19.0 and adopt readable bounded-result counts while retaining the unsigned worktree-preservation safeguard that upstream still lacks.
8
+
9
+ ## 0.17.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#123](https://github.com/HerbertGao/pi-extensions/pull/123) [`acb79c0`](https://github.com/HerbertGao/pi-extensions/commit/acb79c0f7b0a8a65ec2612cb6ce32c8cca496f96) Thanks [@HerbertGao](https://github.com/HerbertGao)! - Sync reviewed pi-subagents 0.18.2 features for effective model display, bounded Markdown viewing, foreground concurrency, BOM-safe agents, and RPC model-scope enforcement.
14
+
3
15
  ## 0.16.1
4
16
 
5
17
  ### Patch Changes
@@ -52,9 +64,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
52
64
  ### Added
53
65
 
54
66
  - **`strictAgentFiles` — fail startup on a broken agent file instead of skipping it.** Off by default and applied only during initial extension activation; later per-call reloads remain tolerant.
67
+ - **Selective upstream 0.18.2 behavior sync.** Agent surfaces now show the effective model/thinking level and disclose overridden requests; `showModel` adds them to widget rows. The conversation viewer adds persistent raw/assistant/all Markdown modes, 16K result truncation, and literal fallback. `maxConcurrentForeground` independently bounds blocking top-level spawns while nested, RPC/detached, and resume paths remain exempt.
55
68
 
56
69
  ### Fixed
57
70
 
71
+ - **Cross-extension model overrides honor `scopeModels`.** String overrides first use this fork's tolerant resolver; resolved strings and `Model` objects are then scope-checked, while `null` still inherits.
72
+ - **UTF-8 BOM-prefixed agent files load and toggle correctly** without weakening the fork's warning-based malformed-file recovery or opt-in strict startup behavior.
73
+ - **Foreground spawn callbacks are per-call**, removing the shared `AgentManager.onSpawned` race for parallel or queued calls.
58
74
  - **Ctrl+C closes the conversation viewer** when it is not composing a steering message.
59
75
  - **Cross-extension callers can consume settled RPC-spawned results** with `subagents:rpc:consume`, suppressing a duplicate completion notification after another extension has already shown the result.
60
76
  - **One malformed agent file no longer aborts extension activation** ([#212](https://github.com/tintinweb/pi-subagents/issues/212) — thanks [@daromaj](https://github.com/daromaj)). Unreadable and unparseable files are skipped with a path-specific warning, including the earlier source that remains active when a broken file was an override.
package/README.md CHANGED
@@ -13,11 +13,11 @@ Spawn specialized agents that run in isolated sessions — each with its own too
13
13
  ## Features
14
14
 
15
15
  - **Claude Code look & feel** — same tool names, calling conventions, and UI patterns (`Agent`, `get_subagent_result`, `steer_subagent`) — feels native
16
- - **Parallel background agents** — top-level agents run detached by default, with automatic queuing (configurable concurrency limit, default 10) and smart group join; nested delegation still defaults to foreground
17
- - **Live widget UI** — persistent above-editor widget with animated spinners, live tool activity, token counts, and colored status icons. Configurable via `/agents → Settings → Widget`: `all` (every agent), `background` (default — hides foreground runs, which already render inline as the `Agent` tool result), or `off`
16
+ - **Independent concurrency pools** — detached agents use `maxConcurrent` (default 10); blocking agents can be bounded separately with `maxConcurrentForeground` (default 0 = unlimited). Nested children occupy neither pool, avoiding parent/child deadlocks
17
+ - **Live widget UI** — persistent above-editor widget with animated spinners, live tool activity, token counts, and colored status icons. Configurable via `/agents → Settings → Widget`: `all` (every agent), `background` (default — hides foreground runs, which already render inline as the `Agent` tool result), or `off`; opt-in `showModel` adds the effective model and thinking level
18
18
  - **FleetView** — Claude Code-style navigable list of `main` + every running subagent rendered below the editor (earliest-launched first). Press `↓` (or `←`) at an empty prompt to jump in, `↑`/`↓` to move the selection, `Enter` to open the selected agent's live, auto-updating conversation, `Esc` to return. Finished agents linger briefly before dropping out, and a viewer stays open through completion so you can read the final output. Toggle via `/agents → Settings → Fleet view`
19
- - **Conversation viewer** — select any agent in `/agents` to open a live-scrolling overlay of its full conversation (auto-follows new content, scroll up to pause). Steer a running agent inline by pressing `Enter` to open a composer, typing, then `Enter` to send (`Esc` or an empty submit returns) the message appears as a user message and redirects the agent after its current tool. Stop a still-running agent by pressing `x` (then `x` again to confirm) — both work for background agents too
20
- - **Custom agent types** — define agents in `.pi/agents/<name>.md` or `.agents/agents/<name>.md` (project) or globally, with YAML frontmatter: custom system prompts, model selection, thinking levels, tool restrictions, and Claude Code-compatible colored name badges
19
+ - **Conversation viewer** — select any agent in `/agents` to open a live-scrolling overlay of its full conversation. Assistant Markdown renders by default; press `m` to cycle raw / assistant-only / assistant+tool-results. Individual tool and bash results are capped at 16K characters with literal fallback if Markdown rendering fails. `Enter` steers and `x`, `x` stops a running agent
20
+ - **Custom agent types** — define agents in `.pi/agents/<name>.md` or `.agents/agents/<name>.md` (project) or globally, with YAML frontmatter: custom system prompts, model selection, thinking levels, tool restrictions, and Claude Code-compatible colored name badges. UTF-8 BOM-prefixed files are parsed safely
21
21
  - **Nested subagents** — opt-in, default-off delegation: a custom agent that sets `allowed_subagents` gets its own ownership-scoped `Agent`, `get_subagent_result`, and `steer_subagent` tools, depth-capped from the main session (default 2). It can control only its own children, they are stopped when it finishes, and their transcripts and token spend roll up to it. The allowlist is a privilege boundary — a child runs with its own tools, so pick it as carefully as `tools:` itself
22
22
  - **Mid-run steering** — inject messages into running agents to redirect their work without restarting
23
23
  - **Session resume** — pick up where an agent left off, preserving full conversation context
@@ -32,7 +32,7 @@ Spawn specialized agents that run in isolated sessions — each with its own too
32
32
  - **Tool denylist** — block specific tools via `disallowed_tools` frontmatter
33
33
  - **Styled completion notifications** — background agent results render as themed, compact notification boxes (icon, stats, result preview) instead of raw XML. Expandable to show full output. Group completions render each agent individually
34
34
  - **Event bus** — lifecycle events (`subagents:created`, `started`, `completed`, `failed`, `steered`, `compacted`) emitted via `pi.events`, enabling other extensions to react to sub-agent activity
35
- - **Cross-extension RPC** — other pi extensions can spawn, stop, and consume subagent results via the `pi.events` event bus (`subagents:rpc:ping`, `subagents:rpc:spawn`, `subagents:rpc:stop`, `subagents:rpc:consume`). Standardized reply envelopes with protocol versioning. Emits `subagents:ready` on session start
35
+ - **Cross-extension RPC** — other pi extensions can spawn, stop, and consume subagent results via the `pi.events` event bus (`subagents:rpc:ping`, `subagents:rpc:spawn`, `subagents:rpc:stop`, `subagents:rpc:consume`). String model overrides use the same tolerant resolver as the Agent tool, and both strings and `Model` objects honor `scopeModels`. Emits `subagents:ready` on session start
36
36
  - **Schedule subagents** — pass `schedule` to the `Agent` tool to fire on cron / interval / one-shot. Session-scoped jobs with PID-locked persistence; results land via the same `subagent-notification` followUp path as manual background completions; manage via `/agents → Scheduled jobs`
37
37
  - **Model scope enforcement** — opt-in validation that subagent model choices stay within your pi `enabledModels` allowlist (sourced from `/scoped-models`, with both global and project-local pi settings honored). Caller-supplied out-of-scope → hard error to orchestrator; frontmatter-pinned out-of-scope → warning + runs anyway (frontmatter authoritative). Toggle via `/agents → Settings → Scope models`
38
38
 
@@ -98,7 +98,7 @@ Restrictions:
98
98
 
99
99
  ## UI
100
100
 
101
- The extension renders a persistent widget above the editor showing active agents. By default it shows background runs only (`widgetMode: background`) — foreground agents already render inline as the `Agent` tool result, so the widget would otherwise double-render them. Switch to `all` (every agent) or `off` (hide the widget) via `/agents → Settings → Widget`:
101
+ The extension renders a persistent widget above the editor showing active agents. By default it shows background runs only (`widgetMode: background`) — foreground agents already render inline as the `Agent` tool result, so the widget would otherwise double-render them. Switch to `all` (every agent) or `off` (hide the widget) via `/agents → Settings → Widget`. Tool results and the conversation viewer always show the effective model and thinking level; enable `/agents → Settings → Show model` to add them to running widget rows:
102
102
 
103
103
  ```
104
104
  ● Agents
@@ -264,7 +264,7 @@ allowed_subagents: support-file-finder, support-callsite-tracer # or `all`
264
264
 
265
265
  The hard cap is depth 2 by default: main session (0) → subagent (1) → nested child (2). Change it project-wide with `maxSubagentDepth` in `subagents.json` (or `/agents → Settings → Nested depth`); `0` or `1` turns nesting off everywhere. An agent already at the cap gets no nested tools at all — not even `get_subagent_result`, since it can never own a child. A child must independently set `allowed_subagents` to delegate again; isolated agents never receive nested tools.
266
266
 
267
- Nested children don't occupy `maxConcurrent` slots — their parent already holds one, and queueing them behind it would deadlock a parent waiting on its own child. The depth cap bounds how _deep_ nesting goes, not how _wide_: a parent's only limit on concurrent children is that each spawn costs it a turn. Pair `allowed_subagents` with a `max_turns` on that agent if you want a hard ceiling on its fan-out.
267
+ Nested children don't occupy `maxConcurrent` or `maxConcurrentForeground` slots — queueing a child behind a parent waiting for it would deadlock. The depth cap bounds how _deep_ nesting goes, not how _wide_: a parent's only limit on concurrent children is that each spawn costs it a turn. Pair `allowed_subagents` with a `max_turns` on that agent if you want a hard ceiling on its fan-out.
268
268
 
269
269
  Because a subagent session never activates this extension (that is what keeps a child from building a second agent manager, and it is why nested tools are injected directly instead), a subagent also gets none of the extension's other surfaces: no `/agents` command, no cross-extension RPC handlers, no `subagents:ready` event.
270
270
 
@@ -418,11 +418,11 @@ When on, each subagent spawn's effective model is validated against pi's own `en
418
418
 
419
419
  **Out-of-scope handling depends on source:**
420
420
 
421
- | Model source | Out-of-scope behavior |
422
- | --------------------------------------------- | -------------------------------------------------------------------- |
423
- | Caller-supplied via `Agent({ model: "..." })` | Hard error returned to the orchestrator, listing allowed models |
424
- | Pinned in agent frontmatter | Warning toast + the pinned model runs (frontmatter is authoritative) |
425
- | Parent-inherited (neither set) | Warning toast + parent's model runs |
421
+ | Model source | Out-of-scope behavior |
422
+ | ---------------------------------------------------------- | -------------------------------------------------------------------- |
423
+ | Caller-supplied via `Agent({ model: "..." })` or spawn RPC | Hard error returned to the orchestrator, listing allowed models |
424
+ | Pinned in agent frontmatter | Warning toast + the pinned model runs (frontmatter is authoritative) |
425
+ | Parent-inherited (neither set) | Warning toast + parent's model runs |
426
426
 
427
427
  **Design:** `scopeModels` is a guardrail against the orchestrator picking unexpected models at runtime, not a hard policy against user-level config. The "frontmatter is authoritative" guarantee from v0.5.1 still holds for `model:` — caller params can't override frontmatter, and frontmatter pins run even when out of scope (with a visible warning).
428
428
 
@@ -434,12 +434,14 @@ When on, each subagent spawn's effective model is validated against pi's own `en
434
434
 
435
435
  ## Persistent Settings
436
436
 
437
- Runtime tuning values set via `/agents` → Settings (including background default, worktree isolation, usage reporting, and cost display) persist across pi restarts. Two files, merged on load:
437
+ Runtime tuning values set via `/agents` → Settings (including concurrency, viewer Markdown, model/cost display, background default, and worktree isolation) persist across pi restarts. Two files, merged on load:
438
438
 
439
439
  - **Global:** `~/.pi/agent/subagents.json` — your machine-wide defaults. Edit by hand; the `/agents` menu never writes here.
440
440
  - **Project:** `<cwd>/.pi/subagents.json` — per-project overrides. Written by `/agents` → Settings.
441
441
 
442
- **Precedence:** project overrides global on any field present in both. Missing fields fall back to the hardcoded defaults (max concurrency `10`, background by default enabled, default max turns unlimited, grace turns `5`, nested depth `2`, join mode `smart`, worktree isolation enabled, usage/cost reporting disabled, strict agent files disabled, defaults enabled).
442
+ **Precedence:** project overrides global on any field present in both. Missing fields fall back to the hardcoded defaults (background concurrency `10`, foreground concurrency unlimited, background by default enabled, default max turns unlimited, grace turns `5`, nested depth `2`, join mode `smart`, worktree isolation enabled, viewer Markdown `assistant`, usage/cost/model widget display disabled, strict agent files disabled, defaults enabled).
443
+
444
+ **Foreground concurrency** (`maxConcurrentForeground`, default `0` = unlimited): independently bounds only top-level blocking `Agent` spawns. RPC, other detached spawns, resume, and nested children remain exempt; nested exemption prevents self-deadlock. Queued blocking calls show that they are waiting and remain cancellable.
443
445
 
444
446
  **Background by default** (`backgroundByDefault`, default `true`): unqualified top-level `Agent` calls run detached. Set `false` to restore foreground-by-default behavior. Explicit tool or agent-file `run_in_background` values win. Nested calls always default to foreground so a parent cannot finish and stop an uncollected child.
445
447
 
@@ -447,6 +449,10 @@ Runtime tuning values set via `/agents` → Settings (including background defau
447
449
 
448
450
  **Usage and cost** (`reportUsage` and `showCost`, both default `false`): `reportUsage` attaches child usage to parent tool results so Pi session stats include delegated work; `showCost` renders Pi's estimated USD totals in subagent surfaces. They are independent.
449
451
 
452
+ **Model display** (`showModel`, default `false`): adds the effective model and thinking level to running widget rows. Tool results and the conversation viewer show them regardless; when a requested value was overridden or clamped, the UI also shows what was asked.
453
+
454
+ **Viewer Markdown** (`viewerMarkdown`, default `"assistant"`): `"off"` wraps all transcript content literally, `"assistant"` renders only assistant text as Markdown, and `"all"` also renders tool results. Press `m` in the viewer to cycle and persist the same setting. Bash output always stays literal.
455
+
450
456
  **Strict agent files** (`strictAgentFiles`, default `false`): fail extension startup when any discovered agent file is unreadable or malformed. Enable via `/agents → Settings → Strict agent files` or set `true` in `subagents.json`. Strictness applies only to startup; reloads before later Agent calls remain tolerant so a file edited incorrectly mid-session is skipped with a warning instead of aborting the call.
451
457
 
452
458
  **Nested depth** (`maxSubagentDepth`, default `2`): the hard ceiling on [nested delegation](#nested-subagents), counted from the main session (main = 0, its subagents = 1). `0` or `1` disables nesting project-wide regardless of any agent's `allowed_subagents`. Read when a subagent session is built, so a change applies to agents started after it.
@@ -564,7 +570,7 @@ pi.events.emit("subagents:rpc:spawn", {
564
570
  })
565
571
  ```
566
572
 
567
- `options.model` accepts either a `Model` object (e.g. `ctx.model`) or a `"provider/modelId"` string strings are resolved against `ctx.modelRegistry` at the RPC boundary, so cross-extension callers can forward serializable values without losing auth context.
573
+ `options.model` accepts either a `Model` object (e.g. `ctx.model`) or a string. Strings use the same tolerant local resolver as the Agent tool; after resolution, both string and object overrides are checked against `scopeModels`. `null` keeps inherit behavior.
568
574
 
569
575
  `options.cwd` (absolute path to an existing directory — anything else returns an error envelope; `null` means unset) runs the agent in a different working directory than the parent session. Its tools operate there and the prompt's environment block describes it, but **`.pi` config still loads from the parent session's project** — the target directory's `.pi` extensions never execute, and its agents/skills/settings are not picked up. Combined with `isolation: "worktree"`, the worktree is created _from_ the target directory's repo, the agent works at the equivalent subdirectory inside the copy (a monorepo-package cwd stays scoped to that package), and the resulting `pi-agent-*` branch lands in that repo — the completion message names it. On session end, worktree registrations are pruned in every repo that received one; only a hard crash can leave a stale entry (then: `git worktree prune` in the target repo). Agents with `memory:` keep reading/writing the parent project's memory.
570
576
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herbertgao/pi-subagents",
3
- "version": "0.16.1",
3
+ "version": "0.17.1",
4
4
  "description": "Claude Code-style autonomous subagents for Pi, with HerbertGao-maintained UI extensions.",
5
5
  "keywords": [
6
6
  "agent",
@@ -68,8 +68,9 @@
68
68
  },
69
69
  "x-upstream": {
70
70
  "package": "@tintinweb/pi-subagents",
71
- "version": "0.18.0",
71
+ "version": "0.18.2",
72
+ "reviewedVersion": "0.19.0",
72
73
  "repository": "https://github.com/tintinweb/pi-subagents",
73
- "commit": "3f9d35cd078d18a141eb5a6d8f4fc5010d756280"
74
+ "commit": "ad81024825b315577692b785831262874e66be95"
74
75
  }
75
76
  }
@@ -28,7 +28,8 @@
28
28
 
29
29
  import { existsSync } from "node:fs"
30
30
  import { join } from "node:path"
31
- import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent"
31
+ import { getAgentDir } from "@earendil-works/pi-coding-agent"
32
+ import { parseAgentFrontmatter } from "./custom-agents.js"
32
33
  import type { AgentConfig } from "./types.js"
33
34
 
34
35
  export type AgentFileLocation = "project" | "workspace" | "personal"
@@ -69,12 +70,11 @@ const FENCE = /^---[ \t]*$/
69
70
 
70
71
  /**
71
72
  * Split a file into its frontmatter lines and everything else, agreeing with
72
- * what `parseFrontmatter` (the load side) considers a frontmatter block.
73
+ * what `parseAgentFrontmatter` (the load side) considers a frontmatter block.
73
74
  *
74
75
  * Lines keep their terminators, so an edit preserves the file's existing line
75
76
  * endings instead of rewriting CRLF to LF. Returns undefined when there is no
76
- * usable block notably for a BOM-prefixed file, which the parser also reads
77
- * as having none, so writing a key into it would change nothing on load.
77
+ * usable block. A leading BOM stays byte-for-byte in the file.
78
78
  */
79
79
  function splitFrontmatter(
80
80
  content: string,
@@ -82,8 +82,11 @@ function splitFrontmatter(
82
82
  | { lines: string[]; openIdx: number; closeIdx: number; eol: string }
83
83
  | undefined {
84
84
  const lines = content.split(/(?<=\n)/)
85
- if (lines.length === 0 || !FENCE.test(lines[0].replace(/\r?\n$/, "")))
86
- return undefined
85
+ if (lines.length === 0) return undefined
86
+ const first = (
87
+ content.startsWith("\uFEFF") ? lines[0].slice(1) : lines[0]
88
+ ).replace(/\r?\n$/, "")
89
+ if (!FENCE.test(first)) return undefined
87
90
  const closeIdx = lines.findIndex(
88
91
  (l, i) => i > 0 && FENCE.test(l.replace(/\r?\n$/, "")),
89
92
  )
@@ -110,8 +113,8 @@ function splitFrontmatter(
110
113
  export function isDisabledContent(content: string): boolean {
111
114
  try {
112
115
  return (
113
- parseFrontmatter<Record<string, unknown>>(content).frontmatter.enabled ===
114
- false
116
+ parseAgentFrontmatter<Record<string, unknown>>(content).frontmatter
117
+ .enabled === false
115
118
  )
116
119
  } catch {
117
120
  return false