@ilya-lesikov/pi-pi 0.8.0 → 0.10.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/3p/pi-ask-user/index.ts +71 -124
- package/3p/pi-ask-user/single-select-layout.ts +3 -14
- package/3p/pi-subagents/package.json +13 -7
- package/3p/pi-subagents/src/agent-manager.ts +243 -79
- package/3p/pi-subagents/src/agent-runner.ts +501 -365
- package/3p/pi-subagents/src/agent-types.ts +46 -57
- package/3p/pi-subagents/src/cross-extension-rpc.ts +52 -46
- package/3p/pi-subagents/src/custom-agents.ts +30 -6
- package/3p/pi-subagents/src/default-agents.ts +25 -43
- package/3p/pi-subagents/src/enabled-models.ts +180 -0
- package/3p/pi-subagents/src/index.ts +599 -839
- package/3p/pi-subagents/src/model-resolver.ts +26 -7
- package/3p/pi-subagents/src/output-file.ts +21 -2
- package/3p/pi-subagents/src/prompts.ts +17 -3
- package/3p/pi-subagents/src/schedule-store.ts +153 -0
- package/3p/pi-subagents/src/schedule.ts +365 -0
- package/3p/pi-subagents/src/settings.ts +261 -0
- package/3p/pi-subagents/src/skill-loader.ts +77 -54
- package/3p/pi-subagents/src/status-note.ts +25 -0
- package/3p/pi-subagents/src/types.ts +97 -6
- package/3p/pi-subagents/src/ui/agent-widget.ts +94 -21
- package/3p/pi-subagents/src/ui/conversation-viewer.ts +147 -35
- package/3p/pi-subagents/src/ui/schedule-menu.ts +104 -0
- package/3p/pi-subagents/src/ui/viewer-keys.ts +39 -0
- package/3p/pi-subagents/src/usage.ts +60 -0
- package/3p/pi-subagents/src/worktree.ts +49 -20
- package/extensions/orchestrator/agents/advisor.ts +13 -8
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +8 -3
- package/extensions/orchestrator/agents/code-reviewer.ts +29 -8
- package/extensions/orchestrator/agents/constraints.test.ts +65 -1
- package/extensions/orchestrator/agents/constraints.ts +73 -2
- package/extensions/orchestrator/agents/deep-debugger.ts +13 -8
- package/extensions/orchestrator/agents/explore.ts +12 -6
- package/extensions/orchestrator/agents/librarian.ts +13 -5
- package/extensions/orchestrator/agents/plan-reviewer.ts +8 -3
- package/extensions/orchestrator/agents/planner.ts +8 -3
- package/extensions/orchestrator/agents/pools.test.ts +56 -0
- package/extensions/orchestrator/agents/prompts.test.ts +52 -10
- package/extensions/orchestrator/agents/registry.test.ts +245 -0
- package/extensions/orchestrator/agents/registry.ts +145 -10
- package/extensions/orchestrator/agents/reviewer.ts +14 -8
- package/extensions/orchestrator/agents/task.ts +12 -6
- package/extensions/orchestrator/agents/tool-routing.ts +213 -69
- package/extensions/orchestrator/agents/wait-for-completion.test.ts +171 -0
- package/extensions/orchestrator/ai-comment-cleanup.test.ts +89 -0
- package/extensions/orchestrator/ai-comment-cleanup.ts +73 -0
- package/extensions/orchestrator/ast-search.test.ts +124 -0
- package/extensions/orchestrator/billing-spoof.test.ts +67 -0
- package/extensions/orchestrator/billing-spoof.ts +95 -0
- package/extensions/orchestrator/cbm.more.test.ts +358 -0
- package/extensions/orchestrator/command-handlers.test.ts +47 -0
- package/extensions/orchestrator/command-handlers.ts +49 -27
- package/extensions/orchestrator/commands.test.ts +15 -2
- package/extensions/orchestrator/commands.ts +1 -1
- package/extensions/orchestrator/config.test.ts +129 -2
- package/extensions/orchestrator/config.ts +115 -19
- package/extensions/orchestrator/context.test.ts +46 -0
- package/extensions/orchestrator/context.ts +34 -5
- package/extensions/orchestrator/custom-footer.test.ts +105 -0
- package/extensions/orchestrator/custom-footer.ts +22 -20
- package/extensions/orchestrator/doctor.more.test.ts +315 -0
- package/extensions/orchestrator/doctor.ts +6 -2
- package/extensions/orchestrator/event-handlers.more.test.ts +561 -0
- package/extensions/orchestrator/event-handlers.test.ts +411 -10
- package/extensions/orchestrator/event-handlers.ts +738 -349
- package/extensions/orchestrator/exa.more.test.ts +118 -0
- package/extensions/orchestrator/flant-infra.more.test.ts +326 -0
- package/extensions/orchestrator/flant-infra.test.ts +127 -0
- package/extensions/orchestrator/flant-infra.ts +113 -39
- package/extensions/orchestrator/index.test.ts +76 -0
- package/extensions/orchestrator/index.ts +2 -0
- package/extensions/orchestrator/integration.test.ts +488 -74
- package/extensions/orchestrator/model-registry.test.ts +2 -1
- package/extensions/orchestrator/model-registry.ts +12 -2
- package/extensions/orchestrator/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/extensions/orchestrator/orchestrator.test.ts +67 -0
- package/extensions/orchestrator/orchestrator.ts +165 -85
- package/extensions/orchestrator/phases/brainstorm.test.ts +30 -1
- package/extensions/orchestrator/phases/brainstorm.ts +53 -12
- package/extensions/orchestrator/phases/implementation.ts +2 -0
- package/extensions/orchestrator/phases/machine.test.ts +53 -1
- package/extensions/orchestrator/phases/machine.ts +15 -2
- package/extensions/orchestrator/phases/planning.test.ts +47 -1
- package/extensions/orchestrator/phases/planning.ts +18 -3
- package/extensions/orchestrator/phases/review-task.test.ts +20 -0
- package/extensions/orchestrator/phases/review-task.ts +47 -14
- package/extensions/orchestrator/phases/review.test.ts +36 -0
- package/extensions/orchestrator/phases/review.ts +62 -10
- package/extensions/orchestrator/phases/spawn-blocking.test.ts +1 -0
- package/extensions/orchestrator/phases/verdict.ts +6 -5
- package/extensions/orchestrator/plannotator-open-failure.test.ts +67 -0
- package/extensions/orchestrator/plannotator.test.ts +38 -1
- package/extensions/orchestrator/plannotator.ts +50 -3
- package/extensions/orchestrator/pp-menu.leaves.test.ts +590 -0
- package/extensions/orchestrator/pp-menu.more.test.ts +524 -0
- package/extensions/orchestrator/pp-menu.test.ts +314 -1
- package/extensions/orchestrator/pp-menu.ts +937 -450
- package/extensions/orchestrator/pp-state-tools.test.ts +80 -0
- package/extensions/orchestrator/pp-state-tools.ts +65 -0
- package/extensions/orchestrator/rate-limit-fallback.more.test.ts +241 -0
- package/extensions/orchestrator/rate-limit-fallback.test.ts +20 -1
- package/extensions/orchestrator/rate-limit-fallback.ts +12 -0
- package/extensions/orchestrator/review-files.test.ts +26 -0
- package/extensions/orchestrator/review-files.ts +3 -0
- package/extensions/orchestrator/state.test.ts +82 -1
- package/extensions/orchestrator/state.ts +110 -23
- package/extensions/orchestrator/suppress-pierre-theme-spam.test.ts +56 -0
- package/extensions/orchestrator/suppress-pierre-theme-spam.ts +45 -0
- package/extensions/orchestrator/transition-controller.test.ts +100 -0
- package/extensions/orchestrator/transition-controller.ts +61 -3
- package/extensions/orchestrator/validate-artifacts.ts +1 -1
- package/package.json +6 -2
- package/scripts/test-3p.sh +52 -0
- package/AGENTS.md +0 -28
|
@@ -5,33 +5,21 @@
|
|
|
5
5
|
* User agents override defaults with the same name. Disabled agents are kept but excluded from spawning.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
createBashTool,
|
|
11
|
-
createEditTool,
|
|
12
|
-
createFindTool,
|
|
13
|
-
createGrepTool,
|
|
14
|
-
createLsTool,
|
|
15
|
-
createReadTool,
|
|
16
|
-
createWriteTool,
|
|
17
|
-
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { createCodingTools, createReadOnlyTools } from "@earendil-works/pi-coding-agent";
|
|
18
9
|
import { DEFAULT_AGENTS } from "./default-agents.js";
|
|
19
10
|
import type { AgentConfig } from "./types.js";
|
|
20
11
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/** All known built-in tool names, derived from the factory registry. */
|
|
34
|
-
export const BUILTIN_TOOL_NAMES = Object.keys(TOOL_FACTORIES);
|
|
12
|
+
/**
|
|
13
|
+
* All known built-in tool names, derived from pi's own tool factories rather
|
|
14
|
+
* than hardcoded so the set tracks pi-mono if it adds/renames a built-in.
|
|
15
|
+
* `createCodingTools` → read/bash/edit/write; `createReadOnlyTools` →
|
|
16
|
+
* read/grep/find/ls; their de-duplicated union is the 7 built-ins
|
|
17
|
+
* (read, bash, edit, write, grep, find, ls). The `cwd` only binds tool
|
|
18
|
+
* operations we never invoke here — we read each tool's `.name` and discard it.
|
|
19
|
+
*/
|
|
20
|
+
export const BUILTIN_TOOL_NAMES: string[] = [
|
|
21
|
+
...new Set([...createCodingTools("."), ...createReadOnlyTools(".")].map((t) => t.name)),
|
|
22
|
+
];
|
|
35
23
|
|
|
36
24
|
/** Unified runtime registry of all agents (defaults + user-defined). */
|
|
37
25
|
const agents = new Map<string, AgentConfig>();
|
|
@@ -39,13 +27,20 @@ const agents = new Map<string, AgentConfig>();
|
|
|
39
27
|
/** Extension-injected agents (highest priority, survive reloads). */
|
|
40
28
|
const extensionAgents = new Map<string, AgentConfig>();
|
|
41
29
|
|
|
30
|
+
/** When true, DEFAULT_AGENTS are skipped during registration. */
|
|
31
|
+
let disableDefaults = false;
|
|
32
|
+
|
|
42
33
|
/** When true, only extension-registered agents are used. Defaults and user .md files are skipped. */
|
|
43
34
|
let extensionOnlyMode = false;
|
|
44
35
|
|
|
36
|
+
/** Check whether default agents are disabled. */
|
|
37
|
+
export function isDefaultsDisabled(): boolean { return disableDefaults; }
|
|
38
|
+
|
|
39
|
+
/** Set whether default agents are disabled. */
|
|
40
|
+
export function setDefaultsDisabled(b: boolean): void { disableDefaults = b; }
|
|
41
|
+
|
|
45
42
|
/** Enable extension-only mode: skip defaults and user .md agents entirely. */
|
|
46
|
-
export function setExtensionOnlyMode(enabled: boolean): void {
|
|
47
|
-
extensionOnlyMode = enabled;
|
|
48
|
-
}
|
|
43
|
+
export function setExtensionOnlyMode(enabled: boolean): void { extensionOnlyMode = enabled; }
|
|
49
44
|
|
|
50
45
|
/**
|
|
51
46
|
* Register agents into the unified registry.
|
|
@@ -57,9 +52,11 @@ export function registerAgents(userAgents: Map<string, AgentConfig>): void {
|
|
|
57
52
|
agents.clear();
|
|
58
53
|
|
|
59
54
|
if (!extensionOnlyMode) {
|
|
60
|
-
// Start with defaults
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
// Start with defaults (unless disabled via settings)
|
|
56
|
+
if (!disableDefaults) {
|
|
57
|
+
for (const [name, config] of DEFAULT_AGENTS) {
|
|
58
|
+
agents.set(name, config);
|
|
59
|
+
}
|
|
63
60
|
}
|
|
64
61
|
|
|
65
62
|
// Overlay user agents (overrides defaults with same name)
|
|
@@ -68,14 +65,14 @@ export function registerAgents(userAgents: Map<string, AgentConfig>): void {
|
|
|
68
65
|
}
|
|
69
66
|
}
|
|
70
67
|
|
|
71
|
-
// Overlay extension-injected agents (highest priority)
|
|
68
|
+
// Overlay extension-injected agents (highest priority, survive reloads)
|
|
72
69
|
for (const [name, config] of extensionAgents) {
|
|
73
70
|
agents.set(name, config);
|
|
74
71
|
}
|
|
75
72
|
}
|
|
76
73
|
|
|
77
74
|
/**
|
|
78
|
-
* Register agents from another extension (e.g. pi-pi).
|
|
75
|
+
* Register agents from another extension (e.g. pi-pi orchestrator).
|
|
79
76
|
* These persist across reloadCustomAgents() calls and take highest priority.
|
|
80
77
|
*/
|
|
81
78
|
export function registerExtensionAgents(newAgents: Map<string, AgentConfig>): void {
|
|
@@ -84,18 +81,14 @@ export function registerExtensionAgents(newAgents: Map<string, AgentConfig>): vo
|
|
|
84
81
|
}
|
|
85
82
|
}
|
|
86
83
|
|
|
87
|
-
/**
|
|
88
|
-
* Remove extension-registered agents by name.
|
|
89
|
-
*/
|
|
84
|
+
/** Remove extension-registered agents by name. */
|
|
90
85
|
export function unregisterExtensionAgents(names: string[]): void {
|
|
91
86
|
for (const name of names) {
|
|
92
87
|
extensionAgents.delete(name);
|
|
93
88
|
}
|
|
94
89
|
}
|
|
95
90
|
|
|
96
|
-
/**
|
|
97
|
-
* Remove extension-registered agents whose names start with a prefix.
|
|
98
|
-
*/
|
|
91
|
+
/** Remove extension-registered agents whose names start with a prefix. */
|
|
99
92
|
export function unregisterExtensionAgentsByPrefix(prefix: string): void {
|
|
100
93
|
for (const name of extensionAgents.keys()) {
|
|
101
94
|
if (name.startsWith(prefix)) {
|
|
@@ -104,9 +97,7 @@ export function unregisterExtensionAgentsByPrefix(prefix: string): void {
|
|
|
104
97
|
}
|
|
105
98
|
}
|
|
106
99
|
|
|
107
|
-
/**
|
|
108
|
-
* Remove all extension-registered agents.
|
|
109
|
-
*/
|
|
100
|
+
/** Remove all extension-registered agents. */
|
|
110
101
|
export function clearExtensionAgents(): void {
|
|
111
102
|
extensionAgents.clear();
|
|
112
103
|
}
|
|
@@ -169,35 +160,30 @@ export function isValidType(type: string): boolean {
|
|
|
169
160
|
const MEMORY_TOOL_NAMES = ["read", "write", "edit"];
|
|
170
161
|
|
|
171
162
|
/**
|
|
172
|
-
* Get
|
|
173
|
-
* Only returns tools that are NOT already in the provided set.
|
|
163
|
+
* Get memory tool names (read/write/edit) not already in the provided set.
|
|
174
164
|
*/
|
|
175
|
-
export function
|
|
176
|
-
return MEMORY_TOOL_NAMES
|
|
177
|
-
.filter(n => !existingToolNames.has(n) && n in TOOL_FACTORIES)
|
|
178
|
-
.map(n => TOOL_FACTORIES[n](cwd));
|
|
165
|
+
export function getMemoryToolNames(existingToolNames: Set<string>): string[] {
|
|
166
|
+
return MEMORY_TOOL_NAMES.filter(n => !existingToolNames.has(n));
|
|
179
167
|
}
|
|
180
168
|
|
|
181
169
|
/** Tool names needed for read-only memory access. */
|
|
182
170
|
const READONLY_MEMORY_TOOL_NAMES = ["read"];
|
|
183
171
|
|
|
184
172
|
/**
|
|
185
|
-
* Get only
|
|
186
|
-
* Only returns tools that are NOT already in the provided set.
|
|
173
|
+
* Get read-only memory tool names not already in the provided set.
|
|
187
174
|
*/
|
|
188
|
-
export function
|
|
189
|
-
return READONLY_MEMORY_TOOL_NAMES
|
|
190
|
-
.filter(n => !existingToolNames.has(n) && n in TOOL_FACTORIES)
|
|
191
|
-
.map(n => TOOL_FACTORIES[n](cwd));
|
|
175
|
+
export function getReadOnlyMemoryToolNames(existingToolNames: Set<string>): string[] {
|
|
176
|
+
return READONLY_MEMORY_TOOL_NAMES.filter(n => !existingToolNames.has(n));
|
|
192
177
|
}
|
|
193
178
|
|
|
194
|
-
/** Get built-in
|
|
195
|
-
export function
|
|
179
|
+
/** Get built-in tool names for a type (case-insensitive). */
|
|
180
|
+
export function getToolNamesForType(type: string): string[] {
|
|
196
181
|
const key = resolveKey(type);
|
|
197
182
|
const raw = key ? agents.get(key) : undefined;
|
|
198
183
|
const config = raw?.enabled !== false ? raw : undefined;
|
|
199
|
-
|
|
200
|
-
|
|
184
|
+
// `undefined` (definition omitted the field) → all built-ins; an explicit `[]`
|
|
185
|
+
// (`tools: none` or a `tools:` with only `ext:` entries) → zero built-ins.
|
|
186
|
+
return config?.builtinToolNames ?? [...BUILTIN_TOOL_NAMES];
|
|
201
187
|
}
|
|
202
188
|
|
|
203
189
|
/** Get config for a type (case-insensitive, returns a SubagentTypeConfig-compatible object). Falls back to general-purpose. */
|
|
@@ -206,6 +192,7 @@ export function getConfig(type: string): {
|
|
|
206
192
|
description: string;
|
|
207
193
|
builtinToolNames: string[];
|
|
208
194
|
extensions: true | string[] | false;
|
|
195
|
+
excludeExtensions?: string[];
|
|
209
196
|
skills: true | string[] | false;
|
|
210
197
|
promptMode: "replace" | "append";
|
|
211
198
|
} {
|
|
@@ -217,6 +204,7 @@ export function getConfig(type: string): {
|
|
|
217
204
|
description: config.description,
|
|
218
205
|
builtinToolNames: config.builtinToolNames ?? BUILTIN_TOOL_NAMES,
|
|
219
206
|
extensions: config.extensions,
|
|
207
|
+
excludeExtensions: config.excludeExtensions,
|
|
220
208
|
skills: config.skills,
|
|
221
209
|
promptMode: config.promptMode,
|
|
222
210
|
};
|
|
@@ -230,6 +218,7 @@ export function getConfig(type: string): {
|
|
|
230
218
|
description: gp.description,
|
|
231
219
|
builtinToolNames: gp.builtinToolNames ?? BUILTIN_TOOL_NAMES,
|
|
232
220
|
extensions: gp.extensions,
|
|
221
|
+
excludeExtensions: gp.excludeExtensions,
|
|
233
222
|
skills: gp.skills,
|
|
234
223
|
promptMode: gp.promptMode,
|
|
235
224
|
};
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* error → { success: false, error: string }
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { type ModelRegistry, resolveModel } from "./model-resolver.js";
|
|
13
|
+
|
|
12
14
|
/** Minimal event bus interface needed by the RPC handlers. */
|
|
13
15
|
export interface EventBus {
|
|
14
16
|
on(event: string, handler: (data: unknown) => void): () => void;
|
|
@@ -29,18 +31,10 @@ export interface SpawnCapable {
|
|
|
29
31
|
abort(id: string): boolean;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
export interface SpawnRequest {
|
|
33
|
-
requestId: string;
|
|
34
|
-
type: string;
|
|
35
|
-
prompt: string;
|
|
36
|
-
options?: any;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
34
|
export interface RpcDeps {
|
|
40
35
|
events: EventBus;
|
|
41
36
|
pi: unknown; // passed through to manager.spawn
|
|
42
37
|
getCtx: () => unknown | undefined; // returns current ExtensionContext
|
|
43
|
-
isSubagentSession?: (ctx: unknown) => boolean;
|
|
44
38
|
manager: SpawnCapable;
|
|
45
39
|
}
|
|
46
40
|
|
|
@@ -50,10 +44,6 @@ export interface RpcHandle {
|
|
|
50
44
|
unsubStop: () => void;
|
|
51
45
|
}
|
|
52
46
|
|
|
53
|
-
function emitReply(events: EventBus, channel: string, requestId: string, reply: RpcReply<unknown>): void {
|
|
54
|
-
events.emit(`${channel}:reply:${requestId}`, reply);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
47
|
/**
|
|
58
48
|
* Wire a single RPC handler: listen on `channel`, run `fn(params)`,
|
|
59
49
|
* emit the reply envelope on `channel:reply:${requestId}`.
|
|
@@ -83,47 +73,63 @@ function handleRpc<P extends { requestId: string }>(
|
|
|
83
73
|
* Returns unsub functions for cleanup.
|
|
84
74
|
*/
|
|
85
75
|
export function registerRpcHandlers(deps: RpcDeps): RpcHandle {
|
|
86
|
-
const { events, pi, getCtx,
|
|
76
|
+
const { events, pi, getCtx, manager } = deps;
|
|
87
77
|
|
|
88
78
|
const unsubPing = handleRpc(events, "subagents:rpc:ping", () => {
|
|
89
79
|
return { version: PROTOCOL_VERSION };
|
|
90
80
|
});
|
|
91
81
|
|
|
92
|
-
const unsubSpawn =
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
emitReply(events, "subagents:rpc:spawn", params.requestId, { success: false, error: "No active session" });
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
if (isSubagentSession?.(ctx)) {
|
|
100
|
-
emitReply(events, "subagents:rpc:spawn", params.requestId, {
|
|
101
|
-
success: false,
|
|
102
|
-
error: "Cannot spawn subagents from a subagent session context",
|
|
103
|
-
});
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
82
|
+
const unsubSpawn = handleRpc<{ requestId: string; type: string; prompt: string; options?: any }>(
|
|
83
|
+
events, "subagents:rpc:spawn", ({ type, prompt, options }) => {
|
|
84
|
+
const ctx = getCtx();
|
|
85
|
+
if (!ctx) throw new Error("No active session");
|
|
106
86
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
87
|
+
// Cross-extension RPC callers (e.g. pi-tasks TaskExecute) naturally
|
|
88
|
+
// forward serializable values, so options.model can be a string like
|
|
89
|
+
// "openai-codex/gpt-5.5". Resolve it to a real Model instance here
|
|
90
|
+
// — same pattern the scheduler path already uses — so the spawned
|
|
91
|
+
// agent's auth lookup doesn't crash with "No API key found for
|
|
92
|
+
// undefined".
|
|
93
|
+
let normalizedOptions = options ?? {};
|
|
94
|
+
if (typeof normalizedOptions.model === "string") {
|
|
95
|
+
const registry = (ctx as { modelRegistry?: ModelRegistry }).modelRegistry;
|
|
96
|
+
if (!registry) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Model override "${normalizedOptions.model}" provided but ctx.modelRegistry is unavailable`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const resolved = resolveModel(normalizedOptions.model, registry);
|
|
102
|
+
if (typeof resolved === "string") {
|
|
103
|
+
// resolveModel returns a human-readable error string when the
|
|
104
|
+
// input doesn't match any available model. Surface it instead of
|
|
105
|
+
// silently falling back so the caller sees the auth/typo issue.
|
|
106
|
+
throw new Error(resolved);
|
|
107
|
+
}
|
|
108
|
+
normalizedOptions = { ...normalizedOptions, model: resolved };
|
|
124
109
|
}
|
|
125
|
-
|
|
126
|
-
|
|
110
|
+
|
|
111
|
+
// Cross-extension RPC callers (e.g. pi-pi orchestrator planners/
|
|
112
|
+
// reviewers) forward `run_in_background`; map it to the manager's
|
|
113
|
+
// `isBackground` flag so background RPC spawns are queued/tracked and
|
|
114
|
+
// stay visible in the widget rather than being treated as foreground.
|
|
115
|
+
if (normalizedOptions.isBackground === undefined && normalizedOptions.run_in_background !== undefined) {
|
|
116
|
+
normalizedOptions = { ...normalizedOptions, isBackground: normalizedOptions.run_in_background };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const id = manager.spawn(pi, ctx, type, prompt, normalizedOptions);
|
|
120
|
+
// Announce the spawn so orchestrating extensions can wire the widget's
|
|
121
|
+
// UICtx and track the agent. RPC spawns happen outside an LLM turn, so
|
|
122
|
+
// without this event the above-editor widget never receives a UICtx and
|
|
123
|
+
// stays blank while orchestrator subagents run.
|
|
124
|
+
events.emit("subagents:created", {
|
|
125
|
+
id,
|
|
126
|
+
type,
|
|
127
|
+
description: normalizedOptions.description ?? type,
|
|
128
|
+
isBackground: normalizedOptions.isBackground ?? true,
|
|
129
|
+
});
|
|
130
|
+
return { id };
|
|
131
|
+
},
|
|
132
|
+
);
|
|
127
133
|
|
|
128
134
|
const unsubStop = handleRpc<{ requestId: string; agentId: string }>(
|
|
129
135
|
events, "subagents:rpc:stop", ({ agentId }) => {
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* custom-agents.ts — Load user-defined agents from project (.pi/agents/) and global (~/.pi/agent/agents/) locations.
|
|
2
|
+
* custom-agents.ts — Load user-defined agents from project (.pi/agents/) and global ($PI_CODING_AGENT_DIR/agents/, default ~/.pi/agent/agents/) locations.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
6
|
-
import { homedir } from "node:os";
|
|
7
6
|
import { basename, join } from "node:path";
|
|
8
|
-
import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
9
8
|
import { BUILTIN_TOOL_NAMES } from "./agent-types.js";
|
|
10
9
|
import type { AgentConfig, MemoryScope, ThinkingLevel } from "./types.js";
|
|
11
10
|
|
|
@@ -13,13 +12,13 @@ import type { AgentConfig, MemoryScope, ThinkingLevel } from "./types.js";
|
|
|
13
12
|
* Scan for custom agent .md files from multiple locations.
|
|
14
13
|
* Discovery hierarchy (higher priority wins):
|
|
15
14
|
* 1. Project: <cwd>/.pi/agents/*.md
|
|
16
|
-
* 2. Global: ~/.pi/agent/agents/*.md
|
|
15
|
+
* 2. Global: $PI_CODING_AGENT_DIR/agents/*.md (default: ~/.pi/agent/agents/*.md)
|
|
17
16
|
*
|
|
18
17
|
* Project-level agents override global ones with the same name.
|
|
19
18
|
* Any name is allowed — names matching defaults (e.g. "Explore") override them.
|
|
20
19
|
*/
|
|
21
20
|
export function loadCustomAgents(cwd: string): Map<string, AgentConfig> {
|
|
22
|
-
const globalDir = join(
|
|
21
|
+
const globalDir = join(getAgentDir(), "agents");
|
|
23
22
|
const projectDir = join(cwd, ".pi", "agents");
|
|
24
23
|
|
|
25
24
|
const agents = new Map<string, AgentConfig>();
|
|
@@ -51,17 +50,23 @@ function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "pro
|
|
|
51
50
|
|
|
52
51
|
const { frontmatter: fm, body } = parseFrontmatter<Record<string, unknown>>(content);
|
|
53
52
|
|
|
53
|
+
const { builtinToolNames, extSelectors } = parseToolsField(fm.tools);
|
|
54
|
+
|
|
54
55
|
agents.set(name, {
|
|
55
56
|
name,
|
|
56
57
|
displayName: str(fm.display_name),
|
|
57
58
|
description: str(fm.description) ?? name,
|
|
58
|
-
builtinToolNames
|
|
59
|
+
builtinToolNames,
|
|
60
|
+
extSelectors,
|
|
59
61
|
disallowedTools: csvListOptional(fm.disallowed_tools),
|
|
60
62
|
extensions: inheritField(fm.extensions ?? fm.inherit_extensions),
|
|
63
|
+
excludeExtensions: csvListOptional(fm.exclude_extensions),
|
|
61
64
|
skills: inheritField(fm.skills ?? fm.inherit_skills),
|
|
62
65
|
model: str(fm.model),
|
|
63
66
|
thinking: str(fm.thinking) as ThinkingLevel | undefined,
|
|
64
67
|
maxTurns: nonNegativeInt(fm.max_turns),
|
|
68
|
+
persistSession: fm.persist_session != null ? fm.persist_session === true : undefined,
|
|
69
|
+
sessionDir: str(fm.session_dir),
|
|
65
70
|
systemPrompt: body.trim(),
|
|
66
71
|
promptMode: fm.prompt_mode === "append" ? "append" : "replace",
|
|
67
72
|
inheritContext: fm.inherit_context != null ? fm.inherit_context === true : undefined,
|
|
@@ -108,6 +113,25 @@ function csvList(val: unknown, defaults: string[]): string[] {
|
|
|
108
113
|
return parseCsvField(val) ?? [];
|
|
109
114
|
}
|
|
110
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Partition the `tools:` CSV into the built-in tool allowlist and raw `ext:` selectors.
|
|
118
|
+
* `*` (and the case-insensitive alias `all`, for `tools: all`) expands to all
|
|
119
|
+
* built-ins; plain entries are built-in names; `ext:` entries are extension-tool
|
|
120
|
+
* selectors parsed later by the runner. omitted → all built-ins, no selectors.
|
|
121
|
+
* `tools:` present with only `ext:` entries → zero built-ins (use `*`).
|
|
122
|
+
*/
|
|
123
|
+
function parseToolsField(val: unknown): { builtinToolNames: string[]; extSelectors: string[] | undefined } {
|
|
124
|
+
const entries = csvList(val, BUILTIN_TOOL_NAMES);
|
|
125
|
+
const isWildcard = (e: string) => e === "*" || e.toLowerCase() === "all";
|
|
126
|
+
const hasWildcard = entries.some(isWildcard);
|
|
127
|
+
const plain = entries.filter(e => !isWildcard(e) && !e.startsWith("ext:"));
|
|
128
|
+
const extEntries = entries.filter(e => e.startsWith("ext:"));
|
|
129
|
+
return {
|
|
130
|
+
builtinToolNames: hasWildcard ? [...new Set([...BUILTIN_TOOL_NAMES, ...plain])] : plain,
|
|
131
|
+
extSelectors: extEntries.length > 0 ? extEntries : undefined,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
111
135
|
/**
|
|
112
136
|
* Parse an optional comma-separated list field.
|
|
113
137
|
* omitted → undefined; "none"/empty → undefined; csv → listed items.
|
|
@@ -14,15 +14,14 @@ export const DEFAULT_AGENTS: Map<string, AgentConfig> = new Map([
|
|
|
14
14
|
{
|
|
15
15
|
name: "general-purpose",
|
|
16
16
|
displayName: "Agent",
|
|
17
|
-
description: "General-purpose agent for complex, multi-step tasks",
|
|
17
|
+
description: "General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you.",
|
|
18
18
|
// builtinToolNames omitted — means "all available tools" (resolved at lookup time)
|
|
19
|
+
// inheritContext / runInBackground / isolated omitted — strategy fields, callers decide per-call.
|
|
20
|
+
// Setting them to false would lock callsite intent (see resolveAgentInvocationConfig in invocation-config.ts).
|
|
19
21
|
extensions: true,
|
|
20
22
|
skills: true,
|
|
21
23
|
systemPrompt: "",
|
|
22
24
|
promptMode: "append",
|
|
23
|
-
inheritContext: false,
|
|
24
|
-
runInBackground: false,
|
|
25
|
-
isolated: false,
|
|
26
25
|
isDefault: true,
|
|
27
26
|
},
|
|
28
27
|
],
|
|
@@ -31,11 +30,14 @@ export const DEFAULT_AGENTS: Map<string, AgentConfig> = new Map([
|
|
|
31
30
|
{
|
|
32
31
|
name: "Explore",
|
|
33
32
|
displayName: "Explore",
|
|
34
|
-
description: "Fast
|
|
33
|
+
description: "Fast read-only search agent for locating code. Use it to find files by pattern (eg. \"src/components/**/*.tsx\"), grep for symbols or keywords (eg. \"API endpoints\"), or answer \"where is X defined / which files reference Y.\" Do NOT use it for code review, design-doc auditing, cross-file consistency checks, or open-ended analysis — it reads excerpts rather than whole files and will miss content past its read window. When calling, specify search breadth: \"quick\" for a single targeted lookup, \"medium\" for moderate exploration, or \"very thorough\" to search across multiple locations and naming conventions.",
|
|
35
34
|
builtinToolNames: READ_ONLY_TOOLS,
|
|
36
35
|
extensions: true,
|
|
37
36
|
skills: true,
|
|
38
|
-
model
|
|
37
|
+
// Fast/cheap model for read-only search. Provider-preferred but resilient:
|
|
38
|
+
// resolveModel matches this fuzzily (date-stamp optional) and falls back to
|
|
39
|
+
// the same model under another provider if anthropic doesn't expose it.
|
|
40
|
+
model: "anthropic/claude-haiku-4-5",
|
|
39
41
|
systemPrompt: `# CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS
|
|
40
42
|
You are a file search specialist. You excel at thoroughly navigating and exploring codebases.
|
|
41
43
|
Your role is EXCLUSIVELY to search and analyze existing code. You do NOT have access to file editing tools.
|
|
@@ -49,32 +51,22 @@ You are STRICTLY PROHIBITED from:
|
|
|
49
51
|
- Using redirect operators (>, >>, |) or heredocs to write to files
|
|
50
52
|
- Running ANY commands that change system state
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
Use Bash ONLY for read-only operations: ls, git status, git log, git diff, find, cat, head, tail.
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
-
|
|
61
|
-
|
|
62
|
-
File navigation:
|
|
63
|
-
- find: locate files by name/glob pattern
|
|
64
|
-
- read: read file contents
|
|
65
|
-
- ls: list directory contents
|
|
66
|
-
|
|
67
|
-
Use Bash ONLY for read-only operations: git status, git log, git diff.
|
|
68
|
-
Make independent tool calls in parallel for efficiency.
|
|
56
|
+
# Tool Usage
|
|
57
|
+
- Use the find tool for file pattern matching (NOT the bash find command)
|
|
58
|
+
- Use the grep tool for content search (NOT bash grep/rg command)
|
|
59
|
+
- Use the read tool for reading files (NOT bash cat/head/tail)
|
|
60
|
+
- Use Bash ONLY for read-only operations
|
|
61
|
+
- Make independent tool calls in parallel for efficiency
|
|
62
|
+
- Adapt search approach based on thoroughness level specified
|
|
69
63
|
|
|
70
64
|
# Output
|
|
71
65
|
- Use absolute file paths in all references
|
|
66
|
+
- Report findings as regular messages
|
|
72
67
|
- Do not use emojis
|
|
73
68
|
- Be thorough and precise`,
|
|
74
69
|
promptMode: "replace",
|
|
75
|
-
inheritContext: false,
|
|
76
|
-
runInBackground: false,
|
|
77
|
-
isolated: false,
|
|
78
70
|
isDefault: true,
|
|
79
71
|
},
|
|
80
72
|
],
|
|
@@ -83,7 +75,7 @@ Make independent tool calls in parallel for efficiency.
|
|
|
83
75
|
{
|
|
84
76
|
name: "Plan",
|
|
85
77
|
displayName: "Plan",
|
|
86
|
-
description: "Software architect for implementation
|
|
78
|
+
description: "Software architect agent for designing implementation plans. Use this when you need to plan the implementation strategy for a task. Returns step-by-step plans, identifies critical files, and considers architectural trade-offs.",
|
|
87
79
|
builtinToolNames: READ_ONLY_TOOLS,
|
|
88
80
|
extensions: true,
|
|
89
81
|
skills: true,
|
|
@@ -101,22 +93,9 @@ You are STRICTLY PROHIBITED from:
|
|
|
101
93
|
- Using redirect operators (>, >>, |) or heredocs to write to files
|
|
102
94
|
- Running ANY commands that change system state
|
|
103
95
|
|
|
104
|
-
# Tool routing — understand the codebase before planning
|
|
105
|
-
|
|
106
|
-
Architecture overview:
|
|
107
|
-
- cbm_search: natural-language search for relevant symbols
|
|
108
|
-
- cbm_search_code: graph-augmented grep — deduplicates into containing functions
|
|
109
|
-
|
|
110
|
-
Detailed understanding:
|
|
111
|
-
- grep: fast text/regex search for known strings
|
|
112
|
-
- read: read file contents
|
|
113
|
-
- find: locate files by name/glob pattern
|
|
114
|
-
|
|
115
|
-
Use Bash ONLY for read-only operations: git status, git log, git diff.
|
|
116
|
-
|
|
117
96
|
# Planning Process
|
|
118
97
|
1. Understand requirements
|
|
119
|
-
2. Explore thoroughly (
|
|
98
|
+
2. Explore thoroughly (read files, find patterns, understand architecture)
|
|
120
99
|
3. Design solution based on your assigned perspective
|
|
121
100
|
4. Detail the plan with step-by-step implementation strategy
|
|
122
101
|
|
|
@@ -126,6 +105,12 @@ Use Bash ONLY for read-only operations: git status, git log, git diff.
|
|
|
126
105
|
- Anticipate potential challenges
|
|
127
106
|
- Follow existing patterns where appropriate
|
|
128
107
|
|
|
108
|
+
# Tool Usage
|
|
109
|
+
- Use the find tool for file pattern matching (NOT the bash find command)
|
|
110
|
+
- Use the grep tool for content search (NOT bash grep/rg command)
|
|
111
|
+
- Use the read tool for reading files (NOT bash cat/head/tail)
|
|
112
|
+
- Use Bash ONLY for read-only operations
|
|
113
|
+
|
|
129
114
|
# Output Format
|
|
130
115
|
- Use absolute file paths
|
|
131
116
|
- Do not use emojis
|
|
@@ -135,9 +120,6 @@ Use Bash ONLY for read-only operations: git status, git log, git diff.
|
|
|
135
120
|
List 3-5 files most critical for implementing this plan:
|
|
136
121
|
- /absolute/path/to/file.ts - [Brief reason]`,
|
|
137
122
|
promptMode: "replace",
|
|
138
|
-
inheritContext: false,
|
|
139
|
-
runInBackground: false,
|
|
140
|
-
isolated: false,
|
|
141
123
|
isDefault: true,
|
|
142
124
|
},
|
|
143
125
|
],
|