@signalridge/pi-subagents 1.11.1 → 1.13.0
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 +27 -0
- package/README.md +77 -10
- package/package.json +3 -3
- package/src/agent-color.ts +1 -1
- package/src/agent-manager.ts +395 -66
- package/src/agent-runner.ts +83 -35
- package/src/extension-resource-names.ts +105 -0
- package/src/index.ts +221 -69
- package/src/output-file.ts +28 -9
- package/src/parent-questions.ts +453 -0
- package/src/question-origin.ts +24 -0
- package/src/supervisor.ts +28 -8
- package/src/types.ts +4 -3
- package/src/ui/agent-display.ts +2 -1
- package/src/ui/conversation-viewer.ts +641 -290
- package/src/ui/fleet-list.ts +258 -66
- package/src/ui/owner-group.ts +110 -0
- package/src/ui/schedule-menu.ts +14 -7
- package/src/ui/workflow-ui-client.ts +189 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.13.0
|
|
4
|
+
### Minor Changes
|
|
5
|
+
|
|
6
|
+
- 2c7f8f1: Add user-controlled foreground-to-background handoff without restarting the child session, an adaptive conversation observer, and authoritative workflow Fleet rows through an optional validated UI protocol. Keep controls bound to an exact session and managed attempt, preserve steering drafts on native rejection, and retain cumulative activity and append-only transcripts across managed thread reuse.
|
|
7
|
+
|
|
8
|
+
### Patch Changes
|
|
9
|
+
|
|
10
|
+
- Updated dependencies [2c7f8f1]
|
|
11
|
+
- @signalridge/pi-subagents-protocol@1.6.0
|
|
12
|
+
|
|
13
|
+
## 1.12.0
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
- c2fd867: - Add an independently implemented, explicitly opted-in background-only parent clarification broker. Main-only answer_subagent_question validates one-use bounded IDs, original child/session/branch/attempt ownership and offered choice tokens; raw choices stay separate from sanitized request-local display. Coalesce bounded pending questions into ordinary prepared idle/settled extension turns without cloning a model, rewriting user text/images, copying parent history, or adding question storage/settings. Cancellation/timeout/retirement returns structured no-answer, never approval. Default human contact, ask_tools and external permission gates remain unchanged. Reject unsupported foreground/scheduled/mode-changing resume requests before spawn and leave managed protocol-v4 payloads unchanged.
|
|
17
|
+
- Fence parent questions to captured public session-manager identity and complete native branch prefixes at both channel construction and each live question invocation, including resumed-child metadata divergence before delayed committed-tree notifications. Track observed root preparation separately from native isIdle and release package-owned handled mentions with INPUT-scoped readiness leases; native throws continue preparation and another concurrent prompt's lease remains intact. Authenticate factory-local extension wakes with bounded nonce/MAC tokens and constant-memory historical proof recognition. Separate accepted-frame provenance and public ctx.signal/native-end run ownership from the outbox flight: a competing preparation's busy-failure settlement cannot erase the executing frame. Distinct frame tokens and authenticated empty-content native metadata prove only unambiguous admitted frames at public message boundaries, without guessing from promptless agent_start or stripping verbatim user quotations. An INPUT-observed copy of an unprepared frame token retires that bounded admission as ambiguous: neither prompt receives proof, abort, question context, or text/image stripping; affected questions return no-answer, while later ordinary input contexts can receive new questions. Recognize the authenticated leading token boundary plus its retained ambiguous admission even after native SDK image hints are appended; suppress only that question channel without creating control ownership, aborting, or modifying user data. Request-local context filters proven control pairs and only the proven obsolete control run can be aborted. ACK delivery against the exact flight, bound each question to one wake attempt with 30-second unacknowledged no-answer recovery, and retain unresolved accepted preparations within a fixed admission cap. Document external handled/auth-rejection and ambiguous-preparation readiness limits, native concurrent-prompt busy rejection, and the absence of atomic pre-persistence cancellation; verify real SDK later-preparation double barriers, user/proof message-start/end awaits through busy settlements, copied-token release orders with/without images and real default-AUTO 4096x4096 PNG normalization while another child's question remains pending, guard-disabled controls, handled mentions, resumed-child summaries, and live ownership without private scheduler/renderer/history patches. Human permission gates and child answer-tool capability isolation remain unchanged.
|
|
18
|
+
- Exclude pi-background from child resource loading before factories execute, including default inherited loaders and wildcard selection. Refuse intentional extension selections by name and warn on filtered inheritance/tool selectors: delayed process exits must not wake a retained child after manager completion, concurrency-slot release, or worktree cleanup. Merge configured and explicit ownership by canonical real file identity, including configured generic file symlinks and direct glob/conventional entries resolved through Pi's public package manager. Canonical declaration probes remain additive across ancestor manifests and disabled resource preferences, so an ordinary lexical owner cannot conceal a background target. Retain package names and source aliases for loaded-name validation and live `ext:` tool narrowing. Ordinary extensions remain available and no background settings schema is duplicated.
|
|
19
|
+
- Selectively port tintinweb/pi-subagents commit 7da6bef828d7582ee33386ece9379c170e7742c4 (2026-10-09, #374; reported by @BarrMan): FleetView recognizes non-inheriting custom editors by callable getText/setText while preserving dialog/selector input ownership and unknown-focus fallback. Raw inherit_context remains refused; no clone-parent-history behavior is imported.
|
|
20
|
+
- Separate per-resource declaring ownership from admission/exclusion alias unions, probe directory-backed module entries with public Node resolution without factory evaluation, and validate explicit sources before cross-source deduplication. Existing file/package aliases execute once; selecting another package's canonical entry no longer suppresses an alias owner's unrelated entries.
|
|
21
|
+
- Add independently implemented container-first workflow-owner groups and a bounded retained-child inspector, inspired by tintinweb/pi-subagents 221df02323c6b6350ea8474579e0ee0f6cc84cea/fd8d81618d80ae83a3faf40cb87dae99cca33264 and collision-only discriminators from nicobailon/pi-subagents 69673c7fbf3351e54a5b0786a7e9d9fff625a749. Counts describe retained children only; no authoritative workflow status/name/phase inference, upstream engine import, or new RPC. Preserve upstream license attribution.
|
|
22
|
+
- Keep Fleet selection by stable ID across refresh/linger, retain mounting and public structural focus evidence during invalidation, and recover overlay ownership after opener errors/disposal. Bind child conversation controls to their retained session and owner attempt so thread replacement cannot receive stale commands.
|
|
23
|
+
- Preserve rejected steering drafts (including boolean/async false and thrown failures) with an error while retaining legacy void callbacks. Completed viewers use retained lifetime usage and known compaction statistics without unguarded disposed-session access. Encode scheduled-job display copies and UI labels with the shared pi-ui sanitizer without rewriting stored prompts/paths/messages.
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [c2fd867]
|
|
28
|
+
- @signalridge/pi-ui@1.4.0
|
|
29
|
+
|
|
3
30
|
## 1.11.1
|
|
4
31
|
### Patch Changes
|
|
5
32
|
|
package/README.md
CHANGED
|
@@ -11,7 +11,8 @@ A [pi](https://pi.dev) extension that brings **Claude Code-style autonomous sub-
|
|
|
11
11
|
- **Parallel background agents** — spawn multiple agents that run concurrently with automatic queuing (configurable concurrency limit, default 4) and smart group join (consolidated notifications)
|
|
12
12
|
- **Live FleetView UI** — one navigable list below the editor with a status mark per row, live tool activity, token counts, and text status labels. Enter opens the selected agent's conversation. Toggle via `/agents → Settings → Fleet view`
|
|
13
13
|
- **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`
|
|
14
|
-
- **Conversation
|
|
14
|
+
- **Conversation observer** — select a retained agent with a session in `/agents` to open a live observer. Public fullscreen hosts use the terminal-sized view; regular hosts keep the floating bordered view. Scroll up to pause following, use `Home` for the first available history and `End`/`Ctrl+End` for the latest. Bounded windows preserve older-message navigation; `]` pages available message text and `[` returns to its first text page. Press `Enter` to compose a steering message; native rejection retains the draft. Controls stay bound to the selected session and complete managed attempt, never a replacement that reuses its ID.
|
|
15
|
+
- **Foreground handoff** — `Ctrl+B` at an empty, focused main prompt hands off one eligible foreground agent without restarting it. In its observer, `b` arms handoff and `y` confirms it; `x` similarly arms stop, `y` confirms, and Esc cancels. The ID, session, transcript and worktree remain the same; managed, nested, resumed and internal generation tasks are excluded. A full background pool refuses the transfer rather than exceeding its capacity.
|
|
15
16
|
- **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
|
|
16
17
|
- **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
|
|
17
18
|
- **Mid-run chat** — inject messages into running or queued agents to redirect their work without restarting. From the main prompt, use `@handle message` (with `@` autocomplete); configure this under `/agents → Settings → Agent mentions`.
|
|
@@ -46,7 +47,15 @@ From the repository root:
|
|
|
46
47
|
pi -e ./packages/pi-subagents
|
|
47
48
|
```
|
|
48
49
|
|
|
49
|
-
**Verified host baseline:** Pi 1.0.4. Wildcard peer dependency ranges and retained capability fallbacks do not establish support for older host versions.
|
|
50
|
+
**Verified host baseline:** Pi 1.0.4. Offline resource-ownership, child tool-policy, and native Fleet input barriers are additionally exercised on Pi 1.1.0; that focused coverage is not complete host qualification. Wildcard peer dependency ranges and retained capability fallbacks do not establish support for older host versions.
|
|
51
|
+
|
|
52
|
+
## Upstream and local divergence
|
|
53
|
+
|
|
54
|
+
Derived from [tintinweb/pi-subagents](https://github.com/tintinweb/pi-subagents); the upstream MIT license is preserved. The FleetView editor-focus fix from [commit `7da6bef828d7582ee33386ece9379c170e7742c4`](https://github.com/tintinweb/pi-subagents/commit/7da6bef828d7582ee33386ece9379c170e7742c4) (2026-10-09, #374; reported by @BarrMan) is selectively ported: callable `getText`/`setText` identifies non-inheriting custom editors without taking dialog or selector keys. No clone-parent-history behavior is imported; raw `inherit_context: true` remains refused. This fork also excludes `pi-background` from child resource loading until asynchronous child continuation has manager-owned lifecycle accounting.
|
|
55
|
+
|
|
56
|
+
The container-first FleetView and bounded drill-down patterns are independently adapted from tintinweb/pi-subagents at [`221df02323c6b6350ea8474579e0ee0f6cc84cea`](https://github.com/tintinweb/pi-subagents/tree/221df02323c6b6350ea8474579e0ee0f6cc84cea/src/ui) and [`fd8d81618d80ae83a3faf40cb87dae99cca33264`](https://github.com/tintinweb/pi-subagents/tree/fd8d81618d80ae83a3faf40cb87dae99cca33264/src/ui). Collision-only child-label discriminators are inspired by [nicobailon/pi-subagents `69673c7fbf3351e54a5b0786a7e9d9fff625a749`](https://github.com/nicobailon/pi-subagents/blob/69673c7fbf3351e54a5b0786a7e9d9fff625a749/src/tui/render-helpers.ts). The workflow engine and phase inspector remain owned by `pi-workflows`, not copied into this package. A separate optional, validated UI protocol supplies authoritative run names, statuses and observed phases and opens that owner's inspector. Without that peer, groups retain the observation-only child list. No workflow source imports, inferred DAG state or second queue/model policy are introduced. The managed protocol remains v4.
|
|
57
|
+
|
|
58
|
+
The opt-in parent clarification broker is locally implemented. It uses the existing main session, not an upstream LLM arbitrator, cloned parent history, private UI automation, or an additional Agent per question. Permission gates remain human-owned.
|
|
50
59
|
|
|
51
60
|
## Lint policy
|
|
52
61
|
|
|
@@ -71,6 +80,40 @@ At a TUI prompt, `@explore find it` starts one background Explore agent. In mode
|
|
|
71
80
|
|
|
72
81
|
**Pi runtime requirement:** On hosts where child sessions require the parent's `ModelRuntime`, Pi currently exposes it to extensions only through the registry's private `.runtime` property. If that bridge is missing or incompatible, Agent, mentions, and managed spawns refuse before allocating an ID or making a provider request; they do not silently construct a fresh runtime that would lose custom providers, credentials, or virtual routes. Existing managed idempotency keys remain readable. An explicit child model is validated against the parent runtime *after* final model selection, so a stale `ctx.model` alone does not block a valid physical child model. The error advises updating to a host that exposes the parent runtime, or disabling dispatch until one does.
|
|
73
82
|
|
|
83
|
+
### Opt-in parent clarification (background only)
|
|
84
|
+
|
|
85
|
+
By default, `contact_supervisor` still asks the human. Explicitly opt a **fresh, non-isolated top-level background** child into ordinary main-agent clarification:
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
Agent({
|
|
89
|
+
subagent_type: "Explore",
|
|
90
|
+
prompt: "Investigate the schema. Ask only ordinary task clarifications, never approval.",
|
|
91
|
+
description: "Investigate schema",
|
|
92
|
+
run_in_background: true,
|
|
93
|
+
supervisor_questions: "parent",
|
|
94
|
+
})
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The child awaits an in-process answer promise. Only its explicit bounded question reaches the main model: sanitized request-local context contains a one-use `questionId`, original `agentId`, and numbered choice tokens. The main-only `answer_subagent_question({ questionId, answer })` accepts free text, an offered `option:N` token, or an empty answer to decline. Display labels are separate from the original choice data returned to the child; duplicate identical answers never resume it twice. Question text is untrusted data, not new instructions or authorization.
|
|
98
|
+
|
|
99
|
+
The broker coalesces at most 16 pending questions (one per child), issues at most 64 IDs per session/branch generation, and expires unanswered questions after two minutes. Each question is displayed in hidden request-local context at most once. It requests one normal prepared extension turn only when the root is idle, has no queued messages, has no **observed input/preparation** in progress, and is not waiting on a dialog; otherwise questions join the next ordinary model context or wake after final `agent_settled`, never at retry-prone `agent_end`. No question means no extra model request. It does not edit genuine user text or images, transfer parent history/credentials into the child, poll a model, persist a separate question history, or automatically retry unanswered questions.
|
|
100
|
+
|
|
101
|
+
Idle wakes are factory-local authenticated control tokens, not a claim on a familiar sentence. Recognition requires `input.source === "extension"` and an exact namespace/nonce/MAC; an interactive/RPC literal, foreign-namespace extension literal, or invalid MAC remains ordinary input. Admission replaces only the extension-owned input with a distinct frame token. While that admission remains unambiguous, preparation adds an authenticated **empty-content** native custom message as prompt-local frame evidence, never persistent model guidance. Native `message_start` identifies the executing frame; the promptless `agent_start` and old branch messages cannot identify it. Superseding genuine input invalidates wake authorization without forgetting accepted control identity. Request-local context removes only proven control pairs; verbatim user quotations remain unchanged. Historical proof recognition uses a fixed-size secret and constant recognition state, not an accumulating ledger. At most 16 unresolved or ambiguous admissions are retained independently of the flight; an exhausted admission budget refuses further wakes rather than forgetting unresolved frames.
|
|
102
|
+
|
|
103
|
+
`before_agent_start` exposes no input source or delivery ID: token text alone cannot prove identity. If genuine INPUT copies an admitted token **before** its preparation reaches this package, both matching frames become ambiguous. That admission is retained inert within the same cap, neither frame receives a control proof or question context, neither is aborted or stripped by token text, and the flight's unannounced questions receive structured no-answer. The extension does not alter literal text or images. Native SDK image normalization can append a hint and resize an attachment; question suppression still requires the first text's authenticated token boundary and its retained ambiguous admission, never generic hint prose. This evidence cannot authorize a control proof or abort. The package cannot atomically cancel the unidentifiable control request before native persistence/provider dispatch. Ambiguous preparations cannot release a genuine input's readiness; idle wakes remain conservatively fenced until session retirement, but later ordinary inputs can still receive new questions normally. An already-prepared wake retains its positive native proof and cannot donate it to a later literal quotation.
|
|
104
|
+
|
|
105
|
+
An owned request-local `context` delivery is the ACK. Each question has **one wake attempt**: no ACK within 30 seconds clears only that flight and returns structured no-answer for its still-unannounced questions. Async native authentication rejection and earlier input interception cannot occupy the flight indefinitely or retry acknowledged questions. A superseding genuine prompt retains its own preparation readiness; except for the explicit copied-token ambiguity above, its questions join that prompt or expire normally. Executing-frame ownership separately captures the public `ctx.signal` and its matching native `agent_end`. A competing preparation's busy-failure `agent_settled` cannot clear that frame while the core operation still owns a public signal; only a proven ended and released owner permits cleanup. Native preparation can record inert control tokens and empty frame proofs in ordinary session history; question text and answers are not stored as separate broker entries.
|
|
106
|
+
|
|
107
|
+
**Public-hook limits:** handlers run in registration/load order. Observed genuine INPUT retires idle readiness synchronously, before later awaits. This package's mention interception releases only its own INPUT-scoped preparation on a native `handled` result; a thrown handler is not handled (Pi reports it and continues), and another concurrently preparing input is never released by that mention. Pi exposes neither an earlier handler's in-progress input nor a generic input-handled/auth-rejection completion notification. External handling/rejection after observation can therefore retain preparation readiness until session retirement; no timer guesses that a genuine prompt is inactive. Unresolved accepted wake preparations also retain their bounded admission slots until preparation arrives or the activation is replaced.
|
|
108
|
+
|
|
109
|
+
Pi has no cancellable post-preparation/pre-persistence boundary. Already-prepared competing prompts cannot be atomically serialized or cancelled before native persistence through public hooks. Cancellation can leave an inert native control record; a proven invalidated control frame is never reclassified as ordinary instructions or given question context. Abort is issued only after an authenticated native message boundary proves the currently executing owned frame, never from a global accepted-token guess or to stop a genuine/other run. SDK 1.0.4 coverage includes later-preparation release orders, user/proof message-start and message-end awaits, copied unprepared tokens with and without images, default AUTO resizing of real 4096x4096 PNGs in both release orders while another child's question stays pending, and guard-disabled controls. Native concurrent prompts can still reject with “already processing”; the package preserves observed frame identity but does not guarantee native concurrent-prompt acceptance or atomic pre-persistence cancellation. This is not a private SDK/renderer/history patch.
|
|
110
|
+
|
|
111
|
+
Answers are **not human approval or permission grants**. `ask_tools`, `createAskGate`, project trust, external permission dialogs, and human rejections are unchanged. A decision outside existing user task authorization must be declined and left to the human. Cancellation (including an aborted main turn), child stop/removal, timeout, navigation, or reload returns structured no-answer/assumption guidance, never approval. Pending IDs capture the public root session-manager identity, session ID, and complete native branch-entry prefix (including non-model metadata) at child channel construction **and at every question invocation from the live root context**, plus the current child attempt. A terminal child's background resume keeps its channel guard but cannot reuse that older prefix as the origin of a new question. Admission, context reads, and answer dispatch validate both live ancestries, not only generation events or model-text projections. Ordinary appends retain ownership; metadata-distinct divergent ancestry retires it even if an earlier asynchronous committed-tree handler has delayed this extension's notification. Old IDs cannot answer a replacement run.
|
|
112
|
+
|
|
113
|
+
Foreground parent-mode calls are rejected **before spawn**: a main model blocked waiting for a child cannot execute another answering tool. Schedules and changing question mode on `resume` are also unsupported. An existing parent-mode child can resume only in background with `supervisor_questions` omitted; it retains its mode. Do not call `get_subagent_result(wait: true)` while an ordinary clarification is pending: that wait blocks the answering model until cancellation or the question timeout. Nested children cannot opt in or call the root answering tool. Managed protocol-v4 requests do not carry this new mode and remain unchanged; this MVP supports direct `Agent` calls and in-process `SpawnOptions.supervisorQuestions: "parent"` only when the root manager has issued the channel. No new global configuration is added. The existing Supervisor questions setting must remain enabled; `supervisor_questions: "off"` withholds contact for one fresh call.
|
|
114
|
+
|
|
115
|
+
Install the changed package and reload/restart Pi to use this feature; an already running older installed package does not acquire new tools or parameters from checkout edits.
|
|
116
|
+
|
|
74
117
|
### Scheduling
|
|
75
118
|
|
|
76
119
|
Add a `schedule` field to register the agent to fire later instead of running now:
|
|
@@ -114,7 +157,7 @@ above-editor widget is gone.
|
|
|
114
157
|
Runs that enter through the scheduler or a cross-extension spawn appear in the
|
|
115
158
|
list too, alongside `Agent`-tool runs.
|
|
116
159
|
|
|
117
|
-
Every string these surfaces take from a child run — its description, activity preview, error, the `Agent` tool's own result and completion notification, and the conversation overlay's messages, tool results and command output — is text the extension did not author, so it is neutralized before rendering
|
|
160
|
+
Every string these surfaces take from a child run — its description, activity preview, error, the `Agent` tool's own result and completion notification, and the conversation overlay's messages, tool results and command output — is text the extension did not author, so it is neutralized before rendering. Fleet, conversation labels and scheduled-job selectors/confirmations/notifications use `@signalridge/pi-ui` display encoding (controls shown as `\\uXXXX`). Transcript previews drop terminal escape sequences, name remaining unsafe code points as `[U+XXXX]`, and replace binary content with a placeholder. These are display copies, not changes to stored prompts, paths, or messages. An agent file's `display_name` and `description` get the same treatment as they are loaded, since a cloned repository supplies them. A subagent that reads a poisoned file or fetches a hostile page cannot repaint or reorder the parent's terminal.
|
|
118
161
|
|
|
119
162
|
The token field is annotated with two optional signals inside parens:
|
|
120
163
|
- **`NN%`** — context-window utilization (color-coded: <70% dim, 70–85% warning, >=85% error). Omitted when the model has no declared `contextWindow`, or briefly right after compaction.
|
|
@@ -141,7 +184,13 @@ The mark is a geometric shape rather than an emoji on purpose: an emoji invites
|
|
|
141
184
|
font fallback, and a fallback glyph is usually double-width, which would push
|
|
142
185
|
the rest of the row out of alignment on some terminals and not others.
|
|
143
186
|
|
|
144
|
-
The list is ordered earliest-launched first, and only shows agents you can actually open (pending/queued agents with no session yet appear once they start). At an **empty prompt**, press down or left to move focus from the prompt into the list. Up and down move the selection, Enter opens the selected agent's live conversation overlay (it auto-updates as the agent works), and Esc (or up above `main`) returns to the prompt. Selecting `main` returns to the normal view.
|
|
187
|
+
The list is ordered earliest-launched first, and only shows agents you can actually open (pending/queued agents with no session yet appear once they start). At an **empty prompt**, press down or left to move focus from the prompt into the list. Up and down move the selection, Enter opens the selected agent's live conversation overlay (it auto-updates as the agent works), and Esc (or up above `main`) returns to the prompt. Selecting `main` returns to the normal view. Regular-mode observers use a complete `╭─╮` / `│` / `╰─╯` border; public fullscreen hosts use a terminal-sized observer. Press Enter to chat with a running agent — type a message and Enter to send it (Esc or an empty submit returns). For a queued agent that has no session yet, type `@handle message` in the main prompt; the message is delivered when it starts. Press `x` and follow the displayed explicit confirmation to stop a running agent; holding one key must not confirm it. A viewer stays open when its agent finishes so you can read the final output, and finished agents linger in the list for a few seconds before dropping out. Typing anything at a non-empty prompt behaves normally — the list only captures navigation keys when the prompt is empty. Disable it entirely via `/agents → Settings → Fleet view`.
|
|
188
|
+
|
|
189
|
+
Workflow containers appear **above ordinary agents**. With the optional `pi-workflows` UI peer, a row shows the actual workflow name/status, observed current phase, call counts and available token totals; even a running workflow with no child yet can appear. Enter opens the workflow owner's inspector, where pause/resume/stop stay with that owner. Paused and interrupted childless runs remain available for explicit resume.
|
|
190
|
+
|
|
191
|
+
Without the peer, the fallback is `Workflow <runId> · N retained children`. That count describes retained manager records, not total workflow calls or authoritative status. Opening it gives a terminal-height-bounded child list with stable-ID selection, Up/Down, PageUp/PageDown, Home/End and Enter to view a conversation; Esc returns from the conversation to the child list, then to FleetView. Sessionless queued children remain visible with an explicit unavailable-session message. Completed owned children remain inspectable while retained, independent of the ordinary four-second linger. Duplicate descriptions receive an ID discriminator only when needed. Detached records are excluded, and controls opened on an old thread attempt cannot steer/stop a replacement attempt.
|
|
192
|
+
|
|
193
|
+
Steering rejection (including a child finishing while the composer is open) preserves the draft and shows an error; Esc explicitly discards it. Completed conversation headers fall back to the retained lifetime usage and compaction count when live activity has been removed.
|
|
145
194
|
|
|
146
195
|
Individual agent results use restrained text status labels:
|
|
147
196
|
|
|
@@ -231,7 +280,7 @@ All fields are optional — sensible defaults for everything.
|
|
|
231
280
|
| `description` | filename | Agent description shown in tool listings |
|
|
232
281
|
| `display_name` | — | Display name for UI (e.g. the agent list, the conversation overlay) |
|
|
233
282
|
| `tools` | all 7 | Which tools the agent can call. Built-in names (`read, grep, …`), `*` / `all` (all built-ins), `none`, and `ext:<extension>` / `ext:<extension>/<tool>` selectors for extension tools. See [Tool & extension scoping](#tool--extension-scoping) below |
|
|
234
|
-
| `extensions` | `true` | Which extensions to load for the agent. `true` (all defaults), `false` (none), or an explicit list: `[mcp, "/abs/path.ts", "*"]`. See [Tool & extension scoping](#tool--extension-scoping) below |
|
|
283
|
+
| `extensions` | `true` | Which extensions to load for the agent. `true` (all child-safe defaults), `false` (none), or an explicit list: `[mcp, "/abs/path.ts", "*"]`. `pi-background` is excluded; intentional selection is refused by name. See [Tool & extension scoping](#tool--extension-scoping) below |
|
|
235
284
|
| `exclude_extensions` | — | Extension denylist applied after `extensions:` — exclude wins. Plain names only (case-insensitive), no paths or `*`. Useful with `extensions: true` to drop one extension (e.g. `pi-notify`) |
|
|
236
285
|
| `skills` | `true` | `true` uses Pi's discovered skills, including skills supplied by loaded extensions; `false` exposes none; a comma-separated list preloads only those named skills into the system prompt (see [Skill Preloading](#skill-preloading) for discovery locations) |
|
|
237
286
|
| `memory` | — | Persistent agent memory scope: `project`, `local`, or `user`. Auto-detects read-only agents |
|
|
@@ -295,7 +344,7 @@ Genuine user/global resources outside denied roots remain available subject to t
|
|
|
295
344
|
`extensions:` decides **which extensions load**, `tools:` decides **which tools surface to the LLM**. They compose:
|
|
296
345
|
|
|
297
346
|
```yaml
|
|
298
|
-
# Default (both omitted):
|
|
347
|
+
# Default (both omitted): child-safe extensions load, all 7 built-ins surface
|
|
299
348
|
|
|
300
349
|
tools: read, grep, find # narrow to listed built-ins; extensions still load
|
|
301
350
|
tools: "*" # all 7 built-ins (alias: `all`)
|
|
@@ -317,16 +366,18 @@ isolated: true # hermetic: built-ins only, no extensions/skil
|
|
|
317
366
|
|
|
318
367
|
A few rules the examples don't make obvious:
|
|
319
368
|
|
|
320
|
-
- `extensions:` is the
|
|
369
|
+
- `extensions:` is the loading authority, subject to child lifecycle safety. `pi-background` is excluded **before its factory loads**, including under default inheritance and `"*"`; an `extension-error:…` warning names it. Its jobs can wake a retained child after `session.prompt()` finishes, when the manager has marked the record terminal, released its concurrency slot, and removed its worktree. Until manager-owned asynchronous continuation exists, explicitly selecting `pi-background` (by name, alias, package path, or entry path) is refused by name rather than silently dropped. `ext:pi-background` cannot override this boundary and warns as an unloaded selector. Ordinary extensions remain available; parent sessions are unaffected. This does not add or change another extension's settings.
|
|
370
|
+
- `ext:foo` in `tools:` narrows what surfaces; it can't load `foo` on its own. Mismatches fire `extension-error:…` warnings.
|
|
321
371
|
- Any `ext:` entry flips extension tools to an explicit allowlist — unnamed extensions still load (handlers fire) but expose no tools. So `tools: "*, ext:mcp/search"` exposes only `search` from `mcp`, nothing from any other extension.
|
|
322
372
|
- Extension names match case-insensitively (`[Mcp]` = `[mcp]`); tool names in `ext:foo/bar` stay case-sensitive.
|
|
323
373
|
- Extensions that register tools **lazily** work too. MCP-backed extensions typically can't enumerate their tools until their servers connect, so they register from `session_start` or `before_agent_start` rather than at load. Subagent scoping is re-derived as tools appear, including under `ext:` selectors. Scoping does not override Pi's own exposure: `deferred`/`codemode` tools and `defaultActive: false` tools are not activated for the model, and tools deliberately deactivated during a session stay inactive on later turns. Pi may still allow nested `ctx.executeTool()` calls to deferred/codemode tools, but the child's selector and `ask_tools:` approval gate both model-issued and nested calls.
|
|
324
374
|
- A configured `defaultToolTimeoutMs` bounds the public `tool_call` policy and each selected extension handler using one deadline per call, including nested calls and handlers registered later with `pi.on`. On expiry the call is blocked; a Promise race returns control but **cannot cancel side effects** of a misbehaving hook that keeps running. Interactive `ask_tools:` confirmation is human decision time, not tool execution time: the deadline starts after approval. The separate execution timeout can still abort a tool whose `execute()` hangs.
|
|
325
|
-
- When the SDK exports public codemode, tool-search, and MCP factories (as on the verified Pi 1.0.4 baseline), they are included as built-in extensions in children. `extensions: false` omits them; `extensions: [mcp]` selects MCP; `exclude_extensions: mcp` removes it; and `tools: ext:codemode`, `ext:tool-search`, or `ext:mcp/<tool>` narrows their tools like any other extension. Capability checks omit factories that the host does not export; this fallback is not an older-host support guarantee.
|
|
375
|
+
- When the SDK exports public codemode, tool-search, and MCP factories (as on the verified Pi 1.0.4 baseline), they are included as built-in extensions in children. `extensions: false` omits them; `extensions: [mcp]` selects MCP; `exclude_extensions: mcp` removes it; and `tools: ext:codemode`, `ext:tool-search`, or `ext:mcp/<tool>` narrows their tools like any other extension. Capability checks omit factories that the host does not export; this fallback is not an older-host support guarantee. Whenever extensions are enabled, the child uses Pi's public package resolver to select discovered paths **before** loading factories. Thus an excluded extension cannot register a conflicting `/mcp` command and displace a selected replaceable built-in before filtering. Resolution can perform configured package installs, as Pi's normal loader does, and a selected extension that fails to load is reported rather than silently dropped.
|
|
376
|
+
- Explicit sources are validated separately before canonical deduplication, so a real file plus its file alias, or two package-directory aliases, load once without falsely reporting a missing source. Ownership names are unioned for admission/exclusion, but whole-package priority uses each resource's declaring package; direct entries use the canonical declaring owner. A package that aliases another package's entry does not lose its unrelated entries when the target is explicitly selected. Directory-backed modules are matched to Node's supported module entry without evaluating factories; undeclared siblings are not attributed to that package.
|
|
326
377
|
- An installed **package** extension matches by its package short name (`@scope/pi-subagents` → `[pi-subagents]`), in addition to its path-derived name (a package whose entry is `src/index.ts` also answers to `[src]`). Prefer the package name — the path-derived one is incidental.
|
|
327
378
|
- Plain `tools:` typos fail loudly: `tools: reed, grep` fires `tools-error:…` instead of silently producing an under-tooled agent.
|
|
328
379
|
- `exclude_extensions:` wins over `extensions:` and over `ext:` selectors — an excluded extension never loads and a `tools: ext:` entry can't pull it back. Plain names only (no paths, no `*`); a name matching nothing fires an `extension-error:…` warning.
|
|
329
|
-
- `exclude_extensions:` is **not a sandbox**:
|
|
380
|
+
- `exclude_extensions:` is **not a sandbox**: preselection prevents excluded discovered factories from loading into the child, but resolving configured packages can still install code. Allowed extensions run with the Pi process's permissions. Don't rely on extension selection to contain untrusted packages.
|
|
330
381
|
- Array and string forms are equivalent: `[a, b]` == `"a, b"`.
|
|
331
382
|
|
|
332
383
|
## Tools
|
|
@@ -343,6 +394,7 @@ Launch a sub-agent.
|
|
|
343
394
|
| `tier` | string | no | Model tier for this spawn, by name. Overrides the agent's own default tier. Unknown tiers are rejected, not substituted — see [Model tiers](#model-tiers) |
|
|
344
395
|
| `max_turns` | number | no | Max agentic turns. Omit for unlimited (default) |
|
|
345
396
|
| `run_in_background` | boolean | no | Run without blocking |
|
|
397
|
+
| `supervisor_questions` | `"human"` / `"parent"` / `"off"` | no | Default human; parent routes only ordinary clarification from fresh non-isolated background calls. No schedules or mode-changing resumes |
|
|
346
398
|
| `resume` | string | no | Agent ID to resume a previous session |
|
|
347
399
|
| `isolated` | boolean | no | No extension/MCP tools |
|
|
348
400
|
| `isolation` | `"worktree"` | no | Run in an isolated git worktree |
|
|
@@ -369,6 +421,10 @@ Send a chat message to a running or queued agent. A running agent receives it af
|
|
|
369
421
|
| `agent_id` | string | yes | Agent ID to steer |
|
|
370
422
|
| `message` | string | yes | Message to inject into agent conversation |
|
|
371
423
|
|
|
424
|
+
### `answer_subagent_question` (main only)
|
|
425
|
+
|
|
426
|
+
Answer a current opted-in background child's ordinary clarification: `questionId` is the bounded one-use ID in extension context; `answer` is at most 2,000 characters, an offered `option:N` token for choices, or empty to decline. Forged, expired, cancelled, or stale IDs and unoffered choices are rejected. The tool is never inherited or injected into children and cannot grant permission or override a human rejection.
|
|
427
|
+
|
|
372
428
|
## Commands
|
|
373
429
|
|
|
374
430
|
| Command | Description |
|
|
@@ -385,7 +441,7 @@ Create new agent
|
|
|
385
441
|
Settings
|
|
386
442
|
```
|
|
387
443
|
|
|
388
|
-
- **Agent runs** — select any retained run with a session to open its conversation viewer. Queued runs without a session remain listed but are not openable until they start; selecting one offers a stop confirmation, and `@handle message` queues a chat message. The summary reports running, queued, completed, wrapped up, stopped, aborted, and failed buckets separately. While a run is still active, press Enter to open the chat composer, then Enter again to send a message that redirects the agent (same mechanism as the `steer_subagent` tool; Esc or an empty submit returns), or press `x`
|
|
444
|
+
- **Agent runs** — select any retained run with a session to open its conversation viewer. Queued runs without a session remain listed but are not openable until they start; selecting one offers a stop confirmation, and `@handle message` queues a chat message. The summary reports running, queued, completed, wrapped up, stopped, aborted, and failed buckets separately. While a run is still active, press Enter to open the chat composer, then Enter again to send a message that redirects the agent (same mechanism as the `steer_subagent` tool; Esc or an empty submit returns), or press `x` and follow the displayed explicit confirmation to stop/abort it — including **background** agents, which a global Esc can't unambiguously target (Esc still stops a blocking foreground `Agent` call). A stopped agent reports its partial output flagged as incomplete, not as a completion.
|
|
389
445
|
- **Agent types** — unified list with textual source labels (`project`, `global`, and `disabled`). Each row shows the agent's model, and the highlighted agent's full description appears below the list. The model column flags `(unavailable, fallback: inherit)` when a configured model can't be resolved (it would silently inherit the parent model), and shows `(resolved: provider/id)` when it resolves to a different provider or version than configured. Select an agent to manage it:
|
|
390
446
|
- **Default agents** (no override): Eject (export as `.md`), Disable
|
|
391
447
|
- **Default agents** (ejected/overridden): Edit, Disable, Reset to default, Delete
|
|
@@ -857,6 +913,14 @@ const unsub = pi.events.on(`subagents:rpc:stop:reply:${requestId}`, (reply) => {
|
|
|
857
913
|
pi.events.emit("subagents:rpc:stop", { requestId, agentId: "agent-id-here" });
|
|
858
914
|
```
|
|
859
915
|
|
|
916
|
+
### Optional workflow UI bridge
|
|
917
|
+
|
|
918
|
+
`@signalridge/pi-subagents-protocol` declares a separate version-1 UI contract: `pi-workflows:ui:query`, `pi-workflows:ui:changed`, `pi-workflows:ui:open`, and `subagents:ui:open-owned`. These are not new required managed capabilities and do not grow the strict v4 ping envelope. Query/change payloads are bounded display projections, not DAG or journal storage. An open request is an explicit user UI action, not an agent dispatch or approval grant.
|
|
919
|
+
|
|
920
|
+
The conversation opener requires the exact `agentId` plus `{ extension, runId, nodeId, attemptId }`, an active root TUI and a retained session. Admission is acknowledged before the dialog closes; unavailable, foreign, sessionless and stale attempts are refused. Reload, tree replacement and shutdown retire pending UI work. Other peers can remain absent without disabling managed execution.
|
|
921
|
+
|
|
922
|
+
Owned lifecycle events may additionally carry `managedTokens`, the current attempt's usage. Existing `tokens` remains lifetime usage; Fleet never resets cumulative counters on thread reuse. Managed terminal `tokenCount` describes that attempt, including cache writes, so sequential thread calls do not bill earlier turns twice. Older stored terminal facts are not rewritten.
|
|
923
|
+
|
|
860
924
|
Reply channels are scoped per `requestId`, so concurrent requests don't interfere.
|
|
861
925
|
Workflow callers should pass a foreground signal only to their own wait. Managed child cancellation is explicit through the owner-scoped lifecycle RPC; aborting an RPC wait does not abort the child.
|
|
862
926
|
|
|
@@ -966,11 +1030,14 @@ src/
|
|
|
966
1030
|
prompts.ts # Config-driven system prompt builder
|
|
967
1031
|
context.ts # Pure parent-context renderer (not used for child dispatch)
|
|
968
1032
|
context-boundary.ts # Raw inheritance and persisted-session guards
|
|
1033
|
+
parent-questions.ts # Opt-in clarification broker, authenticated bounded outbox, public preparation/ACK gates
|
|
1034
|
+
question-origin.ts # Public manager identity and complete native branch-prefix ownership
|
|
969
1035
|
model-runtime-bridge.ts # Checked parent runtime compatibility bridge
|
|
970
1036
|
env.ts # Environment detection (git, platform)
|
|
971
1037
|
ui/
|
|
972
1038
|
agent-display.ts # Shared agent formatting: spinner frames, activity, stats, names
|
|
973
1039
|
conversation-viewer.ts # Live conversation overlay for viewing agent sessions
|
|
1040
|
+
owner-group.ts # Bounded retained workflow-child inspector (no engine coupling)
|
|
974
1041
|
select-item.ts # Numbered identity-safe selectors for duplicate labels
|
|
975
1042
|
```
|
|
976
1043
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signalridge/pi-subagents",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Signalridge's managed subagent runtime with workflow-owned orchestration RPC.",
|
|
5
5
|
"author": "tintinweb and signalridge contributors",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"croner": "^10.0.1",
|
|
35
|
-
"@signalridge/pi-subagents-protocol": "^1.
|
|
36
|
-
"@signalridge/pi-ui": "^1.
|
|
35
|
+
"@signalridge/pi-subagents-protocol": "^1.6.0",
|
|
36
|
+
"@signalridge/pi-ui": "^1.4.0",
|
|
37
37
|
"minimatch": "^10.2.6",
|
|
38
38
|
"nanoid": "^5.1.16"
|
|
39
39
|
},
|
package/src/agent-color.ts
CHANGED
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
* interpolated from the file, so nothing else can ride along inside it.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
import { sanitizeDisplayText } from "@signalridge/pi-ui";
|
|
21
22
|
import { getConfig } from "./agent-types.js";
|
|
22
|
-
import { sanitizeDisplayText } from "./ui/safe-text.js";
|
|
23
23
|
|
|
24
24
|
const NAMED_AGENT_COLORS: Readonly<Record<string, string>> = {
|
|
25
25
|
// Claude Code's eight subagent colors, as its default theme renders them.
|