@bacnh85/pi-subagent 0.4.1 → 0.5.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/README.md +28 -76
- package/agent-format.md +8 -3
- package/agents/reviewer.md +28 -22
- package/agents/scout.md +3 -3
- package/agents/worker.md +2 -2
- package/{agents.ts → extensions/agents.ts} +5 -1
- package/{index.ts → extensions/index.ts} +113 -113
- package/extensions/model.ts +86 -0
- package/extensions/package.json +3 -0
- package/{render.ts → extensions/render.ts} +2 -2
- package/{runner.ts → extensions/runner.ts} +9 -5
- package/extensions/service.ts +79 -0
- package/package.json +36 -31
- /package/{thread-viewer.ts → extensions/thread-viewer.ts} +0 -0
- /package/{threads.ts → extensions/threads.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,100 +1,52 @@
|
|
|
1
1
|
# pi-subagent
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
pi install ./extensions/pi-subagent
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Or test directly:
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
pi -e ./extensions/pi-subagent
|
|
8
|
+
pi install npm:@bacnh85/pi-subagent
|
|
9
|
+
# local checkout
|
|
10
|
+
pi install ./pi-subagent
|
|
29
11
|
```
|
|
30
12
|
|
|
31
|
-
##
|
|
13
|
+
## Bundled roles
|
|
32
14
|
|
|
33
|
-
|
|
15
|
+
| Role | Model | Thinking | Tools |
|
|
16
|
+
| --- | --- | --- | --- |
|
|
17
|
+
| `scout` | parent model | low | read, grep, find, ls |
|
|
18
|
+
| `reviewer` | parent model | high | read, grep, find, ls |
|
|
19
|
+
| `worker` | parent model | medium | standard coding tools |
|
|
20
|
+
| `general-purpose` | parent model | off | standard coding tools |
|
|
34
21
|
|
|
35
|
-
|
|
22
|
+
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`.
|
|
36
23
|
|
|
37
|
-
|
|
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
|
|
24
|
+
## Agent files
|
|
40
25
|
|
|
41
|
-
|
|
42
|
-
see all output at once, you can focus on one thread at a time.
|
|
43
|
-
|
|
44
|
-
### Single agent
|
|
45
|
-
|
|
46
|
-
```
|
|
47
|
-
Use scout to find authentication code in this project
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### Parallel execution
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
Run 2 scouts in parallel: one for models, one for providers
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Chain workflow
|
|
57
|
-
|
|
58
|
-
```
|
|
59
|
-
Chain: scout finds auth code, then reviewer checks it for security issues
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Included Agents
|
|
63
|
-
|
|
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 |
|
|
69
|
-
|
|
70
|
-
## Custom Agents
|
|
71
|
-
|
|
72
|
-
Create Markdown files with YAML frontmatter in `~/.pi/agent/agents/` (user-level) or `.pi/agents/` (project-level):
|
|
26
|
+
Create `~/.pi/agent/agents/*.md` or `.pi/agents/*.md`:
|
|
73
27
|
|
|
74
28
|
```markdown
|
|
75
29
|
---
|
|
76
|
-
name:
|
|
77
|
-
description:
|
|
78
|
-
tools: read, grep, find, ls
|
|
79
|
-
|
|
30
|
+
name: scout-fast
|
|
31
|
+
description: Locate relevant files and symbols
|
|
32
|
+
tools: read, grep, find, ls
|
|
33
|
+
thinking: low
|
|
34
|
+
model: optional-provider/optional-model
|
|
80
35
|
---
|
|
81
36
|
|
|
82
|
-
|
|
37
|
+
Return concise evidence with file/symbol anchors.
|
|
83
38
|
```
|
|
84
39
|
|
|
85
|
-
|
|
40
|
+
Project agents require confirmation when requested through the public tool. Definitions are cached with file-signature invalidation; `/subagent reload` clears the cache.
|
|
41
|
+
|
|
42
|
+
## Context and limits
|
|
86
43
|
|
|
87
|
-
|
|
44
|
+
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.
|
|
88
45
|
|
|
89
|
-
|
|
46
|
+
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`.
|
|
90
47
|
|
|
91
|
-
|
|
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
|
|
48
|
+
## Extension contract
|
|
96
49
|
|
|
97
|
-
|
|
50
|
+
`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.
|
|
98
51
|
|
|
99
|
-
|
|
100
|
-
- Peer dependencies satisfied by the pi runtime (no extra npm install needed)
|
|
52
|
+
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
|
-
| `<
|
|
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:
|
|
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
|
|
|
@@ -47,13 +48,17 @@ Model IDs are resolved via `getModel("provider", "id")`. Common values:
|
|
|
47
48
|
|
|
48
49
|
If not specified, defaults to the parent session's model.
|
|
49
50
|
|
|
51
|
+
## Instruction handoff
|
|
52
|
+
|
|
53
|
+
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.
|
|
54
|
+
|
|
50
55
|
## Token Budget
|
|
51
56
|
|
|
52
57
|
Each sub-agent runs with:
|
|
53
58
|
- **System prompt**: agent body only (~200-1K tokens typical)
|
|
54
59
|
- **No AGENTS.md**: saves 500-5K tokens
|
|
55
60
|
- **No extensions/skills loaded**: saves 200-1K tokens
|
|
56
|
-
- **Thinking
|
|
61
|
+
- **Thinking per role**: defaults off; bundled scout/reviewer/worker choose low/high/medium
|
|
57
62
|
- **No compaction**: avoids compaction token cost
|
|
58
63
|
|
|
59
64
|
This is ~10x leaner than spawning a full `pi` process.
|
package/agents/reviewer.md
CHANGED
|
@@ -1,30 +1,36 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: reviewer
|
|
3
|
-
description: Code review specialist. Use for
|
|
4
|
-
tools: read, grep, find, ls
|
|
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
|
|
8
|
+
You are an independent senior code reviewer. Inspect the requested Git scope with read-only tools.
|
|
9
9
|
|
|
10
|
-
Focus on:
|
|
11
|
-
1.
|
|
12
|
-
2.
|
|
13
|
-
3.
|
|
14
|
-
4.
|
|
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
|
-
|
|
16
|
+
Avoid style noise, praise, and speculative redesign. Every finding needs code evidence.
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
26
|
-
List
|
|
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
|
|
4
|
-
|
|
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-
|
|
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;
|
|
@@ -86,6 +87,9 @@ function loadAgentsFromDir(dir: string, source: "user" | "project" | "bundled"):
|
|
|
86
87
|
description: frontmatter.description,
|
|
87
88
|
tools: tools && tools.length > 0 ? tools : undefined,
|
|
88
89
|
model: typeof frontmatter.model === "string" ? frontmatter.model : undefined,
|
|
90
|
+
thinking: typeof frontmatter.thinking === "string" && ["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(frontmatter.thinking)
|
|
91
|
+
? frontmatter.thinking as AgentConfig["thinking"]
|
|
92
|
+
: undefined,
|
|
89
93
|
systemPrompt: body,
|
|
90
94
|
source,
|
|
91
95
|
filePath,
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
|
|
16
16
|
import * as path from "node:path";
|
|
17
17
|
import type { Model } from "@earendil-works/pi-ai";
|
|
18
|
-
import { getModel } from "@earendil-works/pi-ai/compat";
|
|
19
18
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
20
19
|
import {
|
|
21
20
|
AuthStorage,
|
|
22
21
|
CONFIG_DIR_NAME,
|
|
23
22
|
DynamicBorder,
|
|
24
23
|
type ExtensionAPI,
|
|
24
|
+
type ExtensionContext,
|
|
25
25
|
getAgentDir,
|
|
26
26
|
getMarkdownTheme,
|
|
27
27
|
ModelRegistry,
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
renderSingleResult,
|
|
45
45
|
} from "./render.ts";
|
|
46
46
|
import { type SubagentThread, threadStore } from "./threads.ts";
|
|
47
|
+
import { SUBAGENT_REQUEST_EVENT, runNamedAgent, type SubagentRunRequest } from "./service.ts";
|
|
47
48
|
import { ThreadViewer, type ThreadViewerCallbacks } from "./thread-viewer.ts";
|
|
48
49
|
|
|
49
50
|
// ---------------------------------------------------------------------------
|
|
@@ -54,6 +55,8 @@ const MAX_PARALLEL_TASKS = 8;
|
|
|
54
55
|
const MAX_CONCURRENCY = 4;
|
|
55
56
|
const PER_TASK_OUTPUT_CAP = 50 * 1024; // 50 KB per parallel task
|
|
56
57
|
|
|
58
|
+
import { resolveModel } from "./model.ts";
|
|
59
|
+
|
|
57
60
|
// ---------------------------------------------------------------------------
|
|
58
61
|
// Helpers
|
|
59
62
|
// ---------------------------------------------------------------------------
|
|
@@ -69,35 +72,6 @@ function truncateParallelOutput(output: string): string {
|
|
|
69
72
|
return `${truncated}\n\n[Output truncated: ${byteLength - Buffer.byteLength(truncated, "utf8")} bytes omitted.]`;
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
interface ResolvedModel {
|
|
73
|
-
model: Model | null;
|
|
74
|
-
attempted: string[];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function resolveModel(
|
|
78
|
-
modelName: string | undefined,
|
|
79
|
-
parentModel: Model | undefined,
|
|
80
|
-
): ResolvedModel {
|
|
81
|
-
const attempted: string[] = [];
|
|
82
|
-
if (modelName) {
|
|
83
|
-
// Try as provider/id first, then fall back to anthropic/id
|
|
84
|
-
const parts = modelName.split("/");
|
|
85
|
-
if (parts.length === 2) {
|
|
86
|
-
attempted.push(modelName);
|
|
87
|
-
const found = getModel(parts[0], parts[1]) ?? null;
|
|
88
|
-
if (found) return { model: found, attempted };
|
|
89
|
-
} else {
|
|
90
|
-
// Assume Anthropic shorthand
|
|
91
|
-
attempted.push(`anthropic/${modelName}`);
|
|
92
|
-
const found = getModel("anthropic", modelName) ?? null;
|
|
93
|
-
if (found) return { model: found, attempted };
|
|
94
|
-
}
|
|
95
|
-
} else if (parentModel) {
|
|
96
|
-
attempted.push(`${parentModel.provider}/${parentModel.id}`);
|
|
97
|
-
return { model: parentModel, attempted };
|
|
98
|
-
}
|
|
99
|
-
return { model: null, attempted };
|
|
100
|
-
}
|
|
101
75
|
|
|
102
76
|
// ---------------------------------------------------------------------------
|
|
103
77
|
// Tool parameter schema
|
|
@@ -143,6 +117,7 @@ const SubagentParams = Type.Object({
|
|
|
143
117
|
),
|
|
144
118
|
cwd: Type.Optional(Type.String({ description: "Working directory (single mode)" })),
|
|
145
119
|
timeout: Type.Optional(Type.Number({ description: "Global timeout in milliseconds for all sub-agents (overridden by per-task/step timeouts)" })),
|
|
120
|
+
instructions: Type.Optional(Type.String({ description: "Bounded repository/task instructions passed to each child (max 16 KB)" })),
|
|
146
121
|
abortOnFailure: Type.Optional(Type.Boolean({ description: "In parallel mode, cancel remaining tasks when one fails. Default: false.", default: false })),
|
|
147
122
|
});
|
|
148
123
|
|
|
@@ -162,8 +137,11 @@ interface SubagentDetails {
|
|
|
162
137
|
// ---------------------------------------------------------------------------
|
|
163
138
|
|
|
164
139
|
export default function (pi: ExtensionAPI) {
|
|
165
|
-
|
|
166
|
-
|
|
140
|
+
let currentCtx: ExtensionContext | undefined;
|
|
141
|
+
|
|
142
|
+
// Invalidate agent cache + clear thread store on session replacement.
|
|
143
|
+
pi.on("session_start", (event, ctx) => {
|
|
144
|
+
currentCtx = ctx;
|
|
167
145
|
if (event.reason === "reload") invalidateAgentCache();
|
|
168
146
|
threadStore.clear();
|
|
169
147
|
});
|
|
@@ -180,22 +158,43 @@ export default function (pi: ExtensionAPI) {
|
|
|
180
158
|
}
|
|
181
159
|
});
|
|
182
160
|
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
161
|
+
// Resolve bundled agents directory relative to this extension file
|
|
162
|
+
const bundledAgentsDir = path.resolve(__dirname, "../agents");
|
|
163
|
+
|
|
164
|
+
// Public one-request/one-response service used by pi-review.
|
|
165
|
+
pi.events.on(SUBAGENT_REQUEST_EVENT, (raw) => {
|
|
166
|
+
const request = raw as SubagentRunRequest;
|
|
167
|
+
const ctx = currentCtx;
|
|
168
|
+
if (!ctx || !request?.id || typeof request.respond !== "function") return;
|
|
169
|
+
if (request.accept && !request.accept()) return;
|
|
170
|
+
const agent = discoverAgents(ctx.cwd, "user", bundledAgentsDir).agents.find((item) => item.name === request.agent);
|
|
171
|
+
if (!agent) {
|
|
172
|
+
request.respond({ id: request.id, ok: false, error: `Unknown agent: ${request.agent}` });
|
|
173
|
+
return;
|
|
193
174
|
}
|
|
175
|
+
const thread = threadStore.createThread({ agentName: agent.name, task: request.task, mode: "single" });
|
|
176
|
+
void runNamedAgent({
|
|
177
|
+
agent: request.readOnly ? { ...agent, tools: ["read", "grep", "find", "ls"] } : agent,
|
|
178
|
+
task: request.task,
|
|
179
|
+
cwd: request.cwd ?? ctx.cwd,
|
|
180
|
+
ctx,
|
|
181
|
+
timeout: request.timeout,
|
|
182
|
+
instructions: request.instructions,
|
|
183
|
+
signal: request.signal,
|
|
184
|
+
onMessage: (result) => threadStore.updateThread(thread.id, { result }),
|
|
185
|
+
}).then((result) => {
|
|
186
|
+
threadStore.updateThread(thread.id, {
|
|
187
|
+
status: isFailedResult(result) ? (result.stopReason === "aborted" ? "aborted" : "failed") : "completed",
|
|
188
|
+
result,
|
|
189
|
+
});
|
|
190
|
+
if (isFailedResult(result)) request.respond({ id: request.id, ok: false, error: getResultOutput(result) });
|
|
191
|
+
else request.respond({ id: request.id, ok: true, result });
|
|
192
|
+
}, (error) => {
|
|
193
|
+
threadStore.updateThread(thread.id, { status: "failed" });
|
|
194
|
+
request.respond({ id: request.id, ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
195
|
+
});
|
|
194
196
|
});
|
|
195
197
|
|
|
196
|
-
// Resolve bundled agents directory relative to this extension file
|
|
197
|
-
const bundledAgentsDir = path.resolve(__dirname, "agents");
|
|
198
|
-
|
|
199
198
|
// /subagent command — list available agents
|
|
200
199
|
pi.registerCommand("subagent", {
|
|
201
200
|
description: "List available sub-agents, reload agent definitions, or show agent details",
|
|
@@ -246,6 +245,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
246
245
|
`Agent: ${agent.name} (${agent.source})`,
|
|
247
246
|
`Description: ${agent.description}`,
|
|
248
247
|
`Model: ${agent.model || "inherits from parent"}`,
|
|
248
|
+
`Thinking: ${agent.thinking || "off"}`,
|
|
249
249
|
`Tools: ${agent.tools?.join(", ") || "all default"}`,
|
|
250
250
|
`Source file: ${agent.filePath}`,
|
|
251
251
|
"",
|
|
@@ -326,12 +326,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
326
326
|
};
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
-
//
|
|
330
|
-
if (
|
|
331
|
-
(agentScope === "project" || agentScope === "both") &&
|
|
332
|
-
confirmProjectAgents &&
|
|
333
|
-
ctx.hasUI
|
|
334
|
-
) {
|
|
329
|
+
// Handle project-local agent confirmation
|
|
330
|
+
if (agentScope === "project" || agentScope === "both") {
|
|
335
331
|
const requestedAgentNames = new Set<string>();
|
|
336
332
|
if (params.chain) for (const s of params.chain) requestedAgentNames.add(s.agent);
|
|
337
333
|
if (params.tasks) for (const t of params.tasks) requestedAgentNames.add(t.agent);
|
|
@@ -341,16 +337,30 @@ export default function (pi: ExtensionAPI) {
|
|
|
341
337
|
.map((name) => agents.find((a) => a.name === name))
|
|
342
338
|
.filter((a): a is AgentConfig => a?.source === "project");
|
|
343
339
|
|
|
344
|
-
if (projectAgentsRequested.length > 0) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
340
|
+
if (projectAgentsRequested.length > 0 && confirmProjectAgents) {
|
|
341
|
+
if (ctx.hasUI) {
|
|
342
|
+
const names = projectAgentsRequested.map((a) => a.name).join(", ");
|
|
343
|
+
const dir = discovery.projectAgentsDir ?? "(unknown)";
|
|
344
|
+
const ok = await ctx.ui.confirm(
|
|
345
|
+
"Run project-local agents?",
|
|
346
|
+
`Agents: ${names}\nSource: ${dir}\n\nProject agents are repo-controlled. Only continue for trusted repositories.`,
|
|
347
|
+
);
|
|
348
|
+
if (!ok) {
|
|
349
|
+
return {
|
|
350
|
+
content: [{ type: "text", text: "Canceled: project-local agents not approved." }],
|
|
351
|
+
details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
// ponytail: fail closed in headless sessions — project agent
|
|
356
|
+
// prompts and tools run without user oversight.
|
|
352
357
|
return {
|
|
353
|
-
content: [{
|
|
358
|
+
content: [{
|
|
359
|
+
type: "text",
|
|
360
|
+
text: "Cannot run project-local agents without UI confirmation. "
|
|
361
|
+
+ "Set confirmProjectAgents: false to allow in headless sessions, "
|
|
362
|
+
+ "or use agentScope: 'user' to skip project agents.",
|
|
363
|
+
}],
|
|
354
364
|
details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
|
|
355
365
|
};
|
|
356
366
|
}
|
|
@@ -358,14 +368,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
358
368
|
}
|
|
359
369
|
|
|
360
370
|
// Shared auth/model setup for SDK sessions
|
|
361
|
-
|
|
362
|
-
|
|
371
|
+
// ponytail: reuse parent modelRegistry instead of a fresh copy — avoids
|
|
372
|
+
// internal API casts (storeModelHeaders) and preserves env/headers/OAuth.
|
|
373
|
+
const authStorage = AuthStorage.inMemory();
|
|
374
|
+
const modelRegistry = ctx.modelRegistry;
|
|
363
375
|
|
|
364
376
|
// Helper: inject parent's API key into child auth storage
|
|
365
|
-
async function injectApiKey(model: Model): Promise<void> {
|
|
377
|
+
async function injectApiKey(model: Model<any>): Promise<void> {
|
|
366
378
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
367
|
-
if (auth.ok
|
|
368
|
-
authStorage.setRuntimeApiKey(model.provider, auth.apiKey);
|
|
379
|
+
if (auth.ok) {
|
|
380
|
+
if (auth.apiKey) authStorage.setRuntimeApiKey(model.provider, auth.apiKey);
|
|
381
|
+
// ponytail: headers/env stay on the parent registry — no copy needed.
|
|
369
382
|
}
|
|
370
383
|
}
|
|
371
384
|
|
|
@@ -393,7 +406,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
393
406
|
};
|
|
394
407
|
}
|
|
395
408
|
|
|
396
|
-
const resolved = resolveModel(agent.model, ctx.model);
|
|
409
|
+
const resolved = resolveModel(agent.model, ctx.model, ctx.modelRegistry);
|
|
397
410
|
if (!resolved.model) {
|
|
398
411
|
const tried = resolved.attempted.join(", ") || "none";
|
|
399
412
|
const parentInfo = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "none";
|
|
@@ -417,53 +430,40 @@ export default function (pi: ExtensionAPI) {
|
|
|
417
430
|
let tools = agent.tools ?? defaultTools;
|
|
418
431
|
tools = tools.filter((t) => t !== "subagent");
|
|
419
432
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
433
|
+
const timeoutController = timeoutMs && timeoutMs > 0 ? new AbortController() : undefined;
|
|
434
|
+
const timeoutId = timeoutController ? setTimeout(() => timeoutController.abort(), timeoutMs) : undefined;
|
|
435
|
+
const signals = [parentSignal, timeoutController?.signal].filter((value): value is AbortSignal => Boolean(value));
|
|
436
|
+
const combinedSignal = signals.length > 1
|
|
437
|
+
? typeof (AbortSignal as any).any === "function"
|
|
438
|
+
? (AbortSignal as any).any(signals)
|
|
439
|
+
: signals[0]
|
|
440
|
+
: signals[0];
|
|
441
|
+
|
|
442
|
+
try {
|
|
443
|
+
const result = await runSubAgent({
|
|
444
|
+
cwd: cwd ?? ctx.cwd,
|
|
445
|
+
systemPrompt: params.instructions
|
|
446
|
+
? `${agent.systemPrompt}\n\n## Task Contract\n${params.instructions.slice(0, 16 * 1024)}`
|
|
447
|
+
: agent.systemPrompt,
|
|
448
|
+
task,
|
|
449
|
+
tools,
|
|
450
|
+
model: resolved.model,
|
|
451
|
+
authStorage,
|
|
452
|
+
modelRegistry,
|
|
453
|
+
signal: combinedSignal,
|
|
454
|
+
agentName,
|
|
455
|
+
thinkingLevel: agent.thinking,
|
|
456
|
+
onMessage: onProgress,
|
|
457
|
+
});
|
|
458
|
+
if (timeoutController?.signal.aborted && !parentSignal?.aborted) {
|
|
459
|
+
result.exitCode = 1;
|
|
460
|
+
result.stopReason = "timeout";
|
|
461
|
+
result.errorMessage ||= `Timeout after ${timeoutMs}ms`;
|
|
439
462
|
}
|
|
463
|
+
return result;
|
|
464
|
+
} finally {
|
|
465
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
440
466
|
}
|
|
441
|
-
|
|
442
|
-
const result = await runSubAgent({
|
|
443
|
-
cwd: cwd ?? ctx.cwd,
|
|
444
|
-
systemPrompt: agent.systemPrompt,
|
|
445
|
-
task,
|
|
446
|
-
tools,
|
|
447
|
-
model: resolved.model,
|
|
448
|
-
authStorage,
|
|
449
|
-
modelRegistry,
|
|
450
|
-
signal: combinedSignal,
|
|
451
|
-
agentName,
|
|
452
|
-
onMessage: onProgress,
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
// Clean up timeout
|
|
456
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
457
|
-
|
|
458
|
-
// Detect timeout: our timeout controller fired, not the parent
|
|
459
|
-
const timedOut = timeoutController?.signal.aborted && !parentSignal?.aborted;
|
|
460
|
-
if (timedOut) {
|
|
461
|
-
result.exitCode = 1;
|
|
462
|
-
result.stopReason = "timeout";
|
|
463
|
-
if (!result.errorMessage) result.errorMessage = `Timeout after ${timeoutMs}ms`;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
return result;
|
|
467
467
|
}
|
|
468
468
|
|
|
469
469
|
// --- Chain mode ---
|
|
@@ -1051,7 +1051,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1051
1051
|
ctx: { ui: { custom: <T>(factory: any, opts?: any) => Promise<T> } },
|
|
1052
1052
|
items: PickerItem[],
|
|
1053
1053
|
): Promise<string | null> {
|
|
1054
|
-
return ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
1054
|
+
return ctx.ui.custom<string | null>((tui: any, theme: any, _kb: any, done: (value: string | null) => void) => {
|
|
1055
1055
|
const container = new Container();
|
|
1056
1056
|
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
1057
1057
|
container.addChild(new Text(theme.fg("accent", theme.bold("Subagents")), 1, 0));
|
|
@@ -1101,7 +1101,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1101
1101
|
const getThreads = () => threadStore.getAllThreads();
|
|
1102
1102
|
|
|
1103
1103
|
// Overlay mode: viewer appears above editor, Esc dismisses
|
|
1104
|
-
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
|
|
1104
|
+
await ctx.ui.custom<void>((tui: any, theme: any, _kb: any, done: () => void) => {
|
|
1105
1105
|
let unsubscribe: (() => void) | undefined;
|
|
1106
1106
|
let closed = false;
|
|
1107
1107
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared model resolution for pi-subagent.
|
|
3
|
+
*
|
|
4
|
+
* Provides a single canonical resolveModel() used by both the tool handler
|
|
5
|
+
* (index.ts) and the event-driven service path (service.ts), ensuring
|
|
6
|
+
* consistent error reporting across all sub-agent invocation paths.
|
|
7
|
+
*
|
|
8
|
+
* Queries the parent ModelRegistry first (catches custom-configured models
|
|
9
|
+
* with overridden base URLs, headers, compatibility settings). Falls back
|
|
10
|
+
* to the built-in registry for unconfigured models.
|
|
11
|
+
* For unqualified names (no provider prefix), known naming conventions
|
|
12
|
+
* are tried before assuming Anthropic.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { getModel } from "@earendil-works/pi-ai/compat";
|
|
16
|
+
import type { Model } from "@earendil-works/pi-ai";
|
|
17
|
+
import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
|
|
18
|
+
|
|
19
|
+
export interface ResolvedModel {
|
|
20
|
+
model: Model<any> | null;
|
|
21
|
+
attempted: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Known provider prefixes for unqualified model names. */
|
|
25
|
+
const KNOWN_PROVIDERS: [string, RegExp][] = [
|
|
26
|
+
["openai", /^gpt-/i],
|
|
27
|
+
["anthropic", /^claude-/i],
|
|
28
|
+
["google", /^gemini-/i],
|
|
29
|
+
["cohere", /^command-/i],
|
|
30
|
+
["deepseek", /^(deepseek-|ds-)/i],
|
|
31
|
+
["mistral", /^mistral-/i],
|
|
32
|
+
["groq", /^(groq-|llama-)/i],
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
function tryGetModel(
|
|
36
|
+
provider: string,
|
|
37
|
+
id: string,
|
|
38
|
+
modelRegistry?: ModelRegistry,
|
|
39
|
+
): Model<any> | null {
|
|
40
|
+
// Query parent ModelRegistry first — it includes custom-configured models
|
|
41
|
+
// (overridden base URLs, headers, compatibility settings, per-model overrides).
|
|
42
|
+
// Fall back to built-in registry for unconfigured models.
|
|
43
|
+
if (modelRegistry) {
|
|
44
|
+
const found = modelRegistry.find(provider as any, id as any) ?? null;
|
|
45
|
+
if (found) return found;
|
|
46
|
+
}
|
|
47
|
+
const builtIn = getModel(provider as any, id as any) ?? null;
|
|
48
|
+
if (builtIn) return builtIn;
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function resolveModel(
|
|
53
|
+
modelName: string | undefined,
|
|
54
|
+
parentModel: Model<any> | undefined,
|
|
55
|
+
modelRegistry?: ModelRegistry,
|
|
56
|
+
): ResolvedModel {
|
|
57
|
+
const attempted: string[] = [];
|
|
58
|
+
if (modelName) {
|
|
59
|
+
const idx = modelName.indexOf("/");
|
|
60
|
+
if (idx > 0) {
|
|
61
|
+
// Provider-qualified: "openai/gpt-4o" or "openrouter/anthropic/claude-3.5"
|
|
62
|
+
const provider = modelName.slice(0, idx);
|
|
63
|
+
const id = modelName.slice(idx + 1);
|
|
64
|
+
attempted.push(modelName);
|
|
65
|
+
const found = tryGetModel(provider, id, modelRegistry);
|
|
66
|
+
if (found) return { model: found, attempted };
|
|
67
|
+
} else {
|
|
68
|
+
// Unqualified: try known providers by naming convention
|
|
69
|
+
for (const [provider, pattern] of KNOWN_PROVIDERS) {
|
|
70
|
+
if (pattern.test(modelName)) {
|
|
71
|
+
attempted.push(`${provider}/${modelName}`);
|
|
72
|
+
const found = tryGetModel(provider, modelName, modelRegistry);
|
|
73
|
+
if (found) return { model: found, attempted };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Fall back to Anthropic shorthand (backward compat)
|
|
77
|
+
attempted.push(`anthropic/${modelName}`);
|
|
78
|
+
const found = tryGetModel("anthropic", modelName, modelRegistry);
|
|
79
|
+
if (found) return { model: found, attempted };
|
|
80
|
+
}
|
|
81
|
+
} else if (parentModel) {
|
|
82
|
+
attempted.push(`${parentModel.provider}/${parentModel.id}`);
|
|
83
|
+
return { model: parentModel, attempted };
|
|
84
|
+
}
|
|
85
|
+
return { model: null, attempted };
|
|
86
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TUI rendering for pi-
|
|
2
|
+
* TUI rendering for pi-subagent.
|
|
3
3
|
*
|
|
4
4
|
* Renders sub-agent results in collapsed and expanded views.
|
|
5
5
|
* Collapsed: status icon, agent name, last few items, usage stats.
|
|
@@ -187,7 +187,7 @@ function renderDisplayItems(
|
|
|
187
187
|
export function renderSingleResult(
|
|
188
188
|
result: SubAgentResult,
|
|
189
189
|
expanded: boolean,
|
|
190
|
-
theme: { fg: (c:
|
|
190
|
+
theme: { fg: (c: any, t: string) => string; bold: (t: string) => string },
|
|
191
191
|
): Container | Text {
|
|
192
192
|
const isError = isFailedResult(result);
|
|
193
193
|
const icon = isError ? theme.fg("error", "✗") : theme.fg("success", "✓");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SDK-based sub-agent runner for pi-
|
|
2
|
+
* SDK-based sub-agent runner for pi-subagent.
|
|
3
3
|
*
|
|
4
4
|
* Creates an in-process AgentSession via the pi SDK instead of spawning a
|
|
5
5
|
* separate `pi` process. This eliminates cold-start overhead and allows
|
|
@@ -61,11 +61,12 @@ export async function runSubAgent(options: {
|
|
|
61
61
|
systemPrompt: string;
|
|
62
62
|
task: string;
|
|
63
63
|
tools: string[];
|
|
64
|
-
model: Model
|
|
64
|
+
model: Model<any>;
|
|
65
65
|
authStorage: AuthStorage;
|
|
66
66
|
modelRegistry: ModelRegistry;
|
|
67
67
|
signal?: AbortSignal;
|
|
68
68
|
agentName?: string;
|
|
69
|
+
thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
69
70
|
onUpdate?: (text: string) => void;
|
|
70
71
|
onMessage?: (partialResult: SubAgentResult) => void;
|
|
71
72
|
}): Promise<SubAgentResult> {
|
|
@@ -79,6 +80,7 @@ export async function runSubAgent(options: {
|
|
|
79
80
|
modelRegistry,
|
|
80
81
|
signal,
|
|
81
82
|
agentName = "subagent",
|
|
83
|
+
thinkingLevel = "off",
|
|
82
84
|
onUpdate,
|
|
83
85
|
onMessage,
|
|
84
86
|
} = options;
|
|
@@ -123,7 +125,7 @@ export async function runSubAgent(options: {
|
|
|
123
125
|
const { session } = await createAgentSession({
|
|
124
126
|
cwd,
|
|
125
127
|
model,
|
|
126
|
-
thinkingLevel
|
|
128
|
+
thinkingLevel,
|
|
127
129
|
authStorage,
|
|
128
130
|
modelRegistry,
|
|
129
131
|
resourceLoader,
|
|
@@ -221,8 +223,10 @@ export async function runSubAgent(options: {
|
|
|
221
223
|
}
|
|
222
224
|
});
|
|
223
225
|
|
|
224
|
-
await
|
|
225
|
-
|
|
226
|
+
await Promise.race([
|
|
227
|
+
session.prompt(task),
|
|
228
|
+
eventPromise,
|
|
229
|
+
]);
|
|
226
230
|
|
|
227
231
|
if (result.stopReason !== "aborted") {
|
|
228
232
|
result.exitCode = 0;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { AuthStorage, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { AgentConfig } from "./agents.ts";
|
|
3
|
+
import { runSubAgent, type SubAgentResult } from "./runner.ts";
|
|
4
|
+
import { resolveModel } from "./model.ts";
|
|
5
|
+
|
|
6
|
+
export const SUBAGENT_REQUEST_EVENT = "pi-subagent:run";
|
|
7
|
+
|
|
8
|
+
export interface SubagentRunRequest {
|
|
9
|
+
id: string;
|
|
10
|
+
agent: string;
|
|
11
|
+
task: string;
|
|
12
|
+
cwd?: string;
|
|
13
|
+
timeout?: number;
|
|
14
|
+
instructions?: string;
|
|
15
|
+
readOnly?: boolean;
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
accept?: () => boolean;
|
|
18
|
+
respond: (response: SubagentRunResponse) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type SubagentRunResponse =
|
|
22
|
+
| { id: string; ok: true; result: SubAgentResult }
|
|
23
|
+
| { id: string; ok: false; error: string };
|
|
24
|
+
|
|
25
|
+
export async function runNamedAgent(options: {
|
|
26
|
+
agent: AgentConfig;
|
|
27
|
+
task: string;
|
|
28
|
+
cwd: string;
|
|
29
|
+
ctx: ExtensionContext;
|
|
30
|
+
timeout?: number;
|
|
31
|
+
instructions?: string;
|
|
32
|
+
signal?: AbortSignal;
|
|
33
|
+
onMessage?: (result: SubAgentResult) => void;
|
|
34
|
+
}): Promise<SubAgentResult> {
|
|
35
|
+
const { model, attempted } = resolveModel(options.agent.model, options.ctx.model, options.ctx.modelRegistry);
|
|
36
|
+
if (!model) throw new Error(`No model resolved for agent "${options.agent.name}" (tried: ${attempted.join(", ") || "none"})`);
|
|
37
|
+
|
|
38
|
+
const authStorage = AuthStorage.inMemory();
|
|
39
|
+
const modelRegistry = options.ctx.modelRegistry;
|
|
40
|
+
const auth = await options.ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
41
|
+
if (auth.ok) {
|
|
42
|
+
if (auth.apiKey) authStorage.setRuntimeApiKey(model.provider, auth.apiKey);
|
|
43
|
+
// ponytail: env and headers stay on the parent modelRegistry — reuse it directly.
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const timeoutController = options.timeout && options.timeout > 0 ? new AbortController() : undefined;
|
|
47
|
+
const timeoutId = timeoutController ? setTimeout(() => timeoutController.abort(), options.timeout) : undefined;
|
|
48
|
+
const signals = [options.signal, timeoutController?.signal].filter((value): value is AbortSignal => Boolean(value));
|
|
49
|
+
const signal = signals.length > 1
|
|
50
|
+
? typeof (AbortSignal as any).any === "function"
|
|
51
|
+
? (AbortSignal as any).any(signals)
|
|
52
|
+
: signals[0]
|
|
53
|
+
: signals[0];
|
|
54
|
+
const contract = options.instructions?.slice(0, 16 * 1024);
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const result = await runSubAgent({
|
|
58
|
+
cwd: options.cwd,
|
|
59
|
+
systemPrompt: contract ? `${options.agent.systemPrompt}\n\n## Task Contract\n${contract}` : options.agent.systemPrompt,
|
|
60
|
+
task: options.task,
|
|
61
|
+
tools: (options.agent.tools ?? ["read", "bash", "edit", "write", "grep", "find", "ls"]).filter((tool) => tool !== "subagent"),
|
|
62
|
+
model,
|
|
63
|
+
authStorage,
|
|
64
|
+
modelRegistry,
|
|
65
|
+
signal,
|
|
66
|
+
agentName: options.agent.name,
|
|
67
|
+
thinkingLevel: options.agent.thinking,
|
|
68
|
+
onMessage: options.onMessage,
|
|
69
|
+
});
|
|
70
|
+
if (timeoutController?.signal.aborted && !options.signal?.aborted) {
|
|
71
|
+
result.exitCode = 1;
|
|
72
|
+
result.stopReason = "timeout";
|
|
73
|
+
result.errorMessage ||= `Timeout after ${options.timeout}ms`;
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
} finally {
|
|
77
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
78
|
+
}
|
|
79
|
+
}
|
package/package.json
CHANGED
|
@@ -1,49 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bacnh85/pi-subagent",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Minimal-overhead sub-
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Minimal-overhead sub-agents for Pi with isolated SDK sessions, parallel and chained delegation, and inspectable threads.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"publishConfig": {
|
|
8
|
-
|
|
9
|
-
},
|
|
10
|
-
"homepage": "https://github.com/bacnh85/skills#readme",
|
|
7
|
+
"publishConfig": { "access": "public" },
|
|
8
|
+
"homepage": "https://github.com/bacnh85/pi-extensions#readme",
|
|
11
9
|
"repository": {
|
|
12
10
|
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/bacnh85/
|
|
14
|
-
"directory": "
|
|
15
|
-
},
|
|
16
|
-
"bugs": {
|
|
17
|
-
"url": "https://github.com/bacnh85/skills/issues"
|
|
11
|
+
"url": "git+https://github.com/bacnh85/pi-extensions.git",
|
|
12
|
+
"directory": "pi-subagent"
|
|
18
13
|
},
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
"pi-extension",
|
|
22
|
-
"subagent",
|
|
23
|
-
"sub-agent",
|
|
24
|
-
"delegation",
|
|
25
|
-
"parallel"
|
|
26
|
-
],
|
|
14
|
+
"bugs": { "url": "https://github.com/bacnh85/pi-extensions/issues" },
|
|
15
|
+
"keywords": ["pi-package", "pi-extension", "subagent", "sub-agent", "delegation", "parallel"],
|
|
27
16
|
"files": [
|
|
28
17
|
"README.md",
|
|
29
|
-
"
|
|
30
|
-
"agents.ts",
|
|
31
|
-
"runner.ts",
|
|
32
|
-
"render.ts",
|
|
33
|
-
"threads.ts",
|
|
34
|
-
"thread-viewer.ts",
|
|
18
|
+
"agent-format.md",
|
|
35
19
|
"agents/",
|
|
36
|
-
"
|
|
20
|
+
"extensions/index.ts",
|
|
21
|
+
"extensions/agents.ts",
|
|
22
|
+
"extensions/model.ts",
|
|
23
|
+
"extensions/runner.ts",
|
|
24
|
+
"extensions/service.ts",
|
|
25
|
+
"extensions/render.ts",
|
|
26
|
+
"extensions/threads.ts",
|
|
27
|
+
"extensions/thread-viewer.ts",
|
|
28
|
+
"extensions/package.json"
|
|
37
29
|
],
|
|
38
|
-
"pi": {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
"pi": { "extensions": ["./extensions/index.ts"] },
|
|
31
|
+
"scripts": {
|
|
32
|
+
"test": "cd extensions && npx mocha",
|
|
33
|
+
"typecheck": "tsc --noEmit"
|
|
42
34
|
},
|
|
43
35
|
"peerDependencies": {
|
|
44
36
|
"@earendil-works/pi-coding-agent": "*",
|
|
45
37
|
"@earendil-works/pi-ai": "*",
|
|
46
38
|
"@earendil-works/pi-agent-core": "*",
|
|
47
|
-
"@earendil-works/pi-tui": "*"
|
|
39
|
+
"@earendil-works/pi-tui": "*",
|
|
40
|
+
"typebox": "*"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@earendil-works/pi-agent-core": "^0.80.2",
|
|
44
|
+
"@earendil-works/pi-ai": "^0.80.2",
|
|
45
|
+
"@earendil-works/pi-coding-agent": "^0.80.2",
|
|
46
|
+
"@earendil-works/pi-tui": "^0.80.2",
|
|
47
|
+
"@types/mocha": "^10.0.10",
|
|
48
|
+
"@types/node": "^20.19.43",
|
|
49
|
+
"mocha": "^10.8.2",
|
|
50
|
+
"tsx": "^4.22.4",
|
|
51
|
+
"typescript": "^5.9.3",
|
|
52
|
+
"typebox": "^1.3.1"
|
|
48
53
|
}
|
|
49
54
|
}
|
|
File without changes
|
|
File without changes
|