wave-agent-sdk 1.0.5 → 1.0.7

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