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.
- package/README.md +32 -0
- package/dist/agent.d.ts +96 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +286 -0
- package/dist/hooks/executor.d.ts +56 -0
- package/dist/hooks/executor.d.ts.map +1 -0
- package/dist/hooks/executor.js +312 -0
- package/dist/hooks/index.d.ts +17 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +14 -0
- package/dist/hooks/manager.d.ts +90 -0
- package/dist/hooks/manager.d.ts.map +1 -0
- package/dist/hooks/manager.js +395 -0
- package/dist/hooks/matcher.d.ts +49 -0
- package/dist/hooks/matcher.d.ts.map +1 -0
- package/dist/hooks/matcher.js +147 -0
- package/dist/hooks/settings.d.ts +46 -0
- package/dist/hooks/settings.d.ts.map +1 -0
- package/dist/hooks/settings.js +100 -0
- package/dist/hooks/types.d.ts +80 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +59 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/managers/aiManager.d.ts +61 -0
- package/dist/managers/aiManager.d.ts.map +1 -0
- package/dist/managers/aiManager.js +415 -0
- package/dist/managers/backgroundBashManager.d.ts +27 -0
- package/dist/managers/backgroundBashManager.d.ts.map +1 -0
- package/dist/managers/backgroundBashManager.js +166 -0
- package/dist/managers/bashManager.d.ts +20 -0
- package/dist/managers/bashManager.d.ts.map +1 -0
- package/dist/managers/bashManager.js +66 -0
- package/dist/managers/mcpManager.d.ts +63 -0
- package/dist/managers/mcpManager.d.ts.map +1 -0
- package/dist/managers/mcpManager.js +378 -0
- package/dist/managers/messageManager.d.ts +85 -0
- package/dist/managers/messageManager.d.ts.map +1 -0
- package/dist/managers/messageManager.js +265 -0
- package/dist/managers/skillManager.d.ts +59 -0
- package/dist/managers/skillManager.d.ts.map +1 -0
- package/dist/managers/skillManager.js +317 -0
- package/dist/managers/slashCommandManager.d.ts +77 -0
- package/dist/managers/slashCommandManager.d.ts.map +1 -0
- package/dist/managers/slashCommandManager.js +208 -0
- package/dist/managers/toolManager.d.ts +23 -0
- package/dist/managers/toolManager.d.ts.map +1 -0
- package/dist/managers/toolManager.js +79 -0
- package/dist/services/aiService.d.ts +28 -0
- package/dist/services/aiService.d.ts.map +1 -0
- package/dist/services/aiService.js +180 -0
- package/dist/services/memory.d.ts +8 -0
- package/dist/services/memory.d.ts.map +1 -0
- package/dist/services/memory.js +128 -0
- package/dist/services/session.d.ts +54 -0
- package/dist/services/session.d.ts.map +1 -0
- package/dist/services/session.js +196 -0
- package/dist/tools/bashTool.d.ts +14 -0
- package/dist/tools/bashTool.d.ts.map +1 -0
- package/dist/tools/bashTool.js +351 -0
- package/dist/tools/deleteFileTool.d.ts +6 -0
- package/dist/tools/deleteFileTool.d.ts.map +1 -0
- package/dist/tools/deleteFileTool.js +67 -0
- package/dist/tools/editTool.d.ts +6 -0
- package/dist/tools/editTool.d.ts.map +1 -0
- package/dist/tools/editTool.js +168 -0
- package/dist/tools/globTool.d.ts +6 -0
- package/dist/tools/globTool.d.ts.map +1 -0
- package/dist/tools/globTool.js +113 -0
- package/dist/tools/grepTool.d.ts +6 -0
- package/dist/tools/grepTool.d.ts.map +1 -0
- package/dist/tools/grepTool.js +268 -0
- package/dist/tools/lsTool.d.ts +6 -0
- package/dist/tools/lsTool.d.ts.map +1 -0
- package/dist/tools/lsTool.js +160 -0
- package/dist/tools/multiEditTool.d.ts +6 -0
- package/dist/tools/multiEditTool.d.ts.map +1 -0
- package/dist/tools/multiEditTool.js +222 -0
- package/dist/tools/readTool.d.ts +6 -0
- package/dist/tools/readTool.d.ts.map +1 -0
- package/dist/tools/readTool.js +136 -0
- package/dist/tools/types.d.ts +35 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +4 -0
- package/dist/tools/writeTool.d.ts +6 -0
- package/dist/tools/writeTool.d.ts.map +1 -0
- package/dist/tools/writeTool.js +138 -0
- package/dist/types.d.ts +212 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/utils/bashHistory.d.ts +46 -0
- package/dist/utils/bashHistory.d.ts.map +1 -0
- package/dist/utils/bashHistory.js +236 -0
- package/dist/utils/commandArgumentParser.d.ts +34 -0
- package/dist/utils/commandArgumentParser.d.ts.map +1 -0
- package/dist/utils/commandArgumentParser.js +123 -0
- package/dist/utils/constants.d.ts +27 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +28 -0
- package/dist/utils/convertMessagesForAPI.d.ts +9 -0
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -0
- package/dist/utils/convertMessagesForAPI.js +189 -0
- package/dist/utils/customCommands.d.ts +14 -0
- package/dist/utils/customCommands.d.ts.map +1 -0
- package/dist/utils/customCommands.js +71 -0
- package/dist/utils/fileFilter.d.ts +26 -0
- package/dist/utils/fileFilter.d.ts.map +1 -0
- package/dist/utils/fileFilter.js +177 -0
- package/dist/utils/markdownParser.d.ts +27 -0
- package/dist/utils/markdownParser.d.ts.map +1 -0
- package/dist/utils/markdownParser.js +109 -0
- package/dist/utils/mcpUtils.d.ts +24 -0
- package/dist/utils/mcpUtils.d.ts.map +1 -0
- package/dist/utils/mcpUtils.js +51 -0
- package/dist/utils/messageOperations.d.ts +118 -0
- package/dist/utils/messageOperations.d.ts.map +1 -0
- package/dist/utils/messageOperations.js +334 -0
- package/dist/utils/path.d.ts +25 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +109 -0
- package/dist/utils/skillParser.d.ts +18 -0
- package/dist/utils/skillParser.d.ts.map +1 -0
- package/dist/utils/skillParser.js +147 -0
- package/dist/utils/stringUtils.d.ts +13 -0
- package/dist/utils/stringUtils.d.ts.map +1 -0
- package/dist/utils/stringUtils.js +44 -0
- package/package.json +51 -0
- package/src/agent.ts +405 -0
- package/src/hooks/executor.ts +440 -0
- package/src/hooks/index.ts +52 -0
- package/src/hooks/manager.ts +618 -0
- package/src/hooks/matcher.ts +187 -0
- package/src/hooks/settings.ts +129 -0
- package/src/hooks/types.ts +169 -0
- package/src/index.ts +24 -0
- package/src/managers/aiManager.ts +573 -0
- package/src/managers/backgroundBashManager.ts +203 -0
- package/src/managers/bashManager.ts +97 -0
- package/src/managers/mcpManager.ts +493 -0
- package/src/managers/messageManager.ts +415 -0
- package/src/managers/skillManager.ts +404 -0
- package/src/managers/slashCommandManager.ts +293 -0
- package/src/managers/toolManager.ts +106 -0
- package/src/services/aiService.ts +252 -0
- package/src/services/memory.ts +149 -0
- package/src/services/session.ts +265 -0
- package/src/tools/bashTool.ts +402 -0
- package/src/tools/deleteFileTool.ts +81 -0
- package/src/tools/editTool.ts +192 -0
- package/src/tools/globTool.ts +135 -0
- package/src/tools/grepTool.ts +326 -0
- package/src/tools/lsTool.ts +187 -0
- package/src/tools/multiEditTool.ts +268 -0
- package/src/tools/readTool.ts +165 -0
- package/src/tools/types.ts +47 -0
- package/src/tools/writeTool.ts +163 -0
- package/src/types.ts +260 -0
- package/src/utils/bashHistory.ts +303 -0
- package/src/utils/commandArgumentParser.ts +153 -0
- package/src/utils/constants.ts +37 -0
- package/src/utils/convertMessagesForAPI.ts +236 -0
- package/src/utils/customCommands.ts +85 -0
- package/src/utils/fileFilter.ts +202 -0
- package/src/utils/markdownParser.ts +156 -0
- package/src/utils/mcpUtils.ts +81 -0
- package/src/utils/messageOperations.ts +506 -0
- package/src/utils/path.ts +118 -0
- package/src/utils/skillParser.ts +188 -0
- 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
|
+
}
|