@bacnh85/pi-subagent 0.4.1 → 0.6.1

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 ADDED
@@ -0,0 +1,56 @@
1
+ # Changelog
2
+
3
+ ## 0.6.0 (2026-07-12)
4
+
5
+ ### Security (breaking changes)
6
+
7
+ - **Project-agent confirmation removed from tool schema.** The `confirmProjectAgents` parameter is no longer exposed to the LLM. Project-agent approval is enforced via trusted configuration only. Interactive sessions prompt for confirmation; headless sessions fail closed unless `allowUnconfirmedProjectAgents` is explicitly enabled through trusted configuration (environment variable `PI_SUBAGENT_ALLOW_UNCONFIRMED_PROJECT_AGENTS=true` or pi settings).
8
+
9
+ - **Child working directories confined to the workspace.** Tool-specified `cwd` values are validated against the workspace root. Relative paths are resolved within the workspace; `..` traversal, absolute paths outside the workspace, and symlink escapes are rejected. A trusted `allowExternalCwd` setting (env `PI_SUBAGENT_ALLOW_EXTERNAL_CWD=true` or pi settings) can opt out.
10
+
11
+ - **Tool allowlist enforced.** Child agent tools are validated against a fixed allowlist: `read`, `grep`, `find`, `ls`, `bash`, `edit`, `write`. The `subagent` tool is always rejected. Unknown or misspelled tool names produce clear errors. Read-only service execution cannot gain `bash`, `edit`, or `write`.
12
+
13
+ - **Default timeout added.** Every child execution receives a default 10-minute timeout (`DEFAULT_TIMEOUT_MS`). Maximum allowed timeout is 60 minutes (`MAX_TIMEOUT_MS`). Timeout errors are distinguishable from parent cancellation.
14
+
15
+ ### Reliability
16
+
17
+ - **Abort signal composition fixed.** `createCombinedAbortSignal()` correctly combines multiple abort signals with proper listener cleanup. Works without `AbortSignal.any()` via a manual fallback that removes all listeners after the first abort.
18
+
19
+ - **Parallel abort listeners cleaned up.** Parent-signal listeners attached during parallel execution are removed in a `finally` block after completion.
20
+
21
+ - **Canonical result status.** `SubAgentResult.status` classifies outcomes as `"success"`, `"partial"`, `"error"`, `"aborted"`, or `"timeout"`. Known Pi SDK stop reasons are classified explicitly; unknown reasons default conservatively to `"error"`.
22
+
23
+ - **Validation hardened.** Numeric and collection limits (timeout, max parallel tasks, concurrency, chain length, output cap, instructions length) are enforced at both schema and runtime levels.
24
+
25
+ - **Parallel result ordering preserved.** Results remain in input-task order regardless of completion order.
26
+
27
+ - **`abortOnFailure` behavior deterministic.** First canonical failure aborts running siblings; queued tasks never start; completed tasks retain their results.
28
+
29
+ ### Agent discovery
30
+
31
+ - **Malformed agent files produce diagnostics.** Missing name, missing description, empty name, invalid model, invalid thinking levels, and unreadable files are reported with file path and severity. Valid agents continue to load.
32
+
33
+ ### Packaging
34
+
35
+ - **Peer dependency ranges constrained.** `@earendil-works/pi-*` dependencies use `>=0.80.0 <0.81.0`; `typebox` uses `>=1.3.0 <2.0.0`.
36
+
37
+ - **Node engine requirement added.** `engines.node: ">=20.18"`.
38
+
39
+ - **Scripts fixed.** `npm test` uses locally installed `mocha` (no `npx`). Added `npm run check` (typecheck + test).
40
+
41
+ - **Package metadata updated.** `homepage` points to the package subdirectory.
42
+
43
+ - **`security.ts` added to published files.**
44
+
45
+ - **`CHANGELOG.md` added to published files.**
46
+
47
+ ### Documentation
48
+
49
+ - **Security model section** added to README covering project-agent trust, cwd confinement, tool validation, timeout defaults, cancellation, result status, and compatibility.
50
+
51
+ ### Backward compatibility
52
+
53
+ - `SubAgentResult.status` is a new field; existing consumers that ignore unknown fields remain compatible.
54
+ - Tool schema no longer accepts `confirmProjectAgents`; model-generated calls using it will be silently ignored (the field is fully removed from the schema, not just deprecated).
55
+ - Child `cwd` values that previously worked outside the workspace are now rejected unless the trusted `allowExternalCwd` setting is enabled.
56
+ - `combineAbortSignals()` is still exported from `runner.ts` but delegates to `createCombinedAbortSignal()` internally.
package/README.md CHANGED
@@ -1,100 +1,175 @@
1
1
  # pi-subagent
