@henryqw/pi-subagent 26.5.1 → 27.0.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/CONTEXT.md CHANGED
@@ -13,7 +13,9 @@ Own Role-configured Pi delegation through one `delegate_task` surface:
13
13
  - **Main**: the Pi session and checkout coordinating delegated work.
14
14
  - **Role**: package or user Markdown defining responsibility, tools, trusted extension sources, Skills, optional MCP names, instructions, and an optional model-class default. Roles do not select isolation.
15
15
  - **Model Class**: `fast`, `balanced`, `frontier`, or `fav`, resolved through `pi-task-models`.
16
- - **Direct task**: one bounded task using its Role's declared resources in Main's shared checkout; authorized writes take effect immediately without checked integration.
16
+ - **Direct task**: one bounded task using its Role's declared resources in Main's shared checkout, with a declared `access` of `read` or `write`; authorized writes take effect immediately without checked integration.
17
+ - **Direct reference**: `D<workflow>.<worker>` display reference for one recorded direct worker on the session branch, such as `D7.1`. It identifies a worker for display and `continueFrom`; it never grants ownership.
18
+ - **Continued worker**: a new direct worker, with a new reference and tab, that resumes a finished worker's saved Pi session and private lease through `continueFrom`.
17
19
  - **Isolated request**: one durable ID, goal, and checked task graph.
18
20
  - **Candidate**: exact clean committed task-worktree identity produced by an isolated changeset worker.
19
21
  - **Readiness**: durable proof that one exact live-worker candidate passed its preliminary checks and is sealed for Main's explicit selection; it is not a separate user approval.
@@ -41,10 +43,12 @@ Own Role-configured Pi delegation through one `delegate_task` surface:
41
43
 
42
44
  ### Direct evidence
43
45
 
44
- - Direct mode admits declared write-capable resources for explicitly authorized scope while preserving read-only Role restrictions. Potential-writer requests serialize tasks and retain Pi checkout admission until exact owned worker termination is proved by pane absence and two empty private process-lease scans; uncertain termination retains admission and live recovery ownership. Potential-writer answers require an explicit structured `succeeded` outcome; failed, blocked, or malformed completion stops dispatch but does not prevent release after proved termination. `/subagent` Close/cancel-and-release retries termination under session, branch, epoch and exact ownership guards without replaying work or removing checkout/session evidence. This is a worker report, not proof of validation, cleanliness, rollback, or checked integration. Direct changesets require isolated mode.
45
- - Only direct requests with potential writers reserve checkout admission. They cannot start while another direct writer holds it or a Main potentially-writing call is in flight. Main calls, including Bash and nested `codemode` writes, do not block each other. A direct writer blocks Main's writes until verified worker termination; use isolated mode when Main must continue writing. Reads never wait.
46
+ - Direct mode uses each Role's declared resources for explicitly authorized scope. Each task declares `access`; a request with any `write` task is a write request. `read` is Main's unenforced classification: read requests skip admission and return plain text; their parallel tasks run concurrently, chains stay ordered. Write requests serialize tasks and retain Pi checkout admission until exact owned worker termination is proved by pane absence and two empty private process-lease scans; uncertain termination retains admission and live recovery ownership. Write-request answers require an explicit structured `succeeded` outcome; failed, blocked, or malformed completion stops dispatch but does not prevent release after proved termination. `/subagent` Close/cancel-and-release retries termination under session, branch, epoch and exact ownership guards without replaying work or removing checkout/session evidence. This is a worker report, not proof of validation, cleanliness, rollback, or checked integration. Direct changesets require isolated mode.
47
+ - Only direct write requests reserve checkout admission. They cannot start while another direct writer holds it or a Main potentially-writing call is in flight. Main calls, including Bash and nested `codemode` writes, do not block each other. A direct writer blocks Main's writes until verified worker termination; use isolated mode when Main must continue writing. Reads never wait.
46
48
  - Each direct worker has a recorded Herdr tab and Pi session identity. The first handle returns before completion; subsequent tab identities remain recoverable on the session branch.
47
- - An exact settled worker with a bounded final Pi answer produces a follow-up to Main. Blocked, unknown, idle-stalled, truncated, or ambiguous outcomes retain actionable recovery identity. Session replacement never delivers to the wrong Main session.
49
+ - `continueFrom` resolves to exactly one recorded tab with the same Role and a saved turn identity. It refuses before launch while a local task owns the session, when the session is missing, above 16 MiB, or its exact recorded turn is not terminal, or when a still-listed old agent is not the exact idle or done agent. Old-pane closure needs pane absence and two empty lease scans. The relaunch reuses the session file and private lease in a new tab and record.
50
+ - `/subagent` **Stop this worker** exists only for running workers of locally owned all-read workflows. It aborts one entry, proves its termination, and records `Stopped by user from /subagent`; siblings continue and Main receives one result.
51
+ - An exact settled worker with a bounded final Pi answer produces a follow-up to Main. Worker text reaching Main carries a no-authority header with imitated harness framing escaped; a failed task keeps a bounded partial excerpt, never shown as success. Blocked, unknown, idle-stalled, truncated, or ambiguous outcomes retain actionable recovery identity. Session replacement never delivers to the wrong Main session.
48
52
 
49
53
  ### Isolated lifecycle
50
54
 
@@ -63,7 +67,7 @@ Own Role-configured Pi delegation through one `delegate_task` surface:
63
67
  ### Resource and launch policy
64
68
 
65
69
  - Ambient child extensions and Skills stay disabled. Only Role/caller resources plus required internal adapters load.
66
- - Every Role requires `tools`, `extensions`, and `skills` arrays. `extensions` may name Pi built-in extensions as `builtin:<name>`. A Role activates `codemode` only by naming it in `tools` with `builtin:codemode` loaded; MCP configuration never activates it, and Pi's codemode settings apply unchanged. `mcps` names servers from the global `~/.pi/agent/mcp.json`; their tools keep their configured exposure in a codemode Role and are forced to direct exposure otherwise. Omitted or empty `mcps` denies MCP access. Direct `pi-mcp-adapter` loading is rejected because it bypasses the allowlist.
70
+ - Every Role requires `tools`, `extensions`, and `skills` arrays; unknown frontmatter keys are rejected per file, and an invalid file blocks only the name it claims. `roleDefaults` in config merge into every Role first, deduplicated. `extensions` may name Pi built-in extensions as `builtin:<name>`. A Role activates `codemode` only by naming it in `tools` with `builtin:codemode` loaded; MCP configuration never activates it, and Pi's codemode settings apply unchanged. `mcps` names servers from the global `~/.pi/agent/mcp.json`; their tools keep their configured exposure in a codemode Role and are forced to direct exposure otherwise. Omitted or empty `mcps` denies MCP access. Direct `pi-mcp-adapter` loading is rejected because it bypasses the allowlist.
67
71
  - The Role tool policy declares the Role's `tools` and the extension tools Pi would declare on registration; extension tools with `codemode` or `deferred` exposure stay callable from scripts without being declared.
