wave-agent-sdk 0.0.2 → 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 (64) hide show
  1. package/dist/agent.d.ts +5 -1
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +46 -2
  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.map +1 -1
  8. package/dist/managers/aiManager.js +4 -7
  9. package/dist/managers/messageManager.d.ts +8 -0
  10. package/dist/managers/messageManager.d.ts.map +1 -1
  11. package/dist/managers/messageManager.js +26 -2
  12. package/dist/managers/skillManager.d.ts +4 -5
  13. package/dist/managers/skillManager.d.ts.map +1 -1
  14. package/dist/managers/skillManager.js +6 -82
  15. package/dist/managers/subagentManager.d.ts +96 -0
  16. package/dist/managers/subagentManager.d.ts.map +1 -0
  17. package/dist/managers/subagentManager.js +261 -0
  18. package/dist/managers/toolManager.d.ts +33 -1
  19. package/dist/managers/toolManager.d.ts.map +1 -1
  20. package/dist/managers/toolManager.js +43 -5
  21. package/dist/services/aiService.d.ts.map +1 -1
  22. package/dist/services/aiService.js +40 -14
  23. package/dist/tools/grepTool.d.ts.map +1 -1
  24. package/dist/tools/grepTool.js +8 -6
  25. package/dist/tools/readTool.d.ts.map +1 -1
  26. package/dist/tools/readTool.js +36 -6
  27. package/dist/tools/skillTool.d.ts +8 -0
  28. package/dist/tools/skillTool.d.ts.map +1 -0
  29. package/dist/tools/skillTool.js +72 -0
  30. package/dist/tools/taskTool.d.ts +8 -0
  31. package/dist/tools/taskTool.d.ts.map +1 -0
  32. package/dist/tools/taskTool.js +109 -0
  33. package/dist/tools/todoWriteTool.d.ts +6 -0
  34. package/dist/tools/todoWriteTool.d.ts.map +1 -0
  35. package/dist/tools/todoWriteTool.js +203 -0
  36. package/dist/types.d.ts +8 -1
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/utils/fileFormat.d.ts +17 -0
  39. package/dist/utils/fileFormat.d.ts.map +1 -0
  40. package/dist/utils/fileFormat.js +35 -0
  41. package/dist/utils/messageOperations.d.ts +18 -0
  42. package/dist/utils/messageOperations.d.ts.map +1 -1
  43. package/dist/utils/messageOperations.js +43 -0
  44. package/dist/utils/subagentParser.d.ts +19 -0
  45. package/dist/utils/subagentParser.d.ts.map +1 -0
  46. package/dist/utils/subagentParser.js +159 -0
  47. package/package.json +1 -1
  48. package/src/agent.ts +53 -3
  49. package/src/index.ts +0 -1
  50. package/src/managers/aiManager.ts +5 -8
  51. package/src/managers/messageManager.ts +55 -1
  52. package/src/managers/skillManager.ts +7 -96
  53. package/src/managers/subagentManager.ts +368 -0
  54. package/src/managers/toolManager.ts +50 -5
  55. package/src/services/aiService.ts +43 -15
  56. package/src/tools/grepTool.ts +9 -6
  57. package/src/tools/readTool.ts +40 -6
  58. package/src/tools/skillTool.ts +82 -0
  59. package/src/tools/taskTool.ts +128 -0
  60. package/src/tools/todoWriteTool.ts +232 -0
  61. package/src/types.ts +10 -1
  62. package/src/utils/fileFormat.ts +40 -0
  63. package/src/utils/messageOperations.ts +80 -0
  64. package/src/utils/subagentParser.ts +223 -0