2
2
 
3
- Minimal-overhead sub-agent extension for pi. Delegate tasks to specialized agents with isolated context running in-process via the pi SDK for zero spawn overhead and ~10x fewer tokens than process-spawning.
4
-
5
- ## Features
6
-
7
- - **In-process execution**: Uses `createAgentSession()` directly — no `spawn("pi")` overhead
8
- - **Minimal token budget**: Only the agent's system prompt (no AGENTS.md, no extensions, no thinking)
9
- - **Streaming progress**: Real-time tool-call and text updates during execution
10
- - **Three modes**: single, parallel (max 8 tasks, 4 concurrent), chain (sequential with `{previous}`)
11
- - **Abort support**: Esc propagates to all sub-agents
12
- - **TUI rendering**: Collapsed/expanded views with tool-call formatting and usage stats
13
- - **Bundled agents**: scout, reviewer, worker — overridable with your own
14
- - **Thread viewing**: `/agent` slash command to view subagent threads in isolation
3
+ Isolated in-process subagents for Pi. The `subagent` tool supports single, parallel (8 tasks, 4 concurrent), and chained execution; `/agent` opens inspectable child threads.
15
4
 
16
5
  ## Install
17
6
 
18
7
  ```bash
19
- cd extensions/pi-subagent
20
- npm install
21
- cd ../..
22
- pi install ./extensions/pi-subagent
8
+ pi install npm:@bacnh85/pi-subagent
9
+ # local checkout
10
+ pi install ./pi-subagent
23
11
  ```
24
12
 
25
- Or test directly:
13
+ Requires Node.js >= 20.18.
26
14
 
