wave-agent-sdk 0.6.5 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/dist/agent.d.ts +8 -0
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +49 -240
  4. package/dist/constants/tools.d.ts +0 -2
  5. package/dist/constants/tools.d.ts.map +1 -1
  6. package/dist/constants/tools.js +0 -2
  7. package/dist/core/plugin.d.ts +86 -0
  8. package/dist/core/plugin.d.ts.map +1 -0
  9. package/dist/core/plugin.js +164 -0
  10. package/dist/index.d.ts +1 -4
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -5
  13. package/dist/managers/MemoryRuleManager.d.ts +3 -1
  14. package/dist/managers/MemoryRuleManager.d.ts.map +1 -1
  15. package/dist/managers/MemoryRuleManager.js +2 -1
  16. package/dist/managers/aiManager.d.ts +13 -23
  17. package/dist/managers/aiManager.d.ts.map +1 -1
  18. package/dist/managers/aiManager.js +59 -32
  19. package/dist/managers/backgroundTaskManager.d.ts +3 -1
  20. package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
  21. package/dist/managers/backgroundTaskManager.js +2 -1
  22. package/dist/managers/bashManager.d.ts +4 -4
  23. package/dist/managers/bashManager.d.ts.map +1 -1
  24. package/dist/managers/bashManager.js +5 -2
  25. package/dist/managers/foregroundTaskManager.d.ts +3 -0
  26. package/dist/managers/foregroundTaskManager.d.ts.map +1 -1
  27. package/dist/managers/foregroundTaskManager.js +2 -1
  28. package/dist/managers/hookManager.d.ts +3 -3
  29. package/dist/managers/hookManager.d.ts.map +1 -1
  30. package/dist/managers/hookManager.js +20 -19
  31. package/dist/managers/liveConfigManager.d.ts +6 -13
  32. package/dist/managers/liveConfigManager.d.ts.map +1 -1
  33. package/dist/managers/liveConfigManager.js +50 -45
  34. package/dist/managers/lspManager.d.ts +4 -5
  35. package/dist/managers/lspManager.d.ts.map +1 -1
  36. package/dist/managers/lspManager.js +13 -12
  37. package/dist/managers/mcpManager.d.ts +3 -2
  38. package/dist/managers/mcpManager.d.ts.map +1 -1
  39. package/dist/managers/mcpManager.js +16 -15
  40. package/dist/managers/messageManager.d.ts +5 -7
  41. package/dist/managers/messageManager.d.ts.map +1 -1
  42. package/dist/managers/messageManager.js +12 -7
  43. package/dist/managers/permissionManager.d.ts +6 -4
  44. package/dist/managers/permissionManager.d.ts.map +1 -1
  45. package/dist/managers/permissionManager.js +39 -63
  46. package/dist/managers/planManager.d.ts +4 -6
  47. package/dist/managers/planManager.d.ts.map +1 -1
  48. package/dist/managers/planManager.js +18 -4
  49. package/dist/managers/pluginManager.d.ts +10 -22
  50. package/dist/managers/pluginManager.d.ts.map +1 -1
  51. package/dist/managers/pluginManager.js +27 -14
  52. package/dist/managers/reversionManager.d.ts +4 -3
  53. package/dist/managers/reversionManager.d.ts.map +1 -1
  54. package/dist/managers/reversionManager.js +5 -2
  55. package/dist/managers/skillManager.d.ts +3 -2
  56. package/dist/managers/skillManager.d.ts.map +1 -1
  57. package/dist/managers/skillManager.js +15 -14
  58. package/dist/managers/slashCommandManager.d.ts +9 -16
  59. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  60. package/dist/managers/slashCommandManager.js +21 -10
  61. package/dist/managers/subagentManager.d.ts +7 -17
  62. package/dist/managers/subagentManager.d.ts.map +1 -1
  63. package/dist/managers/subagentManager.js +41 -34
  64. package/dist/managers/toolManager.d.ts +15 -38
  65. package/dist/managers/toolManager.d.ts.map +1 -1
  66. package/dist/managers/toolManager.js +66 -56
  67. package/dist/prompts/index.d.ts +6 -2
  68. package/dist/prompts/index.d.ts.map +1 -1
  69. package/dist/prompts/index.js +8 -4
  70. package/dist/services/MarketplaceService.d.ts.map +1 -1
  71. package/dist/services/MarketplaceService.js +13 -0
  72. package/dist/services/aiService.d.ts +4 -0
  73. package/dist/services/aiService.d.ts.map +1 -1
  74. package/dist/services/aiService.js +47 -7
  75. package/dist/services/configurationService.d.ts.map +1 -1
  76. package/dist/services/configurationService.js +30 -11
  77. package/dist/services/taskManager.d.ts +3 -1
  78. package/dist/services/taskManager.d.ts.map +1 -1
  79. package/dist/services/taskManager.js +2 -1
  80. package/dist/tools/bashTool.js +2 -2
  81. package/dist/tools/editTool.d.ts.map +1 -1
  82. package/dist/tools/editTool.js +9 -1
  83. package/dist/tools/readTool.d.ts.map +1 -1
  84. package/dist/tools/readTool.js +2 -2
  85. package/dist/tools/skillTool.d.ts +2 -4
  86. package/dist/tools/skillTool.d.ts.map +1 -1
  87. package/dist/tools/skillTool.js +61 -61
  88. package/dist/tools/taskOutputTool.js +1 -1
  89. package/dist/tools/taskTool.d.ts +2 -4
  90. package/dist/tools/taskTool.d.ts.map +1 -1
  91. package/dist/tools/taskTool.js +192 -187
  92. package/dist/tools/types.d.ts +11 -1
  93. package/dist/tools/types.d.ts.map +1 -1
  94. package/dist/tools/writeTool.d.ts.map +1 -1
  95. package/dist/tools/writeTool.js +4 -2
  96. package/dist/types/marketplace.d.ts +8 -0
  97. package/dist/types/marketplace.d.ts.map +1 -1
  98. package/dist/types/permissions.d.ts +1 -1
  99. package/dist/types/permissions.d.ts.map +1 -1
  100. package/dist/types/permissions.js +1 -3
  101. package/dist/types/skills.d.ts +0 -2
  102. package/dist/types/skills.d.ts.map +1 -1
  103. package/dist/types/tools.d.ts +0 -15
  104. package/dist/types/tools.d.ts.map +1 -1
  105. package/dist/utils/container.d.ts +31 -0
  106. package/dist/utils/container.d.ts.map +1 -0
  107. package/dist/utils/container.js +79 -0
  108. package/dist/utils/containerSetup.d.ts +26 -0
  109. package/dist/utils/containerSetup.d.ts.map +1 -0
  110. package/dist/utils/containerSetup.js +165 -0
  111. package/dist/utils/editUtils.d.ts +0 -3
  112. package/dist/utils/editUtils.d.ts.map +1 -1
  113. package/dist/utils/editUtils.js +4 -3
  114. package/dist/utils/hookMatcher.d.ts +1 -1
  115. package/dist/utils/hookMatcher.d.ts.map +1 -1
  116. package/dist/utils/hookMatcher.js +2 -2
  117. package/dist/utils/openaiClient.js +2 -2
  118. package/dist/utils/stringUtils.d.ts +6 -0
  119. package/dist/utils/stringUtils.d.ts.map +1 -1
  120. package/dist/utils/stringUtils.js +8 -0
  121. package/package.json +1 -1
  122. package/src/agent.ts +60 -282
  123. package/src/constants/tools.ts +0 -2
  124. package/src/core/plugin.ts +224 -0
  125. package/src/index.ts +1 -6
  126. package/src/managers/MemoryRuleManager.ts +6 -1
  127. package/src/managers/aiManager.ts +83 -58
  128. package/src/managers/backgroundTaskManager.ts +5 -1
  129. package/src/managers/bashManager.ts +9 -4
  130. package/src/managers/foregroundTaskManager.ts +3 -0
  131. package/src/managers/hookManager.ts +21 -23
  132. package/src/managers/liveConfigManager.ts +57 -53
  133. package/src/managers/lspManager.ts +14 -19
  134. package/src/managers/mcpManager.ts +20 -20
  135. package/src/managers/messageManager.ts +19 -12
  136. package/src/managers/permissionManager.ts +45 -70
  137. package/src/managers/planManager.ts +26 -7
  138. package/src/managers/pluginManager.ts +37 -33
  139. package/src/managers/reversionManager.ts +5 -3
  140. package/src/managers/skillManager.ts +19 -20
  141. package/src/managers/slashCommandManager.ts +30 -25
  142. package/src/managers/subagentManager.ts +53 -53
  143. package/src/managers/toolManager.ts +91 -90
  144. package/src/prompts/index.ts +13 -5
  145. package/src/services/MarketplaceService.ts +13 -0
  146. package/src/services/aiService.ts +61 -15
  147. package/src/services/configurationService.ts +34 -13
  148. package/src/services/taskManager.ts +5 -1
  149. package/src/tools/bashTool.ts +2 -2
  150. package/src/tools/editTool.ts +9 -1
  151. package/src/tools/readTool.ts +2 -2
  152. package/src/tools/skillTool.ts +75 -71
  153. package/src/tools/taskOutputTool.ts +1 -1
  154. package/src/tools/taskTool.ts +224 -225
  155. package/src/tools/types.ts +12 -1
  156. package/src/tools/writeTool.ts +4 -2
  157. package/src/types/marketplace.ts +9 -0
  158. package/src/types/permissions.ts +0 -4
  159. package/src/types/skills.ts +0 -3
  160. package/src/types/tools.ts +0 -17
  161. package/src/utils/container.ts +92 -0
  162. package/src/utils/containerSetup.ts +256 -0
  163. package/src/utils/editUtils.ts +4 -3
  164. package/src/utils/hookMatcher.ts +2 -2
  165. package/src/utils/openaiClient.ts +2 -2
  166. package/src/utils/stringUtils.ts +9 -0
  167. package/dist/tools/deleteFileTool.d.ts +0 -6
  168. package/dist/tools/deleteFileTool.d.ts.map +0 -1
  169. package/dist/tools/deleteFileTool.js +0 -100
  170. package/dist/tools/multiEditTool.d.ts +0 -6
  171. package/dist/tools/multiEditTool.d.ts.map +0 -1
  172. package/dist/tools/multiEditTool.js +0 -246
  173. package/src/tools/deleteFileTool.ts +0 -127
  174. package/src/tools/multiEditTool.ts +0 -306
@@ -2,9 +2,7 @@ import type { ToolContext, ToolPlugin, ToolResult } from "../tools/types.js";
2
2
  import { bashTool } from "../tools/bashTool.js";
3
3
  import { taskOutputTool } from "../tools/taskOutputTool.js";
4
4
  import { taskStopTool } from "../tools/taskStopTool.js";
5
- import { deleteFileTool } from "../tools/deleteFileTool.js";
6
5
  import { editTool } from "../tools/editTool.js";
7
- import { multiEditTool } from "../tools/multiEditTool.js";
8
6
  import { writeTool } from "../tools/writeTool.js";
9
7
  import { exitPlanModeTool } from "../tools/exitPlanMode.js";
10
8
  import { askUserQuestionTool } from "../tools/askUserQuestion.js";
@@ -14,8 +12,8 @@ import { grepTool } from "../tools/grepTool.js";
14
12
  import { lsTool } from "../tools/lsTool.js";
15
13
  import { readTool } from "../tools/readTool.js";
16
14
  import { lspTool } from "../tools/lspTool.js";
17
- import { createTaskTool } from "../tools/taskTool.js";
18
- import { createSkillTool } from "../tools/skillTool.js";
15
+ import { taskTool } from "../tools/taskTool.js";
16
+ import { skillTool } from "../tools/skillTool.js";
19
17
  import {
20
18
  taskCreateTool,
21
19
  taskGetTool,
@@ -26,7 +24,6 @@ import { McpManager } from "./mcpManager.js";
26
24
  import { PermissionManager } from "./permissionManager.js";
27
25
  import { ChatCompletionFunctionTool } from "openai/resources.js";
28
26
  import type {
29
- Logger,
30
27
  PermissionMode,
31
28
  PermissionCallback,
32
29
  ILspManager,
@@ -36,24 +33,14 @@ import type { SkillManager } from "./skillManager.js";
36
33
 
37
34
  import { ReversionManager } from "./reversionManager.js";
38
35
 
36
+ import { Container } from "../utils/container.js";
37
+
38
+ import { logger } from "../utils/globalLogger.js";
39
+
39
40
  export interface ToolManagerOptions {
40
- mcpManager: McpManager;
41
- lspManager?: ILspManager;
42
- logger?: Logger;
43
- /** Permission manager for handling tool permission checks */
44
- permissionManager?: PermissionManager;
45
- /** Foreground task manager for backgrounding tasks */
46
- foregroundTaskManager?: import("../types/processes.js").IForegroundTaskManager;
47
- /** Task manager for task management */
48
- taskManager?: import("../services/taskManager.js").TaskManager;
49
- /** Reversion manager for file snapshots */
50
- reversionManager?: ReversionManager;
51
- /** Background task manager for background execution */
52
- backgroundTaskManager?: import("./backgroundTaskManager.js").BackgroundTaskManager;
53
- /** Permission mode for tool execution (defaults to "default") */
54
- permissionMode?: PermissionMode;
55
- /** Custom permission callback for tool usage */
56
- canUseToolCallback?: PermissionCallback;
41
+ container: Container;
42
+ /** Optional list of tool names to enable */
43
+ tools?: string[];
57
44
  }
58
45
 
59
46
  /**
@@ -63,44 +50,31 @@ export interface ToolManagerOptions {
63
50
  * Supports both built-in tools and MCP (Model Context Protocol) tools.
64
51
  */
65
52
  class ToolManager {
66
- private tools = new Map<string, ToolPlugin>();
67
- private mcpManager: McpManager;
68
- private lspManager?: ILspManager;
69
- private logger?: Logger;
70
- private permissionManager?: PermissionManager;
71
- private foregroundTaskManager?: import("../types/processes.js").IForegroundTaskManager;
72
- private reversionManager?: ReversionManager;
73
- private taskManager?: import("../services/taskManager.js").TaskManager;
74
- private backgroundTaskManager?: import("./backgroundTaskManager.js").BackgroundTaskManager;
75
- private permissionMode?: PermissionMode;
76
- private canUseToolCallback?: PermissionCallback;
53
+ private toolsRegistry = new Map<string, ToolPlugin>();
54
+ private tools?: string[];
55
+ private container: Container;
77
56
 
78
57
  constructor(options: ToolManagerOptions) {
79
- this.mcpManager = options.mcpManager;
80
- this.lspManager = options.lspManager;
81
- this.logger = options.logger;
82
- this.permissionManager = options.permissionManager;
83
- this.taskManager = options.taskManager;
84
- this.foregroundTaskManager = options.foregroundTaskManager;
85
- this.reversionManager = options.reversionManager;
86
- this.backgroundTaskManager = options.backgroundTaskManager;
87
- // Store CLI permission mode, let PermissionManager resolve effective mode
88
- this.permissionMode = options.permissionMode;
89
- this.canUseToolCallback = options.canUseToolCallback;
58
+ this.container = options.container;
59
+ this.tools = options.tools;
60
+ }
61
+
62
+ private get mcpManager(): McpManager {
63
+ return this.container.get<McpManager>("McpManager")!;
90
64
  }
91
65
 
92
66
  /**
93
67
  * Register a new tool
94
68
  */
95
69
  public register(tool: ToolPlugin): void {
96
- this.tools.set(tool.name, tool);
70
+ this.toolsRegistry.set(tool.name, tool);
97
71
  }
98
72
 
99
73
  /**
100
74
  * Initialize built-in tools. Can be called with dependencies for tools that require them.
101
75
  *
102
76
  * This method can be called multiple times safely. When called without dependencies,
103
- * it registers basic tools (Bash, Read, Write, TodoWrite, etc.). When called with
77
+ * it registers basic tools (Bash, Read, Write, TaskCreate, etc.). When called with
104
78
  * dependencies, it also registers tools that require managers (Task, Skill).
105
79
  *
106
80
  * @param deps Optional dependencies for advanced tools
@@ -119,17 +93,12 @@ class ToolManager {
119
93
  * });
120
94
  * ```
121
95
  */
122
- public initializeBuiltInTools(deps?: {
123
- subagentManager?: SubagentManager;
124
- skillManager?: SkillManager;
125
- }): void {
96
+ public initializeBuiltInTools(): void {
126
97
  const builtInTools = [
127
98
  bashTool,
128
99
  taskOutputTool,
129
100
  taskStopTool,
130
- deleteFileTool,
131
101
  editTool,
132
- multiEditTool,
133
102
  writeTool,
134
103
  exitPlanModeTool,
135
104
  askUserQuestionTool,
@@ -138,6 +107,8 @@ class ToolManager {
138
107
  lsTool,
139
108
  readTool,
140
109
  lspTool,
110
+ taskTool,
111
+ skillTool,
141
112
  taskCreateTool,
142
113
  taskGetTool,
143
114
  taskUpdateTool,
@@ -145,19 +116,22 @@ class ToolManager {
145
116
  ];
146
117
 
147
118
  for (const tool of builtInTools) {
148
- this.tools.set(tool.name, tool);
149
- }
150
-
151
- // Register tools that require dependencies
152
- if (deps?.subagentManager) {
153
- const taskTool = createTaskTool(deps.subagentManager);
154
- this.tools.set(taskTool.name, taskTool);
119
+ if (this.shouldEnableTool(tool.name)) {
120
+ this.toolsRegistry.set(tool.name, tool);
121
+ }
155
122
  }
123
+ }
156
124
 
157
- if (deps?.skillManager) {
158
- const skillTool = createSkillTool(deps.skillManager);
159
- this.tools.set(skillTool.name, skillTool);
125
+ /**
126
+ * Check if a tool should be enabled based on tools configuration
127
+ */
128
+ private shouldEnableTool(name: string): boolean {
129
+ if (!this.tools) {
130
+ return true;
160
131
  }
132
+ return this.tools.some(
133
+ (toolName) => toolName.toLowerCase() === name.toLowerCase(),
134
+ );
161
135
  }
162
136
 
163
137
  /**
@@ -178,32 +152,53 @@ class ToolManager {
178
152
  args: Record<string, unknown>,
179
153
  context: ToolContext,
180
154
  ): Promise<ToolResult> {
155
+ const permissionManager =
156
+ this.container.get<PermissionManager>("PermissionManager");
157
+ const permissionMode = this.container.has("PermissionMode")
158
+ ? this.container.get<PermissionMode>("PermissionMode")
159
+ : undefined;
160
+
181
161
  // Resolve effective permission mode (CLI override > configuration > default)
182
- const effectivePermissionMode = this.permissionManager
183
- ? this.permissionManager.getCurrentEffectiveMode(this.permissionMode)
184
- : this.permissionMode || "default";
162
+ const effectivePermissionMode = permissionManager
163
+ ? permissionManager.getCurrentEffectiveMode(permissionMode)
164
+ : permissionMode || "default";
185
165
 
186
166
  // Enhance context with permission-related fields
187
167
  const enhancedContext: ToolContext = {
188
168
  ...context,
189
169
  permissionMode: effectivePermissionMode,
190
- canUseToolCallback: this.canUseToolCallback,
191
- permissionManager: this.permissionManager,
192
- taskManager: this.taskManager!,
193
- reversionManager: this.reversionManager,
194
- backgroundTaskManager: this.backgroundTaskManager,
195
- foregroundTaskManager: this.foregroundTaskManager,
170
+ canUseToolCallback: this.container.has("CanUseToolCallback")
171
+ ? this.container.get<PermissionCallback>("CanUseToolCallback")
172
+ : undefined,
173
+ permissionManager,
174
+ taskManager:
175
+ this.container.get<import("../services/taskManager.js").TaskManager>(
176
+ "TaskManager",
177
+ )!,
178
+ reversionManager:
179
+ this.container.get<ReversionManager>("ReversionManager")!,
180
+ backgroundTaskManager: this.container.get<
181
+ import("./backgroundTaskManager.js").BackgroundTaskManager
182
+ >("BackgroundTaskManager")!,
183
+ foregroundTaskManager: this.container.get<
184
+ import("../types/processes.js").IForegroundTaskManager
185
+ >("ForegroundTaskManager")!,
196
186
  mcpManager: this.mcpManager,
197
- lspManager: this.lspManager,
187
+ lspManager: this.container.get<ILspManager>("LspManager")!,
188
+ subagentManager: this.container.has("SubagentManager")
189
+ ? this.container.get<SubagentManager>("SubagentManager")
190
+ : undefined,
191
+ skillManager: this.container.has("SkillManager")
192
+ ? this.container.get<SkillManager>("SkillManager")
193
+ : undefined,
198
194
  sessionId: context.sessionId,
199
195
  };
200
196
 
201
- this.logger?.debug("Executing tool with enhanced context", {
197
+ logger?.debug("Executing tool with enhanced context", {
202
198
  toolName: name,
203
- cliPermissionMode: this.permissionMode,
199
+ permissionMode,
204
200
  effectivePermissionMode,
205
- hasPermissionManager: !!this.permissionManager,
206
- hasPermissionCallback: !!this.canUseToolCallback,
201
+ hasPermissionManager: !!permissionManager,
207
202
  });
208
203
 
209
204
  // Check if it's an MCP tool first
@@ -216,12 +211,12 @@ class ToolManager {
216
211
  }
217
212
 
218
213
  // Check built-in tools
219
- const plugin = this.tools.get(name);
214
+ const plugin = this.toolsRegistry.get(name);
220
215
  if (plugin) {
221
216
  try {
222
217
  return await plugin.execute(args, enhancedContext);
223
218
  } catch (error) {
224
- this.logger?.error("Tool execution failed", {
219
+ logger?.error("Tool execution failed", {
225
220
  toolName: name,
226
221
  error: error instanceof Error ? error.message : String(error),
227
222
  });
@@ -233,7 +228,7 @@ class ToolManager {
233
228
  }
234
229
  }
235
230
 
236
- this.logger?.warn("Tool not found", { toolName: name });
231
+ logger?.warn("Tool not found", { toolName: name });
237
232
  return {
238
233
  success: false,
239
234
  content: "",
@@ -242,14 +237,14 @@ class ToolManager {
242
237
  }
243
238
 
244
239
  list(): ToolPlugin[] {
245
- const builtInTools = Array.from(this.tools.values());
240
+ const builtInTools = Array.from(this.toolsRegistry.values());
246
241
  const mcpTools = this.mcpManager.getMcpToolPlugins();
247
242
  return [...builtInTools, ...mcpTools];
248
243
  }
249
244
 
250
245
  getToolsConfig(): ChatCompletionFunctionTool[] {
251
246
  const effectivePermissionMode = this.getPermissionMode();
252
- const builtInToolsConfig = Array.from(this.tools.values())
247
+ const builtInToolsConfig = Array.from(this.toolsRegistry.values())
253
248
  .filter((tool) => {
254
249
  if (effectivePermissionMode === "bypassPermissions") {
255
250
  if (tool.name === "ExitPlanMode" || tool.name === "AskUserQuestion") {
@@ -270,19 +265,23 @@ class ToolManager {
270
265
  * Get the list of registered tool plugins
271
266
  */
272
267
  public getTools(): ToolPlugin[] {
273
- return Array.from(this.tools.values());
268
+ return Array.from(this.toolsRegistry.values());
274
269
  }
275
270
 
276
271
  /**
277
272
  * Get the current permission mode
278
273
  */
279
274
  public getPermissionMode(): PermissionMode {
280
- if (this.permissionManager) {
281
- return this.permissionManager.getCurrentEffectiveMode(
282
- this.permissionMode,
283
- );
275
+ const permissionManager =
276
+ this.container.get<PermissionManager>("PermissionManager");
277
+ const permissionMode = this.container.has("PermissionMode")
278
+ ? this.container.get<PermissionMode>("PermissionMode")
279
+ : undefined;
280
+
281
+ if (permissionManager) {
282
+ return permissionManager.getCurrentEffectiveMode(permissionMode);
284
283
  }
285
- return this.permissionMode || "default";
284
+ return permissionMode || "default";
286
285
  }
287
286
 
288
287
  /**
@@ -290,14 +289,14 @@ class ToolManager {
290
289
  * @param mode - The new permission mode
291
290
  */
292
291
  public setPermissionMode(mode: PermissionMode): void {
293
- this.permissionMode = mode;
292
+ this.container.register("PermissionMode", mode);
294
293
  }
295
294
 
296
295
  /**
297
296
  * Get the permission manager
298
297
  */
299
298
  public getPermissionManager(): PermissionManager | undefined {
300
- return this.permissionManager;
299
+ return this.container.get<PermissionManager>("PermissionManager");
301
300
  }
302
301
 
303
302
  /**
@@ -306,7 +305,9 @@ class ToolManager {
306
305
  public getTaskManager():
307
306
  | import("../services/taskManager.js").TaskManager
308
307
  | undefined {
309
- return this.taskManager;
308
+ return this.container.get<import("../services/taskManager.js").TaskManager>(
309
+ "TaskManager",
310
+ );
310
311
  }
311
312
  }
312
313
 
@@ -161,7 +161,6 @@ export const EXPLORE_SUBAGENT_SYSTEM_PROMPT = `You are a file search specialist.
161
161
  This is a READ-ONLY exploration task. You are STRICTLY PROHIBITED from:
162
162
  - Creating new files (no Write, touch, or file creation of any kind)
163
163
  - Modifying existing files (no Edit operations)
164
- - Deleting files (no rm or deletion)
165
164
  - Moving or copying files (no mv or cp)
166
165
  - Creating temporary files anywhere, including /tmp
167
166
  - Using redirect operators (>, >>, |) or heredocs to write to files
@@ -199,7 +198,6 @@ export const PLAN_SUBAGENT_SYSTEM_PROMPT = `You are a software architect and pla
199
198
  This is a READ-ONLY planning task. You are STRICTLY PROHIBITED from:
200
199
  - Creating new files (no Write, touch, or file creation of any kind)
201
200
  - Modifying existing files (no Edit operations)
202
- - Deleting files (no rm or deletion)
203
201
  - Moving or copying files (no mv or cp)
204
202
  - Creating temporary files anywhere, including /tmp
205
203
  - Using redirect operators (>, >>, |) or heredocs to write to files
@@ -290,6 +288,9 @@ Any promises made to the user
290
288
  Be concise but complete—err on the side of including information that would prevent duplicate work or repeated mistakes. Write in a way that enables immediate resumption of the task.
291
289
  Wrap your summary in <summary></summary> tags.`;
292
290
 
291
+ import type { SubagentConfiguration } from "../utils/subagentParser.js";
292
+ import type { SkillMetadata } from "../types/skills.js";
293
+
293
294
  export function buildSystemPrompt(
294
295
  basePrompt: string | undefined,
295
296
  tools: ToolPlugin[],
@@ -302,15 +303,22 @@ export function buildSystemPrompt(
302
303
  planFilePath: string;
303
304
  planExists: boolean;
304
305
  };
306
+ availableSubagents?: SubagentConfiguration[];
307
+ availableSkills?: SkillMetadata[];
305
308
  } = {},
306
309
  ): string {
307
310
  let prompt = basePrompt || DEFAULT_SYSTEM_PROMPT;
308
-
309
- prompt += `\n\n${TOOL_POLICY}`;
311
+ if (tools.length > 0) {
312
+ prompt += `\n\n${TOOL_POLICY}`;
313
+ }
310
314
 
311
315
  for (const tool of tools) {
312
316
  if (tool.prompt) {
313
- prompt += tool.prompt();
317
+ prompt += tool.prompt({
318
+ availableSubagents: options.availableSubagents,
319
+ availableSkills: options.availableSkills,
320
+ workdir: options.workdir,
321
+ });
314
322
  }
315
323
  }
316
324
 
@@ -80,6 +80,16 @@ export class MarketplaceService {
80
80
  }
81
81
  try {
82
82
  const content = await fs.readFile(this.knownMarketplacesPath, "utf-8");
83
+ if (!content.trim()) {
84
+ return {
85
+ marketplaces: [
86
+ {
87
+ ...MarketplaceService.BUILTIN_MARKETPLACE,
88
+ isBuiltin: true,
89
+ },
90
+ ],
91
+ };
92
+ }
83
93
  return JSON.parse(content);
84
94
  } catch (error) {
85
95
  console.error("Failed to load known marketplaces:", error);
@@ -96,6 +106,9 @@ export class MarketplaceService {
96
106
  }
97
107
  try {
98
108
  const content = await fs.readFile(this.installedPluginsPath, "utf-8");
109
+ if (!content.trim()) {
110
+ return { plugins: [] };
111
+ }
99
112
  return JSON.parse(content);
100
113
  } catch (error) {
101
114
  console.error("Failed to load installed plugins:", error);
@@ -8,6 +8,7 @@ import {
8
8
  } from "openai/resources.js";
9
9
  import { OpenAIClient } from "../utils/openaiClient.js";
10
10
  import { logger } from "../utils/globalLogger.js";
11
+ import { addOnceAbortListener } from "../utils/abortUtils.js";
11
12
  import type { GatewayConfig, ModelConfig } from "../types/index.js";
12
13
  import {
13
14
  transformMessagesForClaudeCache,
@@ -69,13 +70,50 @@ interface ErrorData {
69
70
  type OpenAIModelConfig = Omit<
70
71
  ChatCompletionCreateParamsNonStreaming,
71
72
  "messages"
72
- > & {
73
- vertexai?: {
74
- thinking_config: {
75
- thinking_level: string;
76
- };
77
- };
78
- };
73
+ >;
74
+
75
+ // Global rate limiter state for 1 QPS
76
+ let nextAllowedTime = 0;
77
+ const MIN_INTERVAL = 1000; // 1 second for 1 QPS
78
+
79
+ /**
80
+ * Resets the rate limiter state. Primarily used for testing.
81
+ */
82
+ export function resetRateLimiter(): void {
83
+ nextAllowedTime = 0;
84
+ }
85
+
86
+ /**
87
+ * Acquires a slot for an AI request, ensuring 1 QPS limit.
88
+ * @param abortSignal Optional abort signal to cancel waiting
89
+ */
90
+ async function acquireSlot(abortSignal?: AbortSignal): Promise<void> {
91
+ if (abortSignal?.aborted) {
92
+ throw new Error("Request was aborted");
93
+ }
94
+
95
+ const now = Date.now();
96
+ const waitTime = Math.max(0, nextAllowedTime - now);
97
+
98
+ // Reserve the slot synchronously to ensure global ordering
99
+ nextAllowedTime = Math.max(now, nextAllowedTime) + MIN_INTERVAL;
100
+
101
+ if (waitTime > 0) {
102
+ await new Promise<void>((resolve, reject) => {
103
+ const timeout = setTimeout(() => {
104
+ cleanup();
105
+ resolve();
106
+ }, waitTime);
107
+
108
+ const cleanup = abortSignal
109
+ ? addOnceAbortListener(abortSignal, () => {
110
+ clearTimeout(timeout);
111
+ reject(new Error("Request was aborted"));
112
+ })
113
+ : () => {};
114
+ });
115
+ }
116
+ }
79
117
 
80
118
  /**
81
119
  * Get specific configuration parameters based on model name
@@ -99,14 +137,6 @@ function getModelConfig(
99
137
  config.temperature = undefined;
100
138
  }
101
139
 
102
- if (modelName.startsWith("gemini-3-flash")) {
103
- config.vertexai = {
104
- thinking_config: {
105
- thinking_level: "minimal",
106
- },
107
- };
108
- }
109
-
110
140
  return config;
111
141
  }
112
142
 
@@ -170,6 +200,14 @@ export async function callAgent(
170
200
  onReasoningUpdate,
171
201
  } = options;
172
202
 
203
+ // Apply global 1 QPS rate limit
204
+ if (
205
+ process.env.NODE_ENV !== "test" ||
206
+ modelConfig.agentModel === "rate-limit-test"
207
+ ) {
208
+ await acquireSlot(abortSignal);
209
+ }
210
+
173
211
  // Declare variables outside try block for error handling access
174
212
  let openaiMessages: ChatCompletionMessageParam[] | undefined;
175
213
  let createParams:
@@ -719,6 +757,14 @@ export async function compressMessages(
719
757
  ): Promise<CompressMessagesResult> {
720
758
  const { gatewayConfig, modelConfig, messages, abortSignal } = options;
721
759
 
760
+ // Apply global 1 QPS rate limit
761
+ if (
762
+ process.env.NODE_ENV !== "test" ||
763
+ modelConfig.agentModel === "rate-limit-test"
764
+ ) {
765
+ await acquireSlot(abortSignal);
766
+ }
767
+
722
768
  // Create OpenAI client with injected configuration
723
769
  const openai = new OpenAIClient({
724
770
  apiKey: gatewayConfig.apiKey,
@@ -374,7 +374,7 @@ export class ConfigurationService {
374
374
  if (apiKey !== undefined) {
375
375
  resolvedApiKey = apiKey;
376
376
  } else {
377
- resolvedApiKey = this.env.WAVE_API_KEY || process.env.WAVE_API_KEY;
377
+ resolvedApiKey = this.env.WAVE_API_KEY;
378
378
  }
379
379
 
380
380
  // Resolve base URL: constructor > env (settings.json) > process.env
@@ -383,8 +383,25 @@ export class ConfigurationService {
383
383
  if (baseURL !== undefined) {
384
384
  resolvedBaseURL = baseURL;
385
385
  } else {
386
- resolvedBaseURL =
387
- this.env.WAVE_BASE_URL || process.env.WAVE_BASE_URL || "";
386
+ resolvedBaseURL = this.env.WAVE_BASE_URL || "";
387
+ }
388
+
389
+ // If we have a parent configuration, use it as a fallback for API key and base URL
390
+ if (this.currentConfiguration?.env) {
391
+ if (resolvedApiKey === undefined) {
392
+ resolvedApiKey = this.currentConfiguration.env.WAVE_API_KEY;
393
+ }
394
+ if (!resolvedBaseURL) {
395
+ resolvedBaseURL = this.currentConfiguration.env.WAVE_BASE_URL || "";
396
+ }
397
+ }
398
+
399
+ // Fallback to process.env if still not resolved (for dynamic updates in tests)
400
+ if (resolvedApiKey === undefined) {
401
+ resolvedApiKey = process.env.WAVE_API_KEY;
402
+ }
403
+ if (!resolvedBaseURL) {
404
+ resolvedBaseURL = process.env.WAVE_BASE_URL || "";
388
405
  }
389
406
 
390
407
  if (!resolvedBaseURL && baseURL === undefined) {
@@ -443,18 +460,22 @@ export class ConfigurationService {
443
460
  const DEFAULT_FAST_MODEL = "gemini-2.5-flash";
444
461
 
445
462
  // Resolve agent model: constructor > env (settings.json) > process.env > default
446
- const resolvedAgentModel =
447
- agentModel ||
448
- this.env.WAVE_MODEL ||
449
- process.env.WAVE_MODEL ||
450
- DEFAULT_AGENT_MODEL;
463
+ let resolvedAgentModel = agentModel || this.env.WAVE_MODEL;
464
+
465
+ if (!resolvedAgentModel && this.currentConfiguration?.env?.WAVE_MODEL) {
466
+ resolvedAgentModel = this.currentConfiguration.env.WAVE_MODEL;
467
+ }
468
+ resolvedAgentModel =
469
+ resolvedAgentModel || process.env.WAVE_MODEL || DEFAULT_AGENT_MODEL;
451
470
 
452
471
  // Resolve fast model: constructor > env (settings.json) > process.env > default
453
- const resolvedFastModel =
454
- fastModel ||
455
- this.env.WAVE_FAST_MODEL ||
456
- process.env.WAVE_FAST_MODEL ||
457
- DEFAULT_FAST_MODEL;
472
+ let resolvedFastModel = fastModel || this.env.WAVE_FAST_MODEL;
473
+
474
+ if (!resolvedFastModel && this.currentConfiguration?.env?.WAVE_FAST_MODEL) {
475
+ resolvedFastModel = this.currentConfiguration.env.WAVE_FAST_MODEL;
476
+ }
477
+ resolvedFastModel =
478
+ resolvedFastModel || process.env.WAVE_FAST_MODEL || DEFAULT_FAST_MODEL;
458
479
 
459
480
  // Resolve max output tokens
460
481
  const resolvedMaxTokens = this.resolveMaxOutputTokens(maxTokens);
@@ -4,12 +4,16 @@ import { homedir } from "os";
4
4
  import { EventEmitter } from "events";
5
5
  import { Task } from "../types/tasks.js";
6
6
  import { logger } from "../utils/globalLogger.js";
7
+ import { Container } from "../utils/container.js";
7
8
 
8
9
  export class TaskManager extends EventEmitter {
9
10
  private readonly baseDir: string;
10
11
  private taskListId: string;
11
12
 
12
- constructor(taskListId: string) {
13
+ constructor(
14
+ private container: Container,
15
+ taskListId: string,
16
+ ) {
13
17
  super();
14
18
  this.taskListId = taskListId;
15
19
  this.baseDir = join(homedir(), ".wave", "tasks");
@@ -306,7 +306,7 @@ Usage notes:
306
306
  }
307
307
 
308
308
  child.stdout?.on("data", (data) => {
309
- if (!isAborted && !isBackgrounded) {
309
+ if (!isAborted && !isBackgrounded && !runInBackground) {
310
310
  const chunk = stripAnsiColors(data.toString());
311
311
  outputBuffer += chunk;
312
312
  context.onShortResultUpdate?.(chunk.trim().split("\n").pop() || "");
@@ -314,7 +314,7 @@ Usage notes:
314
314
  });
315
315
 
316
316
  child.stderr?.on("data", (data) => {
317
- if (!isAborted && !isBackgrounded) {
317
+ if (!isAborted && !isBackgrounded && !runInBackground) {
318
318
  const chunk = stripAnsiColors(data.toString());
319
319
  errorBuffer += chunk;
320
320
  context.onShortResultUpdate?.(chunk.trim().split("\n").pop() || "");
@@ -23,7 +23,15 @@ export const editTool: ToolPlugin = {
23
23
  name: EDIT_TOOL_NAME,
24
24
  formatCompactParams,
25
25
  prompt: () =>
26
- `Performs exact string replacements in files. \n\nUsage:\n- You must use your \`${READ_TOOL_NAME}\` tool at least once in the conversation before editing. This tool will error if you attempt an edit without reading the file. \n- When editing text from read_file tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: spaces + line number + tab. Everything after that tab is the actual file content to match. Never include any part of the line number prefix in the old_string or new_string.\n- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.\n- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.\n- The edit will FAIL if \`old_string\` is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use \`replace_all\` to change every instance of \`old_string\`. \n- Use \`replace_all\` for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.`,
26
+ `Performs exact string replacements in files.
27
+
28
+ Usage:
29
+ - You must use your \`${READ_TOOL_NAME}\` tool at least once in the conversation before editing. This tool will error if you attempt an edit without reading the file.
30
+ - When editing text from ${READ_TOOL_NAME} tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: spaces + line number + tab. Everything after that tab is the actual file content to match. Never include any part of the line number prefix in the old_string or new_string.
31
+ - ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
32
+ - Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.
33
+ - The edit will FAIL if \`old_string\` is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use \`replace_all\` to change every instance of \`old_string\`.
34
+ - Use \`replace_all\` for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.`,
27
35
  config: {
28
36
  type: "function",
29
37
  function: {