wave-agent-sdk 1.0.4 → 1.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 (194) hide show
  1. package/dist/agent.d.ts +23 -1
  2. package/dist/agent.js +34 -4
  3. package/dist/managers/aiManager.d.ts +36 -0
  4. package/dist/managers/aiManager.js +240 -8
  5. package/dist/managers/messageManager.d.ts +1 -3
  6. package/dist/managers/messageManager.js +19 -9
  7. package/dist/managers/slashCommandManager.js +28 -0
  8. package/dist/managers/subagentManager.d.ts +0 -2
  9. package/dist/services/session.js +2 -9
  10. package/dist/telemetry/instrumentation.d.ts +1 -1
  11. package/dist/telemetry/instrumentation.js +57 -28
  12. package/dist/tools/bashTool.js +16 -6
  13. package/dist/tools/grepTool.js +11 -3
  14. package/dist/types/index.d.ts +3 -0
  15. package/dist/types/messaging.d.ts +2 -0
  16. package/dist/utils/containerSetup.js +0 -3
  17. package/dist/utils/messageOperations.d.ts +10 -1
  18. package/dist/utils/messageOperations.js +19 -1
  19. package/dist/utils/notificationXml.js +5 -0
  20. package/dist/utils/ripgrep.d.ts +4 -4
  21. package/dist/utils/ripgrep.js +4 -27
  22. package/package.json +20 -8
  23. package/bin/rg +0 -79
  24. package/dist/managers/forkedAgentManager.d.ts +0 -50
  25. package/dist/managers/forkedAgentManager.js +0 -130
  26. package/scripts/install_ripgrep.js +0 -111
  27. package/scripts/postinstall.js +0 -38
  28. package/src/agent.ts +0 -1271
  29. package/src/constants/subagents.ts +0 -4
  30. package/src/constants/toolLimits.ts +0 -15
  31. package/src/constants/tools.ts +0 -24
  32. package/src/core/plugin.ts +0 -237
  33. package/src/core/session.ts +0 -9
  34. package/src/index.ts +0 -38
  35. package/src/managers/MemoryRuleManager.ts +0 -198
  36. package/src/managers/aiManager.ts +0 -2429
  37. package/src/managers/backgroundTaskManager.ts +0 -480
  38. package/src/managers/bangManager.ts +0 -119
  39. package/src/managers/cronManager.ts +0 -401
  40. package/src/managers/foregroundTaskManager.ts +0 -30
  41. package/src/managers/forkedAgentManager.ts +0 -216
  42. package/src/managers/hookManager.ts +0 -1089
  43. package/src/managers/liveConfigManager.ts +0 -445
  44. package/src/managers/lspManager.ts +0 -470
  45. package/src/managers/mcpManager.ts +0 -1010
  46. package/src/managers/messageManager.ts +0 -1180
  47. package/src/managers/messageQueue.ts +0 -157
  48. package/src/managers/permissionManager.ts +0 -1140
  49. package/src/managers/planManager.ts +0 -112
  50. package/src/managers/pluginManager.ts +0 -330
  51. package/src/managers/pluginScopeManager.ts +0 -124
  52. package/src/managers/reversionManager.ts +0 -178
  53. package/src/managers/skillManager.ts +0 -578
  54. package/src/managers/slashCommandManager.ts +0 -564
  55. package/src/managers/subagentManager.ts +0 -915
  56. package/src/managers/toolManager.ts +0 -489
  57. package/src/managers/workflowManager.ts +0 -493
  58. package/src/prompts/autoMemory.ts +0 -33
  59. package/src/prompts/autoMemoryExtraction.ts +0 -146
  60. package/src/prompts/index.ts +0 -506
  61. package/src/prompts/planModeReminders.ts +0 -126
  62. package/src/services/GitService.ts +0 -131
  63. package/src/services/MarketplaceService.ts +0 -1052
  64. package/src/services/MemoryRuleService.ts +0 -71
  65. package/src/services/aiService.ts +0 -919
  66. package/src/services/authService.ts +0 -565
  67. package/src/services/autoMemoryService.ts +0 -280
  68. package/src/services/configurationService.ts +0 -1520
  69. package/src/services/fileWatcher.ts +0 -319
  70. package/src/services/hook.ts +0 -348
  71. package/src/services/initializationService.ts +0 -357
  72. package/src/services/interactionService.ts +0 -229
  73. package/src/services/jsonlHandler.ts +0 -295
  74. package/src/services/memory.ts +0 -234
  75. package/src/services/pluginLoader.ts +0 -252
  76. package/src/services/remoteSettingsService.ts +0 -366
  77. package/src/services/reversionService.ts +0 -122
  78. package/src/services/session.ts +0 -922
  79. package/src/services/taskManager.ts +0 -321
  80. package/src/telemetry/events.ts +0 -65
  81. package/src/telemetry/instrumentation.ts +0 -499
  82. package/src/telemetry/sessionTracing.ts +0 -348
  83. package/src/tools/agentTool.ts +0 -322
  84. package/src/tools/askUserQuestion.ts +0 -160
  85. package/src/tools/bashTool.ts +0 -621
  86. package/src/tools/buildTool.ts +0 -61
  87. package/src/tools/cronCreateTool.ts +0 -161
  88. package/src/tools/cronDeleteTool.ts +0 -51
  89. package/src/tools/cronListTool.ts +0 -47
  90. package/src/tools/editTool.ts +0 -313
  91. package/src/tools/enterPlanMode.ts +0 -124
  92. package/src/tools/enterWorktreeTool.ts +0 -190
  93. package/src/tools/exitPlanMode.ts +0 -139
  94. package/src/tools/exitWorktreeTool.ts +0 -236
  95. package/src/tools/globTool.ts +0 -167
  96. package/src/tools/grepTool.ts +0 -399
  97. package/src/tools/lspTool.ts +0 -883
  98. package/src/tools/readTool.ts +0 -426
  99. package/src/tools/skillTool.ts +0 -248
  100. package/src/tools/taskManagementTools.ts +0 -574
  101. package/src/tools/taskStopTool.ts +0 -78
  102. package/src/tools/types.ts +0 -137
  103. package/src/tools/webFetchTool.ts +0 -373
  104. package/src/tools/workflowTool.ts +0 -205
  105. package/src/tools/writeTool.ts +0 -267
  106. package/src/types/agent.ts +0 -128
  107. package/src/types/auth.ts +0 -19
  108. package/src/types/commands.ts +0 -30
  109. package/src/types/config.ts +0 -43
  110. package/src/types/configuration.ts +0 -168
  111. package/src/types/core.ts +0 -105
  112. package/src/types/cron.ts +0 -13
  113. package/src/types/environment.ts +0 -106
  114. package/src/types/fileSearch.ts +0 -4
  115. package/src/types/history.ts +0 -7
  116. package/src/types/hooks.ts +0 -256
  117. package/src/types/index.ts +0 -41
  118. package/src/types/lsp.ts +0 -98
  119. package/src/types/marketplace.ts +0 -71
  120. package/src/types/mcp.ts +0 -43
  121. package/src/types/memoryRule.ts +0 -31
  122. package/src/types/messaging.ts +0 -114
  123. package/src/types/permissions.ts +0 -80
  124. package/src/types/plugins.ts +0 -39
  125. package/src/types/processes.ts +0 -99
  126. package/src/types/reversion.ts +0 -29
  127. package/src/types/session.ts +0 -12
  128. package/src/types/skills.ts +0 -108
  129. package/src/types/tasks.ts +0 -13
  130. package/src/types/telemetry.ts +0 -98
  131. package/src/types/tools.ts +0 -44
  132. package/src/types/workflow.ts +0 -6
  133. package/src/utils/abortUtils.ts +0 -118
  134. package/src/utils/atomicWrite.ts +0 -61
  135. package/src/utils/bashParser.ts +0 -899
  136. package/src/utils/cacheControlUtils.ts +0 -426
  137. package/src/utils/commandArgumentParser.ts +0 -161
  138. package/src/utils/commandPathResolver.ts +0 -88
  139. package/src/utils/configPaths.ts +0 -220
  140. package/src/utils/configValidator.ts +0 -100
  141. package/src/utils/constants.ts +0 -39
  142. package/src/utils/container.ts +0 -92
  143. package/src/utils/containerSetup.ts +0 -371
  144. package/src/utils/convertMessagesForAPI.ts +0 -326
  145. package/src/utils/cronTasks.ts +0 -128
  146. package/src/utils/cronTasksLock.ts +0 -182
  147. package/src/utils/cronToHuman.ts +0 -99
  148. package/src/utils/customCommands.ts +0 -95
  149. package/src/utils/editUtils.ts +0 -20
  150. package/src/utils/fileFormat.ts +0 -40
  151. package/src/utils/fileSearch.ts +0 -151
  152. package/src/utils/fileUtils.ts +0 -223
  153. package/src/utils/gitUtils.ts +0 -315
  154. package/src/utils/globalLogger.ts +0 -128
  155. package/src/utils/groupMessagesByApiRound.ts +0 -121
  156. package/src/utils/hookMatcher.ts +0 -176
  157. package/src/utils/markdownParser.ts +0 -317
  158. package/src/utils/mcpUtils.ts +0 -138
  159. package/src/utils/messageOperations.ts +0 -641
  160. package/src/utils/modelCapabilities.ts +0 -30
  161. package/src/utils/nameGenerator.ts +0 -99
  162. package/src/utils/notificationXml.ts +0 -57
  163. package/src/utils/openaiClient.ts +0 -251
  164. package/src/utils/parseCronExpression.ts +0 -78
  165. package/src/utils/path.ts +0 -72
  166. package/src/utils/pathEncoder.ts +0 -400
  167. package/src/utils/pathSafety.ts +0 -40
  168. package/src/utils/promptHistory.ts +0 -168
  169. package/src/utils/ripgrep.ts +0 -35
  170. package/src/utils/shellResolver.ts +0 -190
  171. package/src/utils/skillParser.ts +0 -246
  172. package/src/utils/stringUtils.ts +0 -156
  173. package/src/utils/subagentParser.ts +0 -279
  174. package/src/utils/taskReminder.ts +0 -96
  175. package/src/utils/tokenCalculation.ts +0 -43
  176. package/src/utils/tokenEstimate.ts +0 -34
  177. package/src/utils/toolResultStorage.ts +0 -117
  178. package/src/utils/worktreeSession.ts +0 -26
  179. package/src/utils/worktreeUtils.ts +0 -786
  180. package/src/workflow/budgetTracker.ts +0 -34
  181. package/src/workflow/concurrencyLimiter.ts +0 -47
  182. package/src/workflow/journal.ts +0 -95
  183. package/src/workflow/progressReporter.ts +0 -141
  184. package/src/workflow/runState.ts +0 -65
  185. package/src/workflow/scriptRuntime.ts +0 -274
  186. package/src/workflow/structuredOutput.ts +0 -123
  187. package/src/workflow/types.ts +0 -101
  188. package/src/workflow/workflowApis.ts +0 -410
  189. package/vendor/ripgrep/linux-aarch64/rg +0 -0
  190. package/vendor/ripgrep/linux-x86_64/rg +0 -0
  191. package/vendor/ripgrep/macos-aarch64/rg +0 -0
  192. package/vendor/ripgrep/macos-x86_64/rg +0 -0
  193. package/vendor/ripgrep/windows-aarch64/rg.exe +0 -0
  194. package/vendor/ripgrep/windows-x86_64/rg.exe +0 -0