27
- ```bash
28
- pi -e ./extensions/pi-subagent
15
+ ## Bundled roles
16
+
17
+ | Role | Model | Thinking | Tools |
18
+ | --- | --- | --- | --- |
19
+ | `scout` | parent model | low | read, grep, find, ls |
20
+ | `reviewer` | parent model | high | read, grep, find, ls |
21
+ | `worker` | parent model | medium | read, bash, edit, write, grep, find, ls |
22
+ | `general-purpose` | parent model | off | read, bash, edit, write, grep, find, ls |
23
+
24
+ Bundled roles inherit the parent model so they work with the account already active in Pi. User/project agent files may override `model` and `thinking`.
25
+
26
+ ## Agent files
27
+
28
+ Create `~/.pi/agent/agents/*.md` or `.pi/agents/*.md`:
29
+
30
+ ```markdown
31
+ ---
32
+ name: scout-fast
33
+ description: Locate relevant files and symbols
34
+ tools: read, grep, find, ls
35
+ model: optional-provider/optional-model
36
+ ---
37
+
38
+ Return concise evidence with file/symbol anchors.
29
39
  ```
30
40
 
31
- ## Usage
41
+ Agent definitions are cached with file-signature invalidation; `/subagent reload` clears the cache.
32
42
 
33
- ### Thread Viewing (`/agent`)
43
+ ## Context and limits
34
44
 
35
- After running subagents, type `/agent` to view individual subagent threads:
45
+ Children use in-memory SDK sessions with no extensions, skills, prompt templates, or automatic `AGENTS.md` loading. The optional `instructions` argument passes a bounded 16 KB task/repository contract. Only Pi built-in tools are available; Serena, FFF, web, and Munin are not available in lean children.
36
46
 
37
- 1. A picker shows `Main [default]` + all subagent threads with status icons
38
- 2. Select a thread to view its full output (task, tool calls, final result, usage stats)
39
- 3. Within the viewer: `Esc` to close, `Alt+←`/`Alt+→` to cycle between threads, `↑/↓` to scroll
47
+ Threads are session-memory only and are cleared when Pi replaces or reloads the session. Timeout and parent cancellation propagate to child sessions. Subagents cannot recursively invoke `subagent`.
40
48
 
41
- This is useful when running many parallel subagents — instead of Ctrl+O to
42
- see all output at once, you can focus on one thread at a time.
49
+ ## Security model
43
50
 
44
- ### Single agent
51
+ ### Project-local agents
45
52
 
46
- ```
47
- Use scout to find authentication code in this project
48
- ```
53
+ Agent files under `.pi/agents/` are controlled by the current repository. A project agent's system prompt may instruct a child to execute shell commands or modify files.
54
+
55
+ - **Project-agent approval cannot be disabled by the model.** The `confirmProjectAgents` parameter is not exposed in the tool schema. Confirmation policy comes from trusted user configuration only.
56
+ - **Interactive sessions** prompt the user before executing project agents.
57
+ - **Headless sessions fail closed.** Project agents are not executed without UI confirmation unless the trusted setting `allowUnconfirmedProjectAgents` is enabled (via `PI_SUBAGENT_ALLOW_UNCONFIRMED_PROJECT_AGENTS=true` environment variable or pi settings).
58
+ - **The extension service path** (`pi-subagent:run` event) follows the same policy.
59
+
60
+ ### Child working directories
61
+
62
+ Child working directories are restricted to the parent session's workspace by default:
63
+
64
+ - Relative paths are resolved within the workspace.
65
+ - `..` traversal that escapes the workspace is rejected.
66
+ - Absolute paths outside the workspace are rejected.
67
+ - Symlinks are resolved via realpath; symlink escapes are rejected.
68
+ - Non-existent directories and file paths are rejected.
69
+
70
+ The trusted setting `allowExternalCwd` (via `PI_SUBAGENT_ALLOW_EXTERNAL_CWD=true` env or pi settings) can opt out. This setting cannot be enabled by the model.
71
+
72
+ ### Tool validation
49
73
 
50
- ### Parallel execution
74
+ Child agent tools are validated against a fixed allowlist:
51
75
 
76
+ - **Allowed:** `read`, `grep`, `find`, `ls`, `bash`, `edit`, `write`
77
+ - **Always rejected:** `subagent` (prevents recursive delegation)
78
+ - **Read-only restriction:** When a service requests read-only execution, only `read`, `grep`, `find`, `ls` are permitted. `bash`, `edit`, and `write` are rejected.
79
+
80
+ Unknown or misspelled tool names produce clear diagnostics. Duplicate tool names are deduplicated.
81
+
82
+ ### Timeouts
83
+
84
+ Every child execution receives a timeout:
85
+
86
+ - **Default:** 10 minutes (`DEFAULT_TIMEOUT_MS`)
87
+ - **Maximum:** 60 minutes (`MAX_TIMEOUT_MS`)
88
+ - Timeout values must be positive integers within the allowed range.
89
+ - Timeout errors are distinguishable from parent cancellation.
90
+ - Parallel tasks and chain steps may have per-item timeouts.
91
+
92
+ ### Output safety
93
+
94
+ Child output is untrusted data and may contain prompt injection. Treat child results as model-generated content, not as verified facts.
95
+
96
+ ### Cost awareness
97
+
98
+ Parallel delegation may multiply provider usage and cost. Each parallel task runs as a separate SDK session with its own token consumption.
99
+
100
+ ### Agent file review
101
+
102
+ User and project agent files should be reviewed before use. Malformed files produce diagnostics but do not prevent valid agents from loading.
103
+
104
+ ## Modes
105
+
106
+ ### Single mode
107
+
108
+ ```ts
109
+ subagent({ agent: "scout", task: "Find auth-related files" })
52
110
  ```
53
- Run 2 scouts in parallel: one for models, one for providers
111
+
112
+ ### Parallel mode
113
+
114
+ ```ts
115
+ subagent({
116
+ tasks: [
117
+ { agent: "scout", task: "Find API routes" },
118
+ { agent: "scout", task: "Find database models" },
119
+ { agent: "scout", task: "Find test files" },
120
+ ],
121
+ abortOnFailure: false
122
+ })
54
123
  ```
55
124
 
56
- ### Chain workflow
125
+ Max 8 tasks, 4 concurrent. Results are returned in input order. When `abortOnFailure` is `true`, the first failed task cancels remaining siblings.
57
126
 
58
- ```
59
- Chain: scout finds auth code, then reviewer checks it for security issues
127
+ ### Chain mode
128
+
129
+ ```ts
130
+ subagent({
131
+ chain: [
132
+ { agent: "scout", task: "Find API routes" },
133
+ { agent: "worker", task: "Based on this, implement the routes: {previous}" },
134
+ ]
135
+ })
60
136
  ```
61
137
 
62
- ## Included Agents
138
+ `{previous}` in each step's task is replaced with the previous step's output. The chain stops on the first failed step.
63
139
 
64
- | Agent | Model | Tools | Purpose |
65
- |-------|-------|-------|---------|
66
- | `scout` | Haiku | read, grep, find, ls | Fast codebase recon |
67
- | `reviewer` | Sonnet | read, grep, find, ls, bash | Code review |
68
- | `worker` | Sonnet | all | General implementation |
140
+ ## Result status
69
141
 
70
- ## Custom Agents
142
+ Each `SubAgentResult` includes a canonical `status` field:
71
143
 
72
- Create Markdown files with YAML frontmatter in `~/.pi/agent/agents/` (user-level) or `.pi/agents/` (project-level):
144
+ | Status | Meaning |
145
+ |--------|---------|
146
+ | `success` | Completed normally |
147
+ | `partial` | Truncated (max_tokens, length, context_limit) |
148
+ | `error` | Provider error, tool error, or unknown stop reason |
149
+ | `aborted` | Cancelled by parent or sibling |
150
+ | `timeout` | Exceeded the allowed timeout |
73
151
 
74
- ```markdown
75
- ---
76
- name: my-agent
77
- description: When to use this agent
78
- tools: read, grep, find, ls, bash
79
- model: claude-haiku-4-5
80
- ---
152
+ The raw `stopReason` from the Pi SDK is preserved in the result.
81
153
 
82
- Your system prompt here. This is the ONLY prompt the sub-agent sees.
83
- ```
154
+ ## Timeout and cancellation
84
155
 
85
- See `agent-format.md` for the full format specification.
156
+ - **Default timeout:** 10 minutes per child.
157
+ - **Per-task/step override:** Use `timeout` in task/step params.
158
+ - **Parent cancellation:** Aborting the parent tool call cancels all children.
159
+ - **Sibling cancellation:** In parallel mode with `abortOnFailure: true`, the first failed task cancels running siblings.
160
+ - **Timeout vs. abort:** Timeout errors set `status: "timeout"` and `stopReason: "timeout"`; parent cancellation sets `status: "aborted"`.
86
161
 
87
- ## Architecture
162
+ ## Extension contract
88
163
 
89
- Sub-agents run in-process via the pi SDK. Compared to the process-spawn approach (spawning `pi --mode json`), this saves ~4-11K tokens per sub-agent invocation by:
164
+ `pi-subagent` owns the `pi-subagent:run` event contract for one named-agent request. `pi-review` uses it for isolated review. Requests use an immediate boolean `accept()` claim and exactly one `respond()` callback; this suppresses duplicate responders while missing services and timeouts remain caller-controlled.
90
165
 
91
- - Using only the agent's system prompt (no pi defaults)
92
- - Skipping AGENTS.md, extensions, skills, prompt templates
93
- - Disabling thinking, compaction, retry
94
- - Using in-memory sessions (no disk I/O)
95
- - Sharing the parent's auth/model infrastructure
166
+ ## Compatibility
96
167
 
97
- ## Requirements
168
+ - Requires `@earendil-works/pi-coding-agent >=0.80.0 <0.81.0`
169
+ - Requires `@earendil-works/pi-ai >=0.80.0 <0.81.0`
170
+ - Requires `@earendil-works/pi-agent-core >=0.80.0 <0.81.0`
171
+ - Requires `@earendil-works/pi-tui >=0.80.0 <0.81.0`
172
+ - Requires `typebox >=1.3.0 <2.0.0`
173
+ - Requires Node.js >= 20.18
98
174
 
99
- - pi coding agent with configured API keys
100
- - Peer dependencies satisfied by the pi runtime (no extra npm install needed)
175
+ See [`agent-format.md`](./agent-format.md) for all frontmatter fields.
package/agent-format.md CHANGED
@@ -8,7 +8,7 @@ Sub-agents are defined as Markdown files with YAML frontmatter.
8
8
  |----------|-------|
9
9
  | `~/.pi/agent/agents/*.md` | User-level (all projects) |
10
10
  | `.pi/agents/*.md` | Project-level |
11
- | `<skill>/agents/*.md` | Bundled with pi-sugagents |
11
+ | `<package>/agents/*.md` | Bundled with pi-subagent |
12
12
 
13
13
  Project agents override user agents with the same name when `agentScope: "both"`.
14
14
 
@@ -19,7 +19,8 @@ Project agents override user agents with the same name when `agentScope: "both"`
19
19
  name: my-agent # Required. Unique identifier (kebab-case).
20
20
  description: ... # Required. When to use this agent.
21
21
  tools: read, grep, ... # Optional. Comma-separated tool names. Defaults to all.
22
- model: claude-haiku-4-5 # Optional. Model ID. Defaults to parent's model.
22
+ model: provider/model # Optional. Defaults to parent's model.
23
+ thinking: low # Optional: off|minimal|low|medium|high|xhigh|max.
23
24
  ---
24
25
  ```
25
26
 
@@ -31,12 +32,22 @@ The body after frontmatter becomes the agent's **entire system prompt**. No pi d
31
32
 
32
33
  ## Available Tools
33
34
 
34
- Built-in pi tool names: `read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`
35
+ Child agent tools are validated against a fixed allowlist:
35
36
 
36
- The `subagent` tool is never available to sub-agents (prevents accidental recursion). Sub-agents run at one level of delegation only; they cannot spawn further sub-agents.
37
+ | Category | Tools |
38
+ |----------|-------|
39
+ | Read-only | `read`, `grep`, `find`, `ls` |
40
+ | Mutation | `edit`, `write` |
41
+ | Execution | `bash` |
42
+
43
+ The `subagent` tool is always rejected to prevent recursive delegation.
44
+ Unknown or misspelled tool names produce a clear error.
45
+ Duplicate names are deduplicated automatically.
37
46
 
38
47
  Custom/extension tools are NOT available to sub-agents by default (each runs in an isolated in-memory session with no extensions).
39
48
 
49
+ Read-only service execution (used by `pi-review`) restricts tools to the read-only category.
50
+
40
51
  ## Model Resolution
41
52
 
42
53
  Model IDs are resolved via `getModel("provider", "id")`. Common values:
@@ -47,13 +58,17 @@ Model IDs are resolved via `getModel("provider", "id")`. Common values:
47
58
 
48
59
  If not specified, defaults to the parent session's model.
49
60
 
61
+ ## Instruction handoff
62
+
63
+ Children do not automatically load repository instructions. Callers may pass an `instructions` task contract, truncated to 16 KB. Use this for relevant repository rules or review contracts rather than copying the parent transcript.
64
+
50
65
  ## Token Budget
51
66
 
52
67
  Each sub-agent runs with:
53
68
  - **System prompt**: agent body only (~200-1K tokens typical)
54
69
  - **No AGENTS.md**: saves 500-5K tokens
55
70
  - **No extensions/skills loaded**: saves 200-1K tokens
56
- - **Thinking off**: saves reasoning overhead
71
+ - **Thinking per role**: defaults off; bundled scout/reviewer/worker choose low/high/medium
57
72
  - **No compaction**: avoids compaction token cost
58
73
 
59
74
  This is ~10x leaner than spawning a full `pi` process.
@@ -1,30 +1,36 @@
1
1
  ---
2
2
  name: reviewer
3
- description: Code review specialist. Use for reviewing changes, finding bugs, suggesting improvements.
4
- tools: read, grep, find, ls, bash
5
- model: openai-codex/gpt-5.5
3
+ description: Code review specialist. Use for correctness, security, regression, and meaningful test-gap review.
4
+ tools: read, grep, find, ls
5
+ thinking: high
6
6
  ---
7
7
 
8
- You are a senior code reviewer. Review code changes and provide specific, actionable feedback.
8
+ You are an independent senior code reviewer. Inspect the requested Git scope with read-only tools.
9
9
 
10
- Focus on:
11
- 1. **Correctness**: Logic errors, edge cases, off-by-one
12
- 2. **Security**: Injection risks, auth bypasses, data leaks
13
- 3. **Performance**: N+1 queries, unnecessary allocations, blocking calls
14
- 4. **Maintainability**: Unclear naming, missing error handling, tight coupling
15
- 5. **Best practices**: Idiomatic patterns, testability, documentation
10
+ Focus only on actionable issues introduced by the reviewed change:
11
+ 1. Correctness and edge cases
12
+ 2. Security and data loss
13
+ 3. Regressions and API compatibility
14
+ 4. Missing tests that allow a likely bug to escape
16
15
 
17
- Output format:
16
+ Avoid style noise, praise, and speculative redesign. Every finding needs code evidence.
18
17
 
19
- ## Summary
20
- Brief assessment (1-2 sentences)
18
+ Return JSON only:
19
+ ```json
20
+ {
21
+ "summary": "compact scope/result summary",
22
+ "findings": [
23
+ {
24
+ "severity": "critical|high|medium|low",
25
+ "file": "relative/path",
26
+ "line": 1,
27
+ "issue": "what is wrong and why it matters",
28
+ "evidence": "specific inspected code evidence",
29
+ "suggestedFix": "smallest safe fix",
30
+ "blocking": true
31
+ }
32
+ ]
33
+ }
34
+ ```
21
35
 
22
- ## Issues Found
23
- For each issue:
24
- - **Severity**: critical | high | medium | low
25
- - **File**: path with line numbers
26
- - **Problem**: What's wrong
27
- - **Fix**: Suggested change (code block)
28
-
29
- ## Overall Assessment
30
- Green/yellow/red with reasoning.
36
+ Use an empty `findings` array when clean. Do not modify files or Git state.
package/agents/scout.md CHANGED
@@ -2,7 +2,7 @@
2
2
  name: scout
3
3
  description: Fast codebase recon that returns compressed context for handoff. Use for finding files, understanding structure, locating symbols.
4
4
  tools: read, grep, find, ls
5
- model: opencode-go/deepseek-v4-flash
5
+ thinking: low
6
6
  ---
7
7
 
8
8
  You are a scout. Quickly investigate a codebase and return structured findings that another agent can use without re-reading everything.
@@ -22,8 +22,8 @@ Strategy:
22
22
 
23
23
  Output format:
24
24
 
25
- ## Files Retrieved
26
- List with exact line ranges:
25
+ ## Evidence
26
+ List exact file/symbol anchors and relevant line ranges:
27
27
  1. `path/to/file.ts` (lines 10-50) - Description of what's here
28
28
  2. `path/to/other.ts` (lines 100-150) - Description
29
29
  3. ...
package/agents/worker.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: worker
3
- description: General-purpose coding agent with full tool access. Use for implementation, refactoring, debugging, and complex multi-step tasks.
4
- model: opencode-go/deepseek-v4-pro
3
+ description: General-purpose coding agent with full tool access. Use only when explicitly requested for isolated implementation.
4
+ thinking: medium
5
5
  ---
6
6
 
7
7
  You are a skilled software engineer. Implement the requested task with care and precision.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Agent discovery and configuration for pi-sugagents.
2
+ * Agent discovery and configuration for pi-subagent.
3
3
  *
4
4
  * Loads agent definitions from Markdown files with YAML frontmatter.
5
5
  * Discovers from user-level (~/.pi/agent/agents/), project-level
@@ -18,6 +18,7 @@ export interface AgentConfig {
18
18
  description: string;
19
19
  tools?: string[];
20
20
  model?: string;
21
+ thinking?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
21
22
  systemPrompt: string;
22
23
  source: "user" | "project" | "bundled";
23
24
  filePath: string;
@@ -26,6 +27,14 @@ export interface AgentConfig {
26
27
  export interface AgentDiscoveryResult {
27
28
  agents: AgentConfig[];
28
29
  projectAgentsDir: string | null;
30
+ diagnostics: AgentDiscoveryDiagnostic[];
31
+ }
32
+
33
+ export interface AgentDiscoveryDiagnostic {
34
+ filePath: string;
35
+ issue: string;
36
+ /** 'warn' for recoverable issues, 'error' for file-skip issues. */
37
+ severity: "warn" | "error";
29
38
  }
