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,243 @@
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
+
16
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
17
+ import { Type } from "typebox";
18
+
19
+ import { Supervisor } from "./Supvisor.ts";
20
+ import { BUILTIN_AGENTS } from "./BuiltinAgents.ts";
21
+
22
+ const PI_SUBAGENTS_REPO = "https://github.com/tintinweb/pi-subagents";
23
+
24
+ // ============================================================================
25
+ // Entry point
26
+ // ============================================================================
27
+
28
+ export default async function(pi: ExtensionAPI) {
29
+ // Build the supervisor with builtin agents
30
+ const supervisor = new Supervisor([...BUILTIN_AGENTS], pi);
31
+
32
+ // ---- Register route_agent tool (UNCHANGED behavior) ------------------
33
+ pi.registerTool({
34
+ name: "route_agent",
35
+ label: "Route Agent",
36
+ description: `Route tasks to appropriate sub-agents via pi-subagents (${PI_SUBAGENTS_REPO})`,
37
+ parameters: Type.Object({
38
+ taskDescription: Type.String({ description: "Description of the task to be performed" }),
39
+ forceAgent: Type.Optional(Type.String({ description: "Optionally force a specific agent type" })),
40
+ runInBackground: Type.Optional(Type.Boolean({ description: "Run the agent in background (non-blocking)" })),
41
+ thinking: Type.Optional(Type.String({ description: "Thinking level" })),
42
+ model: Type.Optional(Type.String({ description: "Model to use" })),
43
+ }),
44
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx: any) {
45
+ const { taskDescription, forceAgent, runInBackground, thinking, model } = params;
46
+
47
+ // If forceAgent is specified, use it directly
48
+ if (forceAgent) {
49
+ const result = await supervisor.routeAgent(
50
+ taskDescription,
51
+ forceAgent,
52
+ runInBackground,
53
+ thinking,
54
+ model
55
+ );
56
+
57
+ if (result.success) {
58
+ const agent = BUILTIN_AGENTS.find((a) => a.name === result.agentType);
59
+ return {
60
+ content: [{
61
+ type: "text",
62
+ 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}`,
63
+ }],
64
+ details: {
65
+ agentType: result.agentType,
66
+ reason: result.reason ?? "Forced by user",
67
+ subagentRepo: PI_SUBAGENTS_REPO,
68
+ agentId: result.agentId,
69
+ },
70
+ };
71
+ } else {
72
+ return {
73
+ content: [{
74
+ type: "text",
75
+ text: `Failed to spawn agent '${forceAgent}': ${result.error}`,
76
+ }],
77
+ details: { error: result.error },
78
+ };
79
+ }
80
+ }
81
+
82
+ // Otherwise, analyze and route normally
83
+ const result = await supervisor.routeAgent(
84
+ taskDescription,
85
+ undefined,
86
+ runInBackground,
87
+ thinking,
88
+ model
89
+ );
90
+
91
+ if (result.success) {
92
+ const agent = BUILTIN_AGENTS.find((a) => a.name === result.agentType);
93
+ return {
94
+ content: [{
95
+ type: "text",
96
+ 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}`,
97
+ }],
98
+ details: {
99
+ agentType: result.agentType,
100
+ reason: result.reason,
101
+ subagentRepo: PI_SUBAGENTS_REPO,
102
+ agentId: result.agentId,
103
+ },
104
+ };
105
+ } else {
106
+ return {
107
+ content: [{
108
+ type: "text",
109
+ text: `Failed to spawn agent: ${result.error}`,
110
+ }],
111
+ details: { error: result.error },
112
+ };
113
+ }
114
+ },
115
+ });
116
+
117
+ // ---- Register plan_and_route tool (NEW — multi-agent workflow) --------
118
+ pi.registerTool({
119
+ name: "plan_and_route",
120
+ label: "Plan and Route",
121
+ description: `Plan a multi-agent workflow via pi-subagents (${PI_SUBAGENTS_REPO}). Launches multiple agents in parallel based on goal keywords.`,
122
+ parameters: Type.Object({
123
+ goal: Type.String({ description: "The goal description for multi-agent execution" }),
124
+ forceAgents: Type.Optional(Type.Array(Type.String({ description: "Agent names to force" }))),
125
+ runInBackground: Type.Optional(Type.Boolean({ description: "Run agents in background" })),
126
+ }),
127
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx: any) {
128
+ const { goal, forceAgents, runInBackground } = params;
129
+
130
+ const result = await supervisor.planAndRoute(goal, { forceAgents, background: runInBackground });
131
+
132
+ if (result.success) {
133
+ return {
134
+ content: [{
135
+ type: "text",
136
+ text: `Multi-agent workflow launched successfully!\n\nAgents: ${(result.agents ?? []).join(", ")}\nSteps:\n${(result.steps ?? []).join("\n")}`,
137
+ }],
138
+ details: {
139
+ agents: result.agents,
140
+ results: result.results,
141
+ subagentRepo: PI_SUBAGENTS_REPO,
142
+ },
143
+ };
144
+ } else {
145
+ return {
146
+ content: [{
147
+ type: "text",
148
+ text: `Failed to plan workflow: ${result.error}`,
149
+ }],
150
+ details: { error: result.error },
151
+ };
152
+ }
153
+ },
154
+ });
155
+
156
+ // ---- Register /assign-agent command (UNCHANGED behavior) --------------
157
+ pi.registerCommand("assign-agent", {
158
+ description: `Route tasks to appropriate sub-agents via pi-subagents (${PI_SUBAGENTS_REPO})`,
159
+ handler: async (input: string, ctx: any) => {
160
+ if (!input) {
161
+ ctx.ui.notify("Please provide a task description", "error");
162
+ return;
163
+ }
164
+
165
+ const result = await supervisor.routeAgent(input, undefined, false);
166
+
167
+ if (result.success) {
168
+ ctx.ui.notify(`Routed to ${result.agentType}: ${result.reason ?? ""} (via pi-subagents)`, "info");
169
+ } else {
170
+ ctx.ui.notify(`Failed to spawn agent: ${result.error}`, "error");
171
+ }
172
+ },
173
+ });
174
+
175
+ // ---- Register list_agents tool (UNCHANGED behavior) ------------------
176
+ pi.registerTool({
177
+ name: "list_agents",
178
+ label: "List Agents",
179
+ description: `List all available agents and their routing criteria (${PI_SUBAGENTS_REPO})`,
180
+ parameters: Type.Object({}),
181
+ async execute() {
182
+ const agents = [
183
+ ...BUILTIN_AGENTS,
184
+ // TODO: add filesystem agents here when implemented
185
+ ];
186
+
187
+ let response = `Available Agents (routed via pi-subagents):\n\n`;
188
+ agents.forEach((agent, index) => {
189
+ response += `${index + 1}. **${agent.name}**\n`;
190
+ response += ` Description: ${agent.description}\n`;
191
+ response += ` Thinking: ${agent.thinking ?? "medium"}\n`;
192
+ response += ` Triggers: ${agent.triggers?.join(", ") ?? "none"}\n`;
193
+ response += ` Use When: ${agent.useWhen?.join(", ") ?? "any"}\n`;
194
+ response += ` Avoid When: ${agent.avoidWhen?.join(", ") ?? "none"}\n`;
195
+ response += ` Tools: ${agent.tools?.join(", ") ?? "default"}\n\n`;
196
+ });
197
+
198
+ return {
199
+ content: [{ type: "text", text: response }],
200
+ details: {
201
+ agents: agents.map((a) => ({
202
+ name: a.name,
203
+ description: a.description,
204
+ useWhen: a.useWhen,
205
+ avoidWhen: a.avoidWhen,
206
+ })),
207
+ subagentRepo: PI_SUBAGENTS_REPO,
208
+ },
209
+ };
210
+ },
211
+ });
212
+
213
+ // ---- Intercept input (UNCHANGED behavior from old code) --------------
214
+ pi.on("input", async (event: any, ctx: any) => {
215
+ // Skip commands / special prefixes
216
+ if (event.text.startsWith("/") || event.text.startsWith("!")) {
217
+ return { action: "continue" };
218
+ }
219
+
220
+ // Analyze and route
221
+ const result = await supervisor.routeAgent(event.text, undefined, true);
222
+
223
+ if (result.success) {
224
+ ctx.ui.notify(
225
+ `Auto-routed to ${result.agentType}: ${result.reason ?? ""} (via pi-subagents)`,
226
+ "info"
227
+ );
228
+ return { action: "handled" };
229
+ } else {
230
+ // Fallback to text transformation instead of suppressing
231
+ ctx.ui.notify(`Auto-routing failed: ${result.error}`, "warning");
232
+ return {
233
+ action: "transform",
234
+ text: `[ROUTED] ${event.text}`,
235
+ };
236
+ }
237
+ });
238
+
239
+ // ---- Session start notification (UNCHANGED) --------------------------
240
+ pi.on("session_start", async (_event: any, ctx: any) => {
241
+ ctx.ui.notify(`${PI_SUBAGENTS_REPO} engine loaded — route_agent and plan_and_route available`, "info");
242
+ });
243
+ }