wave-agent-sdk 0.9.6 → 0.10.0
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/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
- package/dist/managers/backgroundTaskManager.js +59 -6
- package/dist/managers/messageManager.d.ts +5 -1
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +11 -1
- package/dist/managers/pluginManager.js +1 -1
- package/dist/managers/skillManager.d.ts +17 -3
- package/dist/managers/skillManager.d.ts.map +1 -1
- package/dist/managers/skillManager.js +64 -26
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +28 -10
- package/dist/managers/subagentManager.d.ts +2 -0
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +22 -0
- package/dist/services/MarketplaceService.d.ts.map +1 -1
- package/dist/services/MarketplaceService.js +4 -0
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +15 -2
- package/dist/tools/agentTool.d.ts.map +1 -1
- package/dist/tools/agentTool.js +7 -3
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +6 -2
- package/dist/tools/globTool.d.ts.map +1 -1
- package/dist/tools/globTool.js +9 -48
- package/dist/tools/grepTool.d.ts.map +1 -1
- package/dist/tools/grepTool.js +0 -6
- package/dist/types/marketplace.d.ts +1 -0
- package/dist/types/marketplace.d.ts.map +1 -1
- package/dist/types/processes.d.ts +4 -0
- package/dist/types/processes.d.ts.map +1 -1
- package/dist/types/skills.d.ts +1 -0
- package/dist/types/skills.d.ts.map +1 -1
- package/dist/utils/fileSearch.d.ts.map +1 -1
- package/dist/utils/fileSearch.js +1 -6
- package/dist/utils/markdownParser.js +1 -1
- package/dist/utils/messageOperations.d.ts +6 -1
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +26 -2
- package/dist/utils/openaiClient.d.ts.map +1 -1
- package/dist/utils/openaiClient.js +24 -7
- package/package.json +1 -1
- package/scripts/install_ripgrep.js +7 -5
- package/scripts/postinstall.js +10 -7
- package/src/index.ts +0 -1
- package/src/managers/backgroundTaskManager.ts +62 -6
- package/src/managers/messageManager.ts +16 -1
- package/src/managers/pluginManager.ts +1 -1
- package/src/managers/skillManager.ts +76 -35
- package/src/managers/slashCommandManager.ts +33 -10
- package/src/managers/subagentManager.ts +31 -0
- package/src/services/MarketplaceService.ts +6 -0
- package/src/services/hook.ts +16 -2
- package/src/tools/agentTool.ts +9 -3
- package/src/tools/bashTool.ts +6 -2
- package/src/tools/globTool.ts +9 -61
- package/src/tools/grepTool.ts +0 -7
- package/src/types/marketplace.ts +1 -0
- package/src/types/processes.ts +4 -0
- package/src/types/skills.ts +1 -0
- package/src/utils/fileSearch.ts +1 -6
- package/src/utils/markdownParser.ts +1 -1
- package/src/utils/messageOperations.ts +32 -1
- package/src/utils/openaiClient.ts +23 -7
- package/dist/utils/fileFilter.d.ts +0 -15
- package/dist/utils/fileFilter.d.ts.map +0 -1
- package/dist/utils/fileFilter.js +0 -35
- package/src/utils/fileFilter.ts +0 -39
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { spawn, type ChildProcess } from "child_process";
|
|
2
|
+
import * as os from "os";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
2
5
|
import { BackgroundTask, BackgroundShell } from "../types/processes.js";
|
|
3
6
|
import { stripAnsiColors } from "../utils/stringUtils.js";
|
|
4
7
|
import { logger } from "../utils/globalLogger.js";
|
|
@@ -64,6 +67,10 @@ export class BackgroundTaskManager {
|
|
|
64
67
|
},
|
|
65
68
|
});
|
|
66
69
|
|
|
70
|
+
// Create log file
|
|
71
|
+
const logPath = path.join(os.tmpdir(), `wave-task-${id}.log`);
|
|
72
|
+
const logStream = fs.createWriteStream(logPath, { flags: "a" });
|
|
73
|
+
|
|
67
74
|
const shell: BackgroundShell = {
|
|
68
75
|
id,
|
|
69
76
|
type: "shell",
|
|
@@ -73,8 +80,10 @@ export class BackgroundTaskManager {
|
|
|
73
80
|
status: "running",
|
|
74
81
|
stdout: "",
|
|
75
82
|
stderr: "",
|
|
83
|
+
outputPath: logPath,
|
|
76
84
|
onStop: () => {
|
|
77
85
|
try {
|
|
86
|
+
logStream.end();
|
|
78
87
|
if (child.pid) {
|
|
79
88
|
process.kill(-child.pid, "SIGTERM");
|
|
80
89
|
setTimeout(() => {
|
|
@@ -109,12 +118,20 @@ export class BackgroundTaskManager {
|
|
|
109
118
|
}
|
|
110
119
|
|
|
111
120
|
const onStdout = (data: Buffer | string) => {
|
|
112
|
-
|
|
121
|
+
const stripped = stripAnsiColors(data.toString());
|
|
122
|
+
shell.stdout += stripped;
|
|
123
|
+
if (logStream.writable) {
|
|
124
|
+
logStream.write(stripped);
|
|
125
|
+
}
|
|
113
126
|
this.notifyTasksChange();
|
|
114
127
|
};
|
|
115
128
|
|
|
116
129
|
const onStderr = (data: Buffer | string) => {
|
|
117
|
-
|
|
130
|
+
const stripped = stripAnsiColors(data.toString());
|
|
131
|
+
shell.stderr += stripped;
|
|
132
|
+
if (logStream.writable) {
|
|
133
|
+
logStream.write(stripped);
|
|
134
|
+
}
|
|
118
135
|
this.notifyTasksChange();
|
|
119
136
|
};
|
|
120
137
|
|
|
@@ -122,6 +139,9 @@ export class BackgroundTaskManager {
|
|
|
122
139
|
if (timeoutHandle) {
|
|
123
140
|
clearTimeout(timeoutHandle);
|
|
124
141
|
}
|
|
142
|
+
if (logStream.writable) {
|
|
143
|
+
logStream.end();
|
|
144
|
+
}
|
|
125
145
|
shell.status = code === 0 ? "completed" : "failed";
|
|
126
146
|
shell.exitCode = code ?? 0;
|
|
127
147
|
shell.endTime = Date.now();
|
|
@@ -133,8 +153,13 @@ export class BackgroundTaskManager {
|
|
|
133
153
|
if (timeoutHandle) {
|
|
134
154
|
clearTimeout(timeoutHandle);
|
|
135
155
|
}
|
|
156
|
+
const stripped = `\nProcess error: ${stripAnsiColors(error.message)}`;
|
|
136
157
|
shell.status = "failed";
|
|
137
|
-
shell.stderr +=
|
|
158
|
+
shell.stderr += stripped;
|
|
159
|
+
if (logStream.writable) {
|
|
160
|
+
logStream.write(stripped);
|
|
161
|
+
logStream.end();
|
|
162
|
+
}
|
|
138
163
|
shell.exitCode = 1;
|
|
139
164
|
shell.endTime = Date.now();
|
|
140
165
|
shell.runtime = shell.endTime - startTime;
|
|
@@ -154,6 +179,7 @@ export class BackgroundTaskManager {
|
|
|
154
179
|
if (timeoutHandle) {
|
|
155
180
|
clearTimeout(timeoutHandle);
|
|
156
181
|
}
|
|
182
|
+
logStream.end();
|
|
157
183
|
this.tasks.delete(id);
|
|
158
184
|
this.notifyTasksChange();
|
|
159
185
|
};
|
|
@@ -170,6 +196,18 @@ export class BackgroundTaskManager {
|
|
|
170
196
|
const id = this.generateId();
|
|
171
197
|
const startTime = Date.now();
|
|
172
198
|
|
|
199
|
+
// Create log file
|
|
200
|
+
const logPath = path.join(os.tmpdir(), `wave-task-${id}.log`);
|
|
201
|
+
const logStream = fs.createWriteStream(logPath, { flags: "a" });
|
|
202
|
+
|
|
203
|
+
// Write initial output to log file
|
|
204
|
+
if (initialStdout) {
|
|
205
|
+
logStream.write(stripAnsiColors(initialStdout));
|
|
206
|
+
}
|
|
207
|
+
if (initialStderr) {
|
|
208
|
+
logStream.write(stripAnsiColors(initialStderr));
|
|
209
|
+
}
|
|
210
|
+
|
|
173
211
|
const shell: BackgroundShell = {
|
|
174
212
|
id,
|
|
175
213
|
type: "shell",
|
|
@@ -179,8 +217,10 @@ export class BackgroundTaskManager {
|
|
|
179
217
|
status: "running",
|
|
180
218
|
stdout: initialStdout,
|
|
181
219
|
stderr: initialStderr,
|
|
220
|
+
outputPath: logPath,
|
|
182
221
|
onStop: () => {
|
|
183
222
|
try {
|
|
223
|
+
logStream.end();
|
|
184
224
|
if (child.pid) {
|
|
185
225
|
process.kill(-child.pid, "SIGTERM");
|
|
186
226
|
setTimeout(() => {
|
|
@@ -205,16 +245,27 @@ export class BackgroundTaskManager {
|
|
|
205
245
|
this.notifyTasksChange();
|
|
206
246
|
|
|
207
247
|
child.stdout?.on("data", (data) => {
|
|
208
|
-
|
|
248
|
+
const stripped = stripAnsiColors(data.toString());
|
|
249
|
+
shell.stdout += stripped;
|
|
250
|
+
if (logStream.writable) {
|
|
251
|
+
logStream.write(stripped);
|
|
252
|
+
}
|
|
209
253
|
this.notifyTasksChange();
|
|
210
254
|
});
|
|
211
255
|
|
|
212
256
|
child.stderr?.on("data", (data) => {
|
|
213
|
-
|
|
257
|
+
const stripped = stripAnsiColors(data.toString());
|
|
258
|
+
shell.stderr += stripped;
|
|
259
|
+
if (logStream.writable) {
|
|
260
|
+
logStream.write(stripped);
|
|
261
|
+
}
|
|
214
262
|
this.notifyTasksChange();
|
|
215
263
|
});
|
|
216
264
|
|
|
217
265
|
child.on("exit", (code) => {
|
|
266
|
+
if (logStream.writable) {
|
|
267
|
+
logStream.end();
|
|
268
|
+
}
|
|
218
269
|
shell.status = code === 0 ? "completed" : "failed";
|
|
219
270
|
shell.exitCode = code ?? 0;
|
|
220
271
|
shell.endTime = Date.now();
|
|
@@ -223,8 +274,13 @@ export class BackgroundTaskManager {
|
|
|
223
274
|
});
|
|
224
275
|
|
|
225
276
|
child.on("error", (error) => {
|
|
277
|
+
const stripped = `\nProcess error: ${stripAnsiColors(error.message)}`;
|
|
226
278
|
shell.status = "failed";
|
|
227
|
-
shell.stderr +=
|
|
279
|
+
shell.stderr += stripped;
|
|
280
|
+
if (logStream.writable) {
|
|
281
|
+
logStream.write(stripped);
|
|
282
|
+
logStream.end();
|
|
283
|
+
}
|
|
228
284
|
shell.exitCode = 1;
|
|
229
285
|
shell.endTime = Date.now();
|
|
230
286
|
shell.runtime = shell.endTime - startTime;
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
updateToolBlockInMessage,
|
|
4
4
|
addErrorBlockToMessage,
|
|
5
5
|
addUserMessageToMessages,
|
|
6
|
+
updateUserMessageInMessages,
|
|
6
7
|
addBangMessage,
|
|
7
8
|
updateBangInMessage,
|
|
8
9
|
completeBangInMessage,
|
|
@@ -318,15 +319,29 @@ export class MessageManager {
|
|
|
318
319
|
}
|
|
319
320
|
|
|
320
321
|
// Encapsulated message operation functions
|
|
321
|
-
public addUserMessage(params: UserMessageParams):
|
|
322
|
+
public addUserMessage(params: UserMessageParams): string {
|
|
323
|
+
const id = generateMessageId();
|
|
322
324
|
const newMessages = addUserMessageToMessages({
|
|
323
325
|
messages: this.messages,
|
|
324
326
|
...params,
|
|
327
|
+
id,
|
|
325
328
|
});
|
|
326
329
|
this.setMessages(newMessages);
|
|
327
330
|
this.callbacks.onUserMessageAdded?.(params);
|
|
328
331
|
|
|
329
332
|
// Note: Subagent-specific callbacks are now handled by SubagentManager
|
|
333
|
+
return id;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Update an existing user message by its ID.
|
|
338
|
+
*/
|
|
339
|
+
public updateUserMessage(
|
|
340
|
+
id: string,
|
|
341
|
+
params: Partial<UserMessageParams>,
|
|
342
|
+
): void {
|
|
343
|
+
const newMessages = updateUserMessageInMessages(this.messages, id, params);
|
|
344
|
+
this.setMessages(newMessages);
|
|
330
345
|
}
|
|
331
346
|
|
|
332
347
|
public addAssistantMessage(
|
|
@@ -164,7 +164,7 @@ export class PluginManager {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
if (this.skillManager && plugin.skills.length > 0) {
|
|
167
|
-
this.skillManager.registerPluginSkills(plugin.skills);
|
|
167
|
+
this.skillManager.registerPluginSkills(plugin.name, plugin.skills);
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
if (this.lspManager && plugin.lspConfig) {
|
|
@@ -243,8 +243,18 @@ export class SkillManager {
|
|
|
243
243
|
const entries = await readdir(skillsPath, { withFileTypes: true });
|
|
244
244
|
|
|
245
245
|
for (const entry of entries) {
|
|
246
|
+
const fullPath = join(skillsPath, entry.name);
|
|
246
247
|
if (entry.isDirectory()) {
|
|
247
|
-
directories.push(
|
|
248
|
+
directories.push(fullPath);
|
|
249
|
+
} else if (entry.isSymbolicLink()) {
|
|
250
|
+
try {
|
|
251
|
+
const s = await stat(fullPath);
|
|
252
|
+
if (s.isDirectory()) {
|
|
253
|
+
directories.push(fullPath);
|
|
254
|
+
}
|
|
255
|
+
} catch {
|
|
256
|
+
// Ignore broken symlinks or other errors
|
|
257
|
+
}
|
|
248
258
|
}
|
|
249
259
|
}
|
|
250
260
|
} catch {
|
|
@@ -262,12 +272,48 @@ export class SkillManager {
|
|
|
262
272
|
context?: SkillInvocationContext;
|
|
263
273
|
allowedTools?: string[];
|
|
264
274
|
}> {
|
|
265
|
-
const { skill_name
|
|
275
|
+
const { skill_name } = args;
|
|
276
|
+
|
|
277
|
+
logger?.debug(`Invoking skill: ${skill_name}`);
|
|
266
278
|
|
|
267
|
-
|
|
279
|
+
const prepared = await this.prepareSkill(args);
|
|
280
|
+
if (!prepared.skill) {
|
|
281
|
+
return { content: prepared.content };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
const finalContent = await this.executeBashInSkillContent(
|
|
286
|
+
prepared.content,
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
content: finalContent,
|
|
291
|
+
context: {
|
|
292
|
+
skillName: skill_name,
|
|
293
|
+
},
|
|
294
|
+
allowedTools: prepared.skill.allowedTools,
|
|
295
|
+
};
|
|
296
|
+
} catch (error) {
|
|
297
|
+
logger?.error(`Failed to execute skill '${skill_name}':`, error);
|
|
298
|
+
return {
|
|
299
|
+
content: `❌ **Error executing skill**: ${error instanceof Error ? error.message : String(error)}`,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Prepare a skill by name without executing bash commands
|
|
306
|
+
*/
|
|
307
|
+
async prepareSkill(args: SkillToolArgs): Promise<
|
|
308
|
+
| {
|
|
309
|
+
content: string;
|
|
310
|
+
skill: Skill;
|
|
311
|
+
}
|
|
312
|
+
| { content: string; skill?: undefined }
|
|
313
|
+
> {
|
|
314
|
+
const { skill_name, args: skillArgs } = args;
|
|
268
315
|
|
|
269
316
|
try {
|
|
270
|
-
// Load the skill
|
|
271
317
|
const skill = await this.loadSkill(skill_name);
|
|
272
318
|
|
|
273
319
|
if (!skill) {
|
|
@@ -283,35 +329,20 @@ export class SkillManager {
|
|
|
283
329
|
};
|
|
284
330
|
}
|
|
285
331
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
skill,
|
|
289
|
-
skillArgs || "",
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
// Return skill content with context
|
|
293
|
-
return {
|
|
294
|
-
content: processedContent,
|
|
295
|
-
context: {
|
|
296
|
-
skillName: skill_name,
|
|
297
|
-
},
|
|
298
|
-
allowedTools: skill.allowedTools,
|
|
299
|
-
};
|
|
332
|
+
const preparedContent = this.prepareSkillContent(skill, skillArgs || "");
|
|
333
|
+
return { content: preparedContent, skill };
|
|
300
334
|
} catch (error) {
|
|
301
|
-
logger?.error(`Failed to
|
|
335
|
+
logger?.error(`Failed to prepare skill '${skill_name}':`, error);
|
|
302
336
|
return {
|
|
303
|
-
content: `❌ **Error
|
|
337
|
+
content: `❌ **Error preparing skill**: ${error instanceof Error ? error.message : String(error)}`,
|
|
304
338
|
};
|
|
305
339
|
}
|
|
306
340
|
}
|
|
307
341
|
|
|
308
342
|
/**
|
|
309
|
-
*
|
|
343
|
+
* Prepare skill content with arguments but without bash execution
|
|
310
344
|
*/
|
|
311
|
-
private
|
|
312
|
-
skill: Skill,
|
|
313
|
-
argsString: string,
|
|
314
|
-
): Promise<string> {
|
|
345
|
+
private prepareSkillContent(skill: Skill, argsString: string): string {
|
|
315
346
|
const header = `🧠 **${skill.name}** (${skill.type} skill)\n\n`;
|
|
316
347
|
const description = `*${skill.description}*\n\n`;
|
|
317
348
|
const skillPath = `📁 Skill location: \`${skill.skillPath}\`\n\n`;
|
|
@@ -326,14 +357,19 @@ export class SkillManager {
|
|
|
326
357
|
// 2. Substitute ${WAVE_SKILL_DIR} with the skill's directory path
|
|
327
358
|
mainContent = mainContent.replace(/\$\{WAVE_SKILL_DIR\}/g, skill.skillPath);
|
|
328
359
|
|
|
329
|
-
|
|
330
|
-
|
|
360
|
+
return header + description + skillPath + mainContent;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Execute bash commands in prepared skill content
|
|
365
|
+
*/
|
|
366
|
+
private async executeBashInSkillContent(content: string): Promise<string> {
|
|
367
|
+
const { commands } = parseBashCommands(content);
|
|
331
368
|
if (commands.length > 0) {
|
|
332
369
|
const results = await executeBashCommands(commands, this.workdir);
|
|
333
|
-
|
|
370
|
+
return replaceBashCommandsWithOutput(content, results);
|
|
334
371
|
}
|
|
335
|
-
|
|
336
|
-
return header + description + skillPath + mainContent;
|
|
372
|
+
return content;
|
|
337
373
|
}
|
|
338
374
|
|
|
339
375
|
/**
|
|
@@ -356,10 +392,11 @@ export class SkillManager {
|
|
|
356
392
|
/**
|
|
357
393
|
* Register skills provided by a plugin
|
|
358
394
|
*/
|
|
359
|
-
registerPluginSkills(skills: Skill[]): void {
|
|
395
|
+
registerPluginSkills(pluginName: string, skills: Skill[]): void {
|
|
360
396
|
for (const skill of skills) {
|
|
397
|
+
const namespacedName = `${pluginName}:${skill.name}`;
|
|
361
398
|
const metadata: SkillMetadata = {
|
|
362
|
-
name:
|
|
399
|
+
name: namespacedName,
|
|
363
400
|
description: skill.description,
|
|
364
401
|
type: skill.type,
|
|
365
402
|
skillPath: skill.skillPath,
|
|
@@ -369,12 +406,16 @@ export class SkillManager {
|
|
|
369
406
|
model: skill.model,
|
|
370
407
|
disableModelInvocation: skill.disableModelInvocation,
|
|
371
408
|
userInvocable: skill.userInvocable,
|
|
409
|
+
pluginName,
|
|
372
410
|
};
|
|
373
|
-
|
|
374
|
-
|
|
411
|
+
// Update the skill object itself to have the namespaced name
|
|
412
|
+
skill.name = namespacedName;
|
|
413
|
+
|
|
414
|
+
this.skillMetadata.set(namespacedName, metadata);
|
|
415
|
+
this.skillContent.set(namespacedName, skill);
|
|
375
416
|
}
|
|
376
417
|
logger?.debug(
|
|
377
|
-
`Registered ${skills.length} plugin skills. Total skills: ${this.skillMetadata.size}`,
|
|
418
|
+
`Registered ${skills.length} plugin skills from ${pluginName}. Total skills: ${this.skillMetadata.size}`,
|
|
378
419
|
);
|
|
379
420
|
}
|
|
380
421
|
}
|
|
@@ -154,21 +154,39 @@ export class SlashCommandManager {
|
|
|
154
154
|
description: `Skill: ${skill.description}`,
|
|
155
155
|
handler: async (args?: string) => {
|
|
156
156
|
try {
|
|
157
|
-
|
|
157
|
+
// 1. Prepare skill content immediately
|
|
158
|
+
const prepared = await this.skillManager.prepareSkill({
|
|
158
159
|
skill_name: skill.name,
|
|
159
160
|
args,
|
|
160
161
|
});
|
|
161
162
|
|
|
162
|
-
// Add user message with skill content
|
|
163
163
|
const originalInput = args
|
|
164
164
|
? `/${skill.name} ${args}`
|
|
165
165
|
: `/${skill.name}`;
|
|
166
|
-
|
|
166
|
+
|
|
167
|
+
// 2. Add user message immediately
|
|
168
|
+
const messageId = this.messageManager.addUserMessage({
|
|
167
169
|
content: originalInput,
|
|
170
|
+
customCommandContent: prepared.content,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (!prepared.skill) {
|
|
174
|
+
// If skill not found or invalid, we're done (error message already in prepared.content)
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 3. Execute bash commands asynchronously
|
|
179
|
+
const result = await this.skillManager.executeSkill({
|
|
180
|
+
skill_name: skill.name,
|
|
181
|
+
args,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// 4. Update the message with final content
|
|
185
|
+
this.messageManager.updateUserMessage(messageId, {
|
|
168
186
|
customCommandContent: result.content,
|
|
169
187
|
});
|
|
170
188
|
|
|
171
|
-
// Trigger AI response
|
|
189
|
+
// 5. Trigger AI response
|
|
172
190
|
await this.aiManager.sendAIMessage({
|
|
173
191
|
model: skill.model,
|
|
174
192
|
allowedRules: result.allowedTools,
|
|
@@ -358,6 +376,15 @@ export class SlashCommandManager {
|
|
|
358
376
|
args?: string,
|
|
359
377
|
): Promise<void> {
|
|
360
378
|
try {
|
|
379
|
+
// Add custom command message immediately to show the command being executed
|
|
380
|
+
const originalInput = args
|
|
381
|
+
? `/${commandName} ${args}`
|
|
382
|
+
: `/${commandName}`;
|
|
383
|
+
const messageId = this.messageManager.addUserMessage({
|
|
384
|
+
content: originalInput,
|
|
385
|
+
customCommandContent: content, // Initial content with bash placeholders
|
|
386
|
+
});
|
|
387
|
+
|
|
361
388
|
// Parse bash commands from the content
|
|
362
389
|
const { commands, processedContent } = parseBashCommands(content);
|
|
363
390
|
|
|
@@ -371,12 +398,8 @@ export class SlashCommandManager {
|
|
|
371
398
|
);
|
|
372
399
|
}
|
|
373
400
|
|
|
374
|
-
//
|
|
375
|
-
|
|
376
|
-
? `/${commandName} ${args}`
|
|
377
|
-
: `/${commandName}`;
|
|
378
|
-
this.messageManager.addUserMessage({
|
|
379
|
-
content: originalInput,
|
|
401
|
+
// Update the message with final content
|
|
402
|
+
this.messageManager.updateUserMessage(messageId, {
|
|
380
403
|
customCommandContent: finalContent,
|
|
381
404
|
});
|
|
382
405
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
|
+
import * as os from "os";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
2
5
|
import type { SubagentConfiguration } from "../utils/subagentParser.js";
|
|
3
6
|
import type { Message, Usage } from "../types/index.js";
|
|
4
7
|
import { AIManager } from "./aiManager.js";
|
|
@@ -70,6 +73,7 @@ export interface SubagentInstance {
|
|
|
70
73
|
backgroundTaskId?: string; // ID of the background task if transitioned
|
|
71
74
|
onUpdate?: () => void; // Optional callback for real-time updates
|
|
72
75
|
model?: string; // Optional model override
|
|
76
|
+
logStream?: fs.WriteStream; // Optional log stream for background tasks
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
export interface SubagentManagerOptions {
|
|
@@ -274,6 +278,11 @@ export class SubagentManager {
|
|
|
274
278
|
const taskId = backgroundTaskManager.generateId();
|
|
275
279
|
const startTime = Date.now();
|
|
276
280
|
|
|
281
|
+
// Create log file
|
|
282
|
+
const logPath = path.join(os.tmpdir(), `wave-subagent-${taskId}.log`);
|
|
283
|
+
const logStream = fs.createWriteStream(logPath, { flags: "a" });
|
|
284
|
+
instance.logStream = logStream;
|
|
285
|
+
|
|
277
286
|
backgroundTaskManager.addTask({
|
|
278
287
|
id: taskId,
|
|
279
288
|
type: "subagent",
|
|
@@ -282,8 +291,10 @@ export class SubagentManager {
|
|
|
282
291
|
description: instance.description,
|
|
283
292
|
stdout: "",
|
|
284
293
|
stderr: "",
|
|
294
|
+
outputPath: logPath,
|
|
285
295
|
subagentId: instance.subagentId,
|
|
286
296
|
onStop: () => {
|
|
297
|
+
instance.logStream?.end();
|
|
287
298
|
instance.aiManager.abortAIMessage();
|
|
288
299
|
this.cleanupInstance(instance.subagentId);
|
|
289
300
|
},
|
|
@@ -345,6 +356,11 @@ export class SubagentManager {
|
|
|
345
356
|
const taskId = backgroundTaskManager.generateId();
|
|
346
357
|
const startTime = Date.now();
|
|
347
358
|
|
|
359
|
+
// Create log file
|
|
360
|
+
const logPath = path.join(os.tmpdir(), `wave-subagent-${taskId}.log`);
|
|
361
|
+
const logStream = fs.createWriteStream(logPath, { flags: "a" });
|
|
362
|
+
instance.logStream = logStream;
|
|
363
|
+
|
|
348
364
|
backgroundTaskManager.addTask({
|
|
349
365
|
id: taskId,
|
|
350
366
|
type: "subagent",
|
|
@@ -353,8 +369,10 @@ export class SubagentManager {
|
|
|
353
369
|
description: instance.description,
|
|
354
370
|
stdout: "",
|
|
355
371
|
stderr: "",
|
|
372
|
+
outputPath: logPath,
|
|
356
373
|
subagentId: instance.subagentId,
|
|
357
374
|
onStop: () => {
|
|
375
|
+
instance.logStream?.end();
|
|
358
376
|
instance.aiManager.abortAIMessage();
|
|
359
377
|
this.cleanupInstance(instance.subagentId);
|
|
360
378
|
},
|
|
@@ -446,6 +464,7 @@ export class SubagentManager {
|
|
|
446
464
|
|
|
447
465
|
// If this was transitioned to background, update the background task
|
|
448
466
|
if (instance.backgroundTaskId && backgroundTaskManager) {
|
|
467
|
+
instance.logStream?.end();
|
|
449
468
|
const task = backgroundTaskManager.getTask(instance.backgroundTaskId);
|
|
450
469
|
if (task) {
|
|
451
470
|
task.status = "completed";
|
|
@@ -465,6 +484,7 @@ export class SubagentManager {
|
|
|
465
484
|
|
|
466
485
|
// If this was transitioned to background, update the background task with error
|
|
467
486
|
if (instance.backgroundTaskId && backgroundTaskManager) {
|
|
487
|
+
instance.logStream?.end();
|
|
468
488
|
const task = backgroundTaskManager.getTask(instance.backgroundTaskId);
|
|
469
489
|
if (task) {
|
|
470
490
|
task.status = "failed";
|
|
@@ -599,6 +619,17 @@ export class SubagentManager {
|
|
|
599
619
|
instance.lastTools.shift();
|
|
600
620
|
}
|
|
601
621
|
instance.onUpdate?.();
|
|
622
|
+
|
|
623
|
+
// Log tool execution to file
|
|
624
|
+
if (instance.logStream) {
|
|
625
|
+
const compactParams = (params.parameters || "{}").substring(
|
|
626
|
+
0,
|
|
627
|
+
100,
|
|
628
|
+
);
|
|
629
|
+
instance.logStream.write(
|
|
630
|
+
`[${new Date().toISOString()}] Running tool: ${params.name} with params: ${compactParams}${compactParams.length >= 100 ? "..." : ""}\n`,
|
|
631
|
+
);
|
|
632
|
+
}
|
|
602
633
|
}
|
|
603
634
|
}
|
|
604
635
|
|
|
@@ -243,6 +243,7 @@ export class MarketplaceService {
|
|
|
243
243
|
? { source: "git", url: urlOrRepo, ref }
|
|
244
244
|
: { source: "github", repo: urlOrRepo, ref },
|
|
245
245
|
autoUpdate: false,
|
|
246
|
+
lastUpdated: new Date().toISOString(),
|
|
246
247
|
};
|
|
247
248
|
} else {
|
|
248
249
|
// Local directory format
|
|
@@ -260,6 +261,7 @@ export class MarketplaceService {
|
|
|
260
261
|
name: manifest.name,
|
|
261
262
|
source: { source: "directory", path: absolutePath },
|
|
262
263
|
autoUpdate: false,
|
|
264
|
+
lastUpdated: new Date().toISOString(),
|
|
263
265
|
};
|
|
264
266
|
}
|
|
265
267
|
|
|
@@ -363,6 +365,8 @@ export class MarketplaceService {
|
|
|
363
365
|
this.getMarketplacePath(marketplace),
|
|
364
366
|
);
|
|
365
367
|
|
|
368
|
+
marketplace.lastUpdated = new Date().toISOString();
|
|
369
|
+
|
|
366
370
|
if (options?.updatePlugins) {
|
|
367
371
|
const installedRegistry = await this.getInstalledPlugins();
|
|
368
372
|
const pluginsToUpdate = installedRegistry.plugins.filter(
|
|
@@ -414,6 +418,8 @@ export class MarketplaceService {
|
|
|
414
418
|
`Some marketplaces failed to update:\n${errors.join("\n")}`,
|
|
415
419
|
);
|
|
416
420
|
}
|
|
421
|
+
|
|
422
|
+
await this.saveKnownMarketplaces(registry);
|
|
417
423
|
}
|
|
418
424
|
|
|
419
425
|
/**
|
package/src/services/hook.ts
CHANGED
|
@@ -155,6 +155,14 @@ export async function executeCommand(
|
|
|
155
155
|
},
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
+
// Handle stdin errors (e.g. EPIPE if the process closes stdin early)
|
|
159
|
+
if (childProcess.stdin) {
|
|
160
|
+
childProcess.stdin.on("error", () => {
|
|
161
|
+
// Ignore stdin errors as they just mean the process closed stdin early
|
|
162
|
+
// or doesn't want to read from it.
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
158
166
|
// Set up timeout
|
|
159
167
|
const timeoutHandle = setTimeout(() => {
|
|
160
168
|
timedOut = true;
|
|
@@ -173,6 +181,9 @@ export async function executeCommand(
|
|
|
173
181
|
childProcess.stdout.on("data", (data: Buffer) => {
|
|
174
182
|
stdout += data.toString();
|
|
175
183
|
});
|
|
184
|
+
childProcess.stdout.on("error", () => {
|
|
185
|
+
// Ignore stdout errors
|
|
186
|
+
});
|
|
176
187
|
}
|
|
177
188
|
|
|
178
189
|
// Handle stderr
|
|
@@ -180,17 +191,20 @@ export async function executeCommand(
|
|
|
180
191
|
childProcess.stderr.on("data", (data: Buffer) => {
|
|
181
192
|
stderr += data.toString();
|
|
182
193
|
});
|
|
194
|
+
childProcess.stderr.on("error", () => {
|
|
195
|
+
// Ignore stderr errors
|
|
196
|
+
});
|
|
183
197
|
}
|
|
184
198
|
|
|
185
199
|
// Send JSON input to stdin if we have prepared it
|
|
186
|
-
if (childProcess.stdin && jsonInput) {
|
|
200
|
+
if (childProcess.stdin && childProcess.stdin.writable && jsonInput) {
|
|
187
201
|
try {
|
|
188
202
|
childProcess.stdin.write(jsonInput);
|
|
189
203
|
childProcess.stdin.end();
|
|
190
204
|
} catch {
|
|
191
205
|
// Continue execution even if JSON input fails
|
|
192
206
|
}
|
|
193
|
-
} else if (childProcess.stdin) {
|
|
207
|
+
} else if (childProcess.stdin && childProcess.stdin.writable) {
|
|
194
208
|
childProcess.stdin.end();
|
|
195
209
|
}
|
|
196
210
|
|
package/src/tools/agentTool.ts
CHANGED
|
@@ -182,10 +182,14 @@ When using the Agent tool, you must specify a subagent_type parameter to select
|
|
|
182
182
|
id: instance.subagentId,
|
|
183
183
|
backgroundHandler: async () => {
|
|
184
184
|
isBackgrounded = true;
|
|
185
|
-
await subagentManager.backgroundInstance(
|
|
185
|
+
const taskId = await subagentManager.backgroundInstance(
|
|
186
|
+
instance.subagentId,
|
|
187
|
+
);
|
|
188
|
+
const task = context.backgroundTaskManager?.getTask(taskId);
|
|
189
|
+
const outputPath = task?.outputPath;
|
|
186
190
|
resolve({
|
|
187
191
|
success: true,
|
|
188
|
-
content:
|
|
192
|
+
content: `Agent backgrounded with ID: ${taskId}.${outputPath ? ` Real-time output: ${outputPath}` : ""}`,
|
|
189
193
|
shortResult: "Agent backgrounded",
|
|
190
194
|
isManuallyBackgrounded: true,
|
|
191
195
|
});
|
|
@@ -206,9 +210,11 @@ When using the Agent tool, you must specify a subagent_type parameter to select
|
|
|
206
210
|
}
|
|
207
211
|
|
|
208
212
|
if (run_in_background) {
|
|
213
|
+
const task = context.backgroundTaskManager?.getTask(result);
|
|
214
|
+
const outputPath = task?.outputPath;
|
|
209
215
|
resolve({
|
|
210
216
|
success: true,
|
|
211
|
-
content: `Agent started in background with ID: ${result}`,
|
|
217
|
+
content: `Agent started in background with ID: ${result}.${outputPath ? ` Real-time output: ${outputPath}` : ""}`,
|
|
212
218
|
shortResult: `Agent started in background: ${result}`,
|
|
213
219
|
});
|
|
214
220
|
return;
|
package/src/tools/bashTool.ts
CHANGED
|
@@ -177,9 +177,11 @@ Usage notes:
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
const { id: taskId } = backgroundTaskManager.startShell(command, timeout);
|
|
180
|
+
const task = backgroundTaskManager.getTask(taskId);
|
|
181
|
+
const outputPath = task?.outputPath;
|
|
180
182
|
return {
|
|
181
183
|
success: true,
|
|
182
|
-
content: `Command started in background with ID: ${taskId}
|
|
184
|
+
content: `Command started in background with ID: ${taskId}.${outputPath ? ` Real-time output: ${outputPath}` : ` Use TaskOutput tool with task_id="${taskId}" to monitor output.`}`,
|
|
183
185
|
shortResult: `Background process ${taskId} started`,
|
|
184
186
|
};
|
|
185
187
|
}
|
|
@@ -220,9 +222,11 @@ Usage notes:
|
|
|
220
222
|
outputBuffer,
|
|
221
223
|
errorBuffer,
|
|
222
224
|
);
|
|
225
|
+
const task = backgroundTaskManager.getTask(taskId);
|
|
226
|
+
const outputPath = task?.outputPath;
|
|
223
227
|
resolve({
|
|
224
228
|
success: true,
|
|
225
|
-
content: `Command moved to background with ID: ${taskId}
|
|
229
|
+
content: `Command moved to background with ID: ${taskId}.${outputPath ? ` Real-time output: ${outputPath}` : ""}`,
|
|
226
230
|
shortResult: `Process ${taskId} backgrounded`,
|
|
227
231
|
isManuallyBackgrounded: true,
|
|
228
232
|
});
|