wave-agent-sdk 1.0.5 → 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 (193) 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 +13 -6
  7. package/dist/managers/slashCommandManager.js +28 -0
  8. package/dist/managers/subagentManager.d.ts +0 -2
  9. package/dist/telemetry/instrumentation.d.ts +1 -1
  10. package/dist/telemetry/instrumentation.js +57 -28
  11. package/dist/tools/bashTool.js +16 -6
  12. package/dist/tools/grepTool.js +11 -3
  13. package/dist/types/index.d.ts +3 -0
  14. package/dist/types/messaging.d.ts +2 -0
  15. package/dist/utils/containerSetup.js +0 -3
  16. package/dist/utils/messageOperations.d.ts +2 -1
  17. package/dist/utils/messageOperations.js +2 -1
  18. package/dist/utils/notificationXml.js +5 -0
  19. package/dist/utils/ripgrep.d.ts +4 -4
  20. package/dist/utils/ripgrep.js +4 -27
  21. package/package.json +20 -8
  22. package/bin/rg +0 -79
  23. package/dist/managers/forkedAgentManager.d.ts +0 -50
  24. package/dist/managers/forkedAgentManager.js +0 -130
  25. package/scripts/install_ripgrep.js +0 -111
  26. package/scripts/postinstall.js +0 -38
  27. package/src/agent.ts +0 -1271
  28. package/src/constants/subagents.ts +0 -4
  29. package/src/constants/toolLimits.ts +0 -15
  30. package/src/constants/tools.ts +0 -24
  31. package/src/core/plugin.ts +0 -237
  32. package/src/core/session.ts +0 -9
  33. package/src/index.ts +0 -38
  34. package/src/managers/MemoryRuleManager.ts +0 -198
  35. package/src/managers/aiManager.ts +0 -2429
  36. package/src/managers/backgroundTaskManager.ts +0 -480
  37. package/src/managers/bangManager.ts +0 -119
  38. package/src/managers/cronManager.ts +0 -401
  39. package/src/managers/foregroundTaskManager.ts +0 -30
  40. package/src/managers/forkedAgentManager.ts +0 -216
  41. package/src/managers/hookManager.ts +0 -1089
  42. package/src/managers/liveConfigManager.ts +0 -445
  43. package/src/managers/lspManager.ts +0 -470
  44. package/src/managers/mcpManager.ts +0 -1010
  45. package/src/managers/messageManager.ts +0 -1184
  46. package/src/managers/messageQueue.ts +0 -157
  47. package/src/managers/permissionManager.ts +0 -1140
  48. package/src/managers/planManager.ts +0 -112
  49. package/src/managers/pluginManager.ts +0 -330
  50. package/src/managers/pluginScopeManager.ts +0 -124
  51. package/src/managers/reversionManager.ts +0 -178
  52. package/src/managers/skillManager.ts +0 -578
  53. package/src/managers/slashCommandManager.ts +0 -564
  54. package/src/managers/subagentManager.ts +0 -915
  55. package/src/managers/toolManager.ts +0 -489
  56. package/src/managers/workflowManager.ts +0 -493
  57. package/src/prompts/autoMemory.ts +0 -33
  58. package/src/prompts/autoMemoryExtraction.ts +0 -146
  59. package/src/prompts/index.ts +0 -506
  60. package/src/prompts/planModeReminders.ts +0 -126
  61. package/src/services/GitService.ts +0 -131
  62. package/src/services/MarketplaceService.ts +0 -1052
  63. package/src/services/MemoryRuleService.ts +0 -71
  64. package/src/services/aiService.ts +0 -919
  65. package/src/services/authService.ts +0 -565
  66. package/src/services/autoMemoryService.ts +0 -280
  67. package/src/services/configurationService.ts +0 -1520
  68. package/src/services/fileWatcher.ts +0 -319
  69. package/src/services/hook.ts +0 -348
  70. package/src/services/initializationService.ts +0 -357
  71. package/src/services/interactionService.ts +0 -229
  72. package/src/services/jsonlHandler.ts +0 -295
  73. package/src/services/memory.ts +0 -234
  74. package/src/services/pluginLoader.ts +0 -252
  75. package/src/services/remoteSettingsService.ts +0 -366
  76. package/src/services/reversionService.ts +0 -122
  77. package/src/services/session.ts +0 -917
  78. package/src/services/taskManager.ts +0 -321
  79. package/src/telemetry/events.ts +0 -65
  80. package/src/telemetry/instrumentation.ts +0 -499
  81. package/src/telemetry/sessionTracing.ts +0 -348
  82. package/src/tools/agentTool.ts +0 -322
  83. package/src/tools/askUserQuestion.ts +0 -160
  84. package/src/tools/bashTool.ts +0 -621
  85. package/src/tools/buildTool.ts +0 -61
  86. package/src/tools/cronCreateTool.ts +0 -161
  87. package/src/tools/cronDeleteTool.ts +0 -51
  88. package/src/tools/cronListTool.ts +0 -47
  89. package/src/tools/editTool.ts +0 -313
  90. package/src/tools/enterPlanMode.ts +0 -124
  91. package/src/tools/enterWorktreeTool.ts +0 -190
  92. package/src/tools/exitPlanMode.ts +0 -139
  93. package/src/tools/exitWorktreeTool.ts +0 -236
  94. package/src/tools/globTool.ts +0 -167
  95. package/src/tools/grepTool.ts +0 -399
  96. package/src/tools/lspTool.ts +0 -883
  97. package/src/tools/readTool.ts +0 -426
  98. package/src/tools/skillTool.ts +0 -248
  99. package/src/tools/taskManagementTools.ts +0 -574
  100. package/src/tools/taskStopTool.ts +0 -78
  101. package/src/tools/types.ts +0 -137
  102. package/src/tools/webFetchTool.ts +0 -373
  103. package/src/tools/workflowTool.ts +0 -205
  104. package/src/tools/writeTool.ts +0 -267
  105. package/src/types/agent.ts +0 -128
  106. package/src/types/auth.ts +0 -19
  107. package/src/types/commands.ts +0 -30
  108. package/src/types/config.ts +0 -43
  109. package/src/types/configuration.ts +0 -168
  110. package/src/types/core.ts +0 -105
  111. package/src/types/cron.ts +0 -13
  112. package/src/types/environment.ts +0 -106
  113. package/src/types/fileSearch.ts +0 -4
  114. package/src/types/history.ts +0 -7
  115. package/src/types/hooks.ts +0 -256
  116. package/src/types/index.ts +0 -41
  117. package/src/types/lsp.ts +0 -98
  118. package/src/types/marketplace.ts +0 -71
  119. package/src/types/mcp.ts +0 -43
  120. package/src/types/memoryRule.ts +0 -31
  121. package/src/types/messaging.ts +0 -114
  122. package/src/types/permissions.ts +0 -80
  123. package/src/types/plugins.ts +0 -39
  124. package/src/types/processes.ts +0 -99
  125. package/src/types/reversion.ts +0 -29
  126. package/src/types/session.ts +0 -12
  127. package/src/types/skills.ts +0 -108
  128. package/src/types/tasks.ts +0 -13
  129. package/src/types/telemetry.ts +0 -98
  130. package/src/types/tools.ts +0 -44
  131. package/src/types/workflow.ts +0 -6
  132. package/src/utils/abortUtils.ts +0 -118
  133. package/src/utils/atomicWrite.ts +0 -61
  134. package/src/utils/bashParser.ts +0 -899
  135. package/src/utils/cacheControlUtils.ts +0 -426
  136. package/src/utils/commandArgumentParser.ts +0 -161
  137. package/src/utils/commandPathResolver.ts +0 -88
  138. package/src/utils/configPaths.ts +0 -220
  139. package/src/utils/configValidator.ts +0 -100
  140. package/src/utils/constants.ts +0 -39
  141. package/src/utils/container.ts +0 -92
  142. package/src/utils/containerSetup.ts +0 -371
  143. package/src/utils/convertMessagesForAPI.ts +0 -326
  144. package/src/utils/cronTasks.ts +0 -128
  145. package/src/utils/cronTasksLock.ts +0 -182
  146. package/src/utils/cronToHuman.ts +0 -99
  147. package/src/utils/customCommands.ts +0 -95
  148. package/src/utils/editUtils.ts +0 -20
  149. package/src/utils/fileFormat.ts +0 -40
  150. package/src/utils/fileSearch.ts +0 -151
  151. package/src/utils/fileUtils.ts +0 -223
  152. package/src/utils/gitUtils.ts +0 -315
  153. package/src/utils/globalLogger.ts +0 -128
  154. package/src/utils/groupMessagesByApiRound.ts +0 -121
  155. package/src/utils/hookMatcher.ts +0 -176
  156. package/src/utils/markdownParser.ts +0 -317
  157. package/src/utils/mcpUtils.ts +0 -138
  158. package/src/utils/messageOperations.ts +0 -659
  159. package/src/utils/modelCapabilities.ts +0 -30
  160. package/src/utils/nameGenerator.ts +0 -99
  161. package/src/utils/notificationXml.ts +0 -57
  162. package/src/utils/openaiClient.ts +0 -251
  163. package/src/utils/parseCronExpression.ts +0 -78
  164. package/src/utils/path.ts +0 -72
  165. package/src/utils/pathEncoder.ts +0 -400
  166. package/src/utils/pathSafety.ts +0 -40
  167. package/src/utils/promptHistory.ts +0 -168
  168. package/src/utils/ripgrep.ts +0 -35
  169. package/src/utils/shellResolver.ts +0 -190
  170. package/src/utils/skillParser.ts +0 -246
  171. package/src/utils/stringUtils.ts +0 -156
  172. package/src/utils/subagentParser.ts +0 -279
  173. package/src/utils/taskReminder.ts +0 -96
  174. package/src/utils/tokenCalculation.ts +0 -43
  175. package/src/utils/tokenEstimate.ts +0 -34
  176. package/src/utils/toolResultStorage.ts +0 -117
  177. package/src/utils/worktreeSession.ts +0 -26
  178. package/src/utils/worktreeUtils.ts +0 -786
  179. package/src/workflow/budgetTracker.ts +0 -34
  180. package/src/workflow/concurrencyLimiter.ts +0 -47
  181. package/src/workflow/journal.ts +0 -95
  182. package/src/workflow/progressReporter.ts +0 -141
  183. package/src/workflow/runState.ts +0 -65
  184. package/src/workflow/scriptRuntime.ts +0 -274
  185. package/src/workflow/structuredOutput.ts +0 -123
  186. package/src/workflow/types.ts +0 -101
  187. package/src/workflow/workflowApis.ts +0 -410
  188. package/vendor/ripgrep/linux-aarch64/rg +0 -0
  189. package/vendor/ripgrep/linux-x86_64/rg +0 -0
  190. package/vendor/ripgrep/macos-aarch64/rg +0 -0
  191. package/vendor/ripgrep/macos-x86_64/rg +0 -0
  192. package/vendor/ripgrep/windows-aarch64/rg.exe +0 -0
  193. 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
- }