pi-subagents-router 1.0.2

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.
@@ -0,0 +1,310 @@
1
+ /**
2
+ * Supvisor — The agent routing supervisor.
3
+ *
4
+ * Extracts the agent management logic from agent-router.ts and provides
5
+ * two entry points:
6
+ * - routeAgent() — single-agent routing (same behavior as old)
7
+ * - planAndRoute() — multi-agent workflow (NEW)
8
+ */
9
+ import { BUILTIN_AGENTS } from "./BuiltinAgents.js";
10
+ async function spawnSubAgent(pi, subagentType, prompt, description, runInBackground = false, options = {}) {
11
+ const PI_SUBAGENTS_REPO = "https://github.com/tintinweb/pi-subagents";
12
+ const requestId = `router-${Date.now()}-${Math.random().toString(16).slice(2)}`;
13
+ return new Promise((resolve) => {
14
+ let unsub = null;
15
+ const timeout = setTimeout(() => {
16
+ if (unsub)
17
+ unsub();
18
+ resolve({
19
+ success: false,
20
+ error: `Timed out waiting for pi-subagents RPC reply. Is ${PI_SUBAGENTS_REPO} installed and loaded?`,
21
+ });
22
+ }, 10_000);
23
+ let emitError;
24
+ try {
25
+ pi.events.emit("subagents:rpc:spawn", {
26
+ requestId,
27
+ type: subagentType,
28
+ prompt,
29
+ options: {
30
+ description,
31
+ run_in_background: runInBackground,
32
+ thinking: options.thinking,
33
+ model: options.model,
34
+ inherit_context: options.inheritContext,
35
+ inherit_skills: options.inheritSkills,
36
+ },
37
+ });
38
+ }
39
+ catch (err) {
40
+ emitError = err.message || "Unknown emit error";
41
+ }
42
+ if (emitError) {
43
+ clearTimeout(timeout);
44
+ resolve({
45
+ success: false,
46
+ error: `Failed to emit spawn event to pi-subagents: ${emitError}`,
47
+ });
48
+ return;
49
+ }
50
+ unsub = pi.events.on(`subagents:rpc:spawn:reply:${requestId}`, (reply) => {
51
+ clearTimeout(timeout);
52
+ unsub();
53
+ if (!reply?.success) {
54
+ resolve({
55
+ success: false,
56
+ error: reply?.error || "Unknown pi-subagents RPC error",
57
+ });
58
+ return;
59
+ }
60
+ resolve({ success: true, agentId: reply.data?.id });
61
+ });
62
+ });
63
+ }
64
+ // ============================================================================
65
+ // Helper: Analyze task description and determine best agent
66
+ // (Same as the old analyzeTaskDescription, extracted and made static)
67
+ // ============================================================================
68
+ export function analyzeTaskDescription(taskDescription, agents) {
69
+ const lowerTask = taskDescription.toLowerCase();
70
+ // FIX: Check ALL triggers across ALL agents, then pick earliest position in input text
71
+ let bestTriggerAgent = null;
72
+ for (const agent of agents) {
73
+ if (agent.triggers) {
74
+ for (const trigger of agent.triggers) {
75
+ const position = lowerTask.indexOf(trigger.toLowerCase());
76
+ if (position >= 0) {
77
+ if (!bestTriggerAgent ||
78
+ position < bestTriggerAgent.position) {
79
+ bestTriggerAgent = {
80
+ name: agent.name,
81
+ trigger,
82
+ position,
83
+ };
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ if (bestTriggerAgent) {
90
+ return {
91
+ agentType: bestTriggerAgent.name,
92
+ reason: `Trigger match: "${bestTriggerAgent.trigger}"`,
93
+ };
94
+ }
95
+ // Check useWhen criteria
96
+ const useWhenScores = [];
97
+ for (const agent of agents) {
98
+ if (agent.useWhen) {
99
+ const matches = agent.useWhen.filter((criterion) => lowerTask.includes(criterion.toLowerCase()));
100
+ if (matches.length > 0) {
101
+ useWhenScores.push({
102
+ agent: agent.name,
103
+ score: matches.length,
104
+ matches: matches,
105
+ });
106
+ }
107
+ }
108
+ }
109
+ // Check avoidWhen penalties
110
+ const avoidWhenPenalties = [];
111
+ for (const agent of agents) {
112
+ if (agent.avoidWhen) {
113
+ const penalties = agent.avoidWhen.filter((criterion) => lowerTask.includes(criterion.toLowerCase()));
114
+ if (penalties.length > 0) {
115
+ avoidWhenPenalties.push({ agent: agent.name, penalties });
116
+ }
117
+ }
118
+ }
119
+ // Find best match with second-best fallback
120
+ if (useWhenScores.length > 0) {
121
+ useWhenScores.sort((a, b) => b.score - a.score);
122
+ const topCandidate = useWhenScores[0];
123
+ const topPenalty = avoidWhenPenalties.find((p) => p.agent === topCandidate.agent);
124
+ if (!topPenalty) {
125
+ return {
126
+ agentType: topCandidate.agent,
127
+ reason: `useWhen match: "${topCandidate.matches.join(", ")}" (score: ${topCandidate.score})`,
128
+ };
129
+ }
130
+ // Second-best fallback — try next best non-penalized agent
131
+ for (let i = 1; i < useWhenScores.length; i++) {
132
+ const candidate = useWhenScores[i];
133
+ const candidatePenalty = avoidWhenPenalties.find((p) => p.agent === candidate.agent);
134
+ if (!candidatePenalty) {
135
+ return {
136
+ agentType: candidate.agent,
137
+ reason: `useWhen match: "${candidate.matches.join(", ")}" (score: ${candidate.score}), fallback from penalized "${topCandidate.agent}"`,
138
+ };
139
+ }
140
+ }
141
+ // No non-penalized match — fall back to second-best
142
+ const secondBest = useWhenScores[1];
143
+ if (secondBest) {
144
+ return {
145
+ agentType: secondBest.agent,
146
+ reason: `FALLBACK: "${topCandidate.agent}" penalized (${topPenalty.penalties.join(", ")}), using "${secondBest.agent}"`,
147
+ };
148
+ }
149
+ }
150
+ return {
151
+ agentType: "general-purpose",
152
+ reason: "No clear match - using general-purpose agent",
153
+ };
154
+ }
155
+ // ============================================================================
156
+ // Supervisor — The main agent routing supervisor class
157
+ // ============================================================================
158
+ export class Supervisor {
159
+ agents;
160
+ pi;
161
+ constructor(agents, pi) {
162
+ this.agents = agents;
163
+ this.pi = pi;
164
+ }
165
+ // ========================================================================
166
+ // Single-Agent Routing (UNCHANGED behavior from old code)
167
+ // ========================================================================
168
+ async routeAgent(taskDescription, forceAgent, runInBackground, thinking, model) {
169
+ // If forceAgent is specified, use it directly
170
+ if (forceAgent) {
171
+ const agent = this.agents.find((a) => a.name === forceAgent);
172
+ if (agent) {
173
+ const spawnResult = await spawnSubAgent(this.pi, agent.name, taskDescription, taskDescription.substring(0, 50) +
174
+ (taskDescription.length > 50 ? "..." : ""), runInBackground || false, { thinking: thinking || agent.thinking, model });
175
+ if (spawnResult.success) {
176
+ return {
177
+ success: true,
178
+ agentType: forceAgent,
179
+ agentId: spawnResult.agentId,
180
+ reason: "Forced by user",
181
+ };
182
+ }
183
+ else {
184
+ return {
185
+ success: false,
186
+ error: `Failed to spawn agent '${forceAgent}': ${spawnResult.error}`,
187
+ };
188
+ }
189
+ }
190
+ else {
191
+ return {
192
+ success: false,
193
+ error: `Agent not found: ${forceAgent}`,
194
+ };
195
+ }
196
+ }
197
+ // Analyze task description against agent criteria
198
+ const routingDecision = analyzeTaskDescription(taskDescription, this.agents);
199
+ const selectedAgent = this.agents.find((a) => a.name === routingDecision.agentType);
200
+ if (!selectedAgent) {
201
+ return {
202
+ success: true,
203
+ agentType: "general-purpose",
204
+ reason: routingDecision.reason,
205
+ };
206
+ }
207
+ // Spawn the sub-agent via pi-subagents RPC
208
+ const spawnResult = await spawnSubAgent(this.pi, selectedAgent.name, taskDescription, taskDescription.substring(0, 50) +
209
+ (taskDescription.length > 50 ? "..." : ""), runInBackground || false, { thinking: thinking || selectedAgent.thinking, model });
210
+ if (spawnResult.success) {
211
+ return {
212
+ success: true,
213
+ agentType: selectedAgent.name,
214
+ reason: routingDecision.reason,
215
+ agentId: spawnResult.agentId,
216
+ };
217
+ }
218
+ else {
219
+ return {
220
+ success: false,
221
+ error: `Failed to spawn agent '${selectedAgent.name}': ${spawnResult.error}`,
222
+ };
223
+ }
224
+ }
225
+ // ========================================================================
226
+ // Multi-Agent Routing (NEW — planAndRoute)
227
+ // ========================================================================
228
+ /**
229
+ * Plan and route a workflow for multiple agents.
230
+ * Uses keyword matching to find relevant agents, then executes
231
+ * them in parallel (where dependencies allow).
232
+ */
233
+ async planAndRoute(goal, options) {
234
+ // Step 1: Find relevant agents via keyword matching
235
+ const relevantAgents = options?.forceAgents
236
+ ? this.agents.filter((a) => options.forceAgents.includes(a.name))
237
+ : this._findRelevantAgents(goal);
238
+ if (relevantAgents.length === 0) {
239
+ return { success: false, error: "No agents matched for goal" };
240
+ }
241
+ // Step 2: Execute agents in parallel (no dependencies in v2.0)
242
+ const results = {};
243
+ const steps = [];
244
+ const promises = relevantAgents.map(async (agent) => {
245
+ const spawnResult = await spawnSubAgent(this.pi, agent.name, goal, goal.substring(0, 50) + (goal.length > 50 ? "..." : ""), options?.background || false, { model: undefined });
246
+ if (spawnResult.success) {
247
+ results[agent.name] = { success: true, agentId: spawnResult.agentId };
248
+ steps.push(`✅ ${agent.name}: spawned (id: ${spawnResult.agentId})`);
249
+ }
250
+ else {
251
+ results[agent.name] = { success: false, error: spawnResult.error };
252
+ steps.push(`❌ ${agent.name}: failed (${spawnResult.error})`);
253
+ }
254
+ });
255
+ await Promise.all(promises);
256
+ return {
257
+ success: true,
258
+ agents: relevantAgents.map((a) => a.name),
259
+ results,
260
+ steps,
261
+ };
262
+ }
263
+ // ========================================================================
264
+ // Internal: Find relevant agents by keyword matching
265
+ // ========================================================================
266
+ _findRelevantAgents(goal) {
267
+ const lowerGoal = goal.toLowerCase();
268
+ const scores = new Map();
269
+ for (const agent of this.agents) {
270
+ let score = 0;
271
+ // Check triggers (highest priority)
272
+ if (agent.triggers) {
273
+ for (const trigger of agent.triggers) {
274
+ if (lowerGoal.includes(trigger.toLowerCase())) {
275
+ score += 10;
276
+ }
277
+ }
278
+ }
279
+ // Check useWhen
280
+ if (agent.useWhen) {
281
+ for (const when of agent.useWhen) {
282
+ if (lowerGoal.includes(when.toLowerCase())) {
283
+ score += 5;
284
+ }
285
+ }
286
+ }
287
+ // Subtract for avoidWhen
288
+ if (agent.avoidWhen) {
289
+ for (const when of agent.avoidWhen) {
290
+ if (lowerGoal.includes(when.toLowerCase())) {
291
+ score -= 3;
292
+ }
293
+ }
294
+ }
295
+ if (score > 0)
296
+ scores.set(agent.name, score);
297
+ }
298
+ // Return agents with score > 0, sorted by score descending
299
+ return Array.from(scores.entries())
300
+ .sort((a, b) => b[1] - a[1])
301
+ .map(([name]) => this.agents.find((a) => a.name === name))
302
+ .filter(Boolean);
303
+ }
304
+ // ========================================================================
305
+ // Helper: Get all agents (builtin + filesystem)
306
+ // ========================================================================
307
+ static async getAllAgents() {
308
+ return BUILTIN_AGENTS;
309
+ }
310
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * pi-subagents — Agent Router Extension
3
+ *
4
+ * v2.0: Supervisor-based routing (refactored from monolithic agent-router.ts)
5
+ *
6
+ * Entry point that wires the Supervisor into the VS Code extension host.
7
+ * Exposes:
8
+ * - "route_agent" tool (single-agent, same behavior as v1)
9
+ * - "plan_and_route" tool (multi-agent workflow, NEW)
10
+ * - "/assign-agent" command (backward-compatible shortcut)
11
+ * - "list_agents" tool (list all available agents)
12
+ *
13
+ * @see https://github.com/tintinweb/pi-subagents
14
+ */
15
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
16
+ export default function (pi: ExtensionAPI): Promise<void>;
@@ -0,0 +1,209 @@
1
+ /**
2
+ * pi-subagents — Agent Router Extension
3
+ *
4
+ * v2.0: Supervisor-based routing (refactored from monolithic agent-router.ts)
5
+ *
6
+ * Entry point that wires the Supervisor into the VS Code extension host.
7
+ * Exposes:
8
+ * - "route_agent" tool (single-agent, same behavior as v1)
9
+ * - "plan_and_route" tool (multi-agent workflow, NEW)
10
+ * - "/assign-agent" command (backward-compatible shortcut)
11
+ * - "list_agents" tool (list all available agents)
12
+ *
13
+ * @see https://github.com/tintinweb/pi-subagents
14
+ */
15
+ import { Type } from "typebox";
16
+ import { Supervisor } from "./Supvisor.js";
17
+ import { BUILTIN_AGENTS } from "./BuiltinAgents.js";
18
+ const PI_SUBAGENTS_REPO = "https://github.com/tintinweb/pi-subagents";
19
+ // ============================================================================
20
+ // Entry point
21
+ // ============================================================================
22
+ export default async function (pi) {
23
+ // Build the supervisor with builtin agents
24
+ const supervisor = new Supervisor([...BUILTIN_AGENTS], pi);
25
+ // ---- Register route_agent tool (UNCHANGED behavior) ------------------
26
+ pi.registerTool({
27
+ name: "route_agent",
28
+ label: "Route Agent",
29
+ description: `Route tasks to appropriate sub-agents via pi-subagents (${PI_SUBAGENTS_REPO})`,
30
+ parameters: Type.Object({
31
+ taskDescription: Type.String({ description: "Description of the task to be performed" }),
32
+ forceAgent: Type.Optional(Type.String({ description: "Optionally force a specific agent type" })),
33
+ runInBackground: Type.Optional(Type.Boolean({ description: "Run the agent in background (non-blocking)" })),
34
+ thinking: Type.Optional(Type.String({ description: "Thinking level" })),
35
+ model: Type.Optional(Type.String({ description: "Model to use" })),
36
+ }),
37
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
38
+ const { taskDescription, forceAgent, runInBackground, thinking, model } = params;
39
+ // If forceAgent is specified, use it directly
40
+ if (forceAgent) {
41
+ const result = await supervisor.routeAgent(taskDescription, forceAgent, runInBackground, thinking, model);
42
+ if (result.success) {
43
+ const agent = BUILTIN_AGENTS.find((a) => a.name === result.agentType);
44
+ return {
45
+ content: [{
46
+ type: "text",
47
+ text: `Routed to agent: ${result.agentType} via pi-subagents\n\n${agent?.description ?? ""}\n\nAgent ID: ${result.agentId ?? "foreground"}\n\nExecuted by: ${PI_SUBAGENTS_REPO}`,
48
+ }],
49
+ details: {
50
+ agentType: result.agentType,
51
+ reason: result.reason ?? "Forced by user",
52
+ subagentRepo: PI_SUBAGENTS_REPO,
53
+ agentId: result.agentId,
54
+ },
55
+ };
56
+ }
57
+ else {
58
+ return {
59
+ content: [{
60
+ type: "text",
61
+ text: `Failed to spawn agent '${forceAgent}': ${result.error}`,
62
+ }],
63
+ details: { error: result.error },
64
+ };
65
+ }
66
+ }
67
+ // Otherwise, analyze and route normally
68
+ const result = await supervisor.routeAgent(taskDescription, undefined, runInBackground, thinking, model);
69
+ if (result.success) {
70
+ const agent = BUILTIN_AGENTS.find((a) => a.name === result.agentType);
71
+ return {
72
+ content: [{
73
+ type: "text",
74
+ text: `Routed to agent: ${result.agentType} via pi-subagents\n\nDescription: ${agent?.description ?? ""}\n\nReason: ${result.reason ?? ""}\n\nAgent ID: ${result.agentId ?? "foreground"}\n\nExecuted by: ${PI_SUBAGENTS_REPO}`,
75
+ }],
76
+ details: {
77
+ agentType: result.agentType,
78
+ reason: result.reason,
79
+ subagentRepo: PI_SUBAGENTS_REPO,
80
+ agentId: result.agentId,
81
+ },
82
+ };
83
+ }
84
+ else {
85
+ return {
86
+ content: [{
87
+ type: "text",
88
+ text: `Failed to spawn agent: ${result.error}`,
89
+ }],
90
+ details: { error: result.error },
91
+ };
92
+ }
93
+ },
94
+ });
95
+ // ---- Register plan_and_route tool (NEW — multi-agent workflow) --------
96
+ pi.registerTool({
97
+ name: "plan_and_route",
98
+ label: "Plan and Route",
99
+ description: `Plan a multi-agent workflow via pi-subagents (${PI_SUBAGENTS_REPO}). Launches multiple agents in parallel based on goal keywords.`,
100
+ parameters: Type.Object({
101
+ goal: Type.String({ description: "The goal description for multi-agent execution" }),
102
+ forceAgents: Type.Optional(Type.Array(Type.String({ description: "Agent names to force" }))),
103
+ runInBackground: Type.Optional(Type.Boolean({ description: "Run agents in background" })),
104
+ }),
105
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
106
+ const { goal, forceAgents, runInBackground } = params;
107
+ const result = await supervisor.planAndRoute(goal, { forceAgents, background: runInBackground });
108
+ if (result.success) {
109
+ return {
110
+ content: [{
111
+ type: "text",
112
+ text: `Multi-agent workflow launched successfully!\n\nAgents: ${(result.agents ?? []).join(", ")}\nSteps:\n${(result.steps ?? []).join("\n")}`,
113
+ }],
114
+ details: {
115
+ agents: result.agents,
116
+ results: result.results,
117
+ subagentRepo: PI_SUBAGENTS_REPO,
118
+ },
119
+ };
120
+ }
121
+ else {
122
+ return {
123
+ content: [{
124
+ type: "text",
125
+ text: `Failed to plan workflow: ${result.error}`,
126
+ }],
127
+ details: { error: result.error },
128
+ };
129
+ }
130
+ },
131
+ });
132
+ // ---- Register /assign-agent command (UNCHANGED behavior) --------------
133
+ pi.registerCommand("assign-agent", {
134
+ description: `Route tasks to appropriate sub-agents via pi-subagents (${PI_SUBAGENTS_REPO})`,
135
+ handler: async (input, ctx) => {
136
+ if (!input) {
137
+ ctx.ui.notify("Please provide a task description", "error");
138
+ return;
139
+ }
140
+ const result = await supervisor.routeAgent(input, undefined, false);
141
+ if (result.success) {
142
+ ctx.ui.notify(`Routed to ${result.agentType}: ${result.reason ?? ""} (via pi-subagents)`, "info");
143
+ }
144
+ else {
145
+ ctx.ui.notify(`Failed to spawn agent: ${result.error}`, "error");
146
+ }
147
+ },
148
+ });
149
+ // ---- Register list_agents tool (UNCHANGED behavior) ------------------
150
+ pi.registerTool({
151
+ name: "list_agents",
152
+ label: "List Agents",
153
+ description: `List all available agents and their routing criteria (${PI_SUBAGENTS_REPO})`,
154
+ parameters: Type.Object({}),
155
+ async execute() {
156
+ const agents = [
157
+ ...BUILTIN_AGENTS,
158
+ // TODO: add filesystem agents here when implemented
159
+ ];
160
+ let response = `Available Agents (routed via pi-subagents):\n\n`;
161
+ agents.forEach((agent, index) => {
162
+ response += `${index + 1}. **${agent.name}**\n`;
163
+ response += ` Description: ${agent.description}\n`;
164
+ response += ` Thinking: ${agent.thinking ?? "medium"}\n`;
165
+ response += ` Triggers: ${agent.triggers?.join(", ") ?? "none"}\n`;
166
+ response += ` Use When: ${agent.useWhen?.join(", ") ?? "any"}\n`;
167
+ response += ` Avoid When: ${agent.avoidWhen?.join(", ") ?? "none"}\n`;
168
+ response += ` Tools: ${agent.tools?.join(", ") ?? "default"}\n\n`;
169
+ });
170
+ return {
171
+ content: [{ type: "text", text: response }],
172
+ details: {
173
+ agents: agents.map((a) => ({
174
+ name: a.name,
175
+ description: a.description,
176
+ useWhen: a.useWhen,
177
+ avoidWhen: a.avoidWhen,
178
+ })),
179
+ subagentRepo: PI_SUBAGENTS_REPO,
180
+ },
181
+ };
182
+ },
183
+ });
184
+ // ---- Intercept input (UNCHANGED behavior from old code) --------------
185
+ pi.on("input", async (event, ctx) => {
186
+ // Skip commands / special prefixes
187
+ if (event.text.startsWith("/") || event.text.startsWith("!")) {
188
+ return { action: "continue" };
189
+ }
190
+ // Analyze and route
191
+ const result = await supervisor.routeAgent(event.text, undefined, true);
192
+ if (result.success) {
193
+ ctx.ui.notify(`Auto-routed to ${result.agentType}: ${result.reason ?? ""} (via pi-subagents)`, "info");
194
+ return { action: "handled" };
195
+ }
196
+ else {
197
+ // Fallback to text transformation instead of suppressing
198
+ ctx.ui.notify(`Auto-routing failed: ${result.error}`, "warning");
199
+ return {
200
+ action: "transform",
201
+ text: `[ROUTED] ${event.text}`,
202
+ };
203
+ }
204
+ });
205
+ // ---- Session start notification (UNCHANGED) --------------------------
206
+ pi.on("session_start", async (_event, ctx) => {
207
+ ctx.ui.notify(`${PI_SUBAGENTS_REPO} engine loaded — route_agent and plan_and_route available`, "info");
208
+ });
209
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "pi-subagents-router",
3
+ "version": "1.0.2",
4
+ "description": "A Pi extension that routes agents based on task descriptions using useWhen and avoidWhen criteria",
5
+ "type": "module",
6
+ "main": "./dist/agent-router.js",
7
+ "types": "./dist/agent-router.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/agent-router.d.ts",
12
+ "default": "./dist/agent-router.js"
13
+ }
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "src",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "test": "node --require ts-node/register test/test-supervisor.ts",
24
+ "typecheck": "tsc --noEmit"
25
+ },
26
+ "keywords": [
27
+ "pi-package",
28
+ "pi-extension",
29
+ "pi",
30
+ "extension",
31
+ "agent",
32
+ "routing",
33
+ "ai"
34
+ ],
35
+ "author": "Asdrubal",
36
+ "license": "MIT",
37
+ "private": false,
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "dependencies": {
42
+ "@earendil-works/pi-coding-agent": "latest",
43
+ "typebox": "^1.3.0"
44
+ },
45
+ "devDependencies": {
46
+ "ts-node": "^10.9.2",
47
+ "typescript": "^6.0.3"
48
+ },
49
+ "pi": {
50
+ "extensions": [
51
+ "./dist/agent-router.js"
52
+ ]
53
+ }
54
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * AgentConfig — Shared type for all agents (builtin + filesystem).
3
+ *
4
+ * This file extracts the AgentConfig interface from the monolithic
5
+ * agent-router.ts so it can be imported by all modules.
6
+ */
7
+
8
+ export type AgentSource = "builtin" | "filesystem";
9
+
10
+ export interface AgentConfig {
11
+ name: string;
12
+ description: string;
13
+ thinking?: string;
14
+ tools?: string[];
15
+ systemPromptMode?: string;
16
+ inheritProjectContext?: boolean;
17
+ inheritSkills?: boolean;
18
+ triggers?: string[];
19
+ useWhen?: string[];
20
+ avoidWhen?: string[];
21
+
22
+ // NEW (Phase 1+): capability-based routing
23
+ capabilities?: string[]; // capabilities this agent provides
24
+ consumes?: string[]; // dependencies (inputs needed)
25
+ produces?: string[]; // dependencies (outputs produced)
26
+ dependsOn?: string[]; // agents this agent depends on
27
+ source?: AgentSource; // "builtin" | "filesystem"
28
+ }
29
+
30
+ /**
31
+ * Simple routing decision
32
+ */
33
+ export interface RoutingDecision {
34
+ agentType: string;
35
+ reason: string;
36
+ }
37
+
38
+ /**
39
+ * Route result — single agent
40
+ */
41
+ export interface RouteResult {
42
+ success: boolean;
43
+ error?: string;
44
+ agentId?: string;
45
+ agentType?: string;
46
+ reason?: string;
47
+ details?: Record<string, any>;
48
+ }
49
+
50
+ /**
51
+ * Plan result — multi-agent workflow
52
+ */
53
+ export interface PlanResult {
54
+ success: boolean;
55
+ error?: string;
56
+ agents?: string[]; // names of agents to execute
57
+ steps?: string[]; // executed step descriptions
58
+ results?: Record<string, any>; // results keyed by agent name
59
+ }