30
39
 
31
40
  interface AgentCache {
@@ -46,7 +55,11 @@ export function invalidateAgentCache(): void {
46
55
  _cache = null;
47
56
  }
48
57
 
49
- function loadAgentsFromDir(dir: string, source: "user" | "project" | "bundled"): AgentConfig[] {
58
+ function loadAgentsFromDir(
59
+ dir: string,
60
+ source: "user" | "project" | "bundled",
61
+ diagnostics: AgentDiscoveryDiagnostic[],
62
+ ): AgentConfig[] {
50
63
  const agents: AgentConfig[] = [];
51
64
 
52
65
  if (!fs.existsSync(dir)) return agents;
@@ -54,25 +67,72 @@ function loadAgentsFromDir(dir: string, source: "user" | "project" | "bundled"):
54
67
  let entries: fs.Dirent[];
55
68
  try {
56
69
  entries = fs.readdirSync(dir, { withFileTypes: true });
57
- } catch {
70
+ } catch (err) {
71
+ diagnostics.push({
72
+ filePath: dir,
73
+ issue: `Cannot read directory: ${err instanceof Error ? err.message : String(err)}`,
74
+ severity: "warn",
75
+ });
58
76
  return agents;
59
77
  }
60
78
 
61
79
  for (const entry of entries) {
62
80
  if (!entry.name.endsWith(".md")) continue;
63
- if (!entry.isFile() && !entry.isSymbolicLink()) continue;
81
+ if (!entry.isFile() && !entry.isSymbolicLink()) {
82
+ diagnostics.push({
83
+ filePath: path.join(dir, entry.name),
84
+ issue: `Not a regular file or symlink, skipping.`,
85
+ severity: "warn",
86
+ });
87
+ continue;
88
+ }
64
89
 
65
90
  const filePath = path.join(dir, entry.name);
66
91
  let content: string;
67
92
  try {
68
93
  content = fs.readFileSync(filePath, "utf-8");
69
- } catch {
94
+ } catch (err) {
95
+ diagnostics.push({
96
+ filePath,
97
+ issue: `Cannot read file: ${err instanceof Error ? err.message : String(err)}`,
98
+ severity: "error",
99
+ });
70
100
  continue;
71
101
  }
72
102
 
73
103
  const { frontmatter, body } = parseFrontmatter<Record<string, unknown>>(content);
74
104
 
75
- if (typeof frontmatter.name !== "string" || typeof frontmatter.description !== "string") continue;
105
+ if (typeof frontmatter.name !== "string" || typeof frontmatter.description !== "string") {
106
+ if (typeof frontmatter.name !== "string" && typeof frontmatter.description !== "string") {
107
+ diagnostics.push({
108
+ filePath,
109
+ issue: `Missing both "name" and "description" in frontmatter. Agent file skipped.`,
110
+ severity: "error",
111
+ });
112
+ } else if (typeof frontmatter.name !== "string") {
113
+ diagnostics.push({
114
+ filePath,
115
+ issue: `Missing "name" in frontmatter. Agent file skipped.`,
116
+ severity: "error",
117
+ });
118
+ } else {
119
+ diagnostics.push({
120
+ filePath,
121
+ issue: `Missing "description" in frontmatter. Agent file skipped.`,
122
+ severity: "error",
123
+ });
124
+ }
125
+ continue;
126
+ }
127
+
128
+ if (!frontmatter.name.trim()) {
129
+ diagnostics.push({
130
+ filePath,
131
+ issue: `"name" in frontmatter is empty. Agent file skipped.`,
132
+ severity: "error",
133
+ });
134
+ continue;
135
+ }
76
136
 
77
137
  const tools =
78
138
  typeof frontmatter.tools === "string"
@@ -81,11 +141,33 @@ function loadAgentsFromDir(dir: string, source: "user" | "project" | "bundled"):
81
141
  ? (frontmatter.tools as unknown[]).filter((t): t is string => typeof t === "string")
82
142
  : undefined;
83
143
 
144
+ if (typeof frontmatter.model === "string" && frontmatter.model && !frontmatter.model.includes("/")) {
145
+ diagnostics.push({
146
+ filePath,
147
+ issue: `Model "${frontmatter.model}" does not include a provider prefix (e.g., "anthropic/claude-sonnet-4-20250514"). Resolution may fail.`,
148
+ severity: "warn",
149
+ });
150
+ }
151
+
152
+ if (typeof frontmatter.thinking === "string" && frontmatter.thinking) {
153
+ const validLevels = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
154
+ if (!validLevels.includes(frontmatter.thinking)) {
155
+ diagnostics.push({
156
+ filePath,
157
+ issue: `Invalid thinking level "${frontmatter.thinking}". Valid values: ${validLevels.join(", ")}. Using default.`,
158
+ severity: "warn",
159
+ });
160
+ }
161
+ }
162
+
84
163
  agents.push({
85
164
  name: frontmatter.name,
86
165
  description: frontmatter.description,
87
166
  tools: tools && tools.length > 0 ? tools : undefined,
88
167
  model: typeof frontmatter.model === "string" ? frontmatter.model : undefined,
168
+ thinking: typeof frontmatter.thinking === "string" && ["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(frontmatter.thinking)
169
+ ? frontmatter.thinking as AgentConfig["thinking"]
170
+ : undefined,
89
171
  systemPrompt: body,
90
172
  source,
91
173
  filePath,
@@ -165,16 +247,18 @@ export function discoverAgents(
165
247
  }
166
248
  }
167
249
  if (!stale) {
168
- return { agents: _cache.agents, projectAgentsDir: _cache.projectAgentsDir };
250
+ return { agents: _cache.agents, projectAgentsDir: _cache.projectAgentsDir, diagnostics: [] };
169
251
  }
170
252
  // Cache is stale — rebuild below
171
253
  _cache = null;
172
254
  }
173
255
 
174
- const userAgents = scope === "project" ? [] : loadAgentsFromDir(userDir, "user");
256
+ const diagnostics: AgentDiscoveryDiagnostic[] = [];
257
+
258
+ const userAgents = scope === "project" ? [] : loadAgentsFromDir(userDir, "user", diagnostics);
175
259
  const projectAgents =
176
- scope === "user" || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, "project");
177
- const bundledAgents = loadAgentsFromDir(bundledAgentsDir, "bundled");
260
+ scope === "user" || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, "project", diagnostics);
261
+ const bundledAgents = loadAgentsFromDir(bundledAgentsDir, "bundled", diagnostics);
178
262
 
179
263
  const agentMap = new Map<string, AgentConfig>();
180
264
 
@@ -205,7 +289,7 @@ export function discoverAgents(
205
289
  dirSignatures,
206
290
  };
207
291
 
208
- return { agents, projectAgentsDir };
292
+ return { agents, projectAgentsDir, diagnostics };
209
293
  }
210
294
 
211
295
  export function formatAgentList(agents: AgentConfig[], maxItems: number): { text: string; remaining: number } {