wave-agent-sdk 0.0.5 → 0.0.6

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 (140) hide show
  1. package/dist/agent.d.ts +3 -6
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +21 -21
  4. package/dist/index.d.ts +3 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +3 -3
  7. package/dist/managers/aiManager.d.ts +4 -2
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +84 -47
  10. package/dist/managers/backgroundBashManager.d.ts +1 -1
  11. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  12. package/dist/{hooks/manager.d.ts → managers/hookManager.d.ts} +26 -7
  13. package/dist/managers/hookManager.d.ts.map +1 -0
  14. package/dist/{hooks/manager.js → managers/hookManager.js} +107 -17
  15. package/dist/managers/mcpManager.d.ts +1 -1
  16. package/dist/managers/mcpManager.d.ts.map +1 -1
  17. package/dist/managers/messageManager.d.ts +17 -4
  18. package/dist/managers/messageManager.d.ts.map +1 -1
  19. package/dist/managers/messageManager.js +13 -5
  20. package/dist/managers/skillManager.d.ts +1 -1
  21. package/dist/managers/skillManager.d.ts.map +1 -1
  22. package/dist/managers/slashCommandManager.d.ts +1 -1
  23. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  24. package/dist/managers/subagentManager.d.ts +7 -12
  25. package/dist/managers/subagentManager.d.ts.map +1 -1
  26. package/dist/managers/subagentManager.js +39 -45
  27. package/dist/managers/toolManager.d.ts +1 -1
  28. package/dist/managers/toolManager.d.ts.map +1 -1
  29. package/dist/services/aiService.d.ts +1 -1
  30. package/dist/services/aiService.d.ts.map +1 -1
  31. package/dist/services/aiService.js +8 -1
  32. package/dist/services/hook.d.ts +56 -0
  33. package/dist/services/hook.d.ts.map +1 -0
  34. package/dist/services/hook.js +276 -0
  35. package/dist/services/session.d.ts +1 -1
  36. package/dist/services/session.d.ts.map +1 -1
  37. package/dist/services/session.js +5 -4
  38. package/dist/tools/taskTool.d.ts.map +1 -1
  39. package/dist/tools/taskTool.js +7 -3
  40. package/dist/types/commands.d.ts +24 -0
  41. package/dist/types/commands.d.ts.map +1 -0
  42. package/dist/types/commands.js +5 -0
  43. package/dist/types/config.d.ts +13 -0
  44. package/dist/types/config.d.ts.map +1 -0
  45. package/dist/types/config.js +5 -0
  46. package/dist/types/core.d.ts +38 -0
  47. package/dist/types/core.d.ts.map +1 -0
  48. package/dist/{types.js → types/core.js} +4 -13
  49. package/dist/{hooks/types.d.ts → types/hooks.d.ts} +2 -1
  50. package/dist/types/hooks.d.ts.map +1 -0
  51. package/dist/types/index.d.ts +20 -0
  52. package/dist/types/index.d.ts.map +1 -0
  53. package/dist/types/index.js +21 -0
  54. package/dist/types/mcp.d.ts +28 -0
  55. package/dist/types/mcp.d.ts.map +1 -0
  56. package/dist/types/mcp.js +5 -0
  57. package/dist/types/messaging.d.ts +80 -0
  58. package/dist/types/messaging.d.ts.map +1 -0
  59. package/dist/types/messaging.js +5 -0
  60. package/dist/types/processes.d.ts +17 -0
  61. package/dist/types/processes.d.ts.map +1 -0
  62. package/dist/types/processes.js +5 -0
  63. package/dist/types/skills.d.ts +78 -0
  64. package/dist/types/skills.d.ts.map +1 -0
  65. package/dist/types/skills.js +17 -0
  66. package/dist/utils/configResolver.d.ts +1 -1
  67. package/dist/utils/configResolver.d.ts.map +1 -1
  68. package/dist/utils/configResolver.js +1 -1
  69. package/dist/utils/configValidator.d.ts +1 -1
  70. package/dist/utils/configValidator.d.ts.map +1 -1
  71. package/dist/utils/configValidator.js +1 -1
  72. package/dist/utils/convertMessagesForAPI.d.ts +1 -1
  73. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  74. package/dist/utils/customCommands.d.ts +1 -1
  75. package/dist/utils/customCommands.d.ts.map +1 -1
  76. package/dist/{hooks/matcher.d.ts → utils/hookMatcher.d.ts} +1 -1
  77. package/dist/utils/hookMatcher.d.ts.map +1 -0
  78. package/dist/utils/markdownParser.d.ts +1 -1
  79. package/dist/utils/markdownParser.d.ts.map +1 -1
  80. package/dist/utils/mcpUtils.d.ts +1 -1
  81. package/dist/utils/mcpUtils.d.ts.map +1 -1
  82. package/dist/utils/messageOperations.d.ts +6 -1
  83. package/dist/utils/messageOperations.d.ts.map +1 -1
  84. package/dist/utils/messageOperations.js +16 -0
  85. package/dist/utils/skillParser.d.ts +1 -1
  86. package/dist/utils/skillParser.d.ts.map +1 -1
  87. package/package.json +1 -1
  88. package/src/agent.ts +49 -43
  89. package/src/index.ts +3 -4
  90. package/src/managers/aiManager.ts +240 -158
  91. package/src/managers/backgroundBashManager.ts +1 -1
  92. package/src/{hooks/manager.ts → managers/hookManager.ts} +159 -26
  93. package/src/managers/mcpManager.ts +1 -1
  94. package/src/managers/messageManager.ts +36 -6
  95. package/src/managers/skillManager.ts +1 -1
  96. package/src/managers/slashCommandManager.ts +5 -1
  97. package/src/managers/subagentManager.ts +46 -53
  98. package/src/managers/toolManager.ts +1 -1
  99. package/src/services/aiService.ts +9 -2
  100. package/src/services/hook.ts +360 -0
  101. package/src/services/session.ts +6 -7
  102. package/src/tools/taskTool.ts +13 -5
  103. package/src/types/commands.ts +26 -0
  104. package/src/types/config.ts +14 -0
  105. package/src/types/core.ts +49 -0
  106. package/src/{hooks/types.ts → types/hooks.ts} +1 -0
  107. package/src/types/index.ts +23 -0
  108. package/src/types/mcp.ts +31 -0
  109. package/src/types/messaging.ts +103 -0
  110. package/src/types/processes.ts +18 -0
  111. package/src/types/skills.ts +91 -0
  112. package/src/utils/configResolver.ts +1 -1
  113. package/src/utils/configValidator.ts +5 -1
  114. package/src/utils/convertMessagesForAPI.ts +1 -1
  115. package/src/utils/customCommands.ts +1 -1
  116. package/src/utils/markdownParser.ts +1 -1
  117. package/src/utils/mcpUtils.ts +1 -1
  118. package/src/utils/messageOperations.ts +20 -1
  119. package/src/utils/skillParser.ts +1 -1
  120. package/dist/hooks/executor.d.ts +0 -56
  121. package/dist/hooks/executor.d.ts.map +0 -1
  122. package/dist/hooks/executor.js +0 -312
  123. package/dist/hooks/index.d.ts +0 -17
  124. package/dist/hooks/index.d.ts.map +0 -1
  125. package/dist/hooks/index.js +0 -14
  126. package/dist/hooks/manager.d.ts.map +0 -1
  127. package/dist/hooks/matcher.d.ts.map +0 -1
  128. package/dist/hooks/settings.d.ts +0 -46
  129. package/dist/hooks/settings.d.ts.map +0 -1
  130. package/dist/hooks/settings.js +0 -100
  131. package/dist/hooks/types.d.ts.map +0 -1
  132. package/dist/types.d.ts +0 -288
  133. package/dist/types.d.ts.map +0 -1
  134. package/src/hooks/executor.ts +0 -440
  135. package/src/hooks/index.ts +0 -52
  136. package/src/hooks/settings.ts +0 -129
  137. /package/dist/{hooks/types.js → types/hooks.js} +0 -0
  138. /package/dist/{hooks/matcher.js → utils/hookMatcher.js} +0 -0
  139. /package/src/{types.ts → types/index.ts.backup} +0 -0
  140. /package/src/{hooks/matcher.ts → utils/hookMatcher.ts} +0 -0
