wave-agent-sdk 0.0.1

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 (170) hide show
  1. package/README.md +32 -0
  2. package/dist/agent.d.ts +96 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +286 -0
  5. package/dist/hooks/executor.d.ts +56 -0
  6. package/dist/hooks/executor.d.ts.map +1 -0
  7. package/dist/hooks/executor.js +312 -0
  8. package/dist/hooks/index.d.ts +17 -0
  9. package/dist/hooks/index.d.ts.map +1 -0
  10. package/dist/hooks/index.js +14 -0
  11. package/dist/hooks/manager.d.ts +90 -0
  12. package/dist/hooks/manager.d.ts.map +1 -0
  13. package/dist/hooks/manager.js +395 -0
  14. package/dist/hooks/matcher.d.ts +49 -0
  15. package/dist/hooks/matcher.d.ts.map +1 -0
  16. package/dist/hooks/matcher.js +147 -0
  17. package/dist/hooks/settings.d.ts +46 -0
  18. package/dist/hooks/settings.d.ts.map +1 -0
  19. package/dist/hooks/settings.js +100 -0
  20. package/dist/hooks/types.d.ts +80 -0
  21. package/dist/hooks/types.d.ts.map +1 -0
  22. package/dist/hooks/types.js +59 -0
  23. package/dist/index.d.ts +16 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +20 -0
  26. package/dist/managers/aiManager.d.ts +61 -0
  27. package/dist/managers/aiManager.d.ts.map +1 -0
  28. package/dist/managers/aiManager.js +415 -0
  29. package/dist/managers/backgroundBashManager.d.ts +27 -0
  30. package/dist/managers/backgroundBashManager.d.ts.map +1 -0
  31. package/dist/managers/backgroundBashManager.js +166 -0
  32. package/dist/managers/bashManager.d.ts +20 -0
  33. package/dist/managers/bashManager.d.ts.map +1 -0
  34. package/dist/managers/bashManager.js +66 -0
  35. package/dist/managers/mcpManager.d.ts +63 -0
  36. package/dist/managers/mcpManager.d.ts.map +1 -0
  37. package/dist/managers/mcpManager.js +378 -0
  38. package/dist/managers/messageManager.d.ts +85 -0
  39. package/dist/managers/messageManager.d.ts.map +1 -0
  40. package/dist/managers/messageManager.js +265 -0
  41. package/dist/managers/skillManager.d.ts +59 -0
  42. package/dist/managers/skillManager.d.ts.map +1 -0
  43. package/dist/managers/skillManager.js +317 -0
  44. package/dist/managers/slashCommandManager.d.ts +77 -0
  45. package/dist/managers/slashCommandManager.d.ts.map +1 -0
  46. package/dist/managers/slashCommandManager.js +208 -0
  47. package/dist/managers/toolManager.d.ts +23 -0
  48. package/dist/managers/toolManager.d.ts.map +1 -0
  49. package/dist/managers/toolManager.js +79 -0
  50. package/dist/services/aiService.d.ts +28 -0
  51. package/dist/services/aiService.d.ts.map +1 -0
  52. package/dist/services/aiService.js +180 -0
  53. package/dist/services/memory.d.ts +8 -0
  54. package/dist/services/memory.d.ts.map +1 -0
  55. package/dist/services/memory.js +128 -0
  56. package/dist/services/session.d.ts +54 -0
  57. package/dist/services/session.d.ts.map +1 -0
  58. package/dist/services/session.js +196 -0
  59. package/dist/tools/bashTool.d.ts +14 -0
  60. package/dist/tools/bashTool.d.ts.map +1 -0
  61. package/dist/tools/bashTool.js +351 -0
  62. package/dist/tools/deleteFileTool.d.ts +6 -0
  63. package/dist/tools/deleteFileTool.d.ts.map +1 -0
  64. package/dist/tools/deleteFileTool.js +67 -0
  65. package/dist/tools/editTool.d.ts +6 -0
  66. package/dist/tools/editTool.d.ts.map +1 -0
  67. package/dist/tools/editTool.js +168 -0
  68. package/dist/tools/globTool.d.ts +6 -0
  69. package/dist/tools/globTool.d.ts.map +1 -0
  70. package/dist/tools/globTool.js +113 -0
  71. package/dist/tools/grepTool.d.ts +6 -0
  72. package/dist/tools/grepTool.d.ts.map +1 -0
  73. package/dist/tools/grepTool.js +268 -0
  74. package/dist/tools/lsTool.d.ts +6 -0
  75. package/dist/tools/lsTool.d.ts.map +1 -0
  76. package/dist/tools/lsTool.js +160 -0
  77. package/dist/tools/multiEditTool.d.ts +6 -0
  78. package/dist/tools/multiEditTool.d.ts.map +1 -0
  79. package/dist/tools/multiEditTool.js +222 -0
  80. package/dist/tools/readTool.d.ts +6 -0
  81. package/dist/tools/readTool.d.ts.map +1 -0
  82. package/dist/tools/readTool.js +136 -0
  83. package/dist/tools/types.d.ts +35 -0
  84. package/dist/tools/types.d.ts.map +1 -0
  85. package/dist/tools/types.js +4 -0
  86. package/dist/tools/writeTool.d.ts +6 -0
  87. package/dist/tools/writeTool.d.ts.map +1 -0
  88. package/dist/tools/writeTool.js +138 -0
  89. package/dist/types.d.ts +212 -0
  90. package/dist/types.d.ts.map +1 -0
  91. package/dist/types.js +13 -0
  92. package/dist/utils/bashHistory.d.ts +46 -0
  93. package/dist/utils/bashHistory.d.ts.map +1 -0
  94. package/dist/utils/bashHistory.js +236 -0
  95. package/dist/utils/commandArgumentParser.d.ts +34 -0
  96. package/dist/utils/commandArgumentParser.d.ts.map +1 -0
  97. package/dist/utils/commandArgumentParser.js +123 -0
  98. package/dist/utils/constants.d.ts +27 -0
  99. package/dist/utils/constants.d.ts.map +1 -0
  100. package/dist/utils/constants.js +28 -0
  101. package/dist/utils/convertMessagesForAPI.d.ts +9 -0
  102. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -0
  103. package/dist/utils/convertMessagesForAPI.js +189 -0
  104. package/dist/utils/customCommands.d.ts +14 -0
  105. package/dist/utils/customCommands.d.ts.map +1 -0
  106. package/dist/utils/customCommands.js +71 -0
  107. package/dist/utils/fileFilter.d.ts +26 -0
  108. package/dist/utils/fileFilter.d.ts.map +1 -0
  109. package/dist/utils/fileFilter.js +177 -0
  110. package/dist/utils/markdownParser.d.ts +27 -0
  111. package/dist/utils/markdownParser.d.ts.map +1 -0
  112. package/dist/utils/markdownParser.js +109 -0
  113. package/dist/utils/mcpUtils.d.ts +24 -0
  114. package/dist/utils/mcpUtils.d.ts.map +1 -0
  115. package/dist/utils/mcpUtils.js +51 -0
  116. package/dist/utils/messageOperations.d.ts +118 -0
  117. package/dist/utils/messageOperations.d.ts.map +1 -0
  118. package/dist/utils/messageOperations.js +334 -0
  119. package/dist/utils/path.d.ts +25 -0
  120. package/dist/utils/path.d.ts.map +1 -0
  121. package/dist/utils/path.js +109 -0
  122. package/dist/utils/skillParser.d.ts +18 -0
  123. package/dist/utils/skillParser.d.ts.map +1 -0
  124. package/dist/utils/skillParser.js +147 -0
  125. package/dist/utils/stringUtils.d.ts +13 -0
  126. package/dist/utils/stringUtils.d.ts.map +1 -0
  127. package/dist/utils/stringUtils.js +44 -0
  128. package/package.json +51 -0
  129. package/src/agent.ts +405 -0
  130. package/src/hooks/executor.ts +440 -0
  131. package/src/hooks/index.ts +52 -0
  132. package/src/hooks/manager.ts +618 -0
  133. package/src/hooks/matcher.ts +187 -0
  134. package/src/hooks/settings.ts +129 -0
  135. package/src/hooks/types.ts +169 -0
  136. package/src/index.ts +24 -0
  137. package/src/managers/aiManager.ts +573 -0
  138. package/src/managers/backgroundBashManager.ts +203 -0
  139. package/src/managers/bashManager.ts +97 -0
  140. package/src/managers/mcpManager.ts +493 -0
  141. package/src/managers/messageManager.ts +415 -0
  142. package/src/managers/skillManager.ts +404 -0
  143. package/src/managers/slashCommandManager.ts +293 -0
  144. package/src/managers/toolManager.ts +106 -0
  145. package/src/services/aiService.ts +252 -0
  146. package/src/services/memory.ts +149 -0
  147. package/src/services/session.ts +265 -0
  148. package/src/tools/bashTool.ts +402 -0
  149. package/src/tools/deleteFileTool.ts +81 -0
  150. package/src/tools/editTool.ts +192 -0
  151. package/src/tools/globTool.ts +135 -0
  152. package/src/tools/grepTool.ts +326 -0
  153. package/src/tools/lsTool.ts +187 -0
  154. package/src/tools/multiEditTool.ts +268 -0
  155. package/src/tools/readTool.ts +165 -0
  156. package/src/tools/types.ts +47 -0
  157. package/src/tools/writeTool.ts +163 -0
  158. package/src/types.ts +260 -0
  159. package/src/utils/bashHistory.ts +303 -0
  160. package/src/utils/commandArgumentParser.ts +153 -0
  161. package/src/utils/constants.ts +37 -0
  162. package/src/utils/convertMessagesForAPI.ts +236 -0
  163. package/src/utils/customCommands.ts +85 -0
  164. package/src/utils/fileFilter.ts +202 -0
  165. package/src/utils/markdownParser.ts +156 -0
  166. package/src/utils/mcpUtils.ts +81 -0
  167. package/src/utils/messageOperations.ts +506 -0
  168. package/src/utils/path.ts +118 -0
  169. package/src/utils/skillParser.ts +188 -0
  170. package/src/utils/stringUtils.ts +50 -0