@@ -1,915 +0,0 @@
1
- import { randomUUID } from "crypto";
2
- import * as os from "os";
3
- import * as fs from "fs";
4
- import * as path from "path";
5
- import type { SubagentConfiguration } from "../utils/subagentParser.js";
6
- import type { Message, Usage } from "../types/index.js";
7
- import { AIManager } from "./aiManager.js";
8
- import { MessageManager } from "./messageManager.js";
9
- import { ToolManager } from "./toolManager.js";
10
- import {
11
- AGENT_TOOL_NAME,
12
- TASK_CREATE_TOOL_NAME,
13
- TASK_GET_TOOL_NAME,
14
- TASK_LIST_TOOL_NAME,
15
- TASK_UPDATE_TOOL_NAME,
16
- } from "../constants/tools.js";
17
- import {
18
- addConsolidatedAbortListener,
19
- createAbortPromise,
20
- } from "../utils/abortUtils.js";
21
- import { BackgroundTaskManager } from "./backgroundTaskManager.js";
22
- import { MessageQueue } from "./messageQueue.js";
23
- import { logger } from "../utils/globalLogger.js";
24
- import {
25
- UserMessageParams,
26
- type ToolBlockUpdateCallbackParams,
27
- } from "../utils/messageOperations.js";
28
-
29
- import { Container } from "../utils/container.js";
30
- import type { PermissionManager } from "./permissionManager.js";
31
- import type { PermissionMode } from "../types/permissions.js";
32
- import { ConfigurationService } from "../services/configurationService.js";
33
-
34
- export interface SubagentManagerCallbacks {
35
- // Granular subagent message callbacks (009-subagent)
36
- /** Triggered when subagent adds user message */
37
- onSubagentUserMessageAdded?: (
38
- subagentId: string,
39
- params: UserMessageParams,
40
- ) => void;
41
- /** Triggered when subagent creates assistant message */
42
- onSubagentAssistantMessageAdded?: (
43
- subagentId: string,
44
- messageId: string,
45
- ) => void;
46
- /** Triggered during subagent content streaming updates */
47
- onSubagentAssistantContentUpdated?: (params: {
48
- subagentId: string;
49
- messageId: string;
50
- chunk: string;
51
- accumulated: string;
52
- stage: "streaming" | "end";
53
- }) => void;
54
- /** Triggered during subagent reasoning streaming updates */
55
- onSubagentAssistantReasoningUpdated?: (params: {
56
- subagentId: string;
57
- messageId: string;
58
- chunk: string;
59
- accumulated: string;
60
- stage: "streaming" | "end";
61
- }) => void;
62
- /** Triggered when subagent tool block is updated */
63
- onSubagentToolBlockUpdated?: (
64
- subagentId: string,
65
- params: ToolBlockUpdateCallbackParams,
66
- ) => void;
67
- /** Triggered when subagent messages change */
68
- onSubagentMessagesChange?: (subagentId: string, messages: Message[]) => void;
69
- /** Triggered when subagent latest total tokens change */
70
- onSubagentLatestTotalTokensChange?: (
71
- subagentId: string,
72
- tokens: number,
73
- ) => void;
74
- }
75
-
76
- export interface SubagentInstance {
77
- subagentId: string;
78
- configuration: SubagentConfiguration;
79
- aiManager: AIManager;
80
- messageManager: MessageManager;
81
- toolManager: ToolManager;
82
- permissionManager: PermissionManager;
83
- backgroundTaskManager: BackgroundTaskManager;
84
- status: "initializing" | "active" | "completed" | "error" | "aborted";
85
- messages: Message[];
86
- usedTools: {
87
- name: string;
88
- parameters: string;
89
- compactParams?: string;
90
- stage?: string;
91
- }[]; // Track tools with display info
92
- subagentType: string; // Store the subagent type for hook context
93
- description: string; // Store the AI-generated description
94
- allowedTools?: string[]; // Optional permission rules (e.g. git:*)
95
- backgroundTaskId?: string; // ID of the background task if transitioned
96
- onUpdate?: () => void; // Optional callback for real-time updates
97
- model?: string; // Optional model override
98
- logStream?: fs.WriteStream; // Optional log stream for background tasks
99
- }
100
-
101
- export interface SubagentManagerOptions {
102
- workdir: string;
103
- callbacks?: SubagentManagerCallbacks; // Use SubagentManagerCallbacks instead of parentCallbacks
104
- onUsageAdded?: (usage: Usage) => void;
105
- stream: boolean;
106
- }
107
-
108
- export class SubagentManager {
109
- private instances = new Map<string, SubagentInstance>();
110
- private cachedConfigurations: SubagentConfiguration[] | null = null;
111
-
112
- private workdir: string;
113
- private callbacks?: SubagentManagerCallbacks; // Use SubagentManagerCallbacks instead of parentCallbacks
114
- private onUsageAdded?: (usage: Usage) => void;
115
- private container: Container;
116
- private stream: boolean;
117
-
118
- constructor(container: Container, options: SubagentManagerOptions) {
119
- this.container = container;
120
- this.workdir = options.workdir;
121
- this.callbacks = options.callbacks; // Store SubagentManagerCallbacks
122
- this.onUsageAdded = options.onUsageAdded;
123
- this.stream = options.stream;
124
- }
125
-
126
- private get configurationService(): ConfigurationService {
127
- return this.container.get<ConfigurationService>("ConfigurationService")!;
128
- }
129
-
130
- /**
131
- * Initialize the SubagentManager by loading and caching configurations
132
- */
133
- async initialize(): Promise<void> {
134
- await this.loadConfigurations();
135
-
136
- // Hook into parent PermissionManager's update methods to propagate rules to subagents
137
- const parentPm = this.container.get<PermissionManager>("PermissionManager");
138
- if (
139
- parentPm &&
140
- typeof parentPm.updateAllowedRules === "function" &&
141
- typeof parentPm.updateDeniedRules === "function" &&
142
- typeof parentPm.updateAdditionalDirectories === "function"
143
- ) {
144
- const origUpdateAllowed = parentPm.updateAllowedRules.bind(parentPm);
145
- const origUpdateDenied = parentPm.updateDeniedRules.bind(parentPm);
146
- const origUpdateDirs =
147
- parentPm.updateAdditionalDirectories.bind(parentPm);
148
-
149
- parentPm.updateAllowedRules = (rules: string[]) => {
150
- origUpdateAllowed(rules);
151
- this.syncPermissionRulesToSubagents();
152
- };
153
- parentPm.updateDeniedRules = (rules: string[]) => {
154
- origUpdateDenied(rules);
155
- this.syncPermissionRulesToSubagents();
156
- };
157
- parentPm.updateAdditionalDirectories = (directories: string[]) => {
158
- origUpdateDirs(directories);
159
- this.syncPermissionRulesToSubagents();
160
- };
161
- }
162
- }
163
-
164
- /**
165
- * Sync parent permission rules to all running subagents
166
- */
167
- private syncPermissionRulesToSubagents(): void {
168
- const parentPm = this.container.get<PermissionManager>("PermissionManager");
169
- if (!parentPm) return;
170
-
171
- for (const instance of this.instances.values()) {
172
- instance.permissionManager.updateAllowedRules(parentPm.getAllowedRules());
173
- instance.permissionManager.updateDeniedRules(parentPm.getDeniedRules());
174
- instance.permissionManager.updateAdditionalDirectories(
175
- parentPm.getAdditionalDirectories(),
176
- );
177
- }
178
- }
179
-
180
- /**
181
- * Load all available subagent configurations and cache them
182
- */
183
- async loadConfigurations(): Promise<SubagentConfiguration[]> {
184
- if (this.cachedConfigurations === null) {
185
- const { loadSubagentConfigurations } = await import(
186
- "../utils/subagentParser.js"
187
- );
188
- this.cachedConfigurations = await loadSubagentConfigurations(
189
- this.workdir,
190
- );
191
- }
192
- return this.cachedConfigurations;
193
- }
194
-
195
- /**
196
- * Get cached configurations synchronously (must call loadConfigurations first)
197
- */
198
- getConfigurations(): SubagentConfiguration[] {
199
- if (this.cachedConfigurations === null) {
200
- throw new Error(
201
- "SubagentManager not initialized. Call loadConfigurations() first.",
202
- );
203
- }
204
- return this.cachedConfigurations;
205
- }
206
-
207
- /**
208
- * Find subagent by exact name match
209
- */
210
- async findSubagent(name: string) {
211
- // Check cached configurations first (includes plugin agents)
212
- if (this.cachedConfigurations !== null) {
213
- const cached = this.cachedConfigurations.find(
214
- (config) => config.name === name,
215
- );
216
- if (cached) return cached;
217
- }
218
- // Fall back to filesystem scan for non-plugin agents
219
- const { findSubagentByName } = await import("../utils/subagentParser.js");
220
- return findSubagentByName(name, this.workdir);
221
- }
222
-
223
- /**
224
- * Register plugin agents into the cached configurations.
225
- * Names each agent as `pluginName:agentName` to avoid collisions.
226
- */
227
- registerPluginAgents(
228
- pluginName: string,
229
- agents: SubagentConfiguration[],
230
- ): void {
231
- if (this.cachedConfigurations === null) {
232
- // Should not happen if initialization order is correct
233
- this.cachedConfigurations = [];
234
- }
235
-
236
- // Remove any previously registered agents for this plugin (by name prefix)
237
- this.cachedConfigurations = this.cachedConfigurations.filter(
238
- (config) => !config.name.startsWith(`${pluginName}:`),
239
- );
240
-
241
- for (const agent of agents) {
242
- const namespacedName = `${pluginName}:${agent.name}`;
243
- const namespacedAgent: SubagentConfiguration = {
244
- ...agent,
245
- name: namespacedName,
246
- // Safety net: substitute any remaining ${WAVE_PLUGIN_ROOT} placeholders
247
- systemPrompt: agent.systemPrompt.replace(
248
- /\$\{WAVE_PLUGIN_ROOT\}/g,
249
- agent.pluginRoot ?? "",
250
- ),
251
- };
252
- this.cachedConfigurations!.push(namespacedAgent);
253
- }
254
-
255
- // Re-sort by priority then name
256
- this.cachedConfigurations!.sort((a, b) => {
257
- if (a.priority !== b.priority) return a.priority - b.priority;
258
- return a.name.localeCompare(b.name);
259
- });
260
- }
261
-
262
- /**
263
- * Create a new subagent instance with isolated managers
264
- */
265
- async createInstance(
266
- configuration: SubagentConfiguration,
267
- parameters: {
268
- description: string;
269
- prompt: string;
270
- subagent_type: string;
271
- allowedTools?: string[];
272
- model?: string;
273
- stream?: boolean;
274
- permissionModeOverride?: PermissionMode;
275
- maxTurns?: number;
276
- },
277
- runInBackground?: boolean,
278
- onUpdate?: () => void,
279
- ): Promise<SubagentInstance> {
280
- const parentToolManager = this.container.get<ToolManager>("ToolManager");
281
-
282
- if (!parentToolManager) {
283
- throw new Error(
284
- "SubagentManager not properly initialized - ToolManager not found in container",
285
- );
286
- }
287
-
288
- const subagentId = randomUUID();
289
-
290
- // Create a child container for the subagent to isolate its managers
291
- const subagentContainer = this.container.createChild();
292
-
293
- // Register an independent MessageQueue so the subagent's AIManager drains its
294
- // own (empty) queue instead of falling back to the parent container's queue.
295
- // Without this, concurrent background subagents steal sibling completion
296
- // notifications from the parent queue, causing the main agent to exit early.
297
- subagentContainer.register("MessageQueue", new MessageQueue());
298
-
299
- // Register a modified AgentOptions without onLoadingChange to prevent subagent loading
300
- // from affecting the parent agent's loading state
301
- const parentOptions =
302
- this.container.get<import("../types/agent.js").AgentOptions>(
303
- "AgentOptions",
304
- );
305
- if (parentOptions) {
306
- const subagentOptions: import("../types/agent.js").AgentOptions = {
307
- ...parentOptions,
308
- defaultHeaders: {
309
- ...parentOptions.defaultHeaders,
310
- ...parentOptions.subagentHeaders?.[parameters.subagent_type],
311
- },
312
- callbacks: {
313
- ...parentOptions.callbacks,
314
- onLoadingChange: undefined,
315
- },
316
- };
317
- subagentContainer.register("AgentOptions", subagentOptions);
318
- }
319
-
320
- // Create isolated PermissionManager for the subagent
321
- const { PermissionManager } = await import("./permissionManager.js");
322
- const parentPermissionManager =
323
- this.container.get<PermissionManager>("PermissionManager");
324
- const subagentPermissionManager = new PermissionManager(subagentContainer, {
325
- configuredPermissionMode:
326
- parameters.permissionModeOverride ??
327
- parentPermissionManager?.getConfiguredPermissionMode(),
328
- allowedRules: parentPermissionManager?.getAllowedRules(),
329
- deniedRules: parentPermissionManager?.getDeniedRules(),
330
- instanceAllowedRules:
331
- parentPermissionManager?.getInstanceAllowedRules?.(),
332
- instanceDeniedRules: [
333
- ...(parentPermissionManager?.getInstanceDeniedRules?.() || []),
334
- AGENT_TOOL_NAME, // Always deny Agent tool in subagents to prevent recursion
335
- // Deny Task tools in subagents — they share the parent's TaskManager
336
- // and could mutate the main agent's task list
337
- TASK_CREATE_TOOL_NAME,
338
- TASK_GET_TOOL_NAME,
339
- TASK_UPDATE_TOOL_NAME,
340
- TASK_LIST_TOOL_NAME,
341
- ],
342
- additionalDirectories:
343
- parentPermissionManager?.getAdditionalDirectories(),
344
- instanceAdditionalDirectories:
345
- parentPermissionManager?.getInstanceAdditionalDirectories?.(),
346
- systemAdditionalDirectories:
347
- parentPermissionManager?.getSystemAdditionalDirectories(),
348
- planFilePath: parentPermissionManager?.getPlanFilePath(),
349
- });
350
- subagentContainer.register("PermissionManager", subagentPermissionManager);
351
-
352
- // Register the permission mode override in the subagent container so it
353
- // shadows the inherited parent value during tool execution
354
- if (parameters.permissionModeOverride) {
355
- subagentContainer.register(
356
- "PermissionMode",
357
- parameters.permissionModeOverride,
358
- );
359
- }
360
-
361
- // Add temporary permission rules if provided
362
- if (parameters.allowedTools) {
363
- logger.debug(
364
- `Adding ${parameters.allowedTools.length} temporary permission rules to subagent ${subagentId}`,
365
- { rules: parameters.allowedTools },
366
- );
367
- subagentPermissionManager.addTemporaryRules(parameters.allowedTools);
368
- }
369
-
370
- // Create isolated MessageManager for the subagent
371
- const subagentCallbacks = this.createSubagentCallbacks(subagentId);
372
-
373
- const messageManager = new MessageManager(subagentContainer, {
374
- callbacks: subagentCallbacks,
375
- workdir: this.workdir,
376
- sessionType: "subagent",
377
- subagentType: parameters.subagent_type,
378
- });
379
- subagentContainer.register("MessageManager", messageManager);
380
-
381
- // Create isolated ToolManager for the subagent to ensure it uses the subagent's PermissionManager
382
- const toolManager = new ToolManager({
383
- container: subagentContainer,
384
- tools: configuration.tools,
385
- });
386
- toolManager.initializeBuiltInTools();
387
- subagentContainer.register("ToolManager", toolManager);
388
-
389
- // Create isolated AIManager for the subagent
390
- const aiManager = new AIManager(subagentContainer, {
391
- workdir: this.workdir,
392
- systemPrompt: configuration.systemPrompt,
393
- subagentType: parameters.subagent_type, // Pass subagent type for hook context
394
- modelOverride: parameters.model || configuration.model, // Pass model override
395
- stream: parameters.stream ?? this.stream, // Pass streaming mode flag
396
- maxTurns: parameters.maxTurns, // Pass maxTurns limit
397
- callbacks: {
398
- onUsageAdded: this.onUsageAdded,
399
- },
400
- });
401
- subagentContainer.register("AIManager", aiManager);
402
-
403
- // Create isolated BackgroundTaskManager for the subagent/forked agent
404
- const subagentBackgroundTaskManager = new BackgroundTaskManager(
405
- subagentContainer,
406
- {
407
- workdir: this.workdir,
408
- },
409
- );
410
- subagentContainer.register(
411
- "BackgroundTaskManager",
412
- subagentBackgroundTaskManager,
413
- );
414
-
415
- const instance: SubagentInstance = {
416
- subagentId,
417
- configuration,
418
- aiManager,
419
- messageManager,
420
- toolManager,
421
- permissionManager: subagentPermissionManager,
422
- backgroundTaskManager: subagentBackgroundTaskManager,
423
- status: "initializing",
424
- messages: [],
425
- usedTools: [], // Initialize usedTools
426
- subagentType: parameters.subagent_type, // Store the subagent type
427
- description: parameters.description, // Store the AI-generated description
428
- allowedTools: parameters.allowedTools, // Store optional permission rules
429
- model: parameters.model, // Store optional model override
430
- onUpdate,
431
- };
432
-
433
- this.instances.set(subagentId, instance);
434
-
435
- return instance;
436
- }
437
-
438
- /**
439
- * Execute agent using subagent instance
440
- *
441
- * IMPORTANT: This method automatically filters out the Agent tool from allowedTools
442
- * to prevent subagents from spawning other subagents (infinite recursion protection)
443
- */
444
- async executeAgent(
445
- instance: SubagentInstance,
446
- prompt: string,
447
- abortSignal?: AbortSignal,
448
- runInBackground?: boolean,
449
- ): Promise<string> {
450
- try {
451
- // Check if already aborted before starting
452
- if (abortSignal?.aborted) {
453
- throw new Error("Agent was aborted before execution started");
454
- }
455
-
456
- // Set status to active and update parent
457
- this.updateInstanceStatus(instance.subagentId, "active");
458
-
459
- const backgroundTaskManager = this.container.has("BackgroundTaskManager")
460
- ? this.container.get<BackgroundTaskManager>("BackgroundTaskManager")
461
- : undefined;
462
-
463
- if (runInBackground && backgroundTaskManager) {
464
- const taskId = backgroundTaskManager.generateId();
465
- const startTime = Date.now();
466
-
467
- // Create log file
468
- const logPath = path.join(os.tmpdir(), `wave-subagent-${taskId}.log`);
469
- const logStream = fs.createWriteStream(logPath, { flags: "a" });
470
- instance.logStream = logStream;
471
-
472
- backgroundTaskManager.addTask({
473
- id: taskId,
474
- type: "subagent",
475
- status: "running",
476
- startTime,
477
- description: instance.description,
478
- stdout: "",
479
- stderr: "",
480
- outputPath: logPath,
481
- subagentId: instance.subagentId,
482
- onStop: () => {
483
- instance.logStream?.destroy();
484
- instance.logStream = undefined;
485
- instance.aiManager.abortAIMessage();
486
- this.cleanupInstance(instance.subagentId);
487
- },
488
- });
489
-
490
- instance.backgroundTaskId = taskId;
491
-
492
- // Execute in background
493
- // Note: notification enqueueing is handled by internalExecute when instance.backgroundTaskId is set
494
- (async () => {
495
- try {
496
- const result = await this.internalExecute(
497
- instance,
498
- prompt,
499
- abortSignal,
500
- );
501
- const task = backgroundTaskManager?.getTask(taskId);
502
- if (task) {
503
- task.status = "completed";
504
- task.stdout = result;
505
- task.endTime = Date.now();
506
- task.runtime = task.endTime - startTime;
507
- }
508
- } catch (error) {
509
- const task = backgroundTaskManager?.getTask(taskId);
510
- if (task) {
511
- task.status = "failed";
512
- task.stderr =
513
- error instanceof Error ? error.message : String(error);
514
- task.endTime = Date.now();
515
- task.runtime = task.endTime - startTime;
516
- }
517
- }
518
- })();
519
-
520
- return taskId;
521
- }
522
-
523
- return await this.internalExecute(instance, prompt, abortSignal);
524
- } catch (error) {
525
- this.updateInstanceStatus(instance.subagentId, "error");
526
- throw error;
527
- }
528
- }
529
-
530
- async backgroundInstance(subagentId: string): Promise<string> {
531
- const instance = this.instances.get(subagentId);
532
- if (!instance) {
533
- throw new Error(`Subagent instance ${subagentId} not found`);
534
- }
535
-
536
- const backgroundTaskManager = this.container.has("BackgroundTaskManager")
537
- ? this.container.get<BackgroundTaskManager>("BackgroundTaskManager")
538
- : undefined;
539
-
540
- if (!backgroundTaskManager) {
541
- throw new Error("BackgroundTaskManager not available");
542
- }
543
-
544
- const taskId = backgroundTaskManager.generateId();
545
- const startTime = Date.now();
546
-
547
- // Create log file
548
- const logPath = path.join(os.tmpdir(), `wave-subagent-${taskId}.log`);
549
- const logStream = fs.createWriteStream(logPath, { flags: "a" });
550
- instance.logStream = logStream;
551
-
552
- backgroundTaskManager.addTask({
553
- id: taskId,
554
- type: "subagent",
555
- status: "running",
556
- startTime,
557
- description: instance.description,
558
- stdout: "",
559
- stderr: "",
560
- outputPath: logPath,
561
- subagentId: instance.subagentId,
562
- onStop: () => {
563
- instance.logStream?.destroy();
564
- instance.logStream = undefined;
565
- instance.aiManager.abortAIMessage();
566
- this.cleanupInstance(instance.subagentId);
567
- },
568
- });
569
-
570
- instance.backgroundTaskId = taskId;
571
-
572
- return taskId;
573
- }
574
-
575
- private async internalExecute(
576
- instance: SubagentInstance,
577
- prompt: string,
578
- abortSignal?: AbortSignal,
579
- ): Promise<string> {
580
- // Set up consolidated abort handler to prevent listener accumulation
581
- let abortCleanup: (() => void) | undefined;
582
- // Only link to parent abort signal if NOT running in background
583
- if (abortSignal && !instance.backgroundTaskId) {
584
- abortCleanup = addConsolidatedAbortListener(abortSignal, [
585
- () => {
586
- // Update status to aborted
587
- this.updateInstanceStatus(instance.subagentId, "aborted");
588
- // Cleanup instance immediately on abort
589
- this.cleanupInstance(instance.subagentId);
590
- },
591
- () => {
592
- // Abort the AI execution
593
- instance.aiManager.abortAIMessage();
594
- },
595
- ]);
596
- }
597
-
598
- try {
599
- // Add the user's prompt as a message
600
- instance.messageManager.addUserMessage({ content: prompt });
601
-
602
- // Execute the AI request
603
- // The AIManager will handle abort signals through its own abort controllers
604
- const executeAI = instance.aiManager.sendAIMessage();
605
-
606
- // If we have an abort signal, race against it using utilities to prevent listener accumulation
607
- if (abortSignal && !instance.backgroundTaskId) {
608
- await Promise.race([
609
- executeAI,
610
- createAbortPromise(abortSignal, "Agent was aborted"),
611
- ]);
612
- } else {
613
- await executeAI;
614
- }
615
-
616
- // Get the latest messages to extract the response
617
- const messages = instance.messageManager.getMessages();
618
- const lastAssistantMessage = messages
619
- .filter((msg) => msg.role === "assistant")
620
- .pop();
621
-
622
- if (!lastAssistantMessage) {
623
- throw new Error("No response from subagent");
624
- }
625
-
626
- // Extract text content from the last assistant message
627
- const textBlocks = lastAssistantMessage.blocks.filter(
628
- (block) => block.type === "text",
629
- );
630
- const response = textBlocks.map((block) => block.content).join("\n");
631
-
632
- // Update status to completed and update parent
633
- this.updateInstanceStatus(instance.subagentId, "completed");
634
-
635
- const backgroundTaskManager = this.container.has("BackgroundTaskManager")
636
- ? this.container.get<BackgroundTaskManager>("BackgroundTaskManager")
637
- : undefined;
638
-
639
- // If this was transitioned to background, update the background task
640
- if (instance.backgroundTaskId && backgroundTaskManager) {
641
- // Write final response and completion status to log before closing
642
- if (instance.logStream && response) {
643
- instance.logStream.write(
644
- `[${new Date().toISOString()}] Final response:\n${response}\n`,
645
- );
646
- }
647
- instance.logStream?.write(
648
- `[${new Date().toISOString()}] Agent completed\n`,
649
- );
650
- instance.logStream?.end();
651
- const task = backgroundTaskManager.getTask(instance.backgroundTaskId);
652
- if (task) {
653
- const wasAlreadyKilled = task.status === "killed";
654
- task.status = "completed";
655
- task.stdout = response || "Agent completed with no text response";
656
- task.endTime = Date.now();
657
- if (task.startTime) {
658
- task.runtime = task.endTime - task.startTime;
659
- }
660
- // Skip notification if task was already stopped (e.g. by main agent shutdown)
661
- if (!wasAlreadyKilled) {
662
- const messageQueue = this.container.has("MessageQueue")
663
- ? this.container.get<MessageQueue>("MessageQueue")
664
- : undefined;
665
- if (messageQueue) {
666
- const summary = `Agent task "${instance.description}" completed`;
667
- messageQueue.enqueueNotification(
668
- `<task-notification>\n<task-id>${instance.backgroundTaskId}</task-id>\n<task-type>agent</task-type>\n<status>completed</status>\n<summary>${summary}</summary>\n</task-notification>`,
669
- );
670
- }
671
- }
672
- }
673
- }
674
-
675
- return response || "Agent completed with no text response";
676
- } catch (error) {
677
- const backgroundTaskManager = this.container.has("BackgroundTaskManager")
678
- ? this.container.get<BackgroundTaskManager>("BackgroundTaskManager")
679
- : undefined;
680
-
681
- // If this was transitioned to background, update the background task with error
682
- if (instance.backgroundTaskId && backgroundTaskManager) {
683
- // Write error to log before closing
684
- instance.logStream?.write(
685
- `[${new Date().toISOString()}] Agent failed: ${error instanceof Error ? error.message : String(error)}\n`,
686
- );
687
- instance.logStream?.end();
688
- const task = backgroundTaskManager.getTask(instance.backgroundTaskId);
689
- if (task) {
690
- const wasAlreadyKilled = task.status === "killed";
691
- task.status = "failed";
692
- task.stderr = error instanceof Error ? error.message : String(error);
693
- task.endTime = Date.now();
694
- if (task.startTime) {
695
- task.runtime = task.endTime - task.startTime;
696
- }
697
- // Skip notification if task was already stopped (e.g. by main agent shutdown)
698
- if (!wasAlreadyKilled) {
699
- const messageQueue = this.container.has("MessageQueue")
700
- ? this.container.get<MessageQueue>("MessageQueue")
701
- : undefined;
702
- if (messageQueue) {
703
- const errorMsg =
704
- error instanceof Error ? error.message : String(error);
705
- const summary = `Agent task "${instance.description}" failed: ${errorMsg}`;
706
- messageQueue.enqueueNotification(
707
- `<task-notification>\n<task-id>${instance.backgroundTaskId}</task-id>\n<task-type>agent</task-type>\n<status>failed</status>\n<summary>${summary}</summary>\n</task-notification>`,
708
- );
709
- }
710
- }
711
- }
712
- }
713
- throw error;
714
- } finally {
715
- // Clean up abort listeners to prevent memory leaks
716
- if (abortCleanup) {
717
- abortCleanup();
718
- }
719
- }
720
- }
721
-
722
- /**
723
- * Get instance by subagent ID
724
- */
725
- getInstance(subagentId: string): SubagentInstance | null {
726
- return this.instances.get(subagentId) || null;
727
- }
728
-
729
- /**
730
- * Update instance status
731
- */
732
- updateInstanceStatus(
733
- subagentId: string,
734
- status: SubagentInstance["status"],
735
- ): void {
736
- const instance = this.instances.get(subagentId);
737
- if (instance) {
738
- instance.status = status;
739
- }
740
- }
741
-
742
- /**
743
- * Add message to instance
744
- */
745
- addMessageToInstance(subagentId: string, message: Message): void {
746
- const instance = this.instances.get(subagentId);
747
- if (instance) {
748
- instance.messages.push(message);
749
- }
750
- }
751
-
752
- /**
753
- * Clean up completed, errored, or aborted instances
754
- */
755
- cleanupInstance(subagentId: string): void {
756
- const instance = this.instances.get(subagentId);
757
- if (
758
- instance &&
759
- (instance.status === "completed" ||
760
- instance.status === "error" ||
761
- instance.status === "aborted")
762
- ) {
763
- this.instances.delete(subagentId);
764
- }
765
- }
766
-
767
- /**
768
- * Get all active instances
769
- */
770
- getActiveInstances(): SubagentInstance[] {
771
- return Array.from(this.instances.values()).filter(
772
- (instance) =>
773
- instance.status === "active" || instance.status === "initializing",
774
- );
775
- }
776
-
777
- /**
778
- * Clean up all instances (for session end)
779
- */
780
- cleanup(): void {
781
- for (const instance of this.instances.values()) {
782
- instance.aiManager.abortAIMessage();
783
- instance.backgroundTaskManager.cleanup();
784
- }
785
- this.instances.clear();
786
- }
787
-
788
- /**
789
- * Create subagent callbacks for a specific subagent ID
790
- * Extracted to reuse in both create and restore flows
791
- */
792
- private createSubagentCallbacks(subagentId: string) {
793
- return {
794
- onUserMessageAdded: (params: UserMessageParams) => {
795
- this.refreshSubagentState(subagentId);
796
- // Forward user message events to parent via SubagentManager callbacks
797
- if (this.callbacks?.onSubagentUserMessageAdded) {
798
- this.callbacks.onSubagentUserMessageAdded(subagentId, params);
799
- }
800
- },
801
-
802
- onAssistantMessageAdded: (messageId: string) => {
803
- this.refreshSubagentState(subagentId);
804
- // Forward assistant message events to parent via SubagentManager callbacks
805
- if (this.callbacks?.onSubagentAssistantMessageAdded) {
806
- this.callbacks.onSubagentAssistantMessageAdded(subagentId, messageId);
807
- }
808
- },
809
-
810
- onAssistantContentUpdated: (params: {
811
- messageId: string;
812
- chunk: string;
813
- accumulated: string;
814
- stage: "streaming" | "end";
815
- }) => {
816
- this.refreshSubagentState(subagentId);
817
- // Forward assistant content updates to parent via SubagentManager callbacks
818
- if (this.callbacks?.onSubagentAssistantContentUpdated) {
819
- this.callbacks.onSubagentAssistantContentUpdated({
820
- ...params,
821
- subagentId,
822
- });
823
- }
824
- },
825
- onAssistantReasoningUpdated: (params: {
826
- messageId: string;
827
- chunk: string;
828
- accumulated: string;
829
- stage: "streaming" | "end";
830
- }) => {
831
- this.refreshSubagentState(subagentId);
832
- // Forward assistant reasoning updates to parent via SubagentManager callbacks
833
- if (this.callbacks?.onSubagentAssistantReasoningUpdated) {
834
- this.callbacks.onSubagentAssistantReasoningUpdated({
835
- ...params,
836
- subagentId,
837
- });
838
- }
839
- },
840
-
841
- onToolBlockUpdated: (params: ToolBlockUpdateCallbackParams) => {
842
- this.refreshSubagentState(subagentId);
843
- const instance = this.instances.get(subagentId);
844
- if (instance) {
845
- // Log tool execution to file only when finalized
846
- if (instance.logStream && params.stage === "end") {
847
- const displayParams =
848
- params.compactParams ||
849
- (params.parameters || "").substring(0, 100);
850
- instance.logStream.write(
851
- `[${new Date().toISOString()}] ${params.name}${displayParams ? ` ${displayParams}` : ""}\n`,
852
- );
853
- }
854
- }
855
-
856
- // Forward tool block updates to parent via SubagentManager callbacks
857
- if (this.callbacks?.onSubagentToolBlockUpdated) {
858
- this.callbacks.onSubagentToolBlockUpdated(subagentId, params);
859
- }
860
- },
861
-
862
- onLatestTotalTokensChange: (tokens: number) => {
863
- const instance = this.instances.get(subagentId);
864
- if (instance) {
865
- // Trigger the onUpdate callback if provided
866
- instance.onUpdate?.();
867
- }
868
- // Forward latest total tokens to parent via SubagentManager callbacks
869
- if (this.callbacks?.onSubagentLatestTotalTokensChange) {
870
- this.callbacks.onSubagentLatestTotalTokensChange(subagentId, tokens);
871
- }
872
- },
873
-
874
- onErrorBlockAdded: (error: string) => {
875
- this.refreshSubagentState(subagentId);
876
- const instance = this.instances.get(subagentId);
877
- if (instance?.logStream) {
878
- instance.logStream.write(
879
- `[${new Date().toISOString()}] Error: ${error}\n`,
880
- );
881
- }
882
- },
883
- };
884
- }
885
-
886
- /**
887
- * Pull the latest messages from the subagent instance's MessageManager and
888
- * refresh the instance's cached messages, usedTools, onUpdate and the
889
- * onSubagentMessagesChange forwarding. Triggered by incremental callbacks.
890
- */
891
- private refreshSubagentState(subagentId: string): void {
892
- const instance = this.instances.get(subagentId);
893
- if (!instance) return;
894
-
895
- const messages = instance.messageManager.getMessages();
896
- instance.messages = messages;
897
- // Compute usedTools from messages (last 2 tool blocks)
898
- const toolBlocks = messages.flatMap(
899
- (m) => m.blocks?.filter((b) => b.type === "tool") ?? [],
900
- );
901
- const last2 = toolBlocks.slice(-2);
902
- instance.usedTools = last2.map((tb) => ({
903
- name: tb.name ?? "",
904
- parameters: tb.parameters ?? "",
905
- compactParams: tb.compactParams,
906
- stage: tb.stage,
907
- }));
908
- // Trigger the onUpdate callback if provided
909
- instance.onUpdate?.();
910
- // Forward subagent message changes to parent via callbacks
911
- if (this.callbacks?.onSubagentMessagesChange) {
912
- this.callbacks.onSubagentMessagesChange(subagentId, messages);
913
- }
914
- }
915
- }