@@ -17,11 +17,15 @@ import {
17
17
  HookConfigurationError,
18
18
  isValidHookEvent,
19
19
  isValidHookEventConfig,
20
- } from "./types.js";
21
- import { type IHookMatcher, HookMatcher } from "./matcher.js";
22
- import { type IHookExecutor, HookExecutor } from "./executor.js";
23
- import { loadMergedHooksConfig } from "./settings.js";
24
- import type { Logger } from "../types.js";
20
+ } from "../types/hooks.js";
21
+ import { type IHookMatcher, HookMatcher } from "../utils/hookMatcher.js";
22
+ import {
23
+ executeCommand,
24
+ isCommandSafe,
25
+ loadMergedHooksConfig,
26
+ } from "../services/hook.js";
27
+ import type { Logger } from "../types/index.js";
28
+ import type { MessageManager } from "./messageManager.js";
25
29
 
26
30
  export interface IHookManager {
27
31
  // Load configuration from settings
@@ -52,22 +56,16 @@ export interface IHookManager {
52
56
  export class HookManager implements IHookManager {
53
57
  private configuration: PartialHookConfiguration | undefined;
54
58
  private readonly matcher: IHookMatcher;
55
- private readonly executor: IHookExecutor;
56
59
  private readonly logger?: Logger;
57
60
  private readonly workdir: string;
58
61
 
59
62
  constructor(
60
63
  workdir: string,
61
64
  matcher: IHookMatcher = new HookMatcher(),
62
- executor?: IHookExecutor,
63
65
  logger?: Logger,
64
66
  ) {
65
67
  this.workdir = workdir;
66
68
  this.matcher = matcher;
67
- // Create executor with logger if provided, or use passed executor, or create default
68
- this.executor = logger
69
- ? new HookExecutor(logger)
70
- : executor || new HookExecutor();
71
69
  this.logger = logger;
72
70
  }
73
71
 
@@ -112,19 +110,21 @@ export class HookManager implements IHookManager {
112
110
  this.logger?.debug(`[HookManager] Loading configuration...`);
113
111
  const mergedConfig = loadMergedHooksConfig(this.workdir);
114
112
  this.logger?.debug(`[HookManager] Merged config result:`, mergedConfig);
115
- this.configuration = mergedConfig;
116
-
117
- // Validate the loaded configuration
118
- const validation = this.validatePartialConfiguration(mergedConfig);
119
- if (!validation.valid) {
120
- throw new HookConfigurationError(
121
- "filesystem settings",
122
- validation.errors,
123
- );
113
+ this.configuration = mergedConfig || undefined;
114
+
115
+ // Validate the loaded configuration if it exists
116
+ if (mergedConfig) {
117
+ const validation = this.validatePartialConfiguration(mergedConfig);
118
+ if (!validation.valid) {
119
+ throw new HookConfigurationError(
120
+ "filesystem settings",
121
+ validation.errors,
122
+ );
123
+ }
124
124
  }
125
125
 
126
126
  this.logger?.debug(
127
- `[HookManager] Configuration loaded successfully with ${Object.keys(mergedConfig).length} event types`,
127
+ `[HookManager] Configuration loaded successfully with ${Object.keys(mergedConfig || {}).length} event types`,
128
128
  );
129
129
  } catch (error) {
130
130
  // If loading fails, start with undefined configuration (no hooks)
@@ -219,10 +219,7 @@ export class HookManager implements IHookManager {
219
219
  `[HookManager] Executing command ${commandIndex + 1}/${config.hooks.length} in configuration ${configIndex + 1}`,
220
220
  );
221
221
 
222
- const result = await this.executor.executeCommand(
223
- hookCommand.command,
224
- context,
225
- );
222
+ const result = await executeCommand(hookCommand.command, context);
226
223
  results.push(result);
227
224
 
228
225
  // Report individual command result
@@ -268,6 +265,142 @@ export class HookManager implements IHookManager {
268
265
  return results;
269
266
  }
270
267
 
268
+ /**
269
+ * Process hook execution results and determine appropriate actions
270
+ * based on exit codes and hook event type
271
+ */
272
+ processHookResults(
273
+ event: HookEvent,
274
+ results: HookExecutionResult[],
275
+ messageManager?: MessageManager,
276
+ toolId?: string,
277
+ originalToolResult?: string,
278
+ ): {
279
+ shouldBlock: boolean;
280
+ errorMessage?: string;
281
+ } {
282
+ if (!messageManager || results.length === 0) {
283
+ return { shouldBlock: false };
284
+ }
285
+
286
+ // First pass: Check for any blocking errors (exit code 2)
287
+ // Blocking errors take precedence and stop all processing
288
+ for (const result of results) {
289
+ if (result.exitCode === 2) {
290
+ // Handle blocking error immediately and return
291
+ return this.handleBlockingError(
292
+ event,
293
+ result,
294
+ messageManager,
295
+ toolId,
296
+ originalToolResult,
297
+ );
298
+ }
299
+ }
300
+
301
+ // Second pass: Process all non-blocking results
302
+ for (const result of results) {
303
+ if (result.exitCode === undefined) {
304
+ continue; // Skip results without exit codes
305
+ }
306
+
307
+ // Handle exit code interpretation
308
+ if (result.exitCode === 0) {
309
+ // Success case - handle stdout based on hook type
310
+ this.handleHookSuccess(event, result, messageManager);
311
+ } else {
312
+ // Non-blocking error case (any exit code except 0 and 2)
313
+ this.handleNonBlockingError(result, messageManager);
314
+ }
315
+ }
316
+
317
+ return { shouldBlock: false };
318
+ }
319
+
320
+ /**
321
+ * Handle successful hook execution (exit code 0)
322
+ */
323
+ private handleHookSuccess(
324
+ event: HookEvent,
325
+ result: HookExecutionResult,
326
+ messageManager: MessageManager,
327
+ ): void {
328
+ if (event === "UserPromptSubmit" && result.stdout?.trim()) {
329
+ // Inject stdout as user message context for UserPromptSubmit
330
+ messageManager.addUserMessage(result.stdout.trim());
331
+ }
332
+ // For other hook types (PreToolUse, PostToolUse, Stop), ignore stdout
333
+ }
334
+
335
+ /**
336
+ * Handle blocking error (exit code 2) - behavior varies by hook type
337
+ */
338
+ private handleBlockingError(
339
+ event: HookEvent,
340
+ result: HookExecutionResult,
341
+ messageManager: MessageManager,
342
+ toolId?: string,
343
+ originalToolResult?: string,
344
+ ): {
345
+ shouldBlock: boolean;
346
+ errorMessage?: string;
347
+ } {
348
+ const errorMessage = result.stderr?.trim() || "Hook execution failed";
349
+
350
+ switch (event) {
351
+ case "UserPromptSubmit":
352
+ // Block prompt processing, show error to user, erase prompt
353
+ messageManager.addErrorBlock(errorMessage);
354
+ messageManager.removeLastUserMessage();
355
+ return {
356
+ shouldBlock: true,
357
+ errorMessage,
358
+ };
359
+
360
+ case "PreToolUse":
361
+ // Block tool execution and show error to Wave Agent via tool block
362
+ if (toolId) {
363
+ messageManager.updateToolBlock({
364
+ toolId,
365
+ result: errorMessage,
366
+ success: false,
367
+ error: "Hook blocked tool execution",
368
+ });
369
+ }
370
+ return { shouldBlock: true };
371
+
372
+ case "PostToolUse":
373
+ // Show error to Wave Agent via tool block, execution continues
374
+ if (toolId && originalToolResult !== undefined) {
375
+ messageManager.updateToolBlock({
376
+ toolId,
377
+ result: `${originalToolResult}\n\nHook feedback: ${errorMessage}`,
378
+ success: false,
379
+ });
380
+ }
381
+ return { shouldBlock: false };
382
+
383
+ case "Stop":
384
+ // Show error to Wave Agent via user message and block stopping to continue conversation
385
+ messageManager.addUserMessage(errorMessage);
386
+ return { shouldBlock: true, errorMessage };
387
+
388
+ default:
389
+ return { shouldBlock: false };
390
+ }
391
+ }
392
+
393
+ /**
394
+ * Handle non-blocking error (other exit codes)
395
+ */
396
+ private handleNonBlockingError(
397
+ result: HookExecutionResult,
398
+ messageManager: MessageManager,
399
+ ): void {
400
+ const errorMessage = result.stderr?.trim() || "Hook execution failed";
401
+ messageManager.addErrorBlock(errorMessage);
402
+ }
403
+
271
404
  /**
272
405
  * Check if hooks are configured for an event/tool combination
273
406
  */
@@ -556,7 +689,7 @@ export class HookManager implements IHookManager {
556
689
 
557
690
  // Validate commands
558
691
  config.hooks.forEach((hookCommand, cmdIndex) => {
559
- if (!this.executor.isCommandSafe(hookCommand.command)) {
692
+ if (!isCommandSafe(hookCommand.command)) {
560
693
  errors.push(
561
694
  `${prefix}.hooks[${cmdIndex}]: Command may be unsafe: ${hookCommand.command}`,
562
695
  );
@@ -11,7 +11,7 @@ import type {
11
11
  McpConfig,
12
12
  McpTool,
13
13
  McpServerStatus,
14
- } from "../types.js";
14
+ } from "../types/index.js";
15
15
 
16
16
  interface McpConnection {
17
17
  client: Client;
@@ -12,11 +12,12 @@ import {
12
12
  completeCommandInMessage,
13
13
  addSubagentBlockToMessage,
14
14
  updateSubagentBlockInMessage,
15
+ removeLastUserMessage,
15
16
  type AddSubagentBlockParams,
16
17
  type UpdateSubagentBlockParams,
17
18
  type AgentToolBlockUpdateParams,
18
19
  } from "../utils/messageOperations.js";
19
- import type { Logger, Message, Usage } from "../types.js";
20
+ import type { Logger, Message, Usage } from "../types/index.js";
20
21
  import {
21
22
  cleanupExpiredSessions,
22
23
  getLatestSession,
@@ -64,8 +65,19 @@ export interface MessageManagerCallbacks {
64
65
  onUpdateCommandOutputMessage?: (command: string, output: string) => void;
65
66
  onCompleteCommandMessage?: (command: string, exitCode: number) => void;
66
67
  // Subagent callbacks
67
- onSubAgentBlockAdded?: (subagentId: string) => void;
68
- onSubAgentBlockUpdated?: (subagentId: string, messages: Message[]) => void;
68
+ onSubAgentBlockAdded?: (
69
+ subagentId: string,
70
+ parameters: {
71
+ description: string;
72
+ prompt: string;
73
+ subagent_type: string;
74
+ },
75
+ ) => void;
76
+ onSubAgentBlockUpdated?: (
77
+ subagentId: string,
78
+ messages: Message[],
79
+ status: "active" | "completed" | "error" | "aborted",
80
+ ) => void;
69
81
  }
70
82
 
71
83
  export interface MessageManagerOptions {
@@ -168,7 +180,7 @@ export class MessageManager {
168
180
  try {
169
181
  await cleanupExpiredSessions(this.workdir, this.sessionDir);
170
182
  } catch (error) {
171
- console.warn("Failed to cleanup expired sessions:", error);
183
+ this.logger?.warn("Failed to cleanup expired sessions:", error);
172
184
  }
173
185
 
174
186
  if (!restoreSessionId && !continueLastSession) {
@@ -448,6 +460,11 @@ export class MessageManager {
448
460
  subagentName: string,
449
461
  status: "active" | "completed" | "error" = "active",
450
462
  subagentMessages: Message[] = [],
463
+ parameters: {
464
+ description: string;
465
+ prompt: string;
466
+ subagent_type: string;
467
+ },
451
468
  ): void {
452
469
  const params: AddSubagentBlockParams = {
453
470
  messages: this.messages,
@@ -458,7 +475,7 @@ export class MessageManager {
458
475
  };
459
476
  const updatedMessages = addSubagentBlockToMessage(params);
460
477
  this.setMessages(updatedMessages);
461
- this.callbacks.onSubAgentBlockAdded?.(params.subagentId);
478
+ this.callbacks.onSubAgentBlockAdded?.(params.subagentId, parameters);
462
479
  }
463
480
 
464
481
  public updateSubagentBlock(
@@ -480,7 +497,11 @@ export class MessageManager {
480
497
  status: updates.status || "active",
481
498
  subagentMessages: updates.messages || [],
482
499
  };
483
- this.callbacks.onSubAgentBlockUpdated?.(params.subagentId, params.messages);
500
+ this.callbacks.onSubAgentBlockUpdated?.(
501
+ params.subagentId,
502
+ params.messages,
503
+ params.status,
504
+ );
484
505
  }
485
506
 
486
507
  /**
@@ -495,4 +516,13 @@ export class MessageManager {
495
516
  }
496
517
  this.callbacks.onUsagesChange?.(usages);
497
518
  }
519
+
520
+ /**
521
+ * Remove the last user message from the conversation
522
+ * Used for hook error handling when the user prompt needs to be erased
523
+ */
524
+ public removeLastUserMessage(): void {
525
+ const newMessages = removeLastUserMessage(this.messages);
526
+ this.setMessages(newMessages);
527
+ }
498
528
  }
@@ -10,7 +10,7 @@ import type {
10
10
  SkillToolArgs,
11
11
  SkillInvocationContext,
12
12
  Logger,
13
- } from "../types.js";
13
+ } from "../types/index.js";
14
14
  import { parseSkillFile, formatSkillError } from "../utils/skillParser.js";
15
15
 
16
16
  /**
@@ -1,6 +1,10 @@
1
1
  import type { MessageManager } from "./messageManager.js";
2
2
  import type { AIManager } from "./aiManager.js";
3
- import type { SlashCommand, CustomSlashCommand, Logger } from "../types.js";
3
+ import type {
4
+ SlashCommand,
5
+ CustomSlashCommand,
6
+ Logger,
7
+ } from "../types/index.js";
4
8
  import { loadCustomSlashCommands } from "../utils/customCommands.js";
5
9
 
6
10
  import {
@@ -6,7 +6,7 @@ import type {
6
6
  GatewayConfig,
7
7
  ModelConfig,
8
8
  Usage,
9
- } from "../types.js";
9
+ } from "../types/index.js";
10
10
  import { AIManager } from "./aiManager.js";
11
11
  import {
12
12
  MessageManager,
@@ -21,7 +21,6 @@ export interface SubagentInstance {
21
21
  messageManager: MessageManager;
22
22
  toolManager: ToolManager;
23
23
  status: "initializing" | "active" | "completed" | "error" | "aborted";
24
- taskDescription: string;
25
24
  messages: Message[];
26
25
  }
27
26
 
@@ -107,7 +106,11 @@ export class SubagentManager {
107
106
  */
108
107
  async createInstance(
109
108
  configuration: SubagentConfiguration,
110
- taskDescription: string,
109
+ parameters: {
110
+ description: string;
111
+ prompt: string;
112
+ subagent_type: string;
113
+ },
111
114
  ): Promise<SubagentInstance> {
112
115
  if (
113
116
  !this.parentToolManager ||
@@ -177,7 +180,6 @@ export class SubagentManager {
177
180
  messageManager,
178
181
  toolManager,
179
182
  status: "initializing",
180
- taskDescription,
181
183
  messages: [],
182
184
  };
183
185
 
@@ -189,6 +191,7 @@ export class SubagentManager {
189
191
  configuration.name,
190
192
  "active",
191
193
  [],
194
+ parameters,
192
195
  );
193
196
 
194
197
  return instance;
@@ -203,14 +206,31 @@ export class SubagentManager {
203
206
  async executeTask(
204
207
  instance: SubagentInstance,
205
208
  prompt: string,
209
+ abortSignal?: AbortSignal,
206
210
  ): Promise<string> {
207
211
  try {
212
+ // Check if already aborted before starting
213
+ if (abortSignal?.aborted) {
214
+ throw new Error("Task was aborted before execution started");
215
+ }
216
+
208
217
  // Set status to active and update parent
209
218
  this.updateInstanceStatus(instance.subagentId, "active");
210
219
  this.parentMessageManager.updateSubagentBlock(instance.subagentId, {
211
220
  status: "active",
212
221
  });
213
222
 
223
+ // Set up abort handler
224
+ if (abortSignal) {
225
+ abortSignal.addEventListener("abort", () => {
226
+ this.updateInstanceStatus(instance.subagentId, "aborted");
227
+ this.parentMessageManager.updateSubagentBlock(instance.subagentId, {
228
+ status: "aborted",
229
+ messages: instance.messages,
230
+ });
231
+ });
232
+ }
233
+
214
234
  // Add the user's prompt as a message
215
235
  instance.messageManager.addUserMessage(prompt);
216
236
 
@@ -227,7 +247,9 @@ export class SubagentManager {
227
247
  }
228
248
 
229
249
  // Execute the AI request with tool restrictions
230
- await instance.aiManager.sendAIMessage({
250
+ // The AIManager will handle abort signals through its own abort controllers
251
+ // We need to abort the AI execution if the external abort signal is triggered
252
+ const executeAI = instance.aiManager.sendAIMessage({
231
253
  allowedTools,
232
254
  model:
233
255
  instance.configuration.model !== "inherit"
@@ -235,6 +257,25 @@ export class SubagentManager {
235
257
  : undefined,
236
258
  });
237
259
 
260
+ // If we have an abort signal, race against it
261
+ if (abortSignal) {
262
+ await Promise.race([
263
+ executeAI,
264
+ new Promise<never>((_, reject) => {
265
+ if (abortSignal.aborted) {
266
+ reject(new Error("Task was aborted"));
267
+ }
268
+ abortSignal.addEventListener("abort", () => {
269
+ // Abort the AI execution
270
+ instance.aiManager.abortAIMessage();
271
+ reject(new Error("Task was aborted"));
272
+ });
273
+ }),
274
+ ]);
275
+ } else {
276
+ await executeAI;
277
+ }
278
+
238
279
  // Get the latest messages to extract the response
239
280
  const messages = instance.messageManager.getMessages();
240
281
  const lastAssistantMessage = messages
@@ -298,52 +339,6 @@ export class SubagentManager {
298
339
  }
299
340
  }
300
341
 
301
- /**
302
- * Abort a running subagent instance
303
- */
304
- abortInstance(subagentId: string): boolean {
305
- const instance = this.instances.get(subagentId);
306
- if (!instance) {
307
- return false;
308
- }
309
-
310
- // Only abort active or initializing instances
311
- if (instance.status !== "active" && instance.status !== "initializing") {
312
- return false;
313
- }
314
-
315
- try {
316
- // Abort the AI manager operations
317
- instance.aiManager.abortAIMessage();
318
-
319
- // Update status
320
- this.updateInstanceStatus(subagentId, "aborted");
321
- this.parentMessageManager.updateSubagentBlock(subagentId, {
322
- status: "aborted",
323
- messages: instance.messages,
324
- });
325
-
326
- this.logger?.debug(`Aborted subagent instance: ${subagentId}`);
327
- return true;
328
- } catch (error) {
329
- this.logger?.error(
330
- `Failed to abort subagent instance ${subagentId}:`,
331
- error,
332
- );
333
- return false;
334
- }
335
- }
336
-
337
- /**
338
- * Abort all active subagent instances
339
- */
340
- abortAllInstances(): void {
341
- const activeInstances = this.getActiveInstances();
342
- for (const instance of activeInstances) {
343
- this.abortInstance(instance.subagentId);
344
- }
345
- }
346
-
347
342
  /**
348
343
  * Clean up completed, errored, or aborted instances
349
344
  */
@@ -373,8 +368,6 @@ export class SubagentManager {
373
368
  * Clean up all instances (for session end)
374
369
  */
375
370
  cleanup(): void {
376
- // Abort all active instances before cleanup
377
- this.abortAllInstances();
378
371
  this.instances.clear();
379
372
  }
380
373
  }
@@ -14,7 +14,7 @@ import { createTaskTool } from "../tools/taskTool.js";
14
14
  import { createSkillTool } from "../tools/skillTool.js";
15
15
  import { McpManager } from "./mcpManager.js";
16
16
  import { ChatCompletionFunctionTool } from "openai/resources.js";
17
- import type { Logger } from "../types.js";
17
+ import type { Logger } from "../types/index.js";
18
18
  import type { SubagentManager } from "./subagentManager.js";
19
19
  import type { SkillManager } from "./skillManager.js";
20
20
 
@@ -5,7 +5,7 @@ import {
5
5
  ChatCompletionMessageParam,
6
6
  ChatCompletionFunctionTool,
7
7
  } from "openai/resources.js";
8
- import type { GatewayConfig, ModelConfig } from "../types.js";
8
+ import type { GatewayConfig, ModelConfig } from "../types/index.js";
9
9
  import * as os from "os";
10
10
  import * as fs from "fs";
11
11
  import * as path from "path";
@@ -116,7 +116,14 @@ export async function callAgent(
116
116
  // Build system prompt content
117
117
  let systemContent =
118
118
  systemPrompt ||
119
- `You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.`;
119
+ `You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
120
+
121
+ # Tool usage policy
122
+ - When doing file search, prefer to use the Task tool in order to reduce context usage.
123
+ - You should proactively use the Task tool with specialized agents when the task at hand matches the agent's description.
124
+ - You have the capability to call multiple tools in a single response. When multiple independent pieces of information are requested, batch your tool calls together for optimal performance. When making multiple bash tool calls, you MUST send a single message with multiple tools calls to run the calls in parallel. For example, if you need to run "git status" and "git diff", send a single message with two tool calls to run the calls in parallel.
125
+
126
+ `;
120
127
 
121
128
  // Always add environment information
122
129
  systemContent += `