pi-subagents 0.12.2 → 0.12.4
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 +16 -0
- package/README.md +66 -6
- package/agent-management.ts +12 -5
- package/agent-manager-chain-detail.ts +2 -2
- package/agent-manager-detail.ts +9 -7
- package/agent-manager-edit.ts +4 -4
- package/agent-manager-list.ts +2 -2
- package/agent-manager-parallel.ts +3 -3
- package/agent-manager.ts +28 -14
- package/agent-scope.ts +1 -1
- package/agent-selection.ts +1 -1
- package/agent-serializer.ts +5 -1
- package/agent-templates.ts +1 -1
- package/agents.ts +31 -9
- package/artifacts.ts +1 -1
- package/async-execution.ts +28 -9
- package/chain-clarify.ts +39 -17
- package/chain-execution.ts +37 -12
- package/chain-serializer.ts +2 -2
- package/execution.ts +20 -11
- package/formatters.ts +3 -3
- package/index.ts +16 -16
- package/notify.ts +1 -1
- package/package.json +8 -1
- package/parallel-utils.ts +1 -0
- package/pi-spawn.ts +6 -9
- package/render.ts +7 -7
- package/schemas.ts +1 -1
- package/settings.ts +2 -2
- package/single-output.ts +50 -10
- package/skills.ts +5 -2
- package/subagent-executor.ts +55 -7
- package/subagent-runner.ts +32 -22
- package/types.ts +31 -5
- package/utils.ts +5 -1
- package/worktree.ts +240 -17
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.12.4] - 2026-04-04
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Added configurable subagent recursion depth controls with global `maxSubagentDepth` config and per-agent `maxSubagentDepth` frontmatter overrides. Child delegation now honors stricter inherited limits while still allowing per-agent tightening.
|
|
9
|
+
- Added optional worktree setup hooks via extension config (`worktreeSetupHook`, `worktreeSetupHookTimeoutMs`). Hooks run once per created worktree, receive JSON over stdin, return JSON on stdout, and can declare synthetic helper paths (e.g. `.venv`, copied local config files) to exclude from patch capture.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Added support for loading agents and skills from `.agents/` and `~/.agents/` directories.
|
|
13
|
+
- Switched internal source imports from `.js` to `.ts` so the extension can be loaded directly from TypeScript sources under the strip-types/transform-types runtime path.
|
|
14
|
+
- Declared pi runtime packages and `@sinclair/typebox` as peer dependencies so direct source-loading environments fail less often from missing package resolution.
|
|
15
|
+
- Single-output runs now preserve agent-written file contents instead of overwriting them with the final assistant receipt, and artifacts/truncation now follow the authoritative saved file content.
|
|
16
|
+
- Async/background runs now reuse the current Node executable and prefer the resolved current pi CLI path on all platforms, avoiding PATH drift from wrapped or version-pinned parent launches.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Added release documentation for TypeScript direct-runtime loading support and related package requirements.
|
|
20
|
+
|
|
5
21
|
## [0.12.2] - 2026-04-04
|
|
6
22
|
|
|
7
23
|
### Changed
|
package/README.md
CHANGED
|
@@ -69,6 +69,7 @@ output: context.md # writes to {chain_dir}/context.md
|
|
|
69
69
|
defaultReads: context.md # comma-separated files to read
|
|
70
70
|
defaultProgress: true # maintain progress.md
|
|
71
71
|
interactive: true # (parsed but not enforced in v1)
|
|
72
|
+
maxSubagentDepth: 1 # tighten nested delegation for this agent's children
|
|
72
73
|
---
|
|
73
74
|
|
|
74
75
|
Your system prompt goes here (the markdown body after frontmatter).
|
|
@@ -303,7 +304,7 @@ Chains can be created from the Agents Manager template picker ("Blank Chain"), o
|
|
|
303
304
|
- **Parallel-in-Chain**: Fan-out/fan-in patterns with `{ parallel: [...] }` steps within chains
|
|
304
305
|
- **Worktree Isolation**: `worktree: true` gives each parallel agent its own git worktree, preventing filesystem conflicts during concurrent execution
|
|
305
306
|
- **Chain Clarification TUI**: Interactive preview/edit of chain templates and behaviors before execution
|
|
306
|
-
- **Agent Frontmatter Extensions**: Agents declare default chain behavior (`output`, `defaultReads`, `defaultProgress`, `skill`)
|
|
307
|
+
- **Agent Frontmatter Extensions**: Agents declare default chain behavior (`output`, `defaultReads`, `defaultProgress`, `skill`) plus optional recursion limits via `maxSubagentDepth`
|
|
307
308
|
- **Chain Artifacts**: Shared directory at `<tmpdir>/pi-chain-runs/{runId}/` for inter-step files
|
|
308
309
|
- **Solo Agent Output**: Agents with `output` write to temp dir and return path to caller
|
|
309
310
|
- **Live Progress Display**: Real-time visibility during sync execution showing current tool, recent output, tokens, and duration
|
|
@@ -589,7 +590,7 @@ Agent definitions are not loaded into LLM context by default. Management actions
|
|
|
589
590
|
Notes:
|
|
590
591
|
- `create` uses `config.scope` (`"user"` or `"project"`), not `agentScope`.
|
|
591
592
|
- `update`/`delete` use `agentScope` only for scope disambiguation when the same name exists in both scopes.
|
|
592
|
-
- Agent config mapping: `reads -> defaultReads`, `progress -> defaultProgress`, `extensions` controls extension sandboxing, and `tools` supports `mcp:` entries that map to direct MCP tools.
|
|
593
|
+
- Agent config mapping: `reads -> defaultReads`, `progress -> defaultProgress`, `extensions` controls extension sandboxing, `maxSubagentDepth` maps directly to agent frontmatter, and `tools` supports `mcp:` entries that map to direct MCP tools.
|
|
593
594
|
- To clear any optional field, set it to `false` or `""` (e.g., `{ model: false }` or `{ skills: "" }`). Both work for all string-typed fields.
|
|
594
595
|
|
|
595
596
|
## Parameters
|
|
@@ -695,14 +696,17 @@ After the parallel step completes, per-agent diff stats are appended to the outp
|
|
|
695
696
|
- Working tree must be clean (no uncommitted changes) — commit or stash first
|
|
696
697
|
- `node_modules/` is symlinked into each worktree to avoid reinstalling
|
|
697
698
|
- Worktree runs use the shared parallel/step `cwd`. Task-level `cwd` overrides must be omitted or match that shared `cwd`; if you need different working directories, disable `worktree` or split the run.
|
|
699
|
+
- If `worktreeSetupHook` is configured, it must return valid JSON and complete before timeout
|
|
698
700
|
|
|
699
701
|
**What happens under the hood:**
|
|
700
702
|
|
|
701
703
|
1. `git worktree add` creates a temporary worktree per agent in `<tmpdir>/pi-worktree-*`
|
|
702
|
-
2.
|
|
703
|
-
3.
|
|
704
|
-
4.
|
|
705
|
-
5.
|
|
704
|
+
2. Optional `worktreeSetupHook` runs once per worktree (JSON in on stdin, JSON out on stdout)
|
|
705
|
+
3. Each agent runs in its worktree's cwd (preserving subdirectory context)
|
|
706
|
+
4. Before diff capture, declared synthetic helper paths (for example `.venv` or copied local config files) are removed
|
|
707
|
+
5. After execution, `git add -A && git diff --cached` captures all real changes (committed, modified, and new files)
|
|
708
|
+
6. Diff stats appear in the aggregated output; full `.patch` files are written to the artifacts directory
|
|
709
|
+
7. Worktrees and temp branches are cleaned up in a `finally` block
|
|
706
710
|
|
|
707
711
|
If you use [pi-prompt-template-model](https://github.com/nicobailon/pi-prompt-template-model), worktree isolation is also available via `worktree: true` in chain template frontmatter or the `--worktree` CLI flag on `chain-prompts`. `pi-prompt-template-model` compare-style prompts can route through the same worktree machinery too; see the `pi-prompt-template-model` README and `examples/` directory for the installable prompt templates.
|
|
708
712
|
|
|
@@ -749,6 +753,54 @@ Session root resolution follows this precedence:
|
|
|
749
753
|
|
|
750
754
|
Sessions are always enabled — every subagent run gets a session directory for tracking.
|
|
751
755
|
|
|
756
|
+
### `maxSubagentDepth`
|
|
757
|
+
|
|
758
|
+
`maxSubagentDepth` sets the default recursion limit for nested delegation when no inherited `PI_SUBAGENT_MAX_DEPTH` is already in effect. Eg:
|
|
759
|
+
|
|
760
|
+
```json
|
|
761
|
+
{
|
|
762
|
+
"maxSubagentDepth": 1
|
|
763
|
+
}
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
Per-agent `maxSubagentDepth` can tighten that limit further for child runs, but it does not relax an already inherited stricter limit.
|
|
767
|
+
|
|
768
|
+
### `worktreeSetupHook`
|
|
769
|
+
|
|
770
|
+
`worktreeSetupHook` configures an optional setup hook for worktree-isolated parallel runs. The hook runs once per created worktree, after `git worktree add` succeeds and before the agent starts.
|
|
771
|
+
|
|
772
|
+
```json
|
|
773
|
+
{
|
|
774
|
+
"worktreeSetupHook": "./scripts/setup-worktree.mjs"
|
|
775
|
+
}
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
Path rules:
|
|
779
|
+
- Must be an absolute path or a repo-relative path
|
|
780
|
+
- Bare command names from `PATH` are rejected
|
|
781
|
+
- `~/...` is supported for home-directory hooks
|
|
782
|
+
|
|
783
|
+
Hook I/O contract (JSON only):
|
|
784
|
+
- stdin: one JSON object with `repoRoot`, `worktreePath`, `agentCwd`, `branch`, `index`, `runId`, and `baseCommit`
|
|
785
|
+
- stdout: one JSON object, e.g. `{ "syntheticPaths": [".venv", ".env.local"] }`
|
|
786
|
+
|
|
787
|
+
`syntheticPaths` must be relative to the worktree root. These paths are removed before diff capture so helper files/symlinks do not pollute generated patches.
|
|
788
|
+
|
|
789
|
+
Tracked-file edits are never excluded. If the hook tries to mark tracked paths as synthetic, setup fails.
|
|
790
|
+
|
|
791
|
+
### `worktreeSetupHookTimeoutMs`
|
|
792
|
+
|
|
793
|
+
Optional timeout (milliseconds) for each worktree hook invocation.
|
|
794
|
+
|
|
795
|
+
```json
|
|
796
|
+
{
|
|
797
|
+
"worktreeSetupHook": "./scripts/setup-worktree.mjs",
|
|
798
|
+
"worktreeSetupHookTimeoutMs": 45000
|
|
799
|
+
}
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
Default: `30000` ms.
|
|
803
|
+
|
|
752
804
|
## Chain Directory
|
|
753
805
|
Each chain run creates `<tmpdir>/pi-chain-runs/{runId}/` containing:
|
|
754
806
|
- `context.md` - Scout/context-builder output
|
|
@@ -821,6 +873,14 @@ Subagents can themselves call the `subagent` tool, which risks unbounded recursi
|
|
|
821
873
|
|
|
822
874
|
By default nesting is limited to **2 levels**: `main session → subagent → sub-subagent`. Any deeper `subagent` calls are blocked and return an error with guidance to the calling agent.
|
|
823
875
|
|
|
876
|
+
You can configure the limit in three places:
|
|
877
|
+
|
|
878
|
+
1. `PI_SUBAGENT_MAX_DEPTH` in the environment before starting `pi`
|
|
879
|
+
2. `config.maxSubagentDepth` in `~/.pi/agent/extensions/subagent/config.json`
|
|
880
|
+
3. `maxSubagentDepth` in an agent's frontmatter to tighten the limit for that agent's child runs
|
|
881
|
+
|
|
882
|
+
Environment inherits downward and wins for the current process. Per-agent limits can tighten child delegation but do not relax an already inherited stricter limit.
|
|
883
|
+
|
|
824
884
|
Override the limit with `PI_SUBAGENT_MAX_DEPTH` **set before starting `pi`**:
|
|
825
885
|
|
|
826
886
|
```bash
|
package/agent-management.ts
CHANGED
|
@@ -9,11 +9,11 @@ import {
|
|
|
9
9
|
type ChainConfig,
|
|
10
10
|
type ChainStepConfig,
|
|
11
11
|
discoverAgentsAll,
|
|
12
|
-
} from "./agents.
|
|
13
|
-
import { serializeAgent } from "./agent-serializer.
|
|
14
|
-
import { serializeChain } from "./chain-serializer.
|
|
15
|
-
import { discoverAvailableSkills } from "./skills.
|
|
16
|
-
import type { Details } from "./types.
|
|
12
|
+
} from "./agents.ts";
|
|
13
|
+
import { serializeAgent } from "./agent-serializer.ts";
|
|
14
|
+
import { serializeChain } from "./chain-serializer.ts";
|
|
15
|
+
import { discoverAvailableSkills } from "./skills.ts";
|
|
16
|
+
import type { Details } from "./types.ts";
|
|
17
17
|
|
|
18
18
|
type ManagementAction = "list" | "get" | "create" | "update" | "delete";
|
|
19
19
|
type ManagementScope = "user" | "project";
|
|
@@ -235,6 +235,12 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
235
235
|
if (typeof cfg.progress !== "boolean") return "config.progress must be a boolean when provided.";
|
|
236
236
|
target.defaultProgress = cfg.progress;
|
|
237
237
|
}
|
|
238
|
+
if (hasKey(cfg, "maxSubagentDepth")) {
|
|
239
|
+
if (cfg.maxSubagentDepth === false || cfg.maxSubagentDepth === "") target.maxSubagentDepth = undefined;
|
|
240
|
+
else if (typeof cfg.maxSubagentDepth === "number" && Number.isInteger(cfg.maxSubagentDepth) && cfg.maxSubagentDepth >= 0) {
|
|
241
|
+
target.maxSubagentDepth = cfg.maxSubagentDepth;
|
|
242
|
+
} else return "config.maxSubagentDepth must be an integer >= 0 or false when provided.";
|
|
243
|
+
}
|
|
238
244
|
return undefined;
|
|
239
245
|
}
|
|
240
246
|
|
|
@@ -293,6 +299,7 @@ export function formatAgentDetail(agent: AgentConfig): string {
|
|
|
293
299
|
if (agent.output) lines.push(`Output: ${agent.output}`);
|
|
294
300
|
if (agent.defaultReads?.length) lines.push(`Reads: ${agent.defaultReads.join(", ")}`);
|
|
295
301
|
if (agent.defaultProgress) lines.push("Progress: true");
|
|
302
|
+
if (agent.maxSubagentDepth !== undefined) lines.push(`Max subagent depth: ${agent.maxSubagentDepth}`);
|
|
296
303
|
if (agent.systemPrompt.trim()) lines.push("", "System Prompt:", agent.systemPrompt);
|
|
297
304
|
return lines.join("\n");
|
|
298
305
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
|
|
3
|
-
import type { ChainConfig, ChainStepConfig } from "./agents.
|
|
4
|
-
import { row, renderFooter, renderHeader, formatPath, formatScrollInfo } from "./render-helpers.
|
|
3
|
+
import type { ChainConfig, ChainStepConfig } from "./agents.ts";
|
|
4
|
+
import { row, renderFooter, renderHeader, formatPath, formatScrollInfo } from "./render-helpers.ts";
|
|
5
5
|
|
|
6
6
|
export interface ChainDetailState {
|
|
7
7
|
scrollOffset: number;
|
package/agent-manager-detail.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
|
|
3
|
-
import type { AgentConfig } from "./agents.
|
|
4
|
-
import { formatDuration } from "./formatters.
|
|
5
|
-
import type { RunEntry } from "./run-history.
|
|
6
|
-
import { buildSkillInjection, resolveSkills } from "./skills.
|
|
7
|
-
import { ensureCursorVisible, getCursorDisplayPos, renderEditor, wrapText } from "./text-editor.
|
|
8
|
-
import type { TextEditorState } from "./text-editor.
|
|
9
|
-
import { pad, row, renderHeader, renderFooter, formatPath, formatScrollInfo } from "./render-helpers.
|
|
3
|
+
import type { AgentConfig } from "./agents.ts";
|
|
4
|
+
import { formatDuration } from "./formatters.ts";
|
|
5
|
+
import type { RunEntry } from "./run-history.ts";
|
|
6
|
+
import { buildSkillInjection, resolveSkills } from "./skills.ts";
|
|
7
|
+
import { ensureCursorVisible, getCursorDisplayPos, renderEditor, wrapText } from "./text-editor.ts";
|
|
8
|
+
import type { TextEditorState } from "./text-editor.ts";
|
|
9
|
+
import { pad, row, renderHeader, renderFooter, formatPath, formatScrollInfo } from "./render-helpers.ts";
|
|
10
10
|
|
|
11
11
|
export interface DetailState {
|
|
12
12
|
resolved: boolean;
|
|
@@ -58,6 +58,7 @@ function buildDetailLines(
|
|
|
58
58
|
const output = agent.output ?? "(none)";
|
|
59
59
|
const reads = agent.defaultReads && agent.defaultReads.length > 0 ? agent.defaultReads.join(", ") : "(none)";
|
|
60
60
|
const progress = agent.defaultProgress ? "on" : "off";
|
|
61
|
+
const maxSubagentDepth = agent.maxSubagentDepth !== undefined ? String(agent.maxSubagentDepth) : "(default)";
|
|
61
62
|
|
|
62
63
|
lines.push(renderFieldLine("Model:", agent.model ?? "default", contentWidth, theme));
|
|
63
64
|
lines.push(renderFieldLine("Thinking:", agent.thinking ?? "off", contentWidth, theme));
|
|
@@ -71,6 +72,7 @@ function buildDetailLines(
|
|
|
71
72
|
lines.push(renderFieldLine("Output:", output, contentWidth, theme));
|
|
72
73
|
lines.push(renderFieldLine("Reads:", reads, contentWidth, theme));
|
|
73
74
|
lines.push(renderFieldLine("Progress:", progress, contentWidth, theme));
|
|
75
|
+
lines.push(renderFieldLine("Max depth:", maxSubagentDepth, contentWidth, theme));
|
|
74
76
|
|
|
75
77
|
if (agent.extraFields) {
|
|
76
78
|
for (const [key, value] of Object.entries(agent.extraFields)) {
|
package/agent-manager-edit.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
|
|
3
|
-
import type { AgentConfig } from "./agents.
|
|
4
|
-
import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "./text-editor.
|
|
5
|
-
import type { TextEditorState } from "./text-editor.
|
|
6
|
-
import { pad, row, renderHeader, renderFooter, formatScrollInfo } from "./render-helpers.
|
|
3
|
+
import type { AgentConfig } from "./agents.ts";
|
|
4
|
+
import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "./text-editor.ts";
|
|
5
|
+
import type { TextEditorState } from "./text-editor.ts";
|
|
6
|
+
import { pad, row, renderHeader, renderFooter, formatScrollInfo } from "./render-helpers.ts";
|
|
7
7
|
|
|
8
8
|
export interface ModelInfo { provider: string; id: string; fullId: string; }
|
|
9
9
|
export interface SkillInfo { name: string; source: string; description?: string; }
|
package/agent-manager-list.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import type { AgentSource } from "./agents.
|
|
2
|
+
import type { AgentSource } from "./agents.ts";
|
|
3
3
|
import { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
4
|
-
import { pad, row, renderHeader, renderFooter, fuzzyFilter, formatScrollInfo } from "./render-helpers.
|
|
4
|
+
import { pad, row, renderHeader, renderFooter, fuzzyFilter, formatScrollInfo } from "./render-helpers.ts";
|
|
5
5
|
|
|
6
6
|
export interface ListAgent {
|
|
7
7
|
id: string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
3
|
-
import type { TextEditorState } from "./text-editor.
|
|
4
|
-
import { createEditorState, handleEditorInput, renderEditor, wrapText, getCursorDisplayPos, ensureCursorVisible } from "./text-editor.
|
|
5
|
-
import { pad, row, renderHeader, renderFooter, fuzzyFilter } from "./render-helpers.
|
|
3
|
+
import type { TextEditorState } from "./text-editor.ts";
|
|
4
|
+
import { createEditorState, handleEditorInput, renderEditor, wrapText, getCursorDisplayPos, ensureCursorVisible } from "./text-editor.ts";
|
|
5
|
+
import { pad, row, renderHeader, renderFooter, fuzzyFilter } from "./render-helpers.ts";
|
|
6
6
|
|
|
7
7
|
export interface ParallelSlot {
|
|
8
8
|
agentName: string;
|
package/agent-manager.ts
CHANGED
|
@@ -3,19 +3,19 @@ import * as path from "node:path";
|
|
|
3
3
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
4
4
|
import type { Component, TUI } from "@mariozechner/pi-tui";
|
|
5
5
|
import { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
6
|
-
import type { AgentConfig, ChainConfig } from "./agents.
|
|
7
|
-
import { serializeAgent } from "./agent-serializer.
|
|
8
|
-
import { TEMPLATE_ITEMS, type AgentTemplate, type TemplateItem } from "./agent-templates.
|
|
9
|
-
import { parseChain, serializeChain } from "./chain-serializer.
|
|
10
|
-
import { renderList, handleListInput, type ListAgent, type ListState, type ListAction } from "./agent-manager-list.
|
|
11
|
-
import { createParallelState, handleParallelInput, renderParallel, formatParallelTitle, type ParallelState, type AgentOption } from "./agent-manager-parallel.
|
|
12
|
-
import { renderDetail, handleDetailInput, renderTaskInput, type DetailState, type DetailAction } from "./agent-manager-detail.
|
|
13
|
-
import { renderChainDetail, handleChainDetailInput, type ChainDetailAction, type ChainDetailState } from "./agent-manager-chain-detail.
|
|
14
|
-
import { createEditState, handleEditInput, renderEdit, type EditScreen, type EditState, type ModelInfo, type SkillInfo } from "./agent-manager-edit.
|
|
15
|
-
import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "./text-editor.
|
|
16
|
-
import type { TextEditorState } from "./text-editor.
|
|
17
|
-
import { loadRunsForAgent } from "./run-history.
|
|
18
|
-
import { pad, row, renderHeader, renderFooter } from "./render-helpers.
|
|
6
|
+
import type { AgentConfig, ChainConfig } from "./agents.ts";
|
|
7
|
+
import { serializeAgent } from "./agent-serializer.ts";
|
|
8
|
+
import { TEMPLATE_ITEMS, type AgentTemplate, type TemplateItem } from "./agent-templates.ts";
|
|
9
|
+
import { parseChain, serializeChain } from "./chain-serializer.ts";
|
|
10
|
+
import { renderList, handleListInput, type ListAgent, type ListState, type ListAction } from "./agent-manager-list.ts";
|
|
11
|
+
import { createParallelState, handleParallelInput, renderParallel, formatParallelTitle, type ParallelState, type AgentOption } from "./agent-manager-parallel.ts";
|
|
12
|
+
import { renderDetail, handleDetailInput, renderTaskInput, type DetailState, type DetailAction } from "./agent-manager-detail.ts";
|
|
13
|
+
import { renderChainDetail, handleChainDetailInput, type ChainDetailAction, type ChainDetailState } from "./agent-manager-chain-detail.ts";
|
|
14
|
+
import { createEditState, handleEditInput, renderEdit, type EditScreen, type EditState, type ModelInfo, type SkillInfo } from "./agent-manager-edit.ts";
|
|
15
|
+
import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "./text-editor.ts";
|
|
16
|
+
import type { TextEditorState } from "./text-editor.ts";
|
|
17
|
+
import { loadRunsForAgent } from "./run-history.ts";
|
|
18
|
+
import { pad, row, renderHeader, renderFooter } from "./render-helpers.ts";
|
|
19
19
|
|
|
20
20
|
export type ManagerResult =
|
|
21
21
|
| { action: "launch"; agent: string; task: string; skipClarify?: boolean }
|
|
@@ -61,8 +61,22 @@ export class AgentManagerComponent implements Component {
|
|
|
61
61
|
private templateCursor = 0;
|
|
62
62
|
private statusMessage?: StatusMessage;
|
|
63
63
|
private nextId = 1;
|
|
64
|
+
private tui: TUI;
|
|
65
|
+
private theme: Theme;
|
|
66
|
+
private agentData: AgentData;
|
|
67
|
+
private models: ModelInfo[];
|
|
68
|
+
private skills: SkillInfo[];
|
|
69
|
+
private done: (result: ManagerResult) => void;
|
|
64
70
|
|
|
65
|
-
constructor(
|
|
71
|
+
constructor(tui: TUI, theme: Theme, agentData: AgentData, models: ModelInfo[], skills: SkillInfo[], done: (result: ManagerResult) => void) {
|
|
72
|
+
this.tui = tui;
|
|
73
|
+
this.theme = theme;
|
|
74
|
+
this.agentData = agentData;
|
|
75
|
+
this.models = models;
|
|
76
|
+
this.skills = skills;
|
|
77
|
+
this.done = done;
|
|
78
|
+
this.loadEntries();
|
|
79
|
+
}
|
|
66
80
|
|
|
67
81
|
private loadEntries(): void {
|
|
68
82
|
const overridden = new Set([...this.agentData.user, ...this.agentData.project].map((c) => c.name));
|
package/agent-scope.ts
CHANGED
package/agent-selection.ts
CHANGED
package/agent-serializer.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import type { AgentConfig } from "./agents.
|
|
2
|
+
import type { AgentConfig } from "./agents.ts";
|
|
3
3
|
|
|
4
4
|
export const KNOWN_FIELDS = new Set([
|
|
5
5
|
"name",
|
|
@@ -14,6 +14,7 @@ export const KNOWN_FIELDS = new Set([
|
|
|
14
14
|
"defaultReads",
|
|
15
15
|
"defaultProgress",
|
|
16
16
|
"interactive",
|
|
17
|
+
"maxSubagentDepth",
|
|
17
18
|
]);
|
|
18
19
|
|
|
19
20
|
function joinComma(values: string[] | undefined): string | undefined {
|
|
@@ -52,6 +53,9 @@ export function serializeAgent(config: AgentConfig): string {
|
|
|
52
53
|
|
|
53
54
|
if (config.defaultProgress) lines.push("defaultProgress: true");
|
|
54
55
|
if (config.interactive) lines.push("interactive: true");
|
|
56
|
+
if (Number.isInteger(config.maxSubagentDepth) && config.maxSubagentDepth >= 0) {
|
|
57
|
+
lines.push(`maxSubagentDepth: ${config.maxSubagentDepth}`);
|
|
58
|
+
}
|
|
55
59
|
|
|
56
60
|
if (config.extraFields) {
|
|
57
61
|
for (const [key, value] of Object.entries(config.extraFields)) {
|
package/agent-templates.ts
CHANGED
package/agents.ts
CHANGED
|
@@ -6,10 +6,10 @@ import * as fs from "node:fs";
|
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
-
import { KNOWN_FIELDS } from "./agent-serializer.
|
|
10
|
-
import { parseChain } from "./chain-serializer.
|
|
11
|
-
import { mergeAgentsForScope } from "./agent-selection.
|
|
12
|
-
import { parseFrontmatter } from "./frontmatter.
|
|
9
|
+
import { KNOWN_FIELDS } from "./agent-serializer.ts";
|
|
10
|
+
import { parseChain } from "./chain-serializer.ts";
|
|
11
|
+
import { mergeAgentsForScope } from "./agent-selection.ts";
|
|
12
|
+
import { parseFrontmatter } from "./frontmatter.ts";
|
|
13
13
|
|
|
14
14
|
export type AgentScope = "user" | "project" | "both";
|
|
15
15
|
|
|
@@ -32,6 +32,7 @@ export interface AgentConfig {
|
|
|
32
32
|
defaultReads?: string[];
|
|
33
33
|
defaultProgress?: boolean;
|
|
34
34
|
interactive?: boolean;
|
|
35
|
+
maxSubagentDepth?: number;
|
|
35
36
|
extraFields?: Record<string, string>;
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -134,6 +135,8 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
134
135
|
if (!KNOWN_FIELDS.has(key)) extraFields[key] = value;
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
const parsedMaxSubagentDepth = Number(frontmatter.maxSubagentDepth);
|
|
139
|
+
|
|
137
140
|
agents.push({
|
|
138
141
|
name: frontmatter.name,
|
|
139
142
|
description: frontmatter.description,
|
|
@@ -151,6 +154,10 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
151
154
|
defaultReads: defaultReads && defaultReads.length > 0 ? defaultReads : undefined,
|
|
152
155
|
defaultProgress: frontmatter.defaultProgress === "true",
|
|
153
156
|
interactive: frontmatter.interactive === "true",
|
|
157
|
+
maxSubagentDepth:
|
|
158
|
+
Number.isInteger(parsedMaxSubagentDepth) && parsedMaxSubagentDepth >= 0
|
|
159
|
+
? parsedMaxSubagentDepth
|
|
160
|
+
: undefined,
|
|
154
161
|
extraFields: Object.keys(extraFields).length > 0 ? extraFields : undefined,
|
|
155
162
|
});
|
|
156
163
|
}
|
|
@@ -205,6 +212,9 @@ function isDirectory(p: string): boolean {
|
|
|
205
212
|
function findNearestProjectAgentsDir(cwd: string): string | null {
|
|
206
213
|
let currentDir = cwd;
|
|
207
214
|
while (true) {
|
|
215
|
+
const candidateAlt = path.join(currentDir, ".agents");
|
|
216
|
+
if (isDirectory(candidateAlt)) return candidateAlt;
|
|
217
|
+
|
|
208
218
|
const candidate = path.join(currentDir, ".pi", "agents");
|
|
209
219
|
if (isDirectory(candidate)) return candidate;
|
|
210
220
|
|
|
@@ -217,11 +227,16 @@ function findNearestProjectAgentsDir(cwd: string): string | null {
|
|
|
217
227
|
const BUILTIN_AGENTS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "agents");
|
|
218
228
|
|
|
219
229
|
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
|
220
|
-
const
|
|
230
|
+
const userDirOld = path.join(os.homedir(), ".pi", "agent", "agents");
|
|
231
|
+
const userDirNew = path.join(os.homedir(), ".agents");
|
|
221
232
|
const projectAgentsDir = findNearestProjectAgentsDir(cwd);
|
|
222
233
|
|
|
223
234
|
const builtinAgents = loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin");
|
|
224
|
-
|
|
235
|
+
|
|
236
|
+
const userAgentsOld = scope === "project" ? [] : loadAgentsFromDir(userDirOld, "user");
|
|
237
|
+
const userAgentsNew = scope === "project" ? [] : loadAgentsFromDir(userDirNew, "user");
|
|
238
|
+
const userAgents = [...userAgentsOld, ...userAgentsNew];
|
|
239
|
+
|
|
225
240
|
const projectAgents = scope === "user" || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, "project");
|
|
226
241
|
const agents = mergeAgentsForScope(scope, userAgents, projectAgents, builtinAgents);
|
|
227
242
|
|
|
@@ -236,16 +251,23 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
236
251
|
userDir: string;
|
|
237
252
|
projectDir: string | null;
|
|
238
253
|
} {
|
|
239
|
-
const
|
|
254
|
+
const userDirOld = path.join(os.homedir(), ".pi", "agent", "agents");
|
|
255
|
+
const userDirNew = path.join(os.homedir(), ".agents");
|
|
240
256
|
const projectDir = findNearestProjectAgentsDir(cwd);
|
|
241
257
|
|
|
242
258
|
const builtin = loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin");
|
|
243
|
-
const user =
|
|
259
|
+
const user = [
|
|
260
|
+
...loadAgentsFromDir(userDirOld, "user"),
|
|
261
|
+
...loadAgentsFromDir(userDirNew, "user"),
|
|
262
|
+
];
|
|
244
263
|
const project = projectDir ? loadAgentsFromDir(projectDir, "project") : [];
|
|
245
264
|
const chains = [
|
|
246
|
-
...loadChainsFromDir(
|
|
265
|
+
...loadChainsFromDir(userDirOld, "user"),
|
|
266
|
+
...loadChainsFromDir(userDirNew, "user"),
|
|
247
267
|
...(projectDir ? loadChainsFromDir(projectDir, "project") : []),
|
|
248
268
|
];
|
|
249
269
|
|
|
270
|
+
const userDir = fs.existsSync(userDirNew) ? userDirNew : userDirOld;
|
|
271
|
+
|
|
250
272
|
return { builtin, user, project, chains, userDir, projectDir };
|
|
251
273
|
}
|
package/artifacts.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import type { ArtifactPaths } from "./types.
|
|
4
|
+
import type { ArtifactPaths } from "./types.ts";
|
|
5
5
|
|
|
6
6
|
const TEMP_ARTIFACTS_DIR = path.join(os.tmpdir(), "pi-subagent-artifacts");
|
|
7
7
|
const CLEANUP_MARKER_FILE = ".last-cleanup";
|
package/async-execution.ts
CHANGED
|
@@ -9,20 +9,21 @@ import * as path from "node:path";
|
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { createRequire } from "node:module";
|
|
11
11
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
12
|
-
import type { AgentConfig } from "./agents.
|
|
13
|
-
import { applyThinkingSuffix } from "./pi-args.
|
|
14
|
-
import { injectSingleOutputInstruction, resolveSingleOutputPath } from "./single-output.
|
|
15
|
-
import { isParallelStep, resolveStepBehavior, type ChainStep, type SequentialStep, type StepOverrides } from "./settings.
|
|
16
|
-
import type { RunnerStep } from "./parallel-utils.
|
|
17
|
-
import { resolvePiPackageRoot } from "./pi-spawn.
|
|
18
|
-
import { buildSkillInjection, normalizeSkillInput, resolveSkills } from "./skills.
|
|
12
|
+
import type { AgentConfig } from "./agents.ts";
|
|
13
|
+
import { applyThinkingSuffix } from "./pi-args.ts";
|
|
14
|
+
import { injectSingleOutputInstruction, resolveSingleOutputPath } from "./single-output.ts";
|
|
15
|
+
import { isParallelStep, resolveStepBehavior, type ChainStep, type SequentialStep, type StepOverrides } from "./settings.ts";
|
|
16
|
+
import type { RunnerStep } from "./parallel-utils.ts";
|
|
17
|
+
import { resolvePiPackageRoot } from "./pi-spawn.ts";
|
|
18
|
+
import { buildSkillInjection, normalizeSkillInput, resolveSkills } from "./skills.ts";
|
|
19
19
|
import {
|
|
20
20
|
type ArtifactConfig,
|
|
21
21
|
type Details,
|
|
22
22
|
type MaxOutputConfig,
|
|
23
23
|
ASYNC_DIR,
|
|
24
24
|
RESULTS_DIR,
|
|
25
|
-
|
|
25
|
+
resolveChildMaxSubagentDepth,
|
|
26
|
+
} from "./types.ts";
|
|
26
27
|
|
|
27
28
|
const require = createRequire(import.meta.url);
|
|
28
29
|
const piPackageRoot = resolvePiPackageRoot();
|
|
@@ -65,6 +66,9 @@ export interface AsyncChainParams {
|
|
|
65
66
|
sessionRoot?: string;
|
|
66
67
|
chainSkills?: string[];
|
|
67
68
|
sessionFilesByFlatIndex?: (string | undefined)[];
|
|
69
|
+
maxSubagentDepth: number;
|
|
70
|
+
worktreeSetupHook?: string;
|
|
71
|
+
worktreeSetupHookTimeoutMs?: number;
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
export interface AsyncSingleParams {
|
|
@@ -81,6 +85,9 @@ export interface AsyncSingleParams {
|
|
|
81
85
|
sessionFile?: string;
|
|
82
86
|
skills?: string[];
|
|
83
87
|
output?: string | false;
|
|
88
|
+
maxSubagentDepth: number;
|
|
89
|
+
worktreeSetupHook?: string;
|
|
90
|
+
worktreeSetupHookTimeoutMs?: number;
|
|
84
91
|
}
|
|
85
92
|
|
|
86
93
|
export interface AsyncExecutionResult {
|
|
@@ -106,7 +113,7 @@ function spawnRunner(cfg: object, suffix: string, cwd: string): number | undefin
|
|
|
106
113
|
fs.writeFileSync(cfgPath, JSON.stringify(cfg));
|
|
107
114
|
const runner = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-runner.ts");
|
|
108
115
|
|
|
109
|
-
const proc = spawn(
|
|
116
|
+
const proc = spawn(process.execPath, [jitiCliPath, runner, cfgPath], {
|
|
110
117
|
cwd,
|
|
111
118
|
detached: true,
|
|
112
119
|
stdio: "ignore",
|
|
@@ -134,6 +141,9 @@ export function executeAsyncChain(
|
|
|
134
141
|
shareEnabled,
|
|
135
142
|
sessionRoot,
|
|
136
143
|
sessionFilesByFlatIndex,
|
|
144
|
+
maxSubagentDepth,
|
|
145
|
+
worktreeSetupHook,
|
|
146
|
+
worktreeSetupHookTimeoutMs,
|
|
137
147
|
} = params;
|
|
138
148
|
const chainSkills = params.chainSkills ?? [];
|
|
139
149
|
|
|
@@ -197,6 +207,7 @@ export function executeAsyncChain(
|
|
|
197
207
|
skills: resolvedSkills.map((r) => r.name),
|
|
198
208
|
outputPath,
|
|
199
209
|
sessionFile,
|
|
210
|
+
maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, a.maxSubagentDepth),
|
|
200
211
|
};
|
|
201
212
|
};
|
|
202
213
|
|
|
@@ -244,6 +255,8 @@ export function executeAsyncChain(
|
|
|
244
255
|
asyncDir,
|
|
245
256
|
sessionId: ctx.currentSessionId,
|
|
246
257
|
piPackageRoot,
|
|
258
|
+
worktreeSetupHook,
|
|
259
|
+
worktreeSetupHookTimeoutMs,
|
|
247
260
|
},
|
|
248
261
|
id,
|
|
249
262
|
runnerCwd,
|
|
@@ -301,6 +314,9 @@ export function executeAsyncSingle(
|
|
|
301
314
|
shareEnabled,
|
|
302
315
|
sessionRoot,
|
|
303
316
|
sessionFile,
|
|
317
|
+
maxSubagentDepth,
|
|
318
|
+
worktreeSetupHook,
|
|
319
|
+
worktreeSetupHookTimeoutMs,
|
|
304
320
|
} = params;
|
|
305
321
|
const skillNames = params.skills ?? agentConfig.skills ?? [];
|
|
306
322
|
const { resolved: resolvedSkills } = resolveSkills(skillNames, ctx.cwd);
|
|
@@ -341,6 +357,7 @@ export function executeAsyncSingle(
|
|
|
341
357
|
skills: resolvedSkills.map((r) => r.name),
|
|
342
358
|
outputPath,
|
|
343
359
|
sessionFile,
|
|
360
|
+
maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, agentConfig.maxSubagentDepth),
|
|
344
361
|
},
|
|
345
362
|
],
|
|
346
363
|
resultPath: path.join(RESULTS_DIR, `${id}.json`),
|
|
@@ -354,6 +371,8 @@ export function executeAsyncSingle(
|
|
|
354
371
|
asyncDir,
|
|
355
372
|
sessionId: ctx.currentSessionId,
|
|
356
373
|
piPackageRoot,
|
|
374
|
+
worktreeSetupHook,
|
|
375
|
+
worktreeSetupHookTimeoutMs,
|
|
357
376
|
},
|
|
358
377
|
id,
|
|
359
378
|
runnerCwd,
|