wave-agent-sdk 0.0.1 → 0.0.3

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.
Files changed (82) hide show
  1. package/dist/agent.d.ts +37 -3
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +82 -5
  4. package/dist/index.d.ts +0 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +0 -1
  7. package/dist/managers/aiManager.d.ts +7 -1
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +11 -5
  10. package/dist/managers/messageManager.d.ts +8 -0
  11. package/dist/managers/messageManager.d.ts.map +1 -1
  12. package/dist/managers/messageManager.js +26 -2
  13. package/dist/managers/skillManager.d.ts +4 -5
  14. package/dist/managers/skillManager.d.ts.map +1 -1
  15. package/dist/managers/skillManager.js +6 -82
  16. package/dist/managers/subagentManager.d.ts +96 -0
  17. package/dist/managers/subagentManager.d.ts.map +1 -0
  18. package/dist/managers/subagentManager.js +261 -0
  19. package/dist/managers/toolManager.d.ts +33 -1
  20. package/dist/managers/toolManager.d.ts.map +1 -1
  21. package/dist/managers/toolManager.js +43 -5
  22. package/dist/services/aiService.d.ts +5 -0
  23. package/dist/services/aiService.d.ts.map +1 -1
  24. package/dist/services/aiService.js +58 -28
  25. package/dist/services/session.d.ts.map +1 -1
  26. package/dist/services/session.js +4 -0
  27. package/dist/tools/grepTool.d.ts.map +1 -1
  28. package/dist/tools/grepTool.js +8 -6
  29. package/dist/tools/readTool.d.ts.map +1 -1
  30. package/dist/tools/readTool.js +36 -6
  31. package/dist/tools/skillTool.d.ts +8 -0
  32. package/dist/tools/skillTool.d.ts.map +1 -0
  33. package/dist/tools/skillTool.js +72 -0
  34. package/dist/tools/taskTool.d.ts +8 -0
  35. package/dist/tools/taskTool.d.ts.map +1 -0
  36. package/dist/tools/taskTool.js +109 -0
  37. package/dist/tools/todoWriteTool.d.ts +6 -0
  38. package/dist/tools/todoWriteTool.d.ts.map +1 -0
  39. package/dist/tools/todoWriteTool.js +203 -0
  40. package/dist/types.d.ts +65 -1
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/types.js +16 -0
  43. package/dist/utils/configResolver.d.ts +38 -0
  44. package/dist/utils/configResolver.d.ts.map +1 -0
  45. package/dist/utils/configResolver.js +106 -0
  46. package/dist/utils/configValidator.d.ts +36 -0
  47. package/dist/utils/configValidator.d.ts.map +1 -0
  48. package/dist/utils/configValidator.js +78 -0
  49. package/dist/utils/constants.d.ts +10 -0
  50. package/dist/utils/constants.d.ts.map +1 -1
  51. package/dist/utils/constants.js +10 -0
  52. package/dist/utils/fileFormat.d.ts +17 -0
  53. package/dist/utils/fileFormat.d.ts.map +1 -0
  54. package/dist/utils/fileFormat.js +35 -0
  55. package/dist/utils/messageOperations.d.ts +18 -0
  56. package/dist/utils/messageOperations.d.ts.map +1 -1
  57. package/dist/utils/messageOperations.js +43 -0
  58. package/dist/utils/subagentParser.d.ts +19 -0
  59. package/dist/utils/subagentParser.d.ts.map +1 -0
  60. package/dist/utils/subagentParser.js +159 -0
  61. package/package.json +11 -15
  62. package/src/agent.ts +130 -9
  63. package/src/index.ts +0 -1
  64. package/src/managers/aiManager.ts +22 -10
  65. package/src/managers/messageManager.ts +55 -1
  66. package/src/managers/skillManager.ts +7 -96
  67. package/src/managers/subagentManager.ts +368 -0
  68. package/src/managers/toolManager.ts +50 -5
  69. package/src/services/aiService.ts +92 -36
  70. package/src/services/session.ts +5 -0
  71. package/src/tools/grepTool.ts +9 -6
  72. package/src/tools/readTool.ts +40 -6
  73. package/src/tools/skillTool.ts +82 -0
  74. package/src/tools/taskTool.ts +128 -0
  75. package/src/tools/todoWriteTool.ts +232 -0
  76. package/src/types.ts +85 -1
  77. package/src/utils/configResolver.ts +142 -0
  78. package/src/utils/configValidator.ts +133 -0
  79. package/src/utils/constants.ts +10 -0
  80. package/src/utils/fileFormat.ts +40 -0
  81. package/src/utils/messageOperations.ts +80 -0
  82. package/src/utils/subagentParser.ts +223 -0
