@tintinweb/pi-subagents 0.2.7 → 0.3.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 +38 -0
- package/README.md +28 -24
- package/package.json +4 -4
- package/src/agent-runner.ts +69 -39
- package/src/agent-types.ts +124 -84
- package/src/custom-agents.ts +10 -7
- package/src/default-agents.ts +164 -0
- package/src/index.ts +251 -173
- package/src/model-resolver.ts +81 -0
- package/src/prompts.ts +26 -141
- package/src/types.ts +14 -34
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,43 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.0] - 2026-03-08
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Case-insensitive agent type lookup** — `"explore"`, `"EXPLORE"`, and `"Explore"` all resolve to the same agent. LLMs frequently lowercase type names; this prevents validation failures.
|
|
12
|
+
- **Unknown type fallback** — unrecognized agent types fall back to `general-purpose` with a note, instead of hard-rejecting. Matches Claude Code behavior.
|
|
13
|
+
- **Dynamic tool list for general-purpose** — `builtinToolNames` is now optional in `AgentConfig`. When omitted, the agent gets all tools from `TOOL_FACTORIES` at lookup time, so new tools added upstream are automatically available.
|
|
14
|
+
- **Agent source indicators in `/agents` menu** — `•` (project), `◦` (global), `✕` (disabled) with legend. Defaults are unmarked.
|
|
15
|
+
- **Disabled agents visible in UI** — disabled agents now show in the "Agent types" list (marked `✕`) with an Enable action, instead of being invisible.
|
|
16
|
+
- **Enable action** — re-enable a disabled agent from the `/agents` menu. Stub files are auto-cleaned.
|
|
17
|
+
- **Disable action for all agent types** — custom and ejected default agents can now be disabled from the UI, not just built-in defaults.
|
|
18
|
+
- `resolveType()` export — case-insensitive type name resolution for external use.
|
|
19
|
+
- `getAllTypes()` export — returns all agent names including disabled (for UI listing).
|
|
20
|
+
- `source` field on `AgentConfig` — tracks where an agent was loaded from (`"default"`, `"project"`, `"global"`).
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- **Model resolver checks auth for exact matches** — `resolveModel("anthropic/claude-haiku-4-5-20251001")` now fails gracefully when no Anthropic API key is configured, instead of returning a model that errors at the API call. Explore silently falls back to the parent model on non-Anthropic setups.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- **Unified agent registry** — built-in and custom agents now use the same `AgentConfig` type and a single registry. No more separate code paths for built-in vs custom agents.
|
|
27
|
+
- **Default agents are overridable** — creating a `.md` file with the same name as a default agent (e.g. `.pi/agents/Explore.md`) overrides it.
|
|
28
|
+
- **`/agents` menu** — "Agent types" list shows defaults and custom agents together with source indicators. Default agents get Eject/Disable actions; overridden defaults get Reset to default.
|
|
29
|
+
- **Eject action** — export a default agent's embedded config as a `.md` file to project or personal location for customization.
|
|
30
|
+
- **Model labels** — provider-agnostic: strips `provider/` prefix and `-YYYYMMDD` date suffix (e.g. `anthropic/claude-haiku-4-5-20251001` → `claude-haiku-4-5`). Works for any provider.
|
|
31
|
+
- **New frontmatter fields** — `display_name` (UI display name) and `enabled` (default: true; set to false to disable).
|
|
32
|
+
- **Menu navigation** — Esc in agent detail returns to agent list (not main menu).
|
|
33
|
+
|
|
34
|
+
### Removed
|
|
35
|
+
- **`statusline-setup` and `claude-code-guide` agents** — removed as built-in types (never spawned programmatically). Users can recreate them as custom agents if needed.
|
|
36
|
+
- `BuiltinSubagentType` union type, `SUBAGENT_TYPES` array, `DISPLAY_NAMES` map, `SubagentTypeConfig` interface — replaced by unified `AgentConfig`.
|
|
37
|
+
- `buildSystemPrompt()` switch statement — replaced by config-driven `buildAgentPrompt()`.
|
|
38
|
+
- `HAIKU_MODEL_IDS` fallback array — Explore's haiku default is now just the `model` field in its config.
|
|
39
|
+
- `BUILTIN_MODEL_LABELS` — model labels now derived from config.
|
|
40
|
+
- `ALL_TOOLS` hardcoded constant — general-purpose now derives tools dynamically.
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
- `src/default-agents.ts` — embedded default configs for general-purpose, Explore, and Plan.
|
|
44
|
+
|
|
8
45
|
## [0.2.7] - 2026-03-08
|
|
9
46
|
|
|
10
47
|
### Fixed
|
|
@@ -130,6 +167,7 @@ Initial release.
|
|
|
130
167
|
- **Thinking level** — per-agent extended thinking control
|
|
131
168
|
- **`/agent` and `/agents` commands**
|
|
132
169
|
|
|
170
|
+
[0.3.0]: https://github.com/tintinweb/pi-subagents/compare/v0.2.7...v0.3.0
|
|
133
171
|
[0.2.7]: https://github.com/tintinweb/pi-subagents/compare/v0.2.6...v0.2.7
|
|
134
172
|
[0.2.6]: https://github.com/tintinweb/pi-subagents/compare/v0.2.5...v0.2.6
|
|
135
173
|
[0.2.5]: https://github.com/tintinweb/pi-subagents/compare/v0.2.4...v0.2.5
|
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ https://github.com/user-attachments/assets/5d1331e8-6d02-420b-b30a-dcbf838b1660
|
|
|
19
19
|
- **Mid-run steering** — inject messages into running agents to redirect their work without restarting
|
|
20
20
|
- **Session resume** — pick up where an agent left off, preserving full conversation context
|
|
21
21
|
- **Graceful turn limits** — agents get a "wrap up" warning before hard abort, producing clean partial results instead of cut-off output
|
|
22
|
+
- **Case-insensitive agent types** — `"explore"`, `"Explore"`, `"EXPLORE"` all work. Unknown types fall back to general-purpose with a note
|
|
22
23
|
- **Fuzzy model selection** — specify models by name (`"haiku"`, `"sonnet"`) instead of full IDs, with automatic filtering to only available/configured models
|
|
23
24
|
- **Context inheritance** — optionally fork the parent conversation into a sub-agent so it knows what's been discussed
|
|
24
25
|
|
|
@@ -75,21 +76,21 @@ Individual agent results render Claude Code-style in the conversation:
|
|
|
75
76
|
|
|
76
77
|
Completed results can be expanded (ctrl+o in pi) to show the full agent output inline.
|
|
77
78
|
|
|
78
|
-
##
|
|
79
|
+
## Default Agent Types
|
|
79
80
|
|
|
80
|
-
| Type | Tools | Description |
|
|
81
|
-
|
|
82
|
-
| `general-purpose` | all 7 | Full read/write access for complex multi-step tasks |
|
|
83
|
-
| `Explore` | read, bash, grep, find, ls | Fast codebase exploration (read-only
|
|
84
|
-
| `Plan` | read, bash, grep, find, ls | Software architect for implementation planning (read-only) |
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
| Type | Tools | Model | Description |
|
|
82
|
+
|------|-------|-------|-------------|
|
|
83
|
+
| `general-purpose` | all 7 | inherit | Full read/write access for complex multi-step tasks |
|
|
84
|
+
| `Explore` | read, bash, grep, find, ls | haiku (falls back to inherit) | Fast codebase exploration (read-only) |
|
|
85
|
+
| `Plan` | read, bash, grep, find, ls | inherit | Software architect for implementation planning (read-only) |
|
|
86
|
+
|
|
87
|
+
Default agents can be **overridden** by creating a `.md` file with the same name (e.g. `.pi/agents/Explore.md`), or **disabled** per-project by creating a `.md` file with `enabled: false` frontmatter.
|
|
87
88
|
|
|
88
89
|
## Custom Agents
|
|
89
90
|
|
|
90
|
-
Define custom agent types by creating `.md` files. The filename becomes the agent type name.
|
|
91
|
+
Define custom agent types by creating `.md` files. The filename becomes the agent type name. Any name is allowed — using a default agent's name overrides it.
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
Agents are discovered from two locations (higher priority wins):
|
|
93
94
|
|
|
94
95
|
| Priority | Location | Scope |
|
|
95
96
|
|----------|----------|-------|
|
|
@@ -131,16 +132,18 @@ All fields are optional — sensible defaults for everything.
|
|
|
131
132
|
| Field | Default | Description |
|
|
132
133
|
|-------|---------|-------------|
|
|
133
134
|
| `description` | filename | Agent description shown in tool listings |
|
|
135
|
+
| `display_name` | — | Display name for UI (e.g. widget, agent list) |
|
|
134
136
|
| `tools` | all 7 | Comma-separated built-in tools: read, bash, edit, write, grep, find, ls. `none` for no tools |
|
|
135
137
|
| `extensions` | `true` | Inherit MCP/extension tools. `false` to disable |
|
|
136
138
|
| `skills` | `true` | Inherit skills from parent |
|
|
137
|
-
| `model` | inherit parent | Model
|
|
139
|
+
| `model` | inherit parent | Model — `provider/modelId` or fuzzy name (`"haiku"`, `"sonnet"`) |
|
|
138
140
|
| `thinking` | inherit | off, minimal, low, medium, high, xhigh |
|
|
139
141
|
| `max_turns` | 50 | Max agentic turns before graceful shutdown |
|
|
140
142
|
| `prompt_mode` | `replace` | `replace`: body is the full system prompt. `append`: body appended to default prompt |
|
|
141
143
|
| `inherit_context` | `false` | Fork parent conversation into agent |
|
|
142
144
|
| `run_in_background` | `false` | Run in background by default |
|
|
143
145
|
| `isolated` | `false` | No extension/MCP tools, only built-in |
|
|
146
|
+
| `enabled` | `true` | Set to `false` to disable an agent (useful for hiding a default agent per-project) |
|
|
144
147
|
|
|
145
148
|
Frontmatter sets defaults. Explicit `Agent` parameters always override them.
|
|
146
149
|
|
|
@@ -193,19 +196,19 @@ The `/agents` command opens an interactive menu:
|
|
|
193
196
|
|
|
194
197
|
```
|
|
195
198
|
Running agents (2) — 1 running, 1 done ← only shown when agents exist
|
|
196
|
-
|
|
199
|
+
Agent types (6) ← unified list: defaults + custom
|
|
197
200
|
Create new agent ← manual wizard or AI-generated
|
|
198
201
|
Settings ← max concurrency, max turns, grace turns, join mode
|
|
199
|
-
|
|
200
|
-
Built-in (always available):
|
|
201
|
-
general-purpose · inherit
|
|
202
|
-
Explore · haiku
|
|
203
|
-
Plan · inherit
|
|
204
|
-
...
|
|
205
202
|
```
|
|
206
203
|
|
|
207
|
-
- **
|
|
208
|
-
- **
|
|
204
|
+
- **Agent types** — unified list with source indicators: `•` (project), `◦` (global), `✕` (disabled). Select an agent to manage it:
|
|
205
|
+
- **Default agents** (no override): Eject (export as `.md`), Disable
|
|
206
|
+
- **Default agents** (ejected/overridden): Edit, Disable, Reset to default, Delete
|
|
207
|
+
- **Custom agents**: Edit, Disable, Delete
|
|
208
|
+
- **Disabled agents**: Enable, Edit, Delete
|
|
209
|
+
- **Eject** — writes the embedded default config as a `.md` file to project or personal location, so you can customize it
|
|
210
|
+
- **Disable/Enable** — toggle agent availability. Disabled agents stay visible in the list (marked `✕`) and can be re-enabled
|
|
211
|
+
- **Create new agent** — choose project/personal location, then manual wizard (step-by-step prompts for name, tools, model, thinking, system prompt) or AI-generated (describe what the agent should do and a sub-agent writes the `.md` file). Any name is allowed, including default agent names (overrides them)
|
|
209
212
|
- **Settings** — configure max concurrency, default max turns, grace turns, and join mode at runtime
|
|
210
213
|
|
|
211
214
|
## Graceful Max Turns
|
|
@@ -250,13 +253,14 @@ When background agents complete, they notify the main agent. The **join mode** c
|
|
|
250
253
|
```
|
|
251
254
|
src/
|
|
252
255
|
index.ts # Extension entry: tool/command registration, rendering
|
|
253
|
-
types.ts # Type definitions (
|
|
254
|
-
|
|
256
|
+
types.ts # Type definitions (AgentConfig, AgentRecord, etc.)
|
|
257
|
+
default-agents.ts # Embedded default agent configs (general-purpose, Explore, Plan)
|
|
258
|
+
agent-types.ts # Unified agent registry (defaults + user), tool factories
|
|
255
259
|
agent-runner.ts # Session creation, execution, graceful max_turns, steer/resume
|
|
256
260
|
agent-manager.ts # Agent lifecycle, concurrency queue, completion notifications
|
|
257
261
|
group-join.ts # Group join manager: batched completion notifications with timeout
|
|
258
|
-
custom-agents.ts # Load
|
|
259
|
-
prompts.ts #
|
|
262
|
+
custom-agents.ts # Load user-defined agents from .pi/agents/*.md
|
|
263
|
+
prompts.ts # Config-driven system prompt builder
|
|
260
264
|
context.ts # Parent conversation context for inherit_context
|
|
261
265
|
env.ts # Environment detection (git, platform)
|
|
262
266
|
ui/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tintinweb/pi-subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A pi extension extension that brings smart Claude Code-style autonomous sub-agents to pi.",
|
|
5
5
|
"author": "tintinweb",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"autonomous"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@mariozechner/pi-ai": "
|
|
25
|
-
"@mariozechner/pi-coding-agent": "
|
|
26
|
-
"@mariozechner/pi-tui": "
|
|
24
|
+
"@mariozechner/pi-ai": "^0.57.1",
|
|
25
|
+
"@mariozechner/pi-coding-agent": "^0.57.1",
|
|
26
|
+
"@mariozechner/pi-tui": "^0.57.1",
|
|
27
27
|
"@sinclair/typebox": "latest"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
package/src/agent-runner.ts
CHANGED
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
} from "@mariozechner/pi-coding-agent";
|
|
14
14
|
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
15
15
|
import type { Model } from "@mariozechner/pi-ai";
|
|
16
|
-
import { getToolsForType, getConfig,
|
|
17
|
-
import {
|
|
16
|
+
import { getToolsForType, getConfig, getAgentConfig } from "./agent-types.js";
|
|
17
|
+
import { buildAgentPrompt } from "./prompts.js";
|
|
18
18
|
import { buildParentContext, extractText } from "./context.js";
|
|
19
19
|
import { detectEnv } from "./env.js";
|
|
20
20
|
import type { SubagentType, ThinkingLevel } from "./types.js";
|
|
@@ -38,47 +38,34 @@ export function getGraceTurns(): number { return graceTurns; }
|
|
|
38
38
|
/** Set the grace turns value (minimum 1). */
|
|
39
39
|
export function setGraceTurns(n: number): void { graceTurns = Math.max(1, n); }
|
|
40
40
|
|
|
41
|
-
/** Haiku model IDs to try for Explore agents (in preference order). */
|
|
42
|
-
const HAIKU_MODEL_IDS = [
|
|
43
|
-
"claude-haiku-4-5-20251001",
|
|
44
|
-
"claude-3-5-haiku-20241022",
|
|
45
|
-
];
|
|
46
|
-
|
|
47
41
|
/**
|
|
48
42
|
* Try to find the right model for an agent type.
|
|
49
|
-
* Priority: explicit option >
|
|
43
|
+
* Priority: explicit option > config.model > parent model.
|
|
50
44
|
*/
|
|
51
45
|
function resolveDefaultModel(
|
|
52
|
-
type: SubagentType,
|
|
53
46
|
parentModel: Model<any> | undefined,
|
|
54
47
|
registry: { find(provider: string, modelId: string): Model<any> | undefined; getAvailable?(): Model<any>[] },
|
|
55
|
-
|
|
48
|
+
configModel?: string,
|
|
56
49
|
): Model<any> | undefined {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const availableKeys = available
|
|
60
|
-
? new Set(available.map((m: any) => `${m.provider}/${m.id}`))
|
|
61
|
-
: undefined;
|
|
62
|
-
const isAvailable = (provider: string, modelId: string) =>
|
|
63
|
-
!availableKeys || availableKeys.has(`${provider}/${modelId}`);
|
|
64
|
-
|
|
65
|
-
// Custom agent model from frontmatter
|
|
66
|
-
if (customModel) {
|
|
67
|
-
const slashIdx = customModel.indexOf("/");
|
|
50
|
+
if (configModel) {
|
|
51
|
+
const slashIdx = configModel.indexOf("/");
|
|
68
52
|
if (slashIdx !== -1) {
|
|
69
|
-
const provider =
|
|
70
|
-
const modelId =
|
|
53
|
+
const provider = configModel.slice(0, slashIdx);
|
|
54
|
+
const modelId = configModel.slice(slashIdx + 1);
|
|
55
|
+
|
|
56
|
+
// Build a set of available model keys for fast lookup
|
|
57
|
+
const available = registry.getAvailable?.();
|
|
58
|
+
const availableKeys = available
|
|
59
|
+
? new Set(available.map((m: any) => `${m.provider}/${m.id}`))
|
|
60
|
+
: undefined;
|
|
61
|
+
const isAvailable = (p: string, id: string) =>
|
|
62
|
+
!availableKeys || availableKeys.has(`${p}/${id}`);
|
|
63
|
+
|
|
71
64
|
const found = registry.find(provider, modelId);
|
|
72
65
|
if (found && isAvailable(provider, modelId)) return found;
|
|
73
66
|
}
|
|
74
67
|
}
|
|
75
68
|
|
|
76
|
-
if (type !== "Explore") return parentModel;
|
|
77
|
-
|
|
78
|
-
for (const modelId of HAIKU_MODEL_IDS) {
|
|
79
|
-
const found = registry.find("anthropic", modelId);
|
|
80
|
-
if (found && isAvailable("anthropic", modelId)) return found;
|
|
81
|
-
}
|
|
82
69
|
return parentModel;
|
|
83
70
|
}
|
|
84
71
|
|
|
@@ -152,17 +139,60 @@ export async function runAgent(
|
|
|
152
139
|
options: RunOptions,
|
|
153
140
|
): Promise<RunResult> {
|
|
154
141
|
const config = getConfig(type);
|
|
155
|
-
const
|
|
142
|
+
const agentConfig = getAgentConfig(type);
|
|
156
143
|
const env = await detectEnv(options.pi, ctx.cwd);
|
|
157
144
|
|
|
158
|
-
// Build system prompt: custom override > custom append >
|
|
145
|
+
// Build system prompt: custom override > custom append > config-driven
|
|
159
146
|
let systemPrompt: string;
|
|
160
147
|
if (options.systemPromptOverride) {
|
|
161
148
|
systemPrompt = options.systemPromptOverride;
|
|
162
149
|
} else if (options.systemPromptAppend) {
|
|
163
|
-
|
|
150
|
+
// Build a default prompt and append to it
|
|
151
|
+
const defaultConfig = agentConfig ?? {
|
|
152
|
+
name: type,
|
|
153
|
+
description: "",
|
|
154
|
+
builtinToolNames: [],
|
|
155
|
+
extensions: true,
|
|
156
|
+
skills: true,
|
|
157
|
+
systemPrompt: "",
|
|
158
|
+
promptMode: "replace" as const,
|
|
159
|
+
inheritContext: false,
|
|
160
|
+
runInBackground: false,
|
|
161
|
+
isolated: false,
|
|
162
|
+
};
|
|
163
|
+
systemPrompt = buildAgentPrompt(defaultConfig, ctx.cwd, env) + "\n\n" + options.systemPromptAppend;
|
|
164
|
+
} else if (agentConfig) {
|
|
165
|
+
systemPrompt = buildAgentPrompt(agentConfig, ctx.cwd, env);
|
|
164
166
|
} else {
|
|
165
|
-
|
|
167
|
+
// Unknown type — use a minimal general-purpose prompt
|
|
168
|
+
systemPrompt = buildAgentPrompt({
|
|
169
|
+
name: type,
|
|
170
|
+
description: "General-purpose agent",
|
|
171
|
+
builtinToolNames: [],
|
|
172
|
+
extensions: true,
|
|
173
|
+
skills: true,
|
|
174
|
+
systemPrompt: `# Role
|
|
175
|
+
You are a general-purpose coding agent for complex, multi-step tasks.
|
|
176
|
+
You have full access to read, write, edit files, and execute commands.
|
|
177
|
+
Do what has been asked; nothing more, nothing less.
|
|
178
|
+
|
|
179
|
+
# Tool Usage
|
|
180
|
+
- Use the read tool instead of cat/head/tail
|
|
181
|
+
- Use the edit tool instead of sed/awk
|
|
182
|
+
- Use the write tool instead of echo/heredoc
|
|
183
|
+
- Use the find tool instead of bash find/ls for file search
|
|
184
|
+
- Use the grep tool instead of bash grep/rg for content search
|
|
185
|
+
- Make independent tool calls in parallel
|
|
186
|
+
|
|
187
|
+
# Output
|
|
188
|
+
- Use absolute file paths
|
|
189
|
+
- Do not use emojis
|
|
190
|
+
- Be concise but complete`,
|
|
191
|
+
promptMode: "replace",
|
|
192
|
+
inheritContext: false,
|
|
193
|
+
runInBackground: false,
|
|
194
|
+
isolated: false,
|
|
195
|
+
}, ctx.cwd, env);
|
|
166
196
|
}
|
|
167
197
|
|
|
168
198
|
const tools = getToolsForType(type, ctx.cwd);
|
|
@@ -182,13 +212,13 @@ export async function runAgent(
|
|
|
182
212
|
});
|
|
183
213
|
await loader.reload();
|
|
184
214
|
|
|
185
|
-
// Resolve model: explicit option >
|
|
215
|
+
// Resolve model: explicit option > config.model > parent model
|
|
186
216
|
const model = options.model ?? resolveDefaultModel(
|
|
187
|
-
|
|
217
|
+
ctx.model, ctx.modelRegistry, agentConfig?.model,
|
|
188
218
|
);
|
|
189
219
|
|
|
190
|
-
// Resolve thinking level: explicit option >
|
|
191
|
-
const thinkingLevel = options.thinkingLevel ??
|
|
220
|
+
// Resolve thinking level: explicit option > agent config > undefined (inherit)
|
|
221
|
+
const thinkingLevel = options.thinkingLevel ?? agentConfig?.thinking;
|
|
192
222
|
|
|
193
223
|
const sessionOpts: Record<string, unknown> = {
|
|
194
224
|
cwd: ctx.cwd,
|
|
@@ -225,7 +255,7 @@ export async function runAgent(
|
|
|
225
255
|
|
|
226
256
|
// Track turns for graceful max_turns enforcement
|
|
227
257
|
let turnCount = 0;
|
|
228
|
-
const maxTurns = options.maxTurns ??
|
|
258
|
+
const maxTurns = options.maxTurns ?? agentConfig?.maxTurns ?? defaultMaxTurns;
|
|
229
259
|
let softLimitReached = false;
|
|
230
260
|
let aborted = false;
|
|
231
261
|
|
package/src/agent-types.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* agent-types.ts —
|
|
2
|
+
* agent-types.ts — Unified agent type registry.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Merges embedded default agents with user-defined agents from .pi/agents/*.md.
|
|
5
|
+
* User agents override defaults with the same name. Disabled agents are kept but excluded from spawning.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import {
|
|
@@ -14,7 +15,8 @@ import {
|
|
|
14
15
|
createLsTool,
|
|
15
16
|
} from "@mariozechner/pi-coding-agent";
|
|
16
17
|
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
|
17
|
-
import type {
|
|
18
|
+
import type { AgentConfig } from "./types.js";
|
|
19
|
+
import { DEFAULT_AGENTS } from "./default-agents.js";
|
|
18
20
|
|
|
19
21
|
type ToolFactory = (cwd: string) => AgentTool<any>;
|
|
20
22
|
|
|
@@ -31,107 +33,145 @@ const TOOL_FACTORIES: Record<string, ToolFactory> = {
|
|
|
31
33
|
/** All known built-in tool names, derived from the factory registry. */
|
|
32
34
|
export const BUILTIN_TOOL_NAMES = Object.keys(TOOL_FACTORIES);
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
displayName: "Agent",
|
|
37
|
-
description: "General-purpose agent for complex, multi-step tasks",
|
|
38
|
-
builtinToolNames: BUILTIN_TOOL_NAMES,
|
|
39
|
-
extensions: true,
|
|
40
|
-
skills: true,
|
|
41
|
-
},
|
|
42
|
-
"Explore": {
|
|
43
|
-
displayName: "Explore",
|
|
44
|
-
description: "Fast codebase exploration agent (read-only)",
|
|
45
|
-
builtinToolNames: ["read", "bash", "grep", "find", "ls"],
|
|
46
|
-
extensions: true,
|
|
47
|
-
skills: true,
|
|
48
|
-
},
|
|
49
|
-
"Plan": {
|
|
50
|
-
displayName: "Plan",
|
|
51
|
-
description: "Software architect for implementation planning (read-only)",
|
|
52
|
-
builtinToolNames: ["read", "bash", "grep", "find", "ls"],
|
|
53
|
-
extensions: true,
|
|
54
|
-
skills: true,
|
|
55
|
-
},
|
|
56
|
-
"statusline-setup": {
|
|
57
|
-
displayName: "Config",
|
|
58
|
-
description: "Configuration editor (read + edit only)",
|
|
59
|
-
builtinToolNames: ["read", "edit"],
|
|
60
|
-
extensions: false,
|
|
61
|
-
skills: false,
|
|
62
|
-
},
|
|
63
|
-
"claude-code-guide": {
|
|
64
|
-
displayName: "Guide",
|
|
65
|
-
description: "Documentation and help queries",
|
|
66
|
-
builtinToolNames: ["read", "grep", "find"],
|
|
67
|
-
extensions: false,
|
|
68
|
-
skills: false,
|
|
69
|
-
},
|
|
70
|
-
};
|
|
36
|
+
/** Unified runtime registry of all agents (defaults + user-defined). */
|
|
37
|
+
const agents = new Map<string, AgentConfig>();
|
|
71
38
|
|
|
72
|
-
/**
|
|
73
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Register agents into the unified registry.
|
|
41
|
+
* Starts with DEFAULT_AGENTS, then overlays user agents (overrides defaults with same name).
|
|
42
|
+
* Disabled agents (enabled === false) are kept in the registry but excluded from spawning.
|
|
43
|
+
*/
|
|
44
|
+
export function registerAgents(userAgents: Map<string, AgentConfig>): void {
|
|
45
|
+
agents.clear();
|
|
46
|
+
|
|
47
|
+
// Start with defaults
|
|
48
|
+
for (const [name, config] of DEFAULT_AGENTS) {
|
|
49
|
+
agents.set(name, config);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Overlay user agents (overrides defaults with same name)
|
|
53
|
+
for (const [name, config] of userAgents) {
|
|
54
|
+
agents.set(name, config);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
74
57
|
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
58
|
+
/** Case-insensitive key resolution. */
|
|
59
|
+
function resolveKey(name: string): string | undefined {
|
|
60
|
+
if (agents.has(name)) return name;
|
|
61
|
+
const lower = name.toLowerCase();
|
|
62
|
+
for (const key of agents.keys()) {
|
|
63
|
+
if (key.toLowerCase() === lower) return key;
|
|
80
64
|
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Resolve a type name case-insensitively. Returns the canonical key or undefined. */
|
|
69
|
+
export function resolveType(name: string): string | undefined {
|
|
70
|
+
return resolveKey(name);
|
|
81
71
|
}
|
|
82
72
|
|
|
83
|
-
/** Get the
|
|
84
|
-
export function
|
|
85
|
-
|
|
73
|
+
/** Get the agent config for a type (case-insensitive). */
|
|
74
|
+
export function getAgentConfig(name: string): AgentConfig | undefined {
|
|
75
|
+
const key = resolveKey(name);
|
|
76
|
+
return key ? agents.get(key) : undefined;
|
|
86
77
|
}
|
|
87
78
|
|
|
88
|
-
/** Get all
|
|
79
|
+
/** Get all enabled type names (for spawning and tool descriptions). */
|
|
89
80
|
export function getAvailableTypes(): string[] {
|
|
90
|
-
return [...
|
|
81
|
+
return [...agents.entries()]
|
|
82
|
+
.filter(([_, config]) => config.enabled !== false)
|
|
83
|
+
.map(([name]) => name);
|
|
91
84
|
}
|
|
92
85
|
|
|
93
|
-
/** Get all
|
|
94
|
-
export function
|
|
95
|
-
return [...
|
|
86
|
+
/** Get all type names including disabled (for UI listing). */
|
|
87
|
+
export function getAllTypes(): string[] {
|
|
88
|
+
return [...agents.keys()];
|
|
96
89
|
}
|
|
97
90
|
|
|
98
|
-
/**
|
|
91
|
+
/** Get names of default agents currently in the registry. */
|
|
92
|
+
export function getDefaultAgentNames(): string[] {
|
|
93
|
+
return [...agents.entries()]
|
|
94
|
+
.filter(([_, config]) => config.isDefault === true)
|
|
95
|
+
.map(([name]) => name);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Get names of user-defined agents (non-defaults) currently in the registry. */
|
|
99
|
+
export function getUserAgentNames(): string[] {
|
|
100
|
+
return [...agents.entries()]
|
|
101
|
+
.filter(([_, config]) => config.isDefault !== true)
|
|
102
|
+
.map(([name]) => name);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Check if a type is valid and enabled (case-insensitive). */
|
|
99
106
|
export function isValidType(type: string): boolean {
|
|
100
|
-
|
|
107
|
+
const key = resolveKey(type);
|
|
108
|
+
if (!key) return false;
|
|
109
|
+
return agents.get(key)?.enabled !== false;
|
|
101
110
|
}
|
|
102
111
|
|
|
103
|
-
/** Get built-in tools for a type
|
|
104
|
-
export function getToolsForType(type:
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (custom) {
|
|
111
|
-
return custom.builtinToolNames
|
|
112
|
-
.filter((n) => n in TOOL_FACTORIES)
|
|
113
|
-
.map((n) => TOOL_FACTORIES[n](cwd));
|
|
114
|
-
}
|
|
115
|
-
// Fallback: all tools
|
|
116
|
-
return BUILTIN_TOOL_NAMES.map((n) => TOOL_FACTORIES[n](cwd));
|
|
112
|
+
/** Get built-in tools for a type (case-insensitive). */
|
|
113
|
+
export function getToolsForType(type: string, cwd: string): AgentTool<any>[] {
|
|
114
|
+
const key = resolveKey(type);
|
|
115
|
+
const raw = key ? agents.get(key) : undefined;
|
|
116
|
+
const config = raw?.enabled !== false ? raw : undefined;
|
|
117
|
+
const toolNames = config?.builtinToolNames?.length ? config.builtinToolNames : BUILTIN_TOOL_NAMES;
|
|
118
|
+
return toolNames.filter((n) => n in TOOL_FACTORIES).map((n) => TOOL_FACTORIES[n](cwd));
|
|
117
119
|
}
|
|
118
120
|
|
|
119
|
-
/** Get config for a type
|
|
120
|
-
export function getConfig(type:
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
/** Get config for a type (case-insensitive, returns a SubagentTypeConfig-compatible object). Falls back to general-purpose. */
|
|
122
|
+
export function getConfig(type: string): {
|
|
123
|
+
displayName: string;
|
|
124
|
+
description: string;
|
|
125
|
+
builtinToolNames: string[];
|
|
126
|
+
extensions: true | string[] | false;
|
|
127
|
+
skills: true | string[] | false;
|
|
128
|
+
} {
|
|
129
|
+
const key = resolveKey(type);
|
|
130
|
+
const config = key ? agents.get(key) : undefined;
|
|
131
|
+
if (config && config.enabled !== false) {
|
|
132
|
+
return {
|
|
133
|
+
displayName: config.displayName ?? config.name,
|
|
134
|
+
description: config.description,
|
|
135
|
+
builtinToolNames: config.builtinToolNames ?? BUILTIN_TOOL_NAMES,
|
|
136
|
+
extensions: config.extensions,
|
|
137
|
+
skills: config.skills,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
123
140
|
|
|
124
|
-
|
|
125
|
-
|
|
141
|
+
// Fallback for unknown/disabled types — general-purpose config
|
|
142
|
+
const gp = agents.get("general-purpose");
|
|
143
|
+
if (gp && gp.enabled !== false) {
|
|
126
144
|
return {
|
|
127
|
-
displayName:
|
|
128
|
-
description:
|
|
129
|
-
builtinToolNames:
|
|
130
|
-
extensions:
|
|
131
|
-
skills:
|
|
145
|
+
displayName: gp.displayName ?? gp.name,
|
|
146
|
+
description: gp.description,
|
|
147
|
+
builtinToolNames: gp.builtinToolNames ?? BUILTIN_TOOL_NAMES,
|
|
148
|
+
extensions: gp.extensions,
|
|
149
|
+
skills: gp.skills,
|
|
132
150
|
};
|
|
133
151
|
}
|
|
134
152
|
|
|
135
|
-
//
|
|
136
|
-
return
|
|
153
|
+
// Absolute fallback (should never happen)
|
|
154
|
+
return {
|
|
155
|
+
displayName: "Agent",
|
|
156
|
+
description: "General-purpose agent for complex, multi-step tasks",
|
|
157
|
+
builtinToolNames: BUILTIN_TOOL_NAMES,
|
|
158
|
+
extensions: true,
|
|
159
|
+
skills: true,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---- Backwards-compatible aliases ----
|
|
164
|
+
|
|
165
|
+
/** @deprecated Use registerAgents instead */
|
|
166
|
+
export const registerCustomAgents = registerAgents;
|
|
167
|
+
|
|
168
|
+
/** @deprecated Use getAgentConfig instead */
|
|
169
|
+
export function getCustomAgentConfig(name: string): AgentConfig | undefined {
|
|
170
|
+
const key = resolveKey(name);
|
|
171
|
+
return key ? agents.get(key) : undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** @deprecated Use getUserAgentNames instead */
|
|
175
|
+
export function getCustomAgentNames(): string[] {
|
|
176
|
+
return getUserAgentNames();
|
|
137
177
|
}
|
package/src/custom-agents.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { parseFrontmatter } from "@mariozechner/pi-coding-agent";
|
|
|
6
6
|
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
7
7
|
import { join, basename } from "node:path";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
|
-
import {
|
|
9
|
+
import type { AgentConfig, ThinkingLevel } from "./types.js";
|
|
10
10
|
import { BUILTIN_TOOL_NAMES } from "./agent-types.js";
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -16,19 +16,20 @@ import { BUILTIN_TOOL_NAMES } from "./agent-types.js";
|
|
|
16
16
|
* 2. Global: ~/.pi/agent/agents/*.md
|
|
17
17
|
*
|
|
18
18
|
* Project-level agents override global ones with the same name.
|
|
19
|
+
* Any name is allowed — names matching defaults (e.g. "Explore") override them.
|
|
19
20
|
*/
|
|
20
|
-
export function loadCustomAgents(cwd: string): Map<string,
|
|
21
|
+
export function loadCustomAgents(cwd: string): Map<string, AgentConfig> {
|
|
21
22
|
const globalDir = join(homedir(), ".pi", "agent", "agents");
|
|
22
23
|
const projectDir = join(cwd, ".pi", "agents");
|
|
23
24
|
|
|
24
|
-
const agents = new Map<string,
|
|
25
|
-
loadFromDir(globalDir, agents); // lower priority
|
|
26
|
-
loadFromDir(projectDir, agents); // higher priority (overwrites)
|
|
25
|
+
const agents = new Map<string, AgentConfig>();
|
|
26
|
+
loadFromDir(globalDir, agents, "global"); // lower priority
|
|
27
|
+
loadFromDir(projectDir, agents, "project"); // higher priority (overwrites)
|
|
27
28
|
return agents;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
/** Load agent configs from a directory into the map. */
|
|
31
|
-
function loadFromDir(dir: string, agents: Map<string,
|
|
32
|
+
function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "project" | "global"): void {
|
|
32
33
|
if (!existsSync(dir)) return;
|
|
33
34
|
|
|
34
35
|
let files: string[];
|
|
@@ -40,7 +41,6 @@ function loadFromDir(dir: string, agents: Map<string, CustomAgentConfig>): void
|
|
|
40
41
|
|
|
41
42
|
for (const file of files) {
|
|
42
43
|
const name = basename(file, ".md");
|
|
43
|
-
if ((SUBAGENT_TYPES as readonly string[]).includes(name)) continue;
|
|
44
44
|
|
|
45
45
|
let content: string;
|
|
46
46
|
try {
|
|
@@ -53,6 +53,7 @@ function loadFromDir(dir: string, agents: Map<string, CustomAgentConfig>): void
|
|
|
53
53
|
|
|
54
54
|
agents.set(name, {
|
|
55
55
|
name,
|
|
56
|
+
displayName: str(fm.display_name),
|
|
56
57
|
description: str(fm.description) ?? name,
|
|
57
58
|
builtinToolNames: csvList(fm.tools, BUILTIN_TOOL_NAMES),
|
|
58
59
|
extensions: inheritField(fm.extensions ?? fm.inherit_extensions),
|
|
@@ -65,6 +66,8 @@ function loadFromDir(dir: string, agents: Map<string, CustomAgentConfig>): void
|
|
|
65
66
|
inheritContext: fm.inherit_context === true,
|
|
66
67
|
runInBackground: fm.run_in_background === true,
|
|
67
68
|
isolated: fm.isolated === true,
|
|
69
|
+
enabled: fm.enabled !== false, // default true; explicitly false disables
|
|
70
|
+
source,
|
|
68
71
|
});
|
|
69
72
|
}
|
|
70
73
|
}
|