@@ -0,0 +1,159 @@
1
+ import { readFileSync, readdirSync, statSync } from "fs";
2
+ import { join, extname } from "path";
3
+ /**
4
+ * Parse YAML frontmatter from markdown file content
5
+ */
6
+ function parseFrontmatter(content) {
7
+ const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
8
+ const match = content.match(frontmatterRegex);
9
+ if (!match) {
10
+ return { frontmatter: {}, body: content.trim() };
11
+ }
12
+ const [, yamlContent, body] = match;
13
+ const frontmatter = parseYamlFrontmatter(yamlContent);
14
+ return { frontmatter, body: body.trim() };
15
+ }
16
+ /**
17
+ * Simple YAML frontmatter parser for subagent files
18
+ */
19
+ function parseYamlFrontmatter(yamlContent) {
20
+ const frontmatter = {};
21
+ try {
22
+ const lines = yamlContent.split("\n");
23
+ for (const line of lines) {
24
+ const trimmed = line.trim();
25
+ if (!trimmed || trimmed.startsWith("#"))
26
+ continue;
27
+ const colonIndex = trimmed.indexOf(":");
28
+ if (colonIndex === -1)
29
+ continue;
30
+ const key = trimmed.substring(0, colonIndex).trim();
31
+ const value = trimmed
32
+ .substring(colonIndex + 1)
33
+ .trim()
34
+ .replace(/^["']|["']$/g, "");
35
+ if (key && value) {
36
+ // Handle array values for tools
37
+ if (key === "tools" && value) {
38
+ let arrayValue = value;
39
+ if (arrayValue.startsWith("[") && arrayValue.endsWith("]")) {
40
+ arrayValue = arrayValue.slice(1, -1);
41
+ }
42
+ frontmatter[key] = arrayValue
43
+ .split(",")
44
+ .map((s) => s.trim())
45
+ .filter(Boolean);
46
+ }
47
+ else {
48
+ if (key === "name" || key === "description" || key === "model") {
49
+ frontmatter[key] = value;
50
+ }
51
+ }
52
+ }
53
+ }
54
+ }
55
+ catch {
56
+ // Return empty frontmatter on parse error - validation will catch missing fields
57
+ }
58
+ return frontmatter;
59
+ }
60
+ /**
61
+ * Validate subagent configuration
62
+ */
63
+ function validateConfiguration(config, filePath) {
64
+ if (!config.name) {
65
+ throw new Error(`Missing required field 'name' in ${filePath}`);
66
+ }
67
+ if (!config.description) {
68
+ throw new Error(`Missing required field 'description' in ${filePath}`);
69
+ }
70
+ // Validate name pattern
71
+ const namePattern = /^[a-z][a-z0-9-]*$/;
72
+ if (!namePattern.test(config.name)) {
73
+ throw new Error(`Invalid subagent name '${config.name}' in ${filePath}. Must start with a letter and contain only lowercase letters, numbers, and hyphens.`);
74
+ }
75
+ // Validate model if specified - allow any non-empty string
76
+ if (config.model && typeof config.model !== "string") {
77
+ throw new Error(`Invalid model '${config.model}' in ${filePath}. Must be a string.`);
78
+ }
79
+ }
80
+ /**
81
+ * Parse a single subagent markdown file
82
+ */
83
+ function parseSubagentFile(filePath, scope) {
84
+ try {
85
+ const content = readFileSync(filePath, "utf-8");
86
+ const { frontmatter, body } = parseFrontmatter(content);
87
+ validateConfiguration(frontmatter, filePath);
88
+ if (!body.trim()) {
89
+ throw new Error(`Empty system prompt in ${filePath}`);
90
+ }
91
+ return {
92
+ name: frontmatter.name,
93
+ description: frontmatter.description,
94
+ tools: frontmatter.tools,
95
+ model: frontmatter.model,
96
+ systemPrompt: body,
97
+ filePath,
98
+ scope,
99
+ priority: scope === "project" ? 1 : 2,
100
+ };
101
+ }
102
+ catch (error) {
103
+ throw new Error(`Failed to parse subagent file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
104
+ }
105
+ }
106
+ /**
107
+ * Scan directory for subagent files
108
+ */
109
+ function scanSubagentDirectory(dirPath, scope) {
110
+ const configurations = [];
111
+ try {
112
+ const entries = readdirSync(dirPath);
113
+ for (const entry of entries) {
114
+ const fullPath = join(dirPath, entry);
115
+ const stat = statSync(fullPath);
116
+ if (stat.isFile() && extname(entry) === ".md") {
117
+ try {
118
+ const config = parseSubagentFile(fullPath, scope);
119
+ configurations.push(config);
120
+ }
121
+ catch (parseError) {
122
+ // Log error but continue with other files
123
+ console.warn(`Warning: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
124
+ }
125
+ }
126
+ }
127
+ }
128
+ catch {
129
+ // Directory doesn't exist or can't be read - this is OK
130
+ }
131
+ return configurations;
132
+ }
133
+ /**
134
+ * Load all subagent configurations from project and user directories
135
+ */
136
+ export async function loadSubagentConfigurations(workdir) {
137
+ const projectDir = join(workdir, ".wave", "agents");
138
+ const userDir = join(process.env.HOME || "~", ".wave", "agents");
139
+ const projectConfigs = scanSubagentDirectory(projectDir, "project");
140
+ const userConfigs = scanSubagentDirectory(userDir, "user");
141
+ // Merge configurations, with project configs taking precedence
142
+ const configMap = new Map();
143
+ // Process in reverse priority order (user first, then project)
144
+ for (const config of [...userConfigs, ...projectConfigs]) {
145
+ configMap.set(config.name, config);
146
+ }
147
+ return Array.from(configMap.values()).sort((a, b) => {
148
+ if (a.priority !== b.priority)
149
+ return a.priority - b.priority;
150
+ return a.name.localeCompare(b.name);
151
+ });
152
+ }
153
+ /**
154
+ * Find subagent by exact name match
155
+ */
156
+ export async function findSubagentByName(name, workdir) {
157
+ const configurations = await loadSubagentConfigurations(workdir);
158
+ return configurations.find((config) => config.name === name) || null;
159
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-agent-sdk",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "SDK for building AI-powered development tools and agents",
5
5
  "keywords": [
6
6
  "ai",
package/src/agent.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  } from "./managers/messageManager.js";
5
5
  import { AIManager } from "./managers/aiManager.js";
6
6
  import { ToolManager } from "./managers/toolManager.js";
7
+ import { SubagentManager } from "./managers/subagentManager.js";
7
8
  import * as memory from "./services/memory.js";
8
9
  import { McpManager, type McpManagerCallbacks } from "./managers/mcpManager.js";
9
10
  import { BashManager } from "./managers/bashManager.js";
@@ -23,6 +24,7 @@ import type {
23
24
  import { HookManager } from "./hooks/index.js";
24
25
  import { configResolver } from "./utils/configResolver.js";
25
26
  import { configValidator } from "./utils/configValidator.js";
27
+ import { SkillManager } from "./managers/skillManager.js";
26
28
 
27
29
  /**
28
30
  * Configuration options for Agent instances
@@ -59,13 +61,13 @@ export interface AgentCallbacks
59
61
  export class Agent {
60
62
  private messageManager: MessageManager;
61
63
  private aiManager: AIManager;
62
- private callbacks: AgentCallbacks;
63
64
 
64
65
  private bashManager: BashManager | null = null;
65
66
  private backgroundBashManager: BackgroundBashManager;
66
67
  private logger?: Logger; // Add optional logger property
67
68
  private toolManager: ToolManager; // Add tool registry instance
68
69
  private mcpManager: McpManager; // Add MCP manager instance
70
+ private subagentManager: SubagentManager; // Add subagent manager instance
69
71
  private slashCommandManager: SlashCommandManager; // Add slash command manager instance
70
72
  private hookManager: HookManager; // Add hooks manager instance
71
73
  private workdir: string; // Working directory
@@ -106,7 +108,6 @@ export class Agent {
106
108
  modelConfig.fastModel,
107
109
  );
108
110
 
109
- this.callbacks = callbacks;
110
111
  this.logger = logger; // Save the passed logger
111
112
  this.workdir = workdir || process.cwd(); // Set working directory, default to current working directory
112
113
  this.systemPrompt = systemPrompt; // Save custom system prompt
@@ -121,7 +122,11 @@ export class Agent {
121
122
  workdir: this.workdir,
122
123
  });
123
124
  this.mcpManager = new McpManager({ callbacks, logger: this.logger }); // Initialize MCP manager
124
- this.toolManager = new ToolManager({ mcpManager: this.mcpManager }); // Initialize tool registry, pass MCP manager
125
+ this.toolManager = new ToolManager({
126
+ mcpManager: this.mcpManager,
127
+ logger: this.logger,
128
+ }); // Initialize tool registry, pass MCP manager
129
+
125
130
  this.hookManager = new HookManager(
126
131
  this.workdir,
127
132
  undefined,
@@ -136,6 +141,18 @@ export class Agent {
136
141
  logger: this.logger,
137
142
  });
138
143
 
144
+ // Initialize subagent manager with all dependencies in constructor
145
+ // IMPORTANT: Must be initialized AFTER MessageManager
146
+ this.subagentManager = new SubagentManager({
147
+ workdir: this.workdir,
148
+ parentToolManager: this.toolManager,
149
+ parentMessageManager: this.messageManager,
150
+ logger: this.logger,
151
+ gatewayConfig,
152
+ modelConfig,
153
+ tokenLimit,
154
+ });
155
+
139
156
  // Initialize AI manager with resolved configuration
140
157
  this.aiManager = new AIManager({
141
158
  messageManager: this.messageManager,
@@ -242,6 +259,25 @@ export class Agent {
242
259
  continueLastSession?: boolean;
243
260
  messages?: Message[];
244
261
  }): Promise<void> {
262
+ // Initialize managers first
263
+ try {
264
+ // Initialize SkillManager
265
+ const skillManager = new SkillManager({ logger: this.logger });
266
+ await skillManager.initialize();
267
+
268
+ // Initialize SubagentManager (load and cache configurations)
269
+ await this.subagentManager.initialize();
270
+
271
+ // Initialize built-in tools with dependencies
272
+ this.toolManager.initializeBuiltInTools({
273
+ subagentManager: this.subagentManager,
274
+ skillManager: skillManager,
275
+ });
276
+ } catch (error) {
277
+ this.logger?.error("Failed to initialize managers and tools:", error);
278
+ // Don't throw error to prevent app startup failure
279
+ }
280
+
245
281
  // Initialize MCP servers with auto-connect
246
282
  try {
247
283
  await this.mcpManager.initialize(this.workdir, true);
@@ -295,6 +331,7 @@ export class Agent {
295
331
  this.abortAIMessage();
296
332
  this.abortBashCommand();
297
333
  this.abortSlashCommand();
334
+ this.abortSubagents();
298
335
  }
299
336
 
300
337
  /** Add to input history */
@@ -312,16 +349,29 @@ export class Agent {
312
349
  this.slashCommandManager.abortCurrentCommand();
313
350
  }
314
351
 
352
+ /** Interrupt all subagent execution */
353
+ public abortSubagents(): void {
354
+ this.subagentManager.abortAllInstances();
355
+ }
356
+
357
+ /** Interrupt specific subagent execution */
358
+ public abortSubagent(subagentId: string): boolean {
359
+ return this.subagentManager.abortInstance(subagentId);
360
+ }
361
+
315
362
  /** Destroy managers, clean up resources */
316
363
  public async destroy(): Promise<void> {
317
364
  this.messageManager.saveSession();
318
365
  this.abortAIMessage();
319
366
  this.abortBashCommand();
320
367
  this.abortSlashCommand();
368
+ this.abortSubagents();
321
369
  // Cleanup background bash manager
322
370
  this.backgroundBashManager.cleanup();
323
371
  // Cleanup MCP connections
324
372
  await this.mcpManager.cleanup();
373
+ // Cleanup subagent manager
374
+ this.subagentManager.cleanup();
325
375
  }
326
376
 
327
377
  public async sendMessage(
package/src/index.ts CHANGED
@@ -14,7 +14,6 @@ export * from "./utils/mcpUtils.js";
14
14
  export * from "./utils/messageOperations.js";
15
15
  export * from "./utils/path.js";
16
16
  export * from "./utils/stringUtils.js";
17
- export * from "./utils/markdownParser.js";
18
17
  export * from "./utils/customCommands.js";
19
18
 
20
19
  // Export hooks system
@@ -444,14 +444,11 @@ export class AIManager {
444
444
  if (recursionDepth === 0) {
445
445
  this.setIsLoading(false);
446
446
 
447
- // Save session and execute Stop hooks in parallel but maintain order
448
- (async () => {
449
- // Save session before executing Stop hooks
450
- await this.messageManager.saveSession();
451
-
452
- // Execute Stop hooks when AI response cycle completes
453
- await this.executeStopHooks();
454
- })();
447
+ // Save session before executing Stop hooks
448
+ await this.messageManager.saveSession();
449
+
450
+ // Execute Stop hooks when AI response cycle completes
451
+ await this.executeStopHooks();
455
452
  }
456
453
  }
457
454
  }
@@ -10,6 +10,10 @@ import {
10
10
  addCommandOutputMessage,
11
11
  updateCommandOutputInMessage,
12
12
  completeCommandInMessage,
13
+ addSubagentBlockToMessage,
14
+ updateSubagentBlockInMessage,
15
+ type AddSubagentBlockParams,
16
+ type UpdateSubagentBlockParams,
13
17
  type AgentToolBlockUpdateParams,
14
18
  } from "../utils/messageOperations.js";
15
19
  import type { Logger, Message } from "../types.js";
@@ -48,10 +52,19 @@ export interface MessageManagerCallbacks {
48
52
  type: "project" | "user",
49
53
  storagePath: string,
50
54
  ) => void;
55
+ // Custom command callback
56
+ onCustomCommandAdded?: (
57
+ commandName: string,
58
+ content: string,
59
+ originalInput?: string,
60
+ ) => void;
51
61
  // Bash command callback
52
62
  onAddCommandOutputMessage?: (command: string) => void;
53
63
  onUpdateCommandOutputMessage?: (command: string, output: string) => void;
54
64
  onCompleteCommandMessage?: (command: string, exitCode: number) => void;
65
+ // Subagent callbacks
66
+ onSubAgentBlockAdded?: (subagentId: string) => void;
67
+ onSubAgentBlockUpdated?: (subagentId: string, messages: Message[]) => void;
55
68
  }
56
69
 
57
70
  export interface MessageManagerOptions {
@@ -271,7 +284,7 @@ export class MessageManager {
271
284
  },
272
285
  });
273
286
  this.setMessages(newMessages);
274
- this.callbacks.onUserMessageAdded?.(content);
287
+ this.callbacks.onCustomCommandAdded?.(commandName, content, originalInput);
275
288
  }
276
289
 
277
290
  public addAssistantMessage(
@@ -412,4 +425,45 @@ export class MessageManager {
412
425
  this.setMessages(updatedMessages);
413
426
  this.callbacks.onCompleteCommandMessage?.(command, exitCode);
414
427
  }
428
+
429
+ // Subagent block methods
430
+ public addSubagentBlock(
431
+ subagentId: string,
432
+ subagentName: string,
433
+ status: "active" | "completed" | "error" = "active",
434
+ subagentMessages: Message[] = [],
435
+ ): void {
436
+ const params: AddSubagentBlockParams = {
437
+ messages: this.messages,
438
+ subagentId,
439
+ subagentName,
440
+ status,
441
+ subagentMessages,
442
+ };
443
+ const updatedMessages = addSubagentBlockToMessage(params);
444
+ this.setMessages(updatedMessages);
445
+ this.callbacks.onSubAgentBlockAdded?.(params.subagentId);
446
+ }
447
+
448
+ public updateSubagentBlock(
449
+ subagentId: string,
450
+ updates: Partial<{
451
+ status: "active" | "completed" | "error" | "aborted";
452
+ messages: Message[];
453
+ }>,
454
+ ): void {
455
+ const updatedMessages = updateSubagentBlockInMessage(
456
+ this.messages,
457
+ subagentId,
458
+ updates,
459
+ );
460
+ this.setMessages(updatedMessages);
461
+ const params: UpdateSubagentBlockParams = {
462
+ messages: this.messages,
463
+ subagentId,
464
+ status: updates.status || "active",
465
+ subagentMessages: updates.messages || [],
466
+ };
467
+ this.callbacks.onSubAgentBlockUpdated?.(params.subagentId, params.messages);
468
+ }
415
469
  }
@@ -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
  */