@@ -11,7 +11,6 @@ import type {
11
11
  SkillInvocationContext,
12
12
  Logger,
13
13
  } from "../types.js";
14
- import type { ToolPlugin, ToolResult } from "../tools/types.js";
15
14
  import { parseSkillFile, formatSkillError } from "../utils/skillParser.js";
16
15
 
17
16
  /**
@@ -76,6 +75,13 @@ export class SkillManager {
76
75
  }
77
76
  }
78
77
 
78
+ /**
79
+ * Check if the skill manager is initialized
80
+ */
81
+ isInitialized(): boolean {
82
+ return this.initialized;
83
+ }
84
+
79
85
  /**
80
86
  * Get all available skills metadata
81
87
  */
@@ -233,101 +239,6 @@ export class SkillManager {
233
239
  return directories;
234
240
  }
235
241
 
236
- /**
237
- * Create a tool plugin for registering with ToolManager
238
- */
239
- createTool(): ToolPlugin {
240
- // Initialize skill manager asynchronously
241
- let initializationPromise: Promise<void> | null = null;
242
-
243
- const ensureInitialized = async (): Promise<void> => {
244
- if (!initializationPromise) {
245
- initializationPromise = this.initialize();
246
- }
247
- await initializationPromise;
248
- };
249
-
250
- const getToolDescription = (): string => {
251
- if (!this.initialized) {
252
- return "Invoke a Wave skill by name. Skills are user-defined automation templates that can be personal or project-specific. Skills will be loaded during initialization.";
253
- }
254
-
255
- const availableSkills = this.getAvailableSkills();
256
-
257
- if (availableSkills.length === 0) {
258
- return "Invoke a Wave skill by name. Skills are user-defined automation templates that can be personal or project-specific. No skills are currently available.";
259
- }
260
-
261
- const skillList = availableSkills
262
- .map(
263
- (skill) =>
264
- `• **${skill.name}** (${skill.type}): ${skill.description}`,
265
- )
266
- .join("\n");
267
-
268
- return `Invoke a Wave skill by name. Skills are user-defined automation templates that can be personal or project-specific.\n\nAvailable skills:\n${skillList}`;
269
- };
270
-
271
- return {
272
- name: "skill",
273
- config: {
274
- type: "function",
275
- function: {
276
- name: "skill",
277
- description: getToolDescription(),
278
- parameters: {
279
- type: "object",
280
- properties: {
281
- skill_name: {
282
- type: "string",
283
- description: "Name of the skill to invoke",
284
- enum: this.initialized
285
- ? this.getAvailableSkills().map((skill) => skill.name)
286
- : [],
287
- },
288
- },
289
- required: ["skill_name"],
290
- },
291
- },
292
- },
293
- execute: async (args: Record<string, unknown>): Promise<ToolResult> => {
294
- try {
295
- // Ensure skill manager is initialized
296
- await ensureInitialized();
297
-
298
- // Validate arguments
299
- const skillName = args.skill_name as string;
300
- if (!skillName || typeof skillName !== "string") {
301
- return {
302
- success: false,
303
- content: "",
304
- error: "skill_name parameter is required and must be a string",
305
- };
306
- }
307
-
308
- // Execute the skill
309
- const result = await this.executeSkill({ skill_name: skillName });
310
-
311
- return {
312
- success: true,
313
- content: result.content,
314
- shortResult: `Invoked skill: ${skillName}`,
315
- };
316
- } catch (error) {
317
- return {
318
- success: false,
319
- content: "",
320
- error: error instanceof Error ? error.message : String(error),
321
- };
322
- }
323
- },
324
- formatCompactParams: (params: Record<string, unknown>) => {
325
- const skillName = params.skill_name as string;
326
- return skillName || "unknown-skill";
327
- },
328
- };
329
- }
330
-
331
242
  /**
332
243
  * Execute a skill by name
333
244
  */