@@ -0,0 +1,265 @@
1
+ import { randomUUID } from "crypto";
2
+ import { addAssistantMessageToMessages, updateToolBlockInMessage, addErrorBlockToMessage, addDiffBlockToMessage, addUserMessageToMessages, extractUserInputHistory, addMemoryBlockToMessage, addCommandOutputMessage, updateCommandOutputInMessage, completeCommandInMessage, } from "../utils/messageOperations.js";
3
+ import { cleanupExpiredSessions, getLatestSession, loadSession, saveSession, getSessionFilePath, } from "../services/session.js";
4
+ export class MessageManager {
5
+ constructor(options) {
6
+ this.sessionId = randomUUID();
7
+ this.messages = [];
8
+ this.latestTotalTokens = 0;
9
+ this.userInputHistory = [];
10
+ this.sessionStartTime = new Date().toISOString();
11
+ this.workdir = options.workdir;
12
+ this.callbacks = options.callbacks;
13
+ this.logger = options.logger;
14
+ }
15
+ // Getter methods
16
+ getSessionId() {
17
+ return this.sessionId;
18
+ }
19
+ getMessages() {
20
+ return [...this.messages];
21
+ }
22
+ getlatestTotalTokens() {
23
+ return this.latestTotalTokens;
24
+ }
25
+ getUserInputHistory() {
26
+ return [...this.userInputHistory];
27
+ }
28
+ getTranscriptPath() {
29
+ return getSessionFilePath(this.sessionId);
30
+ }
31
+ // Setter methods, will trigger callbacks
32
+ setSessionId(sessionId) {
33
+ if (this.sessionId !== sessionId) {
34
+ this.sessionId = sessionId;
35
+ this.callbacks.onSessionIdChange?.(sessionId);
36
+ }
37
+ }
38
+ setMessages(messages) {
39
+ this.messages = [...messages];
40
+ this.callbacks.onMessagesChange?.([...messages]);
41
+ }
42
+ /**
43
+ * Save current session
44
+ */
45
+ async saveSession() {
46
+ try {
47
+ await saveSession(this.sessionId, this.messages, this.workdir, this.latestTotalTokens, this.sessionStartTime);
48
+ }
49
+ catch (error) {
50
+ this.logger?.error("Failed to save session:", error);
51
+ }
52
+ }
53
+ /**
54
+ * Handle session restoration logic
55
+ */
56
+ async handleSessionRestoration(restoreSessionId, continueLastSession) {
57
+ // Clean up expired sessions first
58
+ try {
59
+ await cleanupExpiredSessions(this.workdir);
60
+ }
61
+ catch (error) {
62
+ console.warn("Failed to cleanup expired sessions:", error);
63
+ }
64
+ if (!restoreSessionId && !continueLastSession) {
65
+ return;
66
+ }
67
+ try {
68
+ let sessionToRestore = null;
69
+ if (restoreSessionId) {
70
+ sessionToRestore = await loadSession(restoreSessionId);
71
+ if (!sessionToRestore) {
72
+ console.error(`Session not found: ${restoreSessionId}`);
73
+ process.exit(1);
74
+ }
75
+ }
76
+ else if (continueLastSession) {
77
+ sessionToRestore = await getLatestSession(this.workdir);
78
+ if (!sessionToRestore) {
79
+ console.error(`No previous session found for workdir: ${this.workdir}`);
80
+ process.exit(1);
81
+ }
82
+ }
83
+ if (sessionToRestore) {
84
+ console.log(`Restoring session: ${sessionToRestore.id}`);
85
+ // Initialize from session data
86
+ this.initializeFromSession(sessionToRestore.id, sessionToRestore.messages, sessionToRestore.metadata.latestTotalTokens);
87
+ }
88
+ }
89
+ catch (error) {
90
+ console.error("Failed to restore session:", error);
91
+ process.exit(1);
92
+ }
93
+ }
94
+ setlatestTotalTokens(latestTotalTokens) {
95
+ if (this.latestTotalTokens !== latestTotalTokens) {
96
+ this.latestTotalTokens = latestTotalTokens;
97
+ this.callbacks.onLatestTotalTokensChange?.(latestTotalTokens);
98
+ }
99
+ }
100
+ setUserInputHistory(userInputHistory) {
101
+ this.userInputHistory = [...userInputHistory];
102
+ this.callbacks.onUserInputHistoryChange?.(this.userInputHistory);
103
+ }
104
+ /**
105
+ * Clear messages and input history
106
+ */
107
+ clearMessages() {
108
+ this.setMessages([]);
109
+ this.setUserInputHistory([]);
110
+ this.setSessionId(randomUUID());
111
+ this.setlatestTotalTokens(0);
112
+ this.sessionStartTime = new Date().toISOString();
113
+ }
114
+ // Initialize state from session data
115
+ initializeFromSession(sessionId, messages, latestTotalTokens) {
116
+ this.setSessionId(sessionId);
117
+ this.setMessages([...messages]);
118
+ this.setlatestTotalTokens(latestTotalTokens);
119
+ // Extract user input history from session messages
120
+ this.setUserInputHistory(extractUserInputHistory(messages));
121
+ }
122
+ // Add to input history
123
+ addToInputHistory(input) {
124
+ // Avoid adding duplicate inputs
125
+ if (this.userInputHistory.length > 0 &&
126
+ this.userInputHistory[this.userInputHistory.length - 1] === input) {
127
+ return;
128
+ }
129
+ // Limit history records, keep the latest 100
130
+ this.setUserInputHistory([...this.userInputHistory, input].slice(-100));
131
+ }
132
+ // Clear input history
133
+ clearInputHistory() {
134
+ this.setUserInputHistory([]);
135
+ }
136
+ // Encapsulated message operation functions
137
+ addUserMessage(content, images) {
138
+ const newMessages = addUserMessageToMessages({
139
+ messages: this.messages,
140
+ content,
141
+ images,
142
+ });
143
+ this.setMessages(newMessages);
144
+ this.callbacks.onUserMessageAdded?.(content, images);
145
+ }
146
+ addCustomCommandMessage(commandName, content, originalInput) {
147
+ const newMessages = addUserMessageToMessages({
148
+ messages: this.messages,
149
+ content: "", // Empty content, as we will use CustomCommandBlock
150
+ customCommandBlock: {
151
+ type: "custom_command",
152
+ commandName,
153
+ content,
154
+ originalInput,
155
+ },
156
+ });
157
+ this.setMessages(newMessages);
158
+ this.callbacks.onUserMessageAdded?.(content);
159
+ }
160
+ addAssistantMessage(content, toolCalls) {
161
+ const newMessages = addAssistantMessageToMessages(this.messages, content, toolCalls);
162
+ this.setMessages(newMessages);
163
+ this.callbacks.onAssistantMessageAdded?.(content, toolCalls);
164
+ }
165
+ updateToolBlock(params) {
166
+ const newMessages = updateToolBlockInMessage({
167
+ messages: this.messages,
168
+ id: params.toolId,
169
+ parameters: params.args || "",
170
+ result: params.result,
171
+ success: params.success,
172
+ error: params.error,
173
+ isRunning: params.isRunning,
174
+ name: params.name,
175
+ shortResult: params.shortResult,
176
+ compactParams: params.compactParams,
177
+ });
178
+ this.setMessages(newMessages);
179
+ this.callbacks.onToolBlockUpdated?.(params);
180
+ }
181
+ addDiffBlock(filePath, diffResult) {
182
+ const newMessages = addDiffBlockToMessage({
183
+ messages: this.messages,
184
+ path: filePath,
185
+ diffResult,
186
+ });
187
+ this.setMessages(newMessages);
188
+ this.callbacks.onDiffBlockAdded?.(filePath, JSON.stringify(diffResult));
189
+ }
190
+ addErrorBlock(error) {
191
+ const newMessages = addErrorBlockToMessage({
192
+ messages: this.messages,
193
+ error,
194
+ });
195
+ this.setMessages(newMessages);
196
+ this.callbacks.onErrorBlockAdded?.(error);
197
+ }
198
+ /**
199
+ * Compress messages and update session, delete compressed messages, only keep compressed messages and subsequent messages
200
+ */
201
+ compressMessagesAndUpdateSession(insertIndex, compressedContent) {
202
+ const currentMessages = this.messages;
203
+ // Create compressed message
204
+ const compressMessage = {
205
+ role: "assistant",
206
+ blocks: [
207
+ {
208
+ type: "compress",
209
+ content: compressedContent,
210
+ },
211
+ ],
212
+ };
213
+ // Convert negative index to positive index
214
+ const actualIndex = insertIndex < 0 ? currentMessages.length + insertIndex : insertIndex;
215
+ // Build new message array: keep compressed message and all messages from actualIndex onwards
216
+ const newMessages = [
217
+ compressMessage,
218
+ ...currentMessages.slice(actualIndex),
219
+ ];
220
+ // Update sessionId
221
+ this.setSessionId(randomUUID());
222
+ // Set new message list
223
+ this.setMessages(newMessages);
224
+ // Trigger compression callback, insertIndex remains unchanged
225
+ this.callbacks.onCompressBlockAdded?.(insertIndex, compressedContent);
226
+ }
227
+ addMemoryBlock(content, success, type, storagePath) {
228
+ const newMessages = addMemoryBlockToMessage({
229
+ messages: this.messages,
230
+ content,
231
+ isSuccess: success,
232
+ memoryType: type,
233
+ storagePath,
234
+ });
235
+ this.setMessages(newMessages);
236
+ this.callbacks.onMemoryBlockAdded?.(content, success, type, storagePath);
237
+ }
238
+ // Bash command related message operations
239
+ addCommandOutputMessage(command) {
240
+ const updatedMessages = addCommandOutputMessage({
241
+ messages: this.messages,
242
+ command,
243
+ });
244
+ this.setMessages(updatedMessages);
245
+ this.callbacks.onAddCommandOutputMessage?.(command);
246
+ }
247
+ updateCommandOutputMessage(command, output) {
248
+ const updatedMessages = updateCommandOutputInMessage({
249
+ messages: this.messages,
250
+ command,
251
+ output,
252
+ });
253
+ this.setMessages(updatedMessages);
254
+ this.callbacks.onUpdateCommandOutputMessage?.(command, output);
255
+ }
256
+ completeCommandMessage(command, exitCode) {
257
+ const updatedMessages = completeCommandInMessage({
258
+ messages: this.messages,
259
+ command,
260
+ exitCode,
261
+ });
262
+ this.setMessages(updatedMessages);
263
+ this.callbacks.onCompleteCommandMessage?.(command, exitCode);
264
+ }
265
+ }
@@ -0,0 +1,59 @@
1
+ import type { SkillManagerOptions, SkillMetadata, Skill, SkillToolArgs, SkillInvocationContext } from "../types.js";
2
+ import type { ToolPlugin } from "../tools/types.js";
3
+ /**
4
+ * Manages skill discovery and loading
5
+ */
6
+ export declare class SkillManager {
7
+ private personalSkillsPath;
8
+ private scanTimeout;
9
+ private logger?;
10
+ private skillMetadata;
11
+ private skillContent;
12
+ private initialized;
13
+ constructor(options?: SkillManagerOptions);
14
+ /**
15
+ * Initialize the skill manager by discovering available skills
16
+ */
17
+ initialize(): Promise<void>;
18
+ /**
19
+ * Get all available skills metadata
20
+ */
21
+ getAvailableSkills(): SkillMetadata[];
22
+ /**
23
+ * Load a specific skill by name
24
+ * Returns the skill content that was loaded during initialization
25
+ */
26
+ loadSkill(skillName: string): Promise<Skill | null>;
27
+ /**
28
+ * Discover skills in both personal and project directories
29
+ */
30
+ private discoverSkills;
31
+ /**
32
+ * Discover skills in a specific directory
33
+ */
34
+ private discoverSkillCollection;
35
+ /**
36
+ * Find all directories that could contain skills
37
+ */
38
+ private findSkillDirectories;
39
+ /**
40
+ * Create a tool plugin for registering with ToolManager
41
+ */
42
+ createTool(): ToolPlugin;
43
+ /**
44
+ * Execute a skill by name
45
+ */
46
+ executeSkill(args: SkillToolArgs): Promise<{
47
+ content: string;
48
+ context?: SkillInvocationContext;
49
+ }>;
50
+ /**
51
+ * Format skill content for output
52
+ */
53
+ private formatSkillContent;
54
+ /**
55
+ * Format available skills list for error messages
56
+ */
57
+ private formatAvailableSkills;
58
+ }
59
+ //# sourceMappingURL=skillManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skillManager.d.ts","sourceRoot":"","sources":["../../src/managers/skillManager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EACb,KAAK,EAGL,aAAa,EACb,sBAAsB,EAEvB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,mBAAmB,CAAC;AAGhE;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,MAAM,CAAC,CAAS;IAExB,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,WAAW,CAAS;gBAEhB,OAAO,GAAE,mBAAwB;IAO7C;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAwCjC;;OAEG;IACH,kBAAkB,IAAI,aAAa,EAAE;IAQrC;;;OAGG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IAgBzD;;OAEG;YACW,cAAc;IAkB5B;;OAEG;YACW,uBAAuB;IAiFrC;;OAEG;YACW,oBAAoB;IAkBlC;;OAEG;IACH,UAAU,IAAI,UAAU;IA4FxB;;OAEG;IACG,YAAY,CAChB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,sBAAsB,CAAA;KAAE,CAAC;IAqCjE;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAY1B;;OAEG;IACH,OAAO,CAAC,qBAAqB;CAa9B"}
@@ -0,0 +1,317 @@
1
+ import { readdir, stat } from "fs/promises";
2
+ import { join } from "path";
3
+ import { homedir } from "os";
4
+ import { parseSkillFile, formatSkillError } from "../utils/skillParser.js";
5
+ /**
6
+ * Manages skill discovery and loading
7
+ */
8
+ export class SkillManager {
9
+ constructor(options = {}) {
10
+ this.skillMetadata = new Map();
11
+ this.skillContent = new Map();
12
+ this.initialized = false;
13
+ this.personalSkillsPath =
14
+ options.personalSkillsPath || join(homedir(), ".wave", "skills");
15
+ this.scanTimeout = options.scanTimeout || 5000;
16
+ this.logger = options.logger;
17
+ }
18
+ /**
19
+ * Initialize the skill manager by discovering available skills
20
+ */
21
+ async initialize() {
22
+ this.logger?.info("Initializing SkillManager...");
23
+ try {
24
+ // Clear existing data before discovery
25
+ this.skillMetadata.clear();
26
+ this.skillContent.clear();
27
+ const discovery = await this.discoverSkills();
28
+ // Store discovered skill metadata
29
+ discovery.personalSkills.forEach((skill, name) => {
30
+ this.skillMetadata.set(name, skill);
31
+ });
32
+ discovery.projectSkills.forEach((skill, name) => {
33
+ this.skillMetadata.set(name, skill);
34
+ });
35
+ // Log any discovery errors
36
+ if (discovery.errors.length > 0) {
37
+ this.logger?.warn(`Found ${discovery.errors.length} skill discovery errors`);
38
+ discovery.errors.forEach((error) => {
39
+ this.logger?.warn(`Skill error in ${error.skillPath}: ${error.message}`);
40
+ });
41
+ }
42
+ this.initialized = true;
43
+ this.logger?.info(`SkillManager initialized with ${this.skillMetadata.size} skills`);
44
+ }
45
+ catch (error) {
46
+ this.logger?.error("Failed to initialize SkillManager:", error);
47
+ throw error;
48
+ }
49
+ }
50
+ /**
51
+ * Get all available skills metadata
52
+ */
53
+ getAvailableSkills() {
54
+ if (!this.initialized) {
55
+ throw new Error("SkillManager not initialized. Call initialize() first.");
56
+ }
57
+ return Array.from(this.skillMetadata.values());
58
+ }
59
+ /**
60
+ * Load a specific skill by name
61
+ * Returns the skill content that was loaded during initialization
62
+ */
63
+ async loadSkill(skillName) {
64
+ if (!this.initialized) {
65
+ throw new Error("SkillManager not initialized. Call initialize() first.");
66
+ }
67
+ // Return skill content that was loaded during initialization
68
+ const skill = this.skillContent.get(skillName);
69
+ if (skill) {
70
+ this.logger?.debug(`Skill '${skillName}' retrieved from loaded content`);
71
+ return skill;
72
+ }
73
+ this.logger?.debug(`Skill '${skillName}' not found`);
74
+ return null;
75
+ }
76
+ /**
77
+ * Discover skills in both personal and project directories
78
+ */
79
+ async discoverSkills() {
80
+ const personalCollection = await this.discoverSkillCollection(this.personalSkillsPath, "personal");
81
+ const projectCollection = await this.discoverSkillCollection(process.cwd(), "project");
82
+ return {
83
+ personalSkills: personalCollection.skills,
84
+ projectSkills: projectCollection.skills,
85
+ errors: [...personalCollection.errors, ...projectCollection.errors],
86
+ };
87
+ }
88
+ /**
89
+ * Discover skills in a specific directory
90
+ */
91
+ async discoverSkillCollection(basePath, type) {
92
+ const collection = {
93
+ type,
94
+ basePath,
95
+ skills: new Map(),
96
+ errors: [],
97
+ };
98
+ const skillsPath = type === "personal" ? basePath : join(basePath, ".wave", "skills");
99
+ try {
100
+ const skillDirs = await this.findSkillDirectories(skillsPath);
101
+ this.logger?.debug(`Found ${skillDirs.length} potential skill directories in ${skillsPath}`);
102
+ for (const skillDir of skillDirs) {
103
+ try {
104
+ const skillFilePath = join(skillDir, "SKILL.md");
105
+ // Check if SKILL.md exists
106
+ try {
107
+ await stat(skillFilePath);
108
+ }
109
+ catch {
110
+ continue; // Skip directories without SKILL.md
111
+ }
112
+ const parsed = parseSkillFile(skillFilePath, {
113
+ basePath: skillDir,
114
+ validateMetadata: true,
115
+ });
116
+ if (parsed.isValid) {
117
+ // Override the skill type with the collection type
118
+ const skillMetadata = {
119
+ ...parsed.skillMetadata,
120
+ type,
121
+ };
122
+ // Create full skill object with content
123
+ const skill = {
124
+ name: parsed.skillMetadata.name,
125
+ description: parsed.skillMetadata.description,
126
+ type: type, // Use the collection type
127
+ skillPath: parsed.skillMetadata.skillPath,
128
+ content: parsed.content,
129
+ frontmatter: parsed.frontmatter,
130
+ isValid: parsed.isValid,
131
+ errors: parsed.validationErrors,
132
+ };
133
+ collection.skills.set(skillMetadata.name, skillMetadata);
134
+ // Store the full skill content in the manager's skillContent map
135
+ this.skillContent.set(skillMetadata.name, skill);
136
+ }
137
+ else {
138
+ collection.errors.push({
139
+ skillPath: skillDir,
140
+ message: parsed.validationErrors.join("; "),
141
+ });
142
+ }
143
+ }
144
+ catch (error) {
145
+ collection.errors.push({
146
+ skillPath: skillDir,
147
+ message: `Failed to process skill: ${error instanceof Error ? error.message : String(error)}`,
148
+ });
149
+ }
150
+ }
151
+ }
152
+ catch (error) {
153
+ this.logger?.debug(`Could not scan ${skillsPath}: ${error instanceof Error ? error.message : String(error)}`);
154
+ // Not an error - the directory might not exist yet
155
+ }
156
+ return collection;
157
+ }
158
+ /**
159
+ * Find all directories that could contain skills
160
+ */
161
+ async findSkillDirectories(skillsPath) {
162
+ const directories = [];
163
+ try {
164
+ const entries = await readdir(skillsPath, { withFileTypes: true });
165
+ for (const entry of entries) {
166
+ if (entry.isDirectory()) {
167
+ directories.push(join(skillsPath, entry.name));
168
+ }
169
+ }
170
+ }
171
+ catch {
172
+ // Directory doesn't exist - return empty array
173
+ }
174
+ return directories;
175
+ }
176
+ /**
177
+ * Create a tool plugin for registering with ToolManager
178
+ */
179
+ createTool() {
180
+ // Initialize skill manager asynchronously
181
+ let initializationPromise = null;
182
+ const ensureInitialized = async () => {
183
+ if (!initializationPromise) {
184
+ initializationPromise = this.initialize();
185
+ }
186
+ await initializationPromise;
187
+ };
188
+ const getToolDescription = () => {
189
+ if (!this.initialized) {
190
+ 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.";
191
+ }
192
+ const availableSkills = this.getAvailableSkills();
193
+ if (availableSkills.length === 0) {
194
+ 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.";
195
+ }
196
+ const skillList = availableSkills
197
+ .map((skill) => `• **${skill.name}** (${skill.type}): ${skill.description}`)
198
+ .join("\n");
199
+ 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}`;
200
+ };
201
+ return {
202
+ name: "skill",
203
+ config: {
204
+ type: "function",
205
+ function: {
206
+ name: "skill",
207
+ description: getToolDescription(),
208
+ parameters: {
209
+ type: "object",
210
+ properties: {
211
+ skill_name: {
212
+ type: "string",
213
+ description: "Name of the skill to invoke",
214
+ enum: this.initialized
215
+ ? this.getAvailableSkills().map((skill) => skill.name)
216
+ : [],
217
+ },
218
+ },
219
+ required: ["skill_name"],
220
+ },
221
+ },
222
+ },
223
+ execute: async (args) => {
224
+ try {
225
+ // Ensure skill manager is initialized
226
+ await ensureInitialized();
227
+ // Validate arguments
228
+ const skillName = args.skill_name;
229
+ if (!skillName || typeof skillName !== "string") {
230
+ return {
231
+ success: false,
232
+ content: "",
233
+ error: "skill_name parameter is required and must be a string",
234
+ };
235
+ }
236
+ // Execute the skill
237
+ const result = await this.executeSkill({ skill_name: skillName });
238
+ return {
239
+ success: true,
240
+ content: result.content,
241
+ shortResult: `Invoked skill: ${skillName}`,
242
+ };
243
+ }
244
+ catch (error) {
245
+ return {
246
+ success: false,
247
+ content: "",
248
+ error: error instanceof Error ? error.message : String(error),
249
+ };
250
+ }
251
+ },
252
+ formatCompactParams: (params) => {
253
+ const skillName = params.skill_name;
254
+ return skillName || "unknown-skill";
255
+ },
256
+ };
257
+ }
258
+ /**
259
+ * Execute a skill by name
260
+ */
261
+ async executeSkill(args) {
262
+ const { skill_name } = args;
263
+ this.logger?.info(`Invoking skill: ${skill_name}`);
264
+ try {
265
+ // Load the skill
266
+ const skill = await this.loadSkill(skill_name);
267
+ if (!skill) {
268
+ return {
269
+ content: `❌ **Skill not found**: "${skill_name}"\n\nAvailable skills:\n${this.formatAvailableSkills()}`,
270
+ };
271
+ }
272
+ if (!skill.isValid) {
273
+ const errorMsg = formatSkillError(skill.skillPath, skill.errors);
274
+ return {
275
+ content: `❌ **Skill validation failed**:\n\n\`\`\`\n${errorMsg}\n\`\`\``,
276
+ };
277
+ }
278
+ // Return skill content with context
279
+ return {
280
+ content: this.formatSkillContent(skill),
281
+ context: {
282
+ skillName: skill_name,
283
+ },
284
+ };
285
+ }
286
+ catch (error) {
287
+ this.logger?.error(`Failed to execute skill '${skill_name}':`, error);
288
+ return {
289
+ content: `❌ **Error executing skill**: ${error instanceof Error ? error.message : String(error)}`,
290
+ };
291
+ }
292
+ }
293
+ /**
294
+ * Format skill content for output
295
+ */
296
+ formatSkillContent(skill) {
297
+ const header = `🧠 **${skill.name}** (${skill.type} skill)\n\n`;
298
+ const description = `*${skill.description}*\n\n`;
299
+ const skillPath = `📁 Skill location: \`${skill.skillPath}\`\n\n`;
300
+ // Extract content after frontmatter
301
+ const contentMatch = skill.content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
302
+ const mainContent = contentMatch ? contentMatch[1].trim() : skill.content;
303
+ return header + description + skillPath + mainContent;
304
+ }
305
+ /**
306
+ * Format available skills list for error messages
307
+ */
308
+ formatAvailableSkills() {
309
+ const skills = this.getAvailableSkills();
310
+ if (skills.length === 0) {
311
+ return "• No skills available\n\nTo create skills, see the Wave Skills documentation.";
312
+ }
313
+ return skills
314
+ .map((skill) => `• **${skill.name}** (${skill.type}): ${skill.description}`)
315
+ .join("\n");
316
+ }
317
+ }