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.
Files changed (70) hide show
  1. package/dist/index.d.ts +0 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +0 -1
  4. package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
  5. package/dist/managers/backgroundTaskManager.js +59 -6
  6. package/dist/managers/messageManager.d.ts +5 -1
  7. package/dist/managers/messageManager.d.ts.map +1 -1
  8. package/dist/managers/messageManager.js +11 -1
  9. package/dist/managers/pluginManager.js +1 -1
  10. package/dist/managers/skillManager.d.ts +17 -3
  11. package/dist/managers/skillManager.d.ts.map +1 -1
  12. package/dist/managers/skillManager.js +64 -26
  13. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  14. package/dist/managers/slashCommandManager.js +28 -10
  15. package/dist/managers/subagentManager.d.ts +2 -0
  16. package/dist/managers/subagentManager.d.ts.map +1 -1
  17. package/dist/managers/subagentManager.js +22 -0
  18. package/dist/services/MarketplaceService.d.ts.map +1 -1
  19. package/dist/services/MarketplaceService.js +4 -0
  20. package/dist/services/hook.d.ts.map +1 -1
  21. package/dist/services/hook.js +15 -2
  22. package/dist/tools/agentTool.d.ts.map +1 -1
  23. package/dist/tools/agentTool.js +7 -3
  24. package/dist/tools/bashTool.d.ts.map +1 -1
  25. package/dist/tools/bashTool.js +6 -2
  26. package/dist/tools/globTool.d.ts.map +1 -1
  27. package/dist/tools/globTool.js +9 -48
  28. package/dist/tools/grepTool.d.ts.map +1 -1
  29. package/dist/tools/grepTool.js +0 -6
  30. package/dist/types/marketplace.d.ts +1 -0
  31. package/dist/types/marketplace.d.ts.map +1 -1
  32. package/dist/types/processes.d.ts +4 -0
  33. package/dist/types/processes.d.ts.map +1 -1
  34. package/dist/types/skills.d.ts +1 -0
  35. package/dist/types/skills.d.ts.map +1 -1
  36. package/dist/utils/fileSearch.d.ts.map +1 -1
  37. package/dist/utils/fileSearch.js +1 -6
  38. package/dist/utils/markdownParser.js +1 -1
  39. package/dist/utils/messageOperations.d.ts +6 -1
  40. package/dist/utils/messageOperations.d.ts.map +1 -1
  41. package/dist/utils/messageOperations.js +26 -2
  42. package/dist/utils/openaiClient.d.ts.map +1 -1
  43. package/dist/utils/openaiClient.js +24 -7
  44. package/package.json +1 -1
  45. package/scripts/install_ripgrep.js +7 -5
  46. package/scripts/postinstall.js +10 -7
  47. package/src/index.ts +0 -1
  48. package/src/managers/backgroundTaskManager.ts +62 -6
  49. package/src/managers/messageManager.ts +16 -1
  50. package/src/managers/pluginManager.ts +1 -1
  51. package/src/managers/skillManager.ts +76 -35
  52. package/src/managers/slashCommandManager.ts +33 -10
  53. package/src/managers/subagentManager.ts +31 -0
  54. package/src/services/MarketplaceService.ts +6 -0
  55. package/src/services/hook.ts +16 -2
  56. package/src/tools/agentTool.ts +9 -3
  57. package/src/tools/bashTool.ts +6 -2
  58. package/src/tools/globTool.ts +9 -61
  59. package/src/tools/grepTool.ts +0 -7
  60. package/src/types/marketplace.ts +1 -0
  61. package/src/types/processes.ts +4 -0
  62. package/src/types/skills.ts +1 -0
  63. package/src/utils/fileSearch.ts +1 -6
  64. package/src/utils/markdownParser.ts +1 -1
  65. package/src/utils/messageOperations.ts +32 -1
  66. package/src/utils/openaiClient.ts +23 -7
  67. package/dist/utils/fileFilter.d.ts +0 -15
  68. package/dist/utils/fileFilter.d.ts.map +0 -1
  69. package/dist/utils/fileFilter.js +0 -35
  70. 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
- shell.stdout += stripAnsiColors(data.toString());
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
- shell.stderr += stripAnsiColors(data.toString());
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 += `\nProcess error: ${stripAnsiColors(error.message)}`;
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
- shell.stdout += stripAnsiColors(data.toString());
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
- shell.stderr += stripAnsiColors(data.toString());
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 += `\nProcess error: ${stripAnsiColors(error.message)}`;
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): void {
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(join(skillsPath, entry.name));
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, args: skillArgs } = args;
275
+ const { skill_name } = args;
276
+
277
+ logger?.debug(`Invoking skill: ${skill_name}`);
266
278
 
267
- logger?.debug(`Invoking skill: ${skill_name} with args: ${skillArgs}`);
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
- // Process skill content with arguments and bash commands
287
- const processedContent = await this.processSkillContent(
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 execute skill '${skill_name}':`, error);
335
+ logger?.error(`Failed to prepare skill '${skill_name}':`, error);
302
336
  return {
303
- content: `❌ **Error executing skill**: ${error instanceof Error ? error.message : String(error)}`,
337
+ content: `❌ **Error preparing skill**: ${error instanceof Error ? error.message : String(error)}`,
304
338
  };
305
339
  }
306
340
  }
307
341
 
308
342
  /**
309
- * Process skill content with arguments and bash commands
343
+ * Prepare skill content with arguments but without bash execution
310
344
  */
311
- private async processSkillContent(
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
- // 3. Parse and execute bash commands (!`command`)
330
- const { commands } = parseBashCommands(mainContent);
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
- mainContent = replaceBashCommandsWithOutput(mainContent, results);
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: skill.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
- this.skillMetadata.set(skill.name, metadata);
374
- this.skillContent.set(skill.name, skill);
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
- const result = await this.skillManager.executeSkill({
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
- this.messageManager.addUserMessage({
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
- // Add custom command message to show the command being executed
375
- const originalInput = args
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
  /**
@@ -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
 
@@ -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(instance.subagentId);
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: "Agent backgrounded",
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;
@@ -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}. Use TaskOutput tool with task_id="${taskId}" to monitor output.`,
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
  });