pi-subagents 0.48.0 → 0.49.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 +47 -19
- package/docs/agents.md +1 -0
- package/docs/configuration.md +28 -5
- package/docs/missions.md +4 -2
- package/docs/observability.md +26 -2
- package/package.json +1 -1
- package/src/agents/agents.ts +27 -12
- package/src/api/preflight.ts +1 -1
- package/src/extension/config.ts +20 -0
- package/src/extension/index.ts +18 -8
- package/src/extension/public-execution.ts +30 -4
- package/src/extension/schemas.ts +8 -7
- package/src/extension/tool-description.ts +10 -8
- package/src/inspectors/herdr/actions.ts +11 -2
- package/src/inspectors/herdr/inspector-runner.ts +16 -3
- package/src/intercom/intercom-bridge.ts +3 -1
- package/src/missions/store.ts +8 -3
- package/src/runs/background/active-async-capacity.ts +82 -25
- package/src/runs/background/async-execution.ts +24 -36
- package/src/runs/background/async-job-tracker.ts +4 -0
- package/src/runs/background/async-resume.ts +11 -1
- package/src/runs/background/fleet-view.ts +18 -5
- package/src/runs/background/resume-guidance.ts +27 -7
- package/src/runs/background/retained-children.ts +14 -6
- package/src/runs/background/run-status.ts +96 -4
- package/src/runs/background/subagent-runner.ts +4 -1
- package/src/runs/foreground/prompt-audit.ts +4 -3
- package/src/runs/foreground/subagent-executor.ts +219 -20
- package/src/runs/shared/completion-guard.ts +17 -1
- package/src/runs/shared/llm-intent-arbiter.ts +39 -23
- package/src/shared/agent-stream-options.ts +5 -0
- package/src/shared/artifacts.ts +2 -6
- package/src/shared/display-text.ts +50 -0
- package/src/shared/node-executable.ts +21 -0
- package/src/shared/types.ts +23 -4
- package/src/tui/fleet-transcript.ts +1 -48
- package/src/tui/fleet.ts +3 -1
- package/src/tui/render.ts +86 -39
- package/src/watchdog/permission-arbiter.ts +2 -1
- package/src/watchdog/review.ts +4 -3
- package/src/workflows/chat-progress.ts +8 -2
- package/src/workflows/scripted-workflow.ts +46 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,28 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.49.0] - 2026-08-13
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Run a single child with `{ agent, task? }` when a full workflow script is not needed (#1059).
|
|
9
|
+
- Adjust FleetView spacing and collapsed result height from the main window. Thanks to @pierre-mgmt for #1048.
|
|
10
|
+
- Inspect async run state with `debug.run`, without exposing prompts, secrets, or transcripts (#1037).
|
|
11
|
+
- Let builtin role overrides keep Pi's normal tools and extensions with `tools: "inherit"`. Thanks to @estanexanavsem for #1047 and @davidarny for #1049.
|
|
12
|
+
- Add simple terminal examples for FleetView, the async widget, and inline tool display. Thanks to @czottmann for #1050.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Clean up active-run limits and artifact packaging code without changing behavior.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Trust live Herdr session roots only when the parent executor registered them for that async run.
|
|
19
|
+
- Let a workflow child disable the intercom bridge for one run with `intercomBridge: { mode: "off" }`, while normal async completion still works. Thanks to @jaudiger for #1072.
|
|
20
|
+
- Recover sibling children after a detached workflow fails (#1066).
|
|
21
|
+
- Show child session transcripts in standalone Herdr inspectors when the transcript is in a trusted session folder (#1069).
|
|
22
|
+
- Keep watchdog reviews, permission checks, Prompt Audit rewriting, and completion intent checks on the authenticated provider stream across the Pi 0.81 and 0.84 APIs. Thanks to @nuzayets for #1067.
|
|
23
|
+
- Mark children stopped by a parent workflow as stopped, not failed, and keep the stop reason (#1060).
|
|
24
|
+
- Keep subagent artifacts and automatic mission records out of project worktrees by default, so read-only workflows leave the tree clean (#1062).
|
|
25
|
+
- Make parents wait at dependency barriers after async launches, so child results are available before dependent work continues. Thanks to @exuanbo for #1045.
|
|
26
|
+
- Keep wait callers alive for intercom replies instead of reviving a detached wrapper. Thanks to @yayamaz for #1053.
|
|
27
|
+
- Keep workflow summary reports separate from child reports, and reject report path collisions before launch (#1038).
|
|
28
|
+
- Accept no-edit implementation challenge passes when the writer says the current solution is already best (#1054).
|
|
29
|
+
- Make the mutation guard safer for LLM intent checks, long tasks, and provider authentication. Thanks to @MarcusNeufeldt for #1044.
|
|
30
|
+
- Launch Herdr inspector panes with Node when Pi runs as a standalone executable. Thanks to @kevinpita for #1051.
|
|
31
|
+
- Sanitize async, nested, and result transcript output before showing it in terminal views. Thanks to @riesbri for #1046.
|
|
32
|
+
|
|
5
33
|
## [0.48.0] - 2026-08-13
|
|
6
34
|
|
|
7
35
|
### Added
|
|
8
|
-
-
|
|
9
|
-
- Add an
|
|
10
|
-
- Add a live Prompt Audit drawer to Fleet for
|
|
11
|
-
- Add a global `timeoutMs`
|
|
12
|
-
- Add
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
### Fixed
|
|
16
|
-
- Open Fleet Prompt Audit with the
|
|
17
|
-
- Use full task
|
|
18
|
-
-
|
|
19
|
-
- Explain when a
|
|
20
|
-
-
|
|
21
|
-
- Reject
|
|
22
|
-
-
|
|
23
|
-
- Stop
|
|
24
|
-
- Add an optional LLM
|
|
25
|
-
-
|
|
26
|
-
- Let single external-
|
|
36
|
+
- Limit each run to 64 child launches by default, so accidental fan-out loops stop before they create too many children. Thanks to @asjer for #1031.
|
|
37
|
+
- Add an optional limit for how many top-level async runs one session can have active at the same time. Fleet, status, RPC, and doctor now show the limit and current usage. Thanks to @asjer for #1029.
|
|
38
|
+
- Add a live Prompt Audit drawer to Fleet for foreground children owned by the current session. It shows the prompt on screen without saving it to status files, history, transcripts, metadata, results, progress, or run artifacts (#1021).
|
|
39
|
+
- Add a global `timeoutMs` setting for default run deadlines on foreground launches and plain single-agent async runs. It applies when a launch or agent does not set its own timeout, and it prevents long foreground fan-outs from falling back to the built-in 30-minute limit. Composite async runs stay unbounded at the top level. Thanks to @shaharmor for #1018.
|
|
40
|
+
- Add `PI_SUBAGENT_TASK_DELIVERY=auto|file` for hosts that block child processes when the task text appears in the command line. File mode writes the task to a temporary `task.md` and passes that path instead. Thanks to @yanqianglu for #1028.
|
|
41
|
+
- Retry with file-based task delivery after a child exits with no activity, which helps recover from endpoint protection tools that block long command lines. Thanks to @yanqianglu for #1028.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
- Open Fleet Prompt Audit with the original task visible by default, and show a short task summary in the normal Fleet detail pane (#1021).
|
|
45
|
+
- Use the full task text when caching LLM intent decisions, so similar tasks with the same prefix cannot share the wrong answer.
|
|
46
|
+
- Stop async Pi writer processes as full process groups, and only mark process cleanup as proven after the process tree has actually exited. Thanks to @asjer for #1030.
|
|
47
|
+
- Explain when a mission belongs to another worktree, including both the current project root and the mission directory (#1024).
|
|
48
|
+
- Keep the configured output reference when explicit acceptance rejects a foreground child, so useful reports remain available (#1023).
|
|
49
|
+
- Reject worktree base directories inside the agent extensions directory, including symlinked paths (#1014).
|
|
50
|
+
- Make unnamed intercom fallback targets match pi-intercom's registered name length, so subagents without a custom session name can still reach their parent. Thanks to @mystery4f for #1017.
|
|
51
|
+
- Stop treating phrases like "must-fix items" or "should-fix tests" as instructions to edit files during read-only review tasks. Thanks to @MarcusNeufeldt for #1020.
|
|
52
|
+
- Add an optional LLM check before the mutation guard fails a foreground single, parallel, or chain child that made no edits. If the task was actually read-only, the run now completes instead of failing. Thanks to @MarcusNeufeldt for #1020.
|
|
53
|
+
- Accept empty strings inside acceptance-report string arrays instead of rejecting the full report. Thanks to @hjiang for #1015.
|
|
54
|
+
- Let single external-CLI workflow children start without inheriting a Pi model, so model-less external runners do not fail preflight. Thanks to @twosunnus for #1016.
|
|
27
55
|
|
|
28
56
|
## [0.47.1] - 2026-08-12
|
|
29
57
|
|
package/docs/agents.md
CHANGED
|
@@ -77,6 +77,7 @@ Supported override fields: `description`, `model`, `fallbackModels`, `thinking`,
|
|
|
77
77
|
|
|
78
78
|
- `description` replaces the discovered description for builtin and custom agents, which lets list output show deployment-specific routing or model metadata.
|
|
79
79
|
- Use `defaultContext: false` or `acceptanceRole: false` to clear an inherited override.
|
|
80
|
+
- Use `tools: "inherit"` on a builtin when that one role should omit its bundled tool allowlist and receive Pi's normal builtins and ambient extensions. This keeps strict tools as the default for other builtins.
|
|
80
81
|
- Project overrides beat user overrides.
|
|
81
82
|
- Matching user and project agents also receive override fields that their frontmatter leaves unset, so a shared project config agent can keep the persona while local settings choose the model.
|
|
82
83
|
|
package/docs/configuration.md
CHANGED
|
@@ -44,6 +44,29 @@ Defaults to `false`. The default registered model-facing tool schema and descrip
|
|
|
44
44
|
|
|
45
45
|
Controls the `subagent` tool result shown inline in chat. The default, `"rich"`, shows live child activity and expands to detailed output. `"summary"` keeps the inline result at one stable row for running, completed, failed, stopped, and paused runs; it does not animate, show elapsed time, preview child output, or change when Pi's expand key is pressed. FleetView remains available for live progress and detailed inspection.
|
|
46
46
|
|
|
47
|
+
## `mainWindowRenderer`
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mainWindowRenderer": {
|
|
52
|
+
"horizontalSpacing": 0,
|
|
53
|
+
"compactResultMaxLines": 4
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Controls only the main chat `subagent` call/result renderer. It does not change child execution, orchestration, FleetView, artifacts, transcripts, or model-facing content.
|
|
59
|
+
|
|
60
|
+
`horizontalSpacing` is an integer from `0` to `4`. The default preserves current spacing. Set it to `0` to remove the extra spaces before compact result details and between parts of the call row.
|
|
61
|
+
|
|
62
|
+
`compactResultMaxLines` is a positive integer. It caps only collapsed rich-result rows and adds an expand hint when rows are hidden. Expanded output remains uncapped.
|
|
63
|
+
|
|
64
|
+
With `"summary"`, a tool result looks like this:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
✓ reviewer · completed
|
|
68
|
+
```
|
|
69
|
+
|
|
47
70
|
## `asyncByDefault`
|
|
48
71
|
|
|
49
72
|
```json
|
|
@@ -297,7 +320,6 @@ stdin is a JSON object with `repoRoot`, `worktreePath`, `agentCwd`, `branch`, `i
|
|
|
297
320
|
{
|
|
298
321
|
"missions": {
|
|
299
322
|
"enabled": true,
|
|
300
|
-
"directory": ".pi/subagents/missions",
|
|
301
323
|
"globalIndex": true,
|
|
302
324
|
"retainTerminal": 200
|
|
303
325
|
}
|
|
@@ -306,7 +328,8 @@ stdin is a JSON object with `repoRoot`, `worktreePath`, `agentCwd`, `branch`, `i
|
|
|
306
328
|
|
|
307
329
|
Automatic missions are enabled by default for ordinary launches with a task. Use per-launch `mission: false` for intentionally ephemeral work, or set `enabled: false` to disable automatic creation globally; explicit mission actions and `missionId`/`mission` launch fields still work.
|
|
308
330
|
|
|
309
|
-
-
|
|
331
|
+
- Mission records default to a project-keyed directory under pi's agent directory (`~/.pi/agent/missions/projects/<project-hash>/`). This keeps the project worktree clean.
|
|
332
|
+
- `directory` may be absolute, `~/...`, or project-relative. Set it to `.pi/subagents/missions` to opt in to project-scoped records.
|
|
310
333
|
- `retainTerminal` is a positive count (default `200`); pruning removes only the oldest completed, failed, or cancelled records and their pointers, never planned, active, waiting, needs-decision, or corrupt records.
|
|
311
334
|
- The user-global index contains pointers only; missing-record pointers self-heal when globally listed. Set `globalIndex: false` to disable writes or `globalIndexDir` to redirect it.
|
|
312
335
|
|
|
@@ -335,11 +358,11 @@ Each fixed action resolves to `"auto"`, `"confirm"`, or `"forbid"`. This is inte
|
|
|
335
358
|
|
|
336
359
|
Controls where subagent artifact files (inputs, outputs, transcripts, metadata) are stored:
|
|
337
360
|
|
|
338
|
-
- `"project"
|
|
339
|
-
- `"session"
|
|
361
|
+
- `"project"`: writes to `<cwd>/.pi/subagents/artifacts/`.
|
|
362
|
+
- `"session"` (default): stores artifacts under pi's session directory (`~/.pi/agent/sessions/<session>/subagent-artifacts/`), keeping the working directory clean. It falls back to the OS temp directory when no session file exists.
|
|
340
363
|
- `"temp"`: uses the OS temp directory.
|
|
341
364
|
|
|
342
|
-
This preference also controls the default chain scratch directory. `"project"` uses `<cwd>/.pi/subagents/chain-runs/`, while `"session"` and `"temp"` use the user-scoped temp chain directory.
|
|
365
|
+
This preference also controls the default chain scratch directory. `"project"` uses `<cwd>/.pi/subagents/chain-runs/`, while the default `"session"` and `"temp"` use the user-scoped temp chain directory.
|
|
343
366
|
|
|
344
367
|
The `"session"` option uses the same directory that `cleanupAllArtifactDirs` already scans for age-based cleanup, so artifacts are still cleaned up automatically. Temporary chain directories are cleaned up separately after 24 hours.
|
|
345
368
|
|
package/docs/missions.md
CHANGED
|
@@ -11,7 +11,9 @@ Missions are durable wrappers around runs. The noun map:
|
|
|
11
11
|
- **Run** — one actual subagent execution.
|
|
12
12
|
- **Receipt** — proof or a link for an external outcome, such as a PR, CI check, deployment, or release.
|
|
13
13
|
|
|
14
|
-
Ordinary workflow launches create one enclosing mission by default, with detailed JSON records under
|
|
14
|
+
Ordinary workflow launches create one enclosing mission by default, with detailed JSON records under `~/.pi/agent/missions/projects/<project-hash>/` linking objectives, run ids, lifecycle status, decisions, artifact paths, and delivery receipts. Workflow children do not create separate missions. Each workflow child attempt is stored in the enclosing mission with its stable workflow key, run id when known, agent, task metadata, timestamps, session and artifact paths, and latest status heartbeat.
|
|
15
|
+
|
|
16
|
+
Records created under the old default `<project>/.pi/subagents/missions` stay on disk. Continue them by setting `missions.directory` to that path for the project or by copying the record into the new agent-dir project store. There is no automatic migration.
|
|
15
17
|
|
|
16
18
|
Behavior:
|
|
17
19
|
|
|
@@ -19,7 +21,7 @@ Behavior:
|
|
|
19
21
|
- Human receipts end with `Mission: <id> (<status>)`, while JSON/structured output text stays unchanged and `details.missionId` is authoritative.
|
|
20
22
|
- Pass `mission: false` for an intentionally ephemeral workflow. It creates no mission for the workflow or its children and has no `state` global.
|
|
21
23
|
- Set `missions.enabled: false` to disable automatic mission creation; explicit mission fields and actions still work.
|
|
22
|
-
- A workflow with a mission can use `await state.get(key)` and `await state.set(key, value)` for durable JSON state. Missing keys return `undefined`. Keys use the same format as `runs.run` keys. Each set takes the state-file lock, reads the latest file, merges the key, and atomically writes `<
|
|
24
|
+
- A workflow with a mission can use `await state.get(key)` and `await state.set(key, value)` for durable JSON state. Missing keys return `undefined`. Keys use the same format as `runs.run` keys. Each set takes the state-file lock, reads the latest file, merges the key, and atomically writes `<mission-directory>/<mission-id>/state.json`. The complete file cannot exceed 256 KiB. Each workflow caches the file on its first `get`. A `mission:false` workflow has no `state` global.
|
|
23
25
|
|
|
24
26
|
An explicit `mission` object must have exactly one non-empty `title` or `summary`. `objective` and `labels` are optional. When supplied, `goal` must be `true` and requires `budget: { tokens: <positive integer> }`.
|
|
25
27
|
|
package/docs/observability.md
CHANGED
|
@@ -23,13 +23,37 @@ subagent({ action: "status", id: "..." }) // one run
|
|
|
23
23
|
|
|
24
24
|
Or ask naturally: "Show me the current async runs."
|
|
25
25
|
|
|
26
|
+
The under-editor async widget gives a short view while work runs. Its expand key follows your Pi keybinding:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
async subagent worker · background
|
|
30
|
+
● worker
|
|
31
|
+
● Step 1/1: worker · running
|
|
32
|
+
⎿ read: src/auth.ts | 2.0s
|
|
33
|
+
Press configured-expand-key for live detail
|
|
34
|
+
```
|
|
35
|
+
|
|
26
36
|
To inspect one background child in text, use `subagent({ action: "status", id: "...", view: "transcript" })`; add `index` for a specific child in a parallel or chain run.
|
|
27
37
|
|
|
28
38
|
## FleetView
|
|
29
39
|
|
|
30
40
|
In the TUI, a persistent FleetView below the editor keeps active work visible as a compact summary. Set `fleetViewPlacement` to `"aboveEditor"` to move it above the editor.
|
|
31
41
|
|
|
32
|
-
|
|
42
|
+
```text
|
|
43
|
+
2 active agents · ↓ 4.2k tokens · ↓/← to inspect
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
After you expand it:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
↑↓/jk select · enter inspect · esc back
|
|
50
|
+
|
|
51
|
+
> main
|
|
52
|
+
scout · running 1m 12s · ↓ 2.8k tokens
|
|
53
|
+
reviewer · running 38s · ↓ 1.4k tokens
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
When the focused editor is empty, press `↓` or `←` to expand the summary into `main` plus active children with agent name, state, elapsed time, and token totals. Then use `↑`/`↓` or `j`/`k` to select a child and `Enter` to inspect it. Printable navigation keys are never intercepted before activation.
|
|
33
57
|
|
|
34
58
|
FleetView replaces the legacy above-editor async widget by default. Successful background completions stay quiet so inactive Pi tabs are not marked unread, while failed or paused completions still notify the originating session. Parallel runs show every active child independently. Chains with parallel groups keep their grouped shape in progress and results, so failed or paused agents stay visible next to completed ones. When a child is explicitly allowed to fan out with `tools: subagent`, its nested runs appear under that parent child in the main status tree instead of being hidden inside the child process.
|
|
35
59
|
|
|
@@ -126,7 +150,7 @@ Foreground and async runners share bounded child-protocol handling:
|
|
|
126
150
|
|
|
127
151
|
## Chain and debug artifacts
|
|
128
152
|
|
|
129
|
-
Each chain run creates a scratch directory under its resolved chain root. With the default `artifactDir: "
|
|
153
|
+
Each chain run creates a scratch directory under its resolved chain root. With the default `artifactDir: "session"` or with `"temp"`, it is user-scoped temp storage. With `artifactDir: "project"`, the root is `<cwd>/.pi/subagents/chain-runs/`:
|
|
130
154
|
|
|
131
155
|
```text
|
|
132
156
|
<tmpdir>/pi-subagents-<scope>/chain-runs/{runId}/
|
package/package.json
CHANGED
package/src/agents/agents.ts
CHANGED
|
@@ -92,7 +92,7 @@ interface BuiltinAgentOverrideConfig {
|
|
|
92
92
|
disabled?: boolean;
|
|
93
93
|
systemPrompt?: string;
|
|
94
94
|
skills?: string[] | false;
|
|
95
|
-
tools?: string[] | false;
|
|
95
|
+
tools?: string[] | false | "inherit";
|
|
96
96
|
extensions?: string[] | false;
|
|
97
97
|
subagentOnlyExtensions?: string[] | false;
|
|
98
98
|
completionGuard?: boolean;
|
|
@@ -602,7 +602,7 @@ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentO
|
|
|
602
602
|
...(override.disabled !== undefined ? { disabled: override.disabled } : {}),
|
|
603
603
|
...(override.systemPrompt !== undefined ? { systemPrompt: override.systemPrompt } : {}),
|
|
604
604
|
...(override.skills !== undefined ? { skills: override.skills === false ? false : [...override.skills] } : {}),
|
|
605
|
-
...(override.tools !== undefined ? { tools: override.tools
|
|
605
|
+
...(override.tools !== undefined ? { tools: Array.isArray(override.tools) ? [...override.tools] : override.tools } : {}),
|
|
606
606
|
...(override.extensions !== undefined ? { extensions: override.extensions === false ? false : [...override.extensions] } : {}),
|
|
607
607
|
...(override.subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions: override.subagentOnlyExtensions === false ? false : [...override.subagentOnlyExtensions] } : {}),
|
|
608
608
|
...(override.completionGuard !== undefined ? { completionGuard: override.completionGuard } : {}),
|
|
@@ -738,6 +738,17 @@ function parseOverrideStringArrayOrFalse(
|
|
|
738
738
|
return items;
|
|
739
739
|
}
|
|
740
740
|
|
|
741
|
+
function parseToolsOverride(
|
|
742
|
+
value: unknown,
|
|
743
|
+
meta: { filePath: string; name: string },
|
|
744
|
+
): BuiltinAgentOverrideConfig["tools"] | undefined {
|
|
745
|
+
if (typeof value === "string" && value.trim() === "inherit") return "inherit";
|
|
746
|
+
if (value === undefined || value === false || Array.isArray(value)) {
|
|
747
|
+
return parseOverrideStringArrayOrFalse(value, { ...meta, field: "tools" });
|
|
748
|
+
}
|
|
749
|
+
throw new Error(`Builtin override '${meta.name}' in '${meta.filePath}' has invalid 'tools'; expected an array of strings, "inherit", or false.`);
|
|
750
|
+
}
|
|
751
|
+
|
|
741
752
|
function parseBuiltinOverrideEntry(
|
|
742
753
|
name: string,
|
|
743
754
|
value: unknown,
|
|
@@ -845,7 +856,7 @@ function parseBuiltinOverrideEntry(
|
|
|
845
856
|
const skills = parseOverrideStringArrayOrFalse(input.skills, { filePath, name, field: "skills" });
|
|
846
857
|
if (skills !== undefined) override.skills = skills;
|
|
847
858
|
|
|
848
|
-
const tools =
|
|
859
|
+
const tools = parseToolsOverride(input.tools, { filePath, name });
|
|
849
860
|
if (tools !== undefined) override.tools = tools;
|
|
850
861
|
|
|
851
862
|
const extensions = parseOverrideStringArrayOrFalse(input.extensions, { filePath, name, field: "extensions" });
|
|
@@ -1004,6 +1015,17 @@ function applySubagentDefaults(
|
|
|
1004
1015
|
);
|
|
1005
1016
|
}
|
|
1006
1017
|
|
|
1018
|
+
function applyToolsOverride(target: AgentConfig, toolsOverride: string[] | false | "inherit"): void {
|
|
1019
|
+
if (toolsOverride === "inherit") {
|
|
1020
|
+
delete target.tools;
|
|
1021
|
+
delete target.mcpDirectTools;
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
const { tools, mcpDirectTools } = splitToolList(toolsOverride === false ? [] : toolsOverride);
|
|
1025
|
+
if (tools === undefined) delete target.tools; else target.tools = tools;
|
|
1026
|
+
if (mcpDirectTools === undefined) delete target.mcpDirectTools; else target.mcpDirectTools = mcpDirectTools;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1007
1029
|
function applyBuiltinOverride(
|
|
1008
1030
|
agent: AgentConfig,
|
|
1009
1031
|
override: BuiltinAgentOverrideConfig,
|
|
@@ -1029,11 +1051,7 @@ function applyBuiltinOverride(
|
|
|
1029
1051
|
if (override.disabled !== undefined) next.disabled = override.disabled;
|
|
1030
1052
|
if (override.systemPrompt !== undefined) next.systemPrompt = override.systemPrompt;
|
|
1031
1053
|
if (override.skills !== undefined) { if (override.skills === false) delete next.skills; else next.skills = [...override.skills]; }
|
|
1032
|
-
if (override.tools !== undefined)
|
|
1033
|
-
const { tools, mcpDirectTools } = splitToolList(override.tools === false ? [] : override.tools);
|
|
1034
|
-
if (tools === undefined) delete next.tools; else next.tools = tools;
|
|
1035
|
-
if (mcpDirectTools === undefined) delete next.mcpDirectTools; else next.mcpDirectTools = mcpDirectTools;
|
|
1036
|
-
}
|
|
1054
|
+
if (override.tools !== undefined) applyToolsOverride(next, override.tools);
|
|
1037
1055
|
if (override.extensions !== undefined) { if (override.extensions === false) delete next.extensions; else next.extensions = [...override.extensions]; }
|
|
1038
1056
|
if (override.subagentOnlyExtensions !== undefined) { if (override.subagentOnlyExtensions === false) delete next.subagentOnlyExtensions; else next.subagentOnlyExtensions = [...override.subagentOnlyExtensions]; }
|
|
1039
1057
|
if (override.completionGuard !== undefined) next.completionGuard = override.completionGuard;
|
|
@@ -1175,10 +1193,7 @@ function applyCustomAgentOverride(
|
|
|
1175
1193
|
fill("skills", ["skill", "skills"], override.skills === false ? undefined : [...override.skills]);
|
|
1176
1194
|
}
|
|
1177
1195
|
if (override.tools !== undefined && !agentHasFrontmatterField(agent, "tools")) {
|
|
1178
|
-
|
|
1179
|
-
const target = mutable();
|
|
1180
|
-
if (tools === undefined) delete target.tools; else target.tools = tools;
|
|
1181
|
-
if (mcpDirectTools === undefined) delete target.mcpDirectTools; else target.mcpDirectTools = mcpDirectTools;
|
|
1196
|
+
applyToolsOverride(mutable(), override.tools);
|
|
1182
1197
|
anyFilled = true;
|
|
1183
1198
|
}
|
|
1184
1199
|
if (override.extensions !== undefined) {
|
package/src/api/preflight.ts
CHANGED
|
@@ -285,7 +285,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
285
285
|
return { ok: false, code: "denied_required_tool", message, diagnostics };
|
|
286
286
|
}
|
|
287
287
|
const artifactsEnabled = input.artifacts !== false;
|
|
288
|
-
const artifactsDir = artifactsEnabled ? getArtifactsDir(input.parentSessionFile ?? null, effectiveCwd, input.artifactDir
|
|
288
|
+
const artifactsDir = artifactsEnabled ? getArtifactsDir(input.parentSessionFile ?? null, effectiveCwd, input.artifactDir) : undefined;
|
|
289
289
|
const artifactPaths = artifactsDir ? getArtifactPaths(artifactsDir, runId, agent.name, 0) : undefined;
|
|
290
290
|
const outputPath = resolveSingleOutputPath(behavior.output, effectiveCwd, effectiveCwd, artifactsDir ? path.join(artifactsDir, "outputs", runId) : undefined);
|
|
291
291
|
const sessionRoot = input.sessionDir ? path.resolve(input.sessionDir) : input.sessionRoot ? path.join(path.resolve(input.sessionRoot), runId) : undefined;
|
package/src/extension/config.ts
CHANGED
|
@@ -46,6 +46,25 @@ function validateArtifactConfig(value: unknown): void {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function validateMainWindowRendererConfig(value: unknown): void {
|
|
50
|
+
if (value === undefined) return;
|
|
51
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("config.mainWindowRenderer must be a JSON object");
|
|
52
|
+
const rendererConfig = value as Record<string, unknown>;
|
|
53
|
+
if (rendererConfig.horizontalSpacing !== undefined
|
|
54
|
+
&& (typeof rendererConfig.horizontalSpacing !== "number"
|
|
55
|
+
|| !Number.isInteger(rendererConfig.horizontalSpacing)
|
|
56
|
+
|| rendererConfig.horizontalSpacing < 0
|
|
57
|
+
|| rendererConfig.horizontalSpacing > 4)) {
|
|
58
|
+
throw new Error("config.mainWindowRenderer.horizontalSpacing must be an integer from 0 to 4");
|
|
59
|
+
}
|
|
60
|
+
if (rendererConfig.compactResultMaxLines !== undefined
|
|
61
|
+
&& (typeof rendererConfig.compactResultMaxLines !== "number"
|
|
62
|
+
|| !Number.isInteger(rendererConfig.compactResultMaxLines)
|
|
63
|
+
|| rendererConfig.compactResultMaxLines < 1)) {
|
|
64
|
+
throw new Error("config.mainWindowRenderer.compactResultMaxLines must be a positive integer");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
49
68
|
function validateConfig(config: Record<string, unknown>): void {
|
|
50
69
|
if (config.artifactDir !== undefined && !ARTIFACT_DIR_PREFERENCES.has(config.artifactDir as ArtifactDirPreference)) {
|
|
51
70
|
throw new Error(`config.artifactDir must be "project", "session", or "temp"`);
|
|
@@ -65,6 +84,7 @@ function validateConfig(config: Record<string, unknown>): void {
|
|
|
65
84
|
validateScheduledRunsConfig(config.scheduledRuns);
|
|
66
85
|
validateFleetKeybindingsConfig(config.fleetKeybindings);
|
|
67
86
|
validateArtifactConfig(config.artifactConfig);
|
|
87
|
+
validateMainWindowRendererConfig(config.mainWindowRenderer);
|
|
68
88
|
}
|
|
69
89
|
|
|
70
90
|
export function getConfigPath(): string {
|
package/src/extension/index.ts
CHANGED
|
@@ -59,6 +59,7 @@ import { resolveMissionStoreLocation } from "../missions/store.ts";
|
|
|
59
59
|
import { listRetainedChildren } from "../runs/background/retained-children.ts";
|
|
60
60
|
import {
|
|
61
61
|
type Details,
|
|
62
|
+
type MainWindowRendererConfig,
|
|
62
63
|
type SubagentState,
|
|
63
64
|
DIRS,
|
|
64
65
|
DEFAULT_ARTIFACT_CONFIG,
|
|
@@ -253,12 +254,13 @@ function rebuildSlashResultContainer(
|
|
|
253
254
|
result: AgentToolResult<Details>,
|
|
254
255
|
options: { expanded: boolean },
|
|
255
256
|
theme: ExtensionContext["ui"]["theme"],
|
|
257
|
+
rendererConfig?: MainWindowRendererConfig,
|
|
256
258
|
): void {
|
|
257
259
|
container.clear();
|
|
258
260
|
container.addChild(new Spacer(1));
|
|
259
261
|
const boxTheme = isSlashResultRunning(result) ? "toolPendingBg" : isSlashResultError(result) ? "toolErrorBg" : "toolSuccessBg";
|
|
260
262
|
const box = new Box(1, 1, (text: string) => theme.bg(boxTheme, text));
|
|
261
|
-
box.addChild(renderSubagentResult(result, options, theme));
|
|
263
|
+
box.addChild(renderSubagentResult(result, options, theme, undefined, rendererConfig));
|
|
262
264
|
container.addChild(box);
|
|
263
265
|
}
|
|
264
266
|
|
|
@@ -266,6 +268,7 @@ function createSlashResultComponent(
|
|
|
266
268
|
details: SlashMessageDetails,
|
|
267
269
|
options: { expanded: boolean },
|
|
268
270
|
theme: ExtensionContext["ui"]["theme"],
|
|
271
|
+
rendererConfig?: MainWindowRendererConfig,
|
|
269
272
|
): Container {
|
|
270
273
|
const container = new Container();
|
|
271
274
|
let lastVersion = -1;
|
|
@@ -273,7 +276,7 @@ function createSlashResultComponent(
|
|
|
273
276
|
const snapshot = getSlashRenderableSnapshot(details);
|
|
274
277
|
if (snapshot.version !== lastVersion || isSlashResultRunning(snapshot.result)) {
|
|
275
278
|
lastVersion = snapshot.version;
|
|
276
|
-
rebuildSlashResultContainer(container, snapshot.result, options, theme);
|
|
279
|
+
rebuildSlashResultContainer(container, snapshot.result, options, theme, rendererConfig);
|
|
277
280
|
}
|
|
278
281
|
return Container.prototype.render.call(container, width);
|
|
279
282
|
};
|
|
@@ -378,6 +381,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
378
381
|
...(config.authorityPolicy ? { authorityPolicy: config.authorityPolicy } : {}),
|
|
379
382
|
...(config.missions ? { missionStoreConfig: config.missions } : {}),
|
|
380
383
|
parentSessionFile: null,
|
|
384
|
+
trustedSessionRoots: [],
|
|
381
385
|
subagentInProgress: false,
|
|
382
386
|
subagentSpawns: {
|
|
383
387
|
sessionId: null,
|
|
@@ -481,7 +485,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
481
485
|
pi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {
|
|
482
486
|
const details = resolveSlashMessageDetails(message.details);
|
|
483
487
|
if (!details) return undefined;
|
|
484
|
-
return createSlashResultComponent(details, options, theme);
|
|
488
|
+
return createSlashResultComponent(details, options, theme, config.mainWindowRenderer);
|
|
485
489
|
});
|
|
486
490
|
|
|
487
491
|
pi.registerMessageRenderer<undefined>(SLASH_TEXT_RESULT_TYPE, (message, _options, _theme) => {
|
|
@@ -581,22 +585,24 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
581
585
|
},
|
|
582
586
|
|
|
583
587
|
renderCall(args, theme) {
|
|
588
|
+
const gap = " ".repeat(config.mainWindowRenderer?.horizontalSpacing ?? 1);
|
|
589
|
+
const title = theme.fg("toolTitle", theme.bold("subagent"));
|
|
584
590
|
if (args.action) {
|
|
585
591
|
const target = args.agent || args.chainName || "";
|
|
586
592
|
return new Text(
|
|
587
|
-
`${
|
|
593
|
+
`${title}${gap}${args.action}${target ? `${gap}${theme.fg("accent", target)}` : ""}`,
|
|
588
594
|
0, 0,
|
|
589
595
|
);
|
|
590
596
|
}
|
|
591
597
|
if (args.workflowScript)
|
|
592
598
|
return new Text(
|
|
593
|
-
`${
|
|
599
|
+
`${title}${gap}${formatWorkflowManifest(args.workflowScript, args.async, false)}`,
|
|
594
600
|
0,
|
|
595
601
|
0,
|
|
596
602
|
);
|
|
597
|
-
const asyncLabel = args.async === true ? theme.fg("warning", "
|
|
603
|
+
const asyncLabel = args.async === true ? `${gap}${theme.fg("warning", "[async]")}` : "";
|
|
598
604
|
return new Text(
|
|
599
|
-
`${
|
|
605
|
+
`${title}${gap}${theme.fg("accent", args.agent || "?")}${asyncLabel}`,
|
|
600
606
|
0,
|
|
601
607
|
0,
|
|
602
608
|
);
|
|
@@ -607,7 +613,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
607
613
|
const renderedResult = { ...result, isError: context.isError };
|
|
608
614
|
return summaryInlineToolDisplay
|
|
609
615
|
? renderSubagentSummary(renderedResult, options, theme)
|
|
610
|
-
: renderSubagentResult(renderedResult, options, theme);
|
|
616
|
+
: renderSubagentResult(renderedResult, options, theme, undefined, config.mainWindowRenderer);
|
|
611
617
|
},
|
|
612
618
|
|
|
613
619
|
};
|
|
@@ -754,6 +760,10 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
754
760
|
goalTurnId = 0;
|
|
755
761
|
state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
756
762
|
state.parentSessionFile = ctx.sessionManager.getSessionFile();
|
|
763
|
+
state.trustedSessionRoots = [...new Set([
|
|
764
|
+
...(config.defaultSessionDir ? [path.resolve(expandTilde(config.defaultSessionDir))] : []),
|
|
765
|
+
...(state.parentSessionFile ? [getSubagentSessionRoot(state.parentSessionFile)] : []),
|
|
766
|
+
])];
|
|
757
767
|
state.subagentSpawns = {
|
|
758
768
|
sessionId: state.currentSessionId,
|
|
759
769
|
count: 0,
|
|
@@ -47,7 +47,7 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
47
47
|
if (normalizedAction !== undefined) {
|
|
48
48
|
const legacyAction = normalizedAction.toLowerCase();
|
|
49
49
|
if (legacyAction === "single") {
|
|
50
|
-
return { ok: false, error: "
|
|
50
|
+
return { ok: false, error: "action='single' is not supported. Omit action and pass { agent, task } for one child.", mode: "workflow" };
|
|
51
51
|
}
|
|
52
52
|
if (legacyAction === "parallel" || legacyAction === "tasks" || legacyAction === "chain") {
|
|
53
53
|
return { ok: false, error: "Legacy top-level chain and parallel inputs were removed; use workflowScript.", mode: "workflow" };
|
|
@@ -64,13 +64,39 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
64
64
|
if (params.workflowScript !== undefined) {
|
|
65
65
|
return { ok: false, error: "workflowScript execution must omit action; only schedule.create accepts action with workflowScript.", mode: "management" };
|
|
66
66
|
}
|
|
67
|
+
if (params.task !== undefined) {
|
|
68
|
+
return { ok: false, error: "Structured single-child task cannot be combined with a management/control action.", mode: "management" };
|
|
69
|
+
}
|
|
67
70
|
return { ok: true, params: { ...params, action: normalizedAction } };
|
|
68
71
|
}
|
|
69
|
-
if (params.
|
|
70
|
-
return { ok: false, error: "
|
|
72
|
+
if (params.step !== undefined) {
|
|
73
|
+
return { ok: false, error: "step is only available with action='append-step'; it is not an execution mode.", mode: "workflow" };
|
|
74
|
+
}
|
|
75
|
+
if (params.workflowScript !== undefined && (params.agent !== undefined || params.task !== undefined)) {
|
|
76
|
+
return { ok: false, error: "Structured single-child execution cannot be combined with workflowScript.", mode: "workflow" };
|
|
77
|
+
}
|
|
78
|
+
if (params.agent !== undefined || params.task !== undefined) {
|
|
79
|
+
if (typeof params.agent !== "string" || !params.agent.trim()) {
|
|
80
|
+
return { ok: false, error: "Structured single-child execution requires agent to be a non-empty string.", mode: "workflow" };
|
|
81
|
+
}
|
|
82
|
+
if (params.task !== undefined && typeof params.task !== "string") {
|
|
83
|
+
return { ok: false, error: "Structured single-child task must be a string when provided.", mode: "workflow" };
|
|
84
|
+
}
|
|
85
|
+
const { agent: _agent, task: _task, ...workflowDefaults } = params;
|
|
86
|
+
const child = {
|
|
87
|
+
agent: params.agent.trim(),
|
|
88
|
+
...(params.task !== undefined ? { task: params.task } : {}),
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
ok: true,
|
|
92
|
+
params: {
|
|
93
|
+
...workflowDefaults,
|
|
94
|
+
workflowScript: `console.info("Converted structured single-child request to workflow runs.run('main', ...)."); return runs.run("main", ${JSON.stringify(child)})`,
|
|
95
|
+
} as T,
|
|
96
|
+
};
|
|
71
97
|
}
|
|
72
98
|
if (typeof params.workflowScript !== "string" || !params.workflowScript.trim()) {
|
|
73
|
-
return { ok: false, error: "Execution requires
|
|
99
|
+
return { ok: false, error: "Execution requires either { agent, task? } for one child or a non-empty workflowScript for orchestration.", mode: "workflow" };
|
|
74
100
|
}
|
|
75
101
|
return { ok: true, params };
|
|
76
102
|
}
|
package/src/extension/schemas.ts
CHANGED
|
@@ -255,21 +255,22 @@ const ControlOverrides = Type.Object({
|
|
|
255
255
|
});
|
|
256
256
|
|
|
257
257
|
const SubagentParamProperties = {
|
|
258
|
-
agent: Type.Optional(Type.String({ description: "Agent
|
|
258
|
+
agent: Type.Optional(Type.String({ description: "Agent for one-child execution, or target for agent management actions." })),
|
|
259
|
+
task: Type.Optional(Type.String({ description: "Optional one-child task. Requires agent; cannot combine with action or workflowScript." })),
|
|
259
260
|
resume: Type.Optional(Type.String({ description: "Retained child run id for a workflowScript runs.run/runs.all item. Mutually exclusive with agent; task supplies the follow-up." })),
|
|
260
261
|
// Management action (when present, tool operates in management mode)
|
|
261
262
|
action: Type.Optional(Type.String({ minLength: 1,
|
|
262
|
-
description: "Optional management/control action. Omit this field for workflowScript execution; use it only for management/control actions."
|
|
263
|
+
description: "Optional management/control action. Omit this field for structured single-child or workflowScript execution; use it only for management/control actions."
|
|
263
264
|
})),
|
|
264
265
|
name: Type.Optional(Type.String({ description: "Human-readable name for action='schedule.create'." })),
|
|
265
266
|
id: Type.Optional(Type.String({
|
|
266
|
-
description: "Run id
|
|
267
|
+
description: "Run id/prefix for status/debug.run, interrupt, steer, append-step, approve-checkpoint, reject-checkpoint, or mission."
|
|
267
268
|
})),
|
|
268
269
|
runId: Type.Optional(Type.String({
|
|
269
|
-
description: "Target run ID for
|
|
270
|
+
description: "Target run ID for debug.run, interrupt, steer, append-step, or mission.attach-run. Prefer id."
|
|
270
271
|
})),
|
|
271
272
|
dir: Type.Optional(Type.String({
|
|
272
|
-
description: "Async run directory for
|
|
273
|
+
description: "Async run directory for status/debug.run, stop, resume, or steer."
|
|
273
274
|
})),
|
|
274
275
|
handoffPath: Type.Optional(Type.String({ description: "worktree.discard manifest." })),
|
|
275
276
|
index: Type.Optional(Type.Integer({ minimum: 0, description: "Zero-based child index for actions that target a specific child or transcript." })),
|
|
@@ -359,10 +360,10 @@ const { step: _legacyChainStep, ...subagentParamPropertiesWithoutStep } = Subage
|
|
|
359
360
|
const trimmedSubagentParamProperties = {
|
|
360
361
|
...subagentParamPropertiesWithoutStep,
|
|
361
362
|
id: Type.Optional(Type.String({
|
|
362
|
-
description: "Run id
|
|
363
|
+
description: "Run id/prefix for status/debug.run, interrupt, steer, or mission.attach-run."
|
|
363
364
|
})),
|
|
364
365
|
runId: Type.Optional(Type.String({
|
|
365
|
-
description: "Target run ID for
|
|
366
|
+
description: "Target run ID for debug.run, interrupt, steer, or mission.attach-run. Prefer id."
|
|
366
367
|
})),
|
|
367
368
|
};
|
|
368
369
|
const SubagentParamsSchema = Type.Object(SubagentParamProperties);
|