68
72
  - Selected extensions are trusted executable bundles, not a sandbox. All tools and lifecycle behavior they register load together.
69
73
  - Role Skill names resolve through Main's effective Pi registry. Missing Roles, Skills, tools, MCP servers, routes, models, or thinking levels fail before the first productive turn.
package/README.md CHANGED
@@ -15,6 +15,10 @@ pi install npm:@henryqw/pi-subagent
15
15
 
16
16
  Run `/task-models` and configure the `fast`, `balanced`, `frontier`, and `fav` routes you use.
17
17
 
18
+ ### Upgrade from 26.x
19
+
20
+ Version 27.0.0 has breaking changes: each direct task must declare `access`, Role files with unknown frontmatter keys are rejected, and the library replaces `loadRoles` with `loadRoleCatalog` and `findRole`. Read [Migrate from 26.x to 27.0.0](./docs/migration.md) before you upgrade.
21
+
18
22
  ## Works with
19
23
 
20
24
  | Package | Relationship | Purpose |
@@ -29,7 +33,8 @@ Commands are for you. Delegation and state tools and the packaged skill are for
29
33
 
30
34
  | Surface | Type | Purpose |
31
35
  | --- | --- | --- |
32
- | `/subagent` | command | Browse work, Close/cancel-and-release locally owned direct workers, launch isolated status inspection in a separate Herdr tab with the `fast` model, or send/edit pending instructions. Requires interactive TUI or RPC; inspection also requires a Herdr-managed pane and a read-only `scout` Role. |
36
+ | `/subagent` | command | Browse work, Close/cancel-and-release locally owned direct workers, stop one running worker of a read workflow, launch isolated status inspection in a separate Herdr tab with the `fast` model, or send/edit pending instructions. Requires interactive TUI or RPC; inspection also requires a Herdr-managed pane and a configured `scout` Role. |
37
+ | `/subagent roles` | command | List each Role with its source (built-in, user override, or user), resolved route model and thinking level or route error, tools, extensions, Skills, MCP servers, and per-file load errors. Read-only; no Herdr pane required. |
33
38
  | `/subagent recover` | command | Classify orphaned isolated work in the current canonical repository and send Main one follow-up with request IDs, blockers, and reported next actions. No UI or Herdr pane required. |
34
39
  | `delegate_task` | tool | Start authorized shared-checkout work or a durable isolated checked graph. |
35
40
  | `subagent_status` | tool | Read durable isolated state and the exact allowed continuation without replaying work. Declares an output schema, so `codemode` scripts receive the same bounded public projection as structured content. |
@@ -44,7 +49,7 @@ Commands are for you. Delegation and state tools and the packaged skill are for
44
49
 
45
50
  ![Main, its coordinator extension, and a Herdr subagent through checked work, worker failure, and coordinator interruption](./docs/worker-lifecycle.svg)
46
51
 
47
- `delegate_task`, `subagent_resume`, `subagent_stage`, `subagent_integrate`, and `subagent_abort` are model-only: Pi's `codemode` scripts cannot call them, because delegation and guarded state transitions need Main's visible reasoning. `subagent_status` stays callable from scripts. Main's potentially-writing calls can run together without writer ownership, including all Bash calls and nested `codemode` writes. Only potential-writer direct requests reserve the checkout. They can start only when no direct writer owns it and no Main potentially-writing call is in flight there. Each Main call is tracked separately until its result or execution end; a script's result does not release its active nested writes. Reads never wait. Use `mode: isolated` to keep Main free to write; this adds worktree checks and explicit integration.
52
+ `delegate_task`, `subagent_resume`, `subagent_stage`, `subagent_integrate`, and `subagent_abort` are model-only: Pi's `codemode` scripts cannot call them, because delegation and guarded state transitions need Main's visible reasoning. `subagent_status` stays callable from scripts. Main's potentially-writing calls can run together without writer ownership, including all Bash calls and nested `codemode` writes. Only direct requests with a task declaring `access: "write"` reserve the checkout. They can start only when no direct writer owns it and no Main potentially-writing call is in flight there. Each Main call is tracked separately until its result or execution end; a script's result does not release its active nested writes. Reads never wait. Use `mode: isolated` to keep Main free to write; this adds worktree checks and explicit integration.
48
53
 
49
54
  The coordinator is extension code running in Main's Pi process, not another agent. It persists the request, launches the Herdr subagent, checks its committed work, and reports saved results; Main chooses what to stage and promote. If a worker fails, the coordinator reports attention after the wave settles, not necessarily immediately while siblings work. If Main's Pi process stops, no follow-up is guaranteed: restart and run `/subagent recover` in the canonical repository to classify orphaned interruptions and send Main one recovery report. Main then uses `subagent_status` for exact evidence and deliberate next actions. A stale `running` state does not prove a worker is still active; interrupted or ambiguous actions are never replayed automatically.
50
55
 
@@ -82,27 +87,38 @@ A read-only task:
82
87
  "mode": "direct",
83
88
  "role": "scout",
84
89
  "name": "Map sign-in flow",
85
- "task": "Trace sign-in through session creation. Report files and risks. Do not edit files."
90
+ "task": "Trace sign-in through session creation. Report files and risks. Do not edit files.",
91
+ "access": "read"
86
92
  }
87
93
  ```
88
94
 
89
95
  Direct mode selects exactly one shape:
90
96
 
91
97
  ```text
92
- Single: { mode: "direct", role, name, task, ... }
93
- Parallel: { mode: "direct", tasks: [{ role, name, task, ... }] }
94
- Chain: { mode: "direct", chain: [{ role, name, task, ... }] }
98
+ Single: { mode: "direct", role, name, task, access, ... }
99
+ Parallel: { mode: "direct", tasks: [{ role, name, task, access, ... }] }
100
+ Chain: { mode: "direct", chain: [{ role, name, task, access, ... }] }
95
101
  ```
96
102
 
97
- Direct tasks use the selected Role's declared tools, extensions, Skills, and MCP servers, including write-capable resources. Authorization comes from your request and Main's bounded task packet, not the Role's capabilities: name allowed paths/actions and exclusions, preserve unrelated edits and staged changes, and authorize commits or external actions explicitly. For example, a configured Git operator with `bash` can stage named pending files and commit them on the `fast` route. Read-only Roles retain their tool restrictions. Use isolated mode for a checked changeset.
103
+ Every direct task declares `access: "read"` or `access: "write"`; a missing or other value fails before launch. Main declares `write` when the task may change files, Git state, or external resources. `read` is not enforced: there is no sandbox, and a read task keeps all of its Role's tools. A request is a write request when any task declares `write`. Read requests skip checkout admission and return plain-text answers; their parallel `tasks` run concurrently up to `maxSubagents`, while a `chain` still runs in order. Use isolated mode for checked writes.
98
104
 
99
- Direct writes affect Main's checkout immediately. No clean-checkout prerequisite, automatic checks, rollback, or isolated integration guarantee applies. Requests containing potential writers (including any extensions or MCP servers) can start only when no direct writer owns the checkout and no Main potentially-writing call is in flight there. They run their tasks serially and hold checkout admission after the initial handle returns; competing direct writers and Main writes fail until all owned workers are proved stopped. Use `mode: isolated` to keep Main free to write, with worktree checks and explicit integration instead of immediate shared-checkout changes. Pure read-only parallel requests still run concurrently. A failure in a potential-writer request stops further dispatch; an uncertain worker may still be running. Chains replace each literal `{previous}` with the preceding successful answer and stop on failure. Task outcome and worker termination are separate: after settlement the coordinator closes exact owned writer panes and releases admission only after pane absence and two empty private process-lease scans. Native `lsof` is required for this proof; no Git files, commits, or saved sessions are removed. Failure remains failure even when admission is released. If closing fails, identity changes, a leased child survives, or inspection is uncertain, admission stays held. Open `/subagent`, select the exact direct workflow, and choose **Close/cancel-and-release ALL owned workers in this workflow** to cancel dispatch and retry termination. It stops every owned worker of that workflow, not one selected worker. The action checks session/branch/epoch and live ownership; recorded-only tasks cannot unlock anything. Resolve the reported blocker (including any surviving owned process), then repeat the same action. Restarting Pi is not termination proof. Inspect partial changes before new writes; never blindly retry a commit or delete work to recover.
105
+ Direct tasks use the selected Role's declared tools, extensions, Skills, and MCP servers, including write-capable resources. Authorization comes from your request and Main's bounded task packet, not the Role's capabilities: name allowed paths/actions and exclusions, preserve unrelated edits and staged changes, and authorize commits or external actions explicitly. For example, a configured Git operator with `bash` can stage named pending files and commit them on the `fast` route. Use isolated mode for a checked changeset.
106
+
107
+ Direct writes affect Main's checkout immediately. No clean-checkout prerequisite, automatic checks, rollback, or isolated integration guarantee applies. Write requests can start only when no direct writer owns the checkout and no Main potentially-writing call is in flight there. They run their tasks serially and hold checkout admission after the initial handle returns; competing direct writers and Main writes fail until all owned workers are proved stopped. Use `mode: isolated` to keep Main free to write, with worktree checks and explicit integration instead of immediate shared-checkout changes. Parallel read requests still run concurrently. A failure in a write request stops further dispatch; an uncertain worker may still be running. Chains replace each literal `{previous}` with the preceding successful answer and stop on failure. Task outcome and worker termination are separate: after settlement the coordinator closes exact owned writer panes and releases admission only after pane absence and two empty private process-lease scans. Native `lsof` is required for this proof; no Git files, commits, or saved sessions are removed. Failure remains failure even when admission is released. If closing fails, identity changes, a leased child survives, or inspection is uncertain, admission stays held. Open `/subagent`, select the exact direct workflow, and choose **Close/cancel-and-release ALL owned workers in this workflow** to cancel dispatch and retry termination. It stops every owned worker of that workflow, not one selected worker. The action checks session/branch/epoch and live ownership; recorded-only tasks cannot unlock anything. Resolve the reported blocker (including any surviving owned process), then repeat the same action. Restarting Pi is not termination proof. Inspect partial changes before new writes; never blindly retry a commit or delete work to recover.
100
108
 
101
109
  If tab creation times out or returns incomplete or mismatched identity, `/subagent` retains the allocation intent: workspace, checkout, label, private lease/session paths, and any returned tab/pane IDs. No worker is started until creation identity is verified. These records are evidence, not ownership: Close is unavailable for unverified allocation, and admission remains held pending manual reconciliation against Herdr. Do not recreate a tab, guess missing IDs, or restart to bypass this blocker. A Close begun while a worker is active keeps its branch/session/epoch guards through cancellation settlement; stale dialogs cannot close panes or release admission.
102
110
 
103
- Potential-writer tasks must return one JSON completion object, such as `{"outcome":"succeeded","answer":"Scoped commit completed."}`. `outcome` must be `succeeded`, `failed`, or `blocked`, and `answer` must be non-empty. A normal final Pi turn is not enough: failure, blocked work, plain text, or malformed completion stops later dispatch, but admission depends on proved termination, not the reported outcome. Success is the worker's explicit report, not independent validation of its changes.
111
+ Tasks in a write request must return one JSON completion object, such as `{"outcome":"succeeded","answer":"Scoped commit completed."}`. `outcome` must be `succeeded`, `failed`, or `blocked`, and `answer` must be non-empty. A normal final Pi turn is not enough: failure, blocked work, plain text, or malformed completion stops later dispatch, but admission depends on proved termination, not the reported outcome. Success is the worker's explicit report, not independent validation of its changes.
112
+
113
+ The tool returns a task ID and first Herdr tab after launch, not the answer. The extension observes each worker and sends one result to Main when the workflow finishes. If Main is busy, Pi queues it after the current turn; if idle, it starts a turn. A blocked, stalled, unknown, or truncated result is not reported as success. When a task fails after its worker produced text, the result keeps a bounded excerpt marked as partial output; it is never success. Every worker result delivered to Main starts with a header stating that the text is the worker's output and carries no user authority. Text that imitates harness framing, such as `<system-reminder>`-style tags or lines starting with `Human:` or `Assistant:`, gets an inserted backslash; nothing else changes. Isolated status and follow-up answer previews use the same header. Session switch or shutdown stops observation and preserves tab identities for recovery. Open `/subagent` on the session branch to inspect exact tabs, agents, and Pi session files, including tabs launched after the first. The menu has one row per workflow: a single worker shows its reference, Role, and name (`D7.1 · worker · Search audit`); a multi-worker workflow shows its reference and worker count (`D8 · 3 workers`), and selecting it lists each recorded worker by reference, Role, and name with its exact IDs. References and names help you match rows; they are never used to decide ownership. Older records without saved references show their saved allocation label or exact task ID. A `recorded only` row is not proof that its workers are running or finished; locally observed work is labelled separately.
114
+
115
+ ### Continue or stop one direct worker
116
+
117
+ Main can continue a finished direct worker instead of starting a fresh one. It adds `continueFrom` with the worker's reference, such as `"continueFrom": "D7.1"`, to any single, parallel, or chain task, and uses the same `role` that the worker ran with. The worker resumes its saved Pi session in a new non-focused Herdr tab, so it keeps its earlier context. The new tab gets a new agent name and reference, and its record points to the same session and private lease file. The launch handle and the result say `continued from D7.1`. Access, admission, write rules, and model routes are the same as for a new task. A request can continue each worker only once, and only from the checkout and Herdr workspace where the worker ran. After a continuation, continue the newest reference; an older reference to the same session is refused because a later turn follows its own.
118
+
119
+ Before launch, the request fails if the reference is unknown or matches more than one record on this session branch, the Role is different, a locally active task still owns the worker's session, the session file is missing or larger than 16 MiB, or the session's last turn for the recorded prompt is not finished. If Herdr still lists the old agent, it must match the recorded name, pane, tab, workspace, and checkout, and be idle or done; a working or blocked agent is never closed. The old pane is closed, then pane absence and two empty lease scans are proved. Records saved before 27.0.0 have no turn identity and cannot be continued; start a fresh task.
104
120
 
105
- The tool returns a task ID and first Herdr tab after launch, not the answer. The extension observes each worker and sends one result to Main when the workflow finishes. If Main is busy, Pi queues it after the current turn; if idle, it starts a turn. A blocked, stalled, unknown, or truncated result is not reported as success. Session switch or shutdown stops observation and preserves tab identities for recovery. Open `/subagent` on the session branch to inspect exact tabs, agents, and Pi session files, including tabs launched after the first. The menu has one row per workflow: a single worker shows its reference, Role, and name (`D7.1 · worker · Search audit`); a multi-worker workflow shows its reference and worker count (`D8 · 3 workers`), and selecting it lists each recorded worker by reference, Role, and name with its exact IDs. References and names help you match rows; they are never used to decide ownership. Older records without saved references show their saved allocation label or exact task ID. A `recorded only` row is not proof that its workers are running or finished; locally observed work is labelled separately.
121
+ In a running workflow that has only `read` tasks, `/subagent` shows **Stop this worker** for each running worker. It aborts only that worker, sends Ctrl-C, closes its exact pane, and proves pane absence and two empty lease scans. The worker's result becomes a failure, `Stopped by user from /subagent`, and keeps any partial output. Other workers continue; a chain stops its later steps. If termination of the stopped worker is unproved, the workflow keeps local ownership so **Close/cancel-and-release** stays available. Main receives one result when the workflow finishes. A workflow with a `write` task offers only **Close/cancel-and-release ALL owned workers in this workflow**. Both actions check session, branch, and epoch, so a stale dialog cannot act.
106
122
 
107
123
  ### Isolated checked graphs
108
124
 
@@ -157,7 +173,7 @@ Completed text answers are saved independently of cleanup. Status and terminal f
157
173
 
158
174
  Isolated workers can start while Main has staged, unstaged, or untracked changes: they use only Main's pinned committed branch, HEAD, and tree. Those local edits are neither copied into worker worktrees nor stashed, staged, reset, or merged automatically. Main must remain at that committed identity while workers run; branch or HEAD drift blocks new waves. The worker produces a committed candidate without tracked or untracked changes, then runs focused preliminary checks and optional judgment. Ignored generated files do not block candidacy, but prevent automatic worktree cleanup; inspect them before deleting retained work. The runner records each ready candidate immediately but **does not integrate it automatically**. Main can inspect `subagent_status` and use `subagent_stage` to select the exact candidate in an owned integration worktree while siblings are still working; conflicts are resolved there. Stage and resolve are available during an active wave, but rejection and revision wait for the wave to settle. `subagent_integrate` can advance dependent tasks from a staged snapshot, validate the combined tip with full root checks and optional final judgment after the wave settles, and promote it to Main only after exact validation. A definitive failed combined check/review allows one committed correction in the integration checkout followed by another validation. Main must explicitly promote; promotion and cleanup never silently discard retained work.
159
175
 
160
- Open `/subagent` without arguments to pick work by label. The menu shows direct records on this session branch and isolated requests in the current canonical Git checkout (including requests launched in other sessions). Pick an isolated request and **Inspect status and recovery** to launch a non-focused Herdr tab with a read-only `scout` Role on the configured `fast` route. Unlike ordinary direct delegation, status inspection rejects Roles with extensions or MCP servers because their tools are not verified read-only. Its prompt contains bounded saved status (including retained paths, but not raw launch environment or session transcripts); the analysis stays in that tab and does not queue a Main turn. The tab appears in the direct list with its own reference and name, such as `D9.1 · scout · Inspect <request ID>`. The tab and session file are recorded for recovery even if launch becomes uncertain. The read-only `subagent_status` tool remains Main's authority for exact identities before any stage or recovery action. If Herdr or the read-only fast route is unavailable, inspection fails without changing the request; Main can use `subagent_status`. Refresh or choose completed/history for older work. Invalid state IDs are warned about and preserved. Outside Git or when isolated inventory is unavailable, direct recovery still works. Browsing does not resume work or change resources; direct **Close/cancel-and-release** is an explicit termination action.
176
+ Open `/subagent` without arguments to pick work by label. The menu shows direct records on this session branch and isolated requests in the current canonical Git checkout (including requests launched in other sessions). Pick an isolated request and **Inspect status and recovery** to launch a non-focused Herdr tab with the configured `scout` Role on the configured `fast` route, as a read-access task with the Role's own resources. Its prompt contains bounded saved status (including retained paths, but not raw launch environment or session transcripts); the analysis stays in that tab and does not queue a Main turn. The tab appears in the direct list with its own reference and name, such as `D9.1 · scout · Inspect <request ID>`. The tab and session file are recorded for recovery even if launch becomes uncertain. The read-only `subagent_status` tool remains Main's authority for exact identities before any stage or recovery action. If Herdr, the `scout` Role, or the fast route is unavailable, inspection fails without changing the request; Main can use `subagent_status`. Refresh or choose completed/history for older work. Invalid state IDs are warned about and preserved. Outside Git or when isolated inventory is unavailable, direct recovery still works. Browsing does not resume work or change resources; direct **Close/cancel-and-release** is an explicit termination action.
161
177
 
162
178
  For a locally active unsealed isolated changeset, choose **Send follow-up instructions** to open a native multiline editor; submission queues the instruction, not a completed revision. Choose **Edit queued instructions** to withdraw *all still-pending* instructions for that selected task before the editor opens. Their full text appears in FIFO order separated by blank lines. Explicit submission queues **one replacement** at the tail. Cancel leaves the withdrawn instructions removed; already-claimed instructions, initial prompts, and automatic corrections cannot be recalled. A worker may seal during editing: failed submission reopens the full submitted text for deliberate editing or copying, never automatically requeues it. Combined text above the single-instruction limit is preserved in the editor but rejected until shortened. Cancelling before withdrawal makes no change. Neither action touches Main's editor or Pi's global Option+Up queue shortcut. No-UI sessions can run `/subagent recover` or use agent tools for individual inspection and recovery.
163
179
 
@@ -191,8 +207,9 @@ pi-subagent owns `~/.pi/agent/config/pi-subagent/config.json`. A missing file si
191
207
  | `maxCorrections` | Same-worker automatic corrections per isolated request | Safe integer ≥ 0 | `1` |
192
208
  | `timeout.idleMinutes` | Direct worker and ephemeral child idle timeout | Positive and within Node's timer range | `10` |
193
209
  | `timeout.maxMinutes` | Ephemeral child hard runtime | Greater than idle and within Node's timer range | `30` |
210
+ | `roleDefaults` | Optional `extensions`, `tools`, and `skills` arrays merged into every built-in and user Role, with the same validation as Role fields | Object; unknown keys rejected | None |
194
211
 
195
- Limits come only from this global file. Request fields cannot override or replenish them. Existing durable requests keep their recorded policy, while a lower current correction limit can tighten recovery. There is intentionally no whole-run timeout setting.
212
+ `roleDefaults` entries come first in each merged list, followed by the Role's own entries, with duplicates removed; merging happens before resource resolution. Use it for the provider extension that every child needs. Limits come only from this global file. Request fields cannot override or replenish them. Existing durable requests keep their recorded policy, while a lower current correction limit can tighten recovery. There is intentionally no whole-run timeout setting.
196
213
 
197
214
  Malformed or unreadable JSON, unknown keys, and invalid values block delegation with one actionable warning. The file is preserved and never rewritten automatically.
198
215
 
@@ -210,11 +227,11 @@ Role Markdown lives in `~/.pi/agent/config/pi-subagent/` and requires frontmatte
210
227
  | `mcps` | Optional exact server names from `~/.pi/agent/mcp.json` in Pi's native `mcpServers` format; omitted or `[]` denies MCP access |
211
228
  | body | Required system instructions |
212
229
 
213
- Roles describe responsibility and capabilities. They do not choose isolation; each request does. A same-named user Role overrides a built-in Role. The package ships `implementer`, `reviewer`, and `scout`. The built-in reviewer includes `git_read`; add it to an existing user reviewer's `tools` array to opt in. No additional extension or Bash access is needed.
230
+ Unknown frontmatter keys are rejected with the file and key named. Each invalid file is reported on its own: valid Roles still load, delegating to the Role an invalid file claims fails with that file's error, and duplicate names make that name unavailable. `/subagent roles` lists the errors. Roles describe responsibility and capabilities, not write access or isolation; each task declares access and each request chooses its mode. A same-named user Role overrides a built-in Role. The package ships `implementer`, `reviewer`, and `scout`. The built-in reviewer includes `git_read`; add it to an existing user reviewer's `tools` array to opt in. No additional extension or Bash access is needed.
214
231
 
215
232
  ### Reviewer Git inspection
216
233
 
217
- `git_read` is an internal child tool, loaded and registered only for Roles that declare it. Direct delegation treats it as read-only. It accepts structured parameters, not shell commands or arbitrary Git options:
234
+ `git_read` is an internal child tool, loaded and registered only for Roles that declare it. It accepts structured parameters, not shell commands or arbitrary Git options:
218
235
 
219
236
  | `operation` | Parameters and evidence |
220
237
  | --- | --- |
@@ -243,7 +260,7 @@ The servers named in `mcps` come from the global `~/.pi/agent/mcp.json` only. In
243
260
 
244
261
  | Surface | Type | Purpose |
245
262
  | --- | --- | --- |
246
- | `loadRoles`, `parseRoleName` | functions | Load and validate built-in and user Roles. |
263
+ | `loadRoleCatalog`, `findRole`, `parseRoleName`, `parseRoleDefaults` | functions | Load built-in and user Roles with `roleDefaults` and per-file errors, then select one by name. |
247
264
  | `resolveRoleSkills`, `resolveRoleLaunch`, `resolveConfiguredRoleLaunch` | functions | Resolve effective Pi resources and model routes. |
248
265
  | `prepareRoleLaunch`, `finalizeRoleLaunch` | functions | Prepare Role prompts, arguments, and tool policy. |
249
266
  | `createEphemeralSubagentExecutor` | function | Run bounded no-session Pi children through a FIFO pool. |
@@ -9,11 +9,15 @@ export interface DirectHandle {
9
9
  prompt: string;
10
10
  answer(maxBytes: number): Promise<string>;
11
11
  usageTokens(): Promise<number | undefined>;
12
+ /** Bounded assistant text from this turn so far; failure evidence only, never completion. */
13
+ partialText(maxChars: number): Promise<string | undefined>;
12
14
  /** Display-only activity from the last usageTokens() sample. */
13
15
  readonly activity?: DirectToolActivity;
14
16
  cancel(): Promise<void>;
15
17
  }
16
18
  export declare function exactDirectTerminalTurn(jsonl: string, prompt: string): boolean;
19
+ /** Refuse continuation unless the saved session's exact recorded turn is terminal and no later user turn exists. */
20
+ export declare function assertSettledDirectSession(sessionFile: string, turn: string): Promise<void>;
17
21
  export declare function exactDirectAnswer(jsonl: string, prompt: string, maxBytes?: number, requireSuccess?: boolean): string;
18
22
  export interface DirectToolActivity {
19
23
  pending: string[];
@@ -26,13 +30,18 @@ export declare function directSessionUsage(jsonl: string, prompt: string): {
26
30
  tokens?: number;
27
31
  activity?: DirectToolActivity;
28
32
  };
33
+ /** The last `maxChars` of assistant text in the exact prompt's turn; ignores a partial trailing entry. */
34
+ export declare function directPartialText(jsonl: string, prompt: string, maxChars: number): string | undefined;
35
+ /** `turn` is the prompt's random turn identity; it proves which session turn a record owns.
36
+ * `workspaceId` and `cwd` bind the record to its launching Herdr workspace and checkout. */
29
37
  export type DirectTab = Pick<DirectHandle, "name" | "tabId" | "paneId" | "sessionFile"> & {
30
38
  leasePath: string;
39
+ turn: string;
40
+ workspaceId: string;
41
+ cwd: string;
31
42
  };
32
43
  /** Allocation evidence is not ownership and must never authorize pane mutation. */
33
44
  export type DirectAllocation = Omit<DirectTab, "tabId" | "paneId"> & {
34
- workspaceId: string;
35
- cwd: string;
36
45
  label: string;
37
46
  tabId?: string;
38
47
  paneId?: string;
@@ -42,8 +51,12 @@ export declare class DirectAllocationError extends Error {
42
51
  constructor(allocation: DirectAllocation, cause: unknown);
43
52
  }
44
53
  export declare function createDirectHerdr(pi: Pick<ExtensionAPI, "exec">, cwd: string, idleMs: number): {
54
+ workspaceId: string;
55
+ cwd: string;
45
56
  /** Close only an exact owned pane, then prove it and its leased children stopped.
46
- * A task result or Ctrl-C acknowledgement is not termination evidence. */
47
- stop(tab: DirectTab, current?: () => boolean): Promise<void>;
48
- start(launch: ResolvedRoleLaunch, name: string, label: string, task: string, signal: AbortSignal, onTab: (tab: DirectTab) => void, requireSuccess?: boolean): Promise<DirectHandle>;
57
+ * A task result or Ctrl-C acknowledgement is not termination evidence.
58
+ * `settledOnly` refuses to close a listed agent that is not idle or done. */
59
+ stop(tab: Pick<DirectTab, "name" | "tabId" | "paneId" | "leasePath">, current?: () => boolean, settledOnly?: boolean): Promise<void>;
60
+ /** `resume` reuses a stopped worker's saved session and private lease instead of a new temp directory. */
61
+ start(launch: ResolvedRoleLaunch, name: string, label: string, task: string, signal: AbortSignal, onTab: (tab: DirectTab) => void, requireSuccess?: boolean, resume?: Pick<DirectTab, "sessionFile" | "leasePath">): Promise<DirectHandle>;
49
62
  };
@@ -69,6 +69,28 @@ export function exactDirectTerminalTurn(jsonl, prompt) {
69
69
  const reason = exactFinalTurn(jsonl, prompt)?.stopReason;
70
70
  return reason === "stop" || reason === "error" || reason === "aborted";
71
71
  }
72
+ const turnSuffix = (turn) => `\n\nTurn identity: ${turn}`;
73
+ /** Refuse continuation unless the saved session's exact recorded turn is terminal and no later user turn exists. */
74
+ export async function assertSettledDirectSession(sessionFile, turn) {
75
+ const info = await stat(sessionFile).catch((error) => {
76
+ throw new Error(`Saved Pi session ${sessionFile} is missing or unreadable`, { cause: error });
77
+ });
78
+ if (info.size > SESSION_LIMIT)
79
+ throw new Error(`Saved Pi session ${sessionFile} exceeds 16 MiB`);
80
+ const jsonl = await readFile(sessionFile, "utf8");
81
+ let prompt;
82
+ for (const line of jsonl.trimEnd().split("\n")) {
83
+ const entry = JSON.parse(line);
84
+ const message = entry?.type === "message" ? entry.message : undefined;
85
+ const text = message?.role === "user" && Array.isArray(message.content) && message.content.length === 1
86
+ ? message.content[0]?.text : undefined;
87
+ if (typeof text === "string" && text.endsWith(turnSuffix(turn)))
88
+ prompt = text;
89
+ }
90
+ if (prompt === undefined || !exactDirectTerminalTurn(jsonl, prompt)) {
91
+ throw new Error(`Saved Pi session ${sessionFile} has no terminal final turn for the recorded prompt; the worker may still be running`);
92
+ }
93
+ }
72
94
  export function exactDirectAnswer(jsonl, prompt, maxBytes = ANSWER_LIMIT, requireSuccess = false) {
73
95
  const final = exactFinalTurn(jsonl, prompt);
74
96
  if (!final || final.stopReason !== "stop" || !Array.isArray(final.content)) {
@@ -89,12 +111,12 @@ export function exactDirectAnswer(jsonl, prompt, maxBytes = ANSWER_LIMIT, requir
89
111
  completion = JSON.parse(text);
90
112
  }
91
113
  catch (error) {
92
- throw new Error("Pi potential-writer final answer must be a JSON completion object.", { cause: error });
114
+ throw new Error("Pi write-access final answer must be a JSON completion object.", { cause: error });
93
115
  }
94
116
  if (!completion || typeof completion !== "object" || Array.isArray(completion)
95
117
  || !DIRECT_OUTCOMES.some((outcome) => outcome === completion.outcome)
96
118
  || typeof completion.answer !== "string" || !completion.answer.trim()) {
97
- throw new Error("Pi potential-writer completion must have a valid outcome and a non-empty answer.");
119
+ throw new Error("Pi write-access completion must have a valid outcome and a non-empty answer.");
98
120
  }
99
121
  if (completion.outcome !== "succeeded")
100
122
  throw new Error(`Direct task reported ${completion.outcome}: ${completion.answer}`);
@@ -160,6 +182,40 @@ export function directSessionUsage(jsonl, prompt) {
160
182
  return { tokens: observed ? total : undefined,
161
183
  activity: pending.size || latest ? { pending: [...pending.values()], latest } : undefined };
162
184
  }
185
+ /** The last `maxChars` of assistant text in the exact prompt's turn; ignores a partial trailing entry. */
186
+ export function directPartialText(jsonl, prompt, maxChars) {
187
+ let active = false;
188
+ let texts = [];
189
+ for (const line of jsonl.split("\n")) {
190
+ if (!line.trim())
191
+ continue;
192
+ let entry;
193
+ try {
194
+ entry = JSON.parse(line);
195
+ }
196
+ catch {
197
+ break;
198
+ }
199
+ if (!entry || entry.type !== "message" || !entry.message || typeof entry.message !== "object")
200
+ continue;
201
+ const message = entry.message;
202
+ if (message.role === "user") {
203
+ active = Array.isArray(message.content) && message.content.length === 1
204
+ && message.content[0]?.text === prompt;
205
+ if (active)
206
+ texts = [];
207
+ }
208
+ else if (active && message.role === "assistant" && Array.isArray(message.content)) {
209
+ for (const part of message.content)
210
+ if (part?.type === "text" && typeof part.text === "string" && part.text.trim())
211
+ texts.push(part.text);
212
+ }
213
+ }
214
+ const chars = Array.from(texts.join("\n").trim());
215
+ if (!chars.length)
216
+ return undefined;
217
+ return chars.length <= maxChars ? chars.join("") : `…${chars.slice(-maxChars).join("")}`;
218
+ }
163
219
  export class DirectAllocationError extends Error {
164
220
  allocation;
165
221
  constructor(allocation, cause) {
@@ -184,9 +240,12 @@ export function createDirectHerdr(pi, cwd, idleMs) {
184
240
  return field(agent.agent_status, "agent state");
185
241
  };
186
242
  return {
243
+ workspaceId,
244
+ cwd: workingDir,
187
245
  /** Close only an exact owned pane, then prove it and its leased children stopped.
188
- * A task result or Ctrl-C acknowledgement is not termination evidence. */
189
- async stop(tab, current = () => true) {
246
+ * A task result or Ctrl-C acknowledgement is not termination evidence.
247
+ * `settledOnly` refuses to close a listed agent that is not idle or done. */
248
+ async stop(tab, current = () => true, settledOnly = false) {
190
249
  const requireCurrent = () => { if (!current())
191
250
  throw new Error("Direct task scope changed; reopen /subagent."); };
192
251
  requireCurrent();
@@ -219,6 +278,9 @@ export function createDirectHerdr(pi, cwd, idleMs) {
219
278
  || agent.tab_id !== tab.tabId || agent.workspace_id !== workspaceId || agent.cwd !== workingDir
220
279
  || tab.paneId === callerPane)
221
280
  throw new Error("Worker termination requires one exact owned agent, workspace, checkout, tab and pane match.");
281
+ if (settledOnly && agent.agent_status !== "idle" && agent.agent_status !== "done") {
282
+ throw new Error(`Herdr agent ${tab.name} is ${String(agent.agent_status)}, not idle or done; it was not closed`);
283
+ }
222
284
  requireCurrent();
223
285
  result(await herdr.json(["pane", "close", tab.paneId], options()), "ok");
224
286
  }
@@ -234,17 +296,27 @@ export function createDirectHerdr(pi, cwd, idleMs) {
234
296
  }
235
297
  requireCurrent();
236
298
  },
237
- async start(launch, name, label, task, signal, onTab, requireSuccess = false) {
299
+ /** `resume` reuses a stopped worker's saved session and private lease instead of a new temp directory. */
300
+ async start(launch, name, label, task, signal, onTab, requireSuccess = false, resume) {
238
301
  const caller = result(await herdr.json(["pane", "current", "--current"], options(signal)), "pane_current");
239
302
  const pane = object(caller.pane, "calling pane");
240
303
  if (pane.pane_id !== callerPane || pane.workspace_id !== workspaceId)
241
304
  throw new Error("Herdr caller pane no longer matches the launching workspace.");
242
305
  if (Object.keys(launch.env).length)
243
306
  throw new Error("Direct Herdr launch cannot transfer Role environment overrides.");
244
- const sessionDir = await mkdtemp(join(tmpdir(), "pi-subagent-direct-"));
245
- const sessionFile = join(sessionDir, "session.jsonl");
246
- const leasePath = join(sessionDir, "process.lease");
247
- await writeFile(leasePath, "", { flag: "wx", mode: 0o600 });
307
+ let sessionFile;
308
+ let leasePath;
309
+ if (resume) {
310
+ ({ sessionFile, leasePath } = resume);
311
+ await assertPrivateLease(leasePath, false);
312
+ }
313
+ else {
314
+ const sessionDir = await mkdtemp(join(tmpdir(), "pi-subagent-direct-"));
315
+ sessionFile = join(sessionDir, "session.jsonl");
316
+ leasePath = join(sessionDir, "process.lease");
317
+ await writeFile(leasePath, "", { flag: "wx", mode: 0o600 });
318
+ }
319
+ const turn = randomBytes(16).toString("hex");
248
320
  // Pi's native session is the exact answer channel. Herdr screen output is diagnostic only.
249
321
  const sessionArgs = launch.args.filter((arg) => arg !== "--no-session");
250
322
  if (sessionArgs.length !== launch.args.length - 1)
@@ -252,7 +324,7 @@ export function createDirectHerdr(pi, cwd, idleMs) {
252
324
  signal.throwIfAborted();
253
325
  // Tab creation is bounded but not session-cancellable: Herdr may create it
254
326
  // before the CLI responds, and aborting the CLI loses the exact tab ID.
255
- const allocation = { name, workspaceId, cwd: workingDir, label, sessionFile, leasePath };
327
+ const allocation = { name, workspaceId, cwd: workingDir, label, sessionFile, leasePath, turn };
256
328
  let tabId;
257
329
  let paneId;
258
330
  try {
@@ -279,7 +351,7 @@ export function createDirectHerdr(pi, cwd, idleMs) {
279
351
  }
280
352
  try {
281
353
  // Record the tab before the agent start can outlive an aborted launch.
282
- onTab({ name, tabId, paneId, sessionFile, leasePath });
354
+ onTab({ name, tabId, paneId, sessionFile, leasePath, turn, workspaceId, cwd: workingDir });
283
355
  signal.throwIfAborted();
284
356
  const started = await startPiAgent(herdr, { name, pane: paneId,
285
357
  args: [...sessionArgs, "--session", sessionFile], options: options(signal), shouldRetry: () => false });
@@ -291,9 +363,9 @@ export function createDirectHerdr(pi, cwd, idleMs) {
291
363
  // never submit a new prompt after cancellation.
292
364
  signal.throwIfAborted();
293
365
  const completionInstruction = requireSuccess
294
- ? `\n\nPotential-writer completion: Return exactly one JSON object without Markdown, {"outcome":"succeeded","answer":"summary"}. outcome must be ${DIRECT_OUTCOMES.join(", ")}. Use succeeded only when the entire authorized task, including requested checks and commits, completed successfully; a failed mutation, unresolved partial work, or missing authorization must be failed or blocked, even if your Pi turn ends normally. Describe changes, checks, commit identity, remaining risks, and checkout status in answer.`
366
+ ? `\n\nWrite-access completion: Return exactly one JSON object without Markdown, {"outcome":"succeeded","answer":"summary"}. outcome must be ${DIRECT_OUTCOMES.join(", ")}. Use succeeded only when the entire authorized task, including requested checks and commits, completed successfully; a failed mutation, unresolved partial work, or missing authorization must be failed or blocked, even if your Pi turn ends normally. Describe changes, checks, commit identity, remaining risks, and checkout status in answer.`
295
367
  : "";
296
- const prompt = `${task}\n\nDirect shared-checkout boundary: use only your Role's resources for the explicitly authorized task and scope. Writes and commits affect Main's existing checkout immediately; preserve unrelated files and staged changes. Do not create or manage worktrees, change branches, push, or perform external mutations without explicit task authorization. There is no isolated integration, automatic validation, or rollback. If instructions conflict or authorization is missing, stop and report it. Report changes, commit identity if any, checks, remaining risks, and checkout status; never claim isolated guarantees.${completionInstruction}\n\nTurn identity: ${randomBytes(16).toString("hex")}`;
368
+ const prompt = `${task}\n\nDirect shared-checkout boundary: use only your Role's resources for the explicitly authorized task and scope. Writes and commits affect Main's existing checkout immediately; preserve unrelated files and staged changes. Do not create or manage worktrees, change branches, push, or perform external mutations without explicit task authorization. There is no isolated integration, automatic validation, or rollback. If instructions conflict or authorization is missing, stop and report it. Report changes, commit identity if any, checks, remaining risks, and checkout status; never claim isolated guarantees.${completionInstruction}${turnSuffix(turn)}`;
297
369
  // Native prompt without --wait acknowledges submission, not completion of the turn.
298
370
  const accepted = await herdr.json(["agent", "prompt", name, prompt], options(signal));
299
371
  const state = inspect(accepted, "agent_prompted", name, paneId, tabId);
@@ -316,6 +388,11 @@ export function createDirectHerdr(pi, cwd, idleMs) {
316
388
  }
317
389
  return lastUsage.tokens;
318
390
  },
391
+ async partialText(maxChars) {
392
+ if ((await stat(sessionFile)).size > SESSION_LIMIT)
393
+ return undefined;
394
+ return directPartialText(await readFile(sessionFile, "utf8"), prompt, maxChars);
395
+ },
319
396
  async answer(maxBytes) {
320
397
  const readAnswer = async () => {
321
398
  const info = await stat(sessionFile);
package/dist/index.d.ts CHANGED
@@ -37,6 +37,24 @@ export interface Role {
37
37
  mcps?: string[];
38
38
  systemPrompt: string;
39
39
  }
40
+ /** Optional `config.json` resources merged into every Role, defaults first. */
41
+ export interface RoleDefaults {
42
+ extensions?: string[];
43
+ tools?: string[];
44
+ skills?: string[];
45
+ }
46
+ export type RoleSource = "built-in" | "user override" | "user";
47
+ /** One invalid user Role file. `name` is the claimed Role name, or the file stem when unreadable. */
48
+ export interface RoleFileError {
49
+ file: string;
50
+ name: RoleName;
51
+ message: string;
52
+ }
53
+ export interface RoleCatalog {
54
+ roles: Role[];
55
+ sources: ReadonlyMap<RoleName, RoleSource>;
56
+ errors: RoleFileError[];
57
+ }
40
58
  export interface PiLaunch {
41
59
  env: Record<string, string>;
42
60
  args: string[];
@@ -74,15 +92,20 @@ export interface ResolvedRoleSkills {
74
92
  }
75
93
  /** Normalize one arbitrary Role name using the Role configuration contract. */
76
94
  export declare function parseRoleName(value: unknown, source?: string): RoleName;
95
+ /** Validate `config.json` `roleDefaults` with the same rules as Role fields; `label` prefixes errors. */
96
+ export declare function parseRoleDefaults(value: unknown, label?: string): RoleDefaults;
77
97
  declare const BUILTIN_ROLE_NAMES: readonly ["implementer", "reviewer", "scout"];
78
98
  export type BuiltinRoleName = (typeof BUILTIN_ROLE_NAMES)[number];
79
99
  export declare function loadBuiltinRole(name: BuiltinRoleName): Role;
80
100
  /**
81
- * Validated package-shipped Roles plus valid user roles from `config/pi-subagent`.
82
- * A user role with a built-in name overrides the default; duplicate names among
83
- * user files are an error. Missing user directory returns the built-ins.
101
+ * Package-shipped Roles plus user Roles from `config/pi-subagent`, with
102
+ * `roleDefaults` merged in. A user Role overrides a same-named built-in. Each
103
+ * invalid or duplicate user file is reported and makes its claimed name
104
+ * unavailable without hiding valid Roles. Malformed `roleDefaults` throws.
84
105
  */
85
- export declare function loadRoles(agentDir?: string): Role[];
106
+ export declare function loadRoleCatalog(agentDir?: string): RoleCatalog;
107
+ /** Select one catalog Role, naming invalid files that claim it. */
108
+ export declare function findRole(catalog: RoleCatalog, name: RoleName): Role;
86
109
  type TaskRouteContext = Parameters<typeof resolveTaskModelRoute>[0];
87
110
  type RoleLaunchContext = TaskRouteContext & Pick<ExtensionContext, "isProjectTrusted">;
88
111
  export declare function resolveTaskRoute(ctx: TaskRouteContext, profileName: ProfileName, agentDir?: string): ResolvedTaskRoute;