@@ -0,0 +1,368 @@
1
+ import { randomUUID } from "crypto";
2
+ import type { SubagentConfiguration } from "../utils/subagentParser.js";
3
+ import type { Message, Logger, GatewayConfig, ModelConfig } from "../types.js";
4
+ import { AIManager } from "./aiManager.js";
5
+ import {
6
+ MessageManager,
7
+ type MessageManagerCallbacks,
8
+ } from "./messageManager.js";
9
+ import { ToolManager } from "./toolManager.js";
10
+
11
+ export interface SubagentInstance {
12
+ subagentId: string;
13
+ configuration: SubagentConfiguration;
14
+ aiManager: AIManager;
15
+ messageManager: MessageManager;
16
+ toolManager: ToolManager;
17
+ status: "initializing" | "active" | "completed" | "error" | "aborted";
18
+ taskDescription: string;
19
+ messages: Message[];
20
+ }
21
+
22
+ export interface SubagentManagerOptions {
23
+ workdir: string;
24
+ parentToolManager: ToolManager;
25
+ parentMessageManager: MessageManager;
26
+ logger?: Logger;
27
+ gatewayConfig: GatewayConfig;
28
+ modelConfig: ModelConfig;
29
+ tokenLimit: number;
30
+ }
31
+
32
+ export class SubagentManager {
33
+ private instances = new Map<string, SubagentInstance>();
34
+ private cachedConfigurations: SubagentConfiguration[] | null = null;
35
+
36
+ private workdir: string;
37
+ private parentToolManager: ToolManager;
38
+ private parentMessageManager: MessageManager;
39
+ private logger?: Logger;
40
+ private gatewayConfig: GatewayConfig;
41
+ private modelConfig: ModelConfig;
42
+ private tokenLimit: number;
43
+
44
+ constructor(options: SubagentManagerOptions) {
45
+ this.workdir = options.workdir;
46
+ this.parentToolManager = options.parentToolManager;
47
+ this.parentMessageManager = options.parentMessageManager;
48
+ this.logger = options.logger;
49
+ this.gatewayConfig = options.gatewayConfig;
50
+ this.modelConfig = options.modelConfig;
51
+ this.tokenLimit = options.tokenLimit;
52
+ }
53
+
54
+ /**
55
+ * Initialize the SubagentManager by loading and caching configurations
56
+ */
57
+ async initialize(): Promise<void> {
58
+ await this.loadConfigurations();
59
+ }
60
+
61
+ /**
62
+ * Load all available subagent configurations and cache them
63
+ */
64
+ async loadConfigurations(): Promise<SubagentConfiguration[]> {
65
+ if (this.cachedConfigurations === null) {
66
+ const { loadSubagentConfigurations } = await import(
67
+ "../utils/subagentParser.js"
68
+ );
69
+ this.cachedConfigurations = await loadSubagentConfigurations(
70
+ this.workdir,
71
+ );
72
+ }
73
+ return this.cachedConfigurations;
74
+ }
75
+
76
+ /**
77
+ * Get cached configurations synchronously (must call loadConfigurations first)
78
+ */
79
+ getConfigurations(): SubagentConfiguration[] {
80
+ if (this.cachedConfigurations === null) {
81
+ throw new Error(
82
+ "SubagentManager not initialized. Call loadConfigurations() first.",
83
+ );
84
+ }
85
+ return this.cachedConfigurations;
86
+ }
87
+
88
+ /**
89
+ * Find subagent by exact name match
90
+ */
91
+ async findSubagent(name: string) {
92
+ const { findSubagentByName } = await import("../utils/subagentParser.js");
93
+ return findSubagentByName(name, this.workdir);
94
+ }
95
+
96
+ /**
97
+ * Create a new subagent instance with isolated managers
98
+ */
99
+ async createInstance(
100
+ configuration: SubagentConfiguration,
101
+ taskDescription: string,
102
+ ): Promise<SubagentInstance> {
103
+ if (
104
+ !this.parentToolManager ||
105
+ !this.gatewayConfig ||
106
+ !this.modelConfig ||
107
+ !this.tokenLimit
108
+ ) {
109
+ throw new Error(
110
+ "SubagentManager not properly initialized - call initialize() first",
111
+ );
112
+ }
113
+
114
+ const subagentId = randomUUID();
115
+
116
+ // Create isolated MessageManager for the subagent
117
+ const subagentCallbacks: MessageManagerCallbacks = {
118
+ // These callbacks will be handled by the parent agent
119
+ onMessagesChange: (messages: Message[]) => {
120
+ const instance = this.instances.get(subagentId);
121
+ if (instance) {
122
+ instance.messages = messages;
123
+ // Update parent's subagent block with latest messages
124
+ this.parentMessageManager.updateSubagentBlock(subagentId, {
125
+ messages: messages,
126
+ });
127
+ }
128
+ },
129
+ };
130
+
131
+ const messageManager = new MessageManager({
132
+ callbacks: subagentCallbacks,
133
+ workdir: this.workdir,
134
+ logger: this.logger,
135
+ });
136
+
137
+ // Use the parent tool manager directly - tool restrictions will be handled by allowedTools parameter
138
+ const toolManager = this.parentToolManager;
139
+
140
+ // Determine model to use
141
+ const modelToUse =
142
+ configuration.model && configuration.model !== "inherit"
143
+ ? configuration.model
144
+ : this.modelConfig.agentModel;
145
+
146
+ // Create isolated AIManager for the subagent
147
+ const aiManager = new AIManager({
148
+ messageManager,
149
+ toolManager,
150
+ logger: this.logger,
151
+ workdir: this.workdir,
152
+ systemPrompt: configuration.systemPrompt,
153
+ gatewayConfig: this.gatewayConfig,
154
+ modelConfig: {
155
+ ...this.modelConfig,
156
+ agentModel: modelToUse,
157
+ },
158
+ tokenLimit: this.tokenLimit,
159
+ });
160
+
161
+ const instance: SubagentInstance = {
162
+ subagentId,
163
+ configuration,
164
+ aiManager,
165
+ messageManager,
166
+ toolManager,
167
+ status: "initializing",
168
+ taskDescription,
169
+ messages: [],
170
+ };
171
+
172
+ this.instances.set(subagentId, instance);
173
+
174
+ // Create subagent block in parent message manager
175
+ this.parentMessageManager.addSubagentBlock(
176
+ subagentId,
177
+ configuration.name,
178
+ "active",
179
+ [],
180
+ );
181
+
182
+ return instance;
183
+ }
184
+
185
+ /**
186
+ * Execute task using subagent instance
187
+ *
188
+ * IMPORTANT: This method automatically filters out the Task tool from allowedTools
189
+ * to prevent subagents from spawning other subagents (infinite recursion protection)
190
+ */
191
+ async executeTask(
192
+ instance: SubagentInstance,
193
+ prompt: string,
194
+ ): Promise<string> {
195
+ try {
196
+ // Set status to active and update parent
197
+ this.updateInstanceStatus(instance.subagentId, "active");
198
+ this.parentMessageManager.updateSubagentBlock(instance.subagentId, {
199
+ status: "active",
200
+ });
201
+
202
+ // Add the user's prompt as a message
203
+ instance.messageManager.addUserMessage(prompt);
204
+
205
+ // Create allowed tools list - always exclude Task tool to prevent subagent recursion
206
+ let allowedTools = instance.configuration.tools;
207
+
208
+ // Always filter out the Task tool to prevent subagents from creating sub-subagents
209
+ if (allowedTools) {
210
+ allowedTools = allowedTools.filter((tool) => tool !== "Task");
211
+ } else {
212
+ // If no tools specified, get all tools except Task
213
+ const allTools = instance.toolManager.list().map((tool) => tool.name);
214
+ allowedTools = allTools.filter((tool) => tool !== "Task");
215
+ }
216
+
217
+ // Execute the AI request with tool restrictions
218
+ await instance.aiManager.sendAIMessage({
219
+ allowedTools,
220
+ model:
221
+ instance.configuration.model !== "inherit"
222
+ ? instance.configuration.model
223
+ : undefined,
224
+ });
225
+
226
+ // Get the latest messages to extract the response
227
+ const messages = instance.messageManager.getMessages();
228
+ const lastAssistantMessage = messages
229
+ .filter((msg) => msg.role === "assistant")
230
+ .pop();
231
+
232
+ if (!lastAssistantMessage) {
233
+ throw new Error("No response from subagent");
234
+ }
235
+
236
+ // Extract text content from the last assistant message
237
+ const textBlocks = lastAssistantMessage.blocks.filter(
238
+ (block) => block.type === "text",
239
+ );
240
+ const response = textBlocks.map((block) => block.content).join("\n");
241
+
242
+ // Update status to completed and update parent with final messages
243
+ this.updateInstanceStatus(instance.subagentId, "completed");
244
+ this.parentMessageManager.updateSubagentBlock(instance.subagentId, {
245
+ status: "completed",
246
+ messages: messages,
247
+ });
248
+
249
+ return response || "Task completed with no text response";
250
+ } catch (error) {
251
+ this.updateInstanceStatus(instance.subagentId, "error");
252
+ this.parentMessageManager.updateSubagentBlock(instance.subagentId, {
253
+ status: "error",
254
+ });
255
+ throw error;
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Get instance by subagent ID
261
+ */
262
+ getInstance(subagentId: string): SubagentInstance | null {
263
+ return this.instances.get(subagentId) || null;
264
+ }
265
+
266
+ /**
267
+ * Update instance status
268
+ */
269
+ updateInstanceStatus(
270
+ subagentId: string,
271
+ status: SubagentInstance["status"],
272
+ ): void {
273
+ const instance = this.instances.get(subagentId);
274
+ if (instance) {
275
+ instance.status = status;
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Add message to instance
281
+ */
282
+ addMessageToInstance(subagentId: string, message: Message): void {
283
+ const instance = this.instances.get(subagentId);
284
+ if (instance) {
285
+ instance.messages.push(message);
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Abort a running subagent instance
291
+ */
292
+ abortInstance(subagentId: string): boolean {
293
+ const instance = this.instances.get(subagentId);
294
+ if (!instance) {
295
+ return false;
296
+ }
297
+
298
+ // Only abort active or initializing instances
299
+ if (instance.status !== "active" && instance.status !== "initializing") {
300
+ return false;
301
+ }
302
+
303
+ try {
304
+ // Abort the AI manager operations
305
+ instance.aiManager.abortAIMessage();
306
+
307
+ // Update status
308
+ this.updateInstanceStatus(subagentId, "aborted");
309
+ this.parentMessageManager.updateSubagentBlock(subagentId, {
310
+ status: "aborted",
311
+ messages: instance.messages,
312
+ });
313
+
314
+ this.logger?.info(`Aborted subagent instance: ${subagentId}`);
315
+ return true;
316
+ } catch (error) {
317
+ this.logger?.error(
318
+ `Failed to abort subagent instance ${subagentId}:`,
319
+ error,
320
+ );
321
+ return false;
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Abort all active subagent instances
327
+ */
328
+ abortAllInstances(): void {
329
+ const activeInstances = this.getActiveInstances();
330
+ for (const instance of activeInstances) {
331
+ this.abortInstance(instance.subagentId);
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Clean up completed, errored, or aborted instances
337
+ */
338
+ cleanupInstance(subagentId: string): void {
339
+ const instance = this.instances.get(subagentId);
340
+ if (
341
+ instance &&
342
+ (instance.status === "completed" ||
343
+ instance.status === "error" ||
344
+ instance.status === "aborted")
345
+ ) {
346
+ this.instances.delete(subagentId);
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Get all active instances
352
+ */
353
+ getActiveInstances(): SubagentInstance[] {
354
+ return Array.from(this.instances.values()).filter(
355
+ (instance) =>
356
+ instance.status === "active" || instance.status === "initializing",
357
+ );
358
+ }
359
+
360
+ /**
361
+ * Clean up all instances (for session end)
362
+ */
363
+ cleanup(): void {
364
+ // Abort all active instances before cleanup
365
+ this.abortAllInstances();
366
+ this.instances.clear();
367
+ }
368
+ }
@@ -9,10 +9,14 @@ import { globTool } from "../tools/globTool.js";
9
9
  import { grepTool } from "../tools/grepTool.js";
10
10
  import { lsTool } from "../tools/lsTool.js";
11
11
  import { readTool } from "../tools/readTool.js";
12
- import { SkillManager } from "./skillManager.js";
12
+ import { todoWriteTool } from "../tools/todoWriteTool.js";
13
+ import { createTaskTool } from "../tools/taskTool.js";
14
+ import { createSkillTool } from "../tools/skillTool.js";
13
15
  import { McpManager } from "./mcpManager.js";
14
16
  import { ChatCompletionFunctionTool } from "openai/resources.js";
15
17
  import type { Logger } from "../types.js";
18
+ import type { SubagentManager } from "./subagentManager.js";
19
+ import type { SkillManager } from "./skillManager.js";
16
20
 
17
21
  export interface ToolManagerOptions {
18
22
  mcpManager: McpManager;
@@ -30,12 +34,42 @@ class ToolManager {
30
34
  constructor(options: ToolManagerOptions) {
31
35
  this.mcpManager = options.mcpManager;
32
36
  this.logger = options.logger;
37
+ }
33
38
 
34
- // Initialize built-in tools
35
- this.initializeBuiltInTools();
39
+ /**
40
+ * Register a new tool
41
+ */
42
+ public register(tool: ToolPlugin): void {
43
+ this.tools.set(tool.name, tool);
36
44
  }
37
45
 
38
- private initializeBuiltInTools(): void {
46
+ /**
47
+ * Initialize built-in tools. Can be called with dependencies for tools that require them.
48
+ *
49
+ * This method can be called multiple times safely. When called without dependencies,
50
+ * it registers basic tools (Bash, Read, Write, TodoWrite, etc.). When called with
51
+ * dependencies, it also registers tools that require managers (Task, Skill).
52
+ *
53
+ * @param deps Optional dependencies for advanced tools
54
+ * @param deps.subagentManager SubagentManager instance for Task tool
55
+ * @param deps.skillManager SkillManager instance for Skill tool
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * // Initialize basic tools only
60
+ * toolManager.initializeBuiltInTools();
61
+ *
62
+ * // Initialize all tools including those requiring dependencies
63
+ * toolManager.initializeBuiltInTools({
64
+ * subagentManager: mySubagentManager,
65
+ * skillManager: mySkillManager
66
+ * });
67
+ * ```
68
+ */
69
+ public initializeBuiltInTools(deps?: {
70
+ subagentManager?: SubagentManager;
71
+ skillManager?: SkillManager;
72
+ }): void {
39
73
  const builtInTools = [
40
74
  bashTool,
41
75
  bashOutputTool,
@@ -48,12 +82,23 @@ class ToolManager {
48
82
  grepTool,
49
83
  lsTool,
50
84
  readTool,
51
- new SkillManager({ logger: this.logger }).createTool(),
85
+ todoWriteTool,
52
86
  ];
53
87
 
54
88
  for (const tool of builtInTools) {
55
89
  this.tools.set(tool.name, tool);
56
90
  }
91
+
92
+ // Register tools that require dependencies
93
+ if (deps?.subagentManager) {
94
+ const taskTool = createTaskTool(deps.subagentManager);
95
+ this.tools.set(taskTool.name, taskTool);
96
+ }
97
+
98
+ if (deps?.skillManager) {
99
+ const skillTool = createSkillTool(deps.skillManager);
100
+ this.tools.set(skillTool.name, skillTool);
101
+ }
57
102
  }
58
103
 
59
104
  async execute(