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
package/src/agent.ts DELETED
@@ -1,1271 +0,0 @@
1
- import { ForegroundTaskManager } from "./managers/foregroundTaskManager.js";
2
- import { MessageManager } from "./managers/messageManager.js";
3
- import { AIManager } from "./managers/aiManager.js";
4
- import { ToolManager } from "./managers/toolManager.js";
5
- import { SubagentManager } from "./managers/subagentManager.js";
6
- import { ForkedAgentManager } from "./managers/forkedAgentManager.js";
7
- import { McpManager } from "./managers/mcpManager.js";
8
- import { LspManager } from "./managers/lspManager.js";
9
- import { BangManager } from "./managers/bangManager.js";
10
- import { CronManager } from "./managers/cronManager.js";
11
- import { BackgroundTaskManager } from "./managers/backgroundTaskManager.js";
12
- import { MessageQueue, type QueuedMessage } from "./managers/messageQueue.js";
13
- import { SlashCommandManager } from "./managers/slashCommandManager.js";
14
- import { PluginManager } from "./managers/pluginManager.js";
15
- import { HookManager } from "./managers/hookManager.js";
16
- import { ReversionManager } from "./managers/reversionManager.js";
17
- import { PermissionManager } from "./managers/permissionManager.js";
18
- import { PlanManager } from "./managers/planManager.js";
19
- import {
20
- SlashCommand,
21
- CustomSlashCommand,
22
- ILspManager,
23
- AgentOptions,
24
- } from "./types/index.js";
25
- import type {
26
- Message,
27
- Logger,
28
- McpServerStatus,
29
- GatewayConfig,
30
- ModelConfig,
31
- Usage,
32
- PermissionMode,
33
- ForegroundTask,
34
- } from "./types/index.js";
35
- import { MemoryRuleManager } from "./managers/MemoryRuleManager.js";
36
- import { LiveConfigManager } from "./managers/liveConfigManager.js";
37
- import { configValidator } from "./utils/configValidator.js";
38
- import { SkillManager } from "./managers/skillManager.js";
39
- import { TaskManager } from "./services/taskManager.js";
40
- import type { WorktreeSession } from "./utils/worktreeSession.js";
41
- import path from "node:path";
42
- import { parseTaskNotificationXml } from "./utils/notificationXml.js";
43
- import { InitializationService } from "./services/initializationService.js";
44
- import { InteractionService } from "./services/interactionService.js";
45
- import { ConfigurationService } from "./services/configurationService.js";
46
- import { Container } from "./utils/container.js";
47
- import { setupAgentContainer } from "./utils/containerSetup.js";
48
- import {
49
- initializeTelemetry,
50
- shutdownTelemetry,
51
- } from "./telemetry/instrumentation.js";
52
- import { logOTelEvent } from "./telemetry/events.js";
53
- import { remoteSettingsService } from "./services/remoteSettingsService.js";
54
-
55
- export class Agent {
56
- private messageManager: MessageManager;
57
- private aiManager: AIManager;
58
-
59
- private bangManager: BangManager | null = null;
60
- private backgroundTaskManager: BackgroundTaskManager;
61
- private logger?: Logger; // Add optional logger property
62
- private toolManager: ToolManager; // Add tool registry instance
63
- private mcpManager: McpManager; // Add MCP manager instance
64
- private lspManager: ILspManager; // Add LSP manager instance
65
- private permissionManager: PermissionManager; // Add permission manager instance
66
- private planManager: PlanManager; // Add plan manager instance
67
- private subagentManager: SubagentManager; // Add subagent manager instance
68
- private forkedAgentManager: ForkedAgentManager; // Add forked agent manager instance
69
- private slashCommandManager: SlashCommandManager; // Add slash command manager instance
70
- private pluginManager: PluginManager; // Add plugin manager instance
71
- private skillManager: SkillManager; // Add skill manager instance
72
- private cronManager: CronManager; // Add cron manager instance
73
- private hookManager: HookManager; // Add hooks manager instance
74
- private reversionManager: ReversionManager;
75
- private messageQueue: MessageQueue; // Unified queue for messages, bang commands, and notifications
76
- private dispatchPromise: Promise<void> | null = null; // Track current dispatch for teardown
77
- private isAborting = false; // Guard: prevents tryDispatch from firing during abortMessage
78
- private memoryRuleManager: MemoryRuleManager; // Add memory rule manager instance
79
- private liveConfigManager: LiveConfigManager; // Add live configuration manager
80
- private taskManager: TaskManager;
81
- private foregroundTaskManager: ForegroundTaskManager;
82
- private container: Container;
83
- private configurationService: ConfigurationService; // Add configuration service
84
- private workdir: string; // Working directory
85
- private systemPrompt?: string; // Custom system prompt
86
- private stream: boolean; // Streaming mode flag
87
- private sessionStartTime: number = Date.now();
88
-
89
- // Configuration options storage for dynamic resolution
90
- private options: AgentOptions;
91
-
92
- // Dynamic configuration getter methods
93
- public getGatewayConfig(): GatewayConfig {
94
- return {
95
- ...this.configurationService.resolveGatewayConfig(),
96
- sessionId: this.messageManager.getSessionId(),
97
- };
98
- }
99
-
100
- public getModelConfig(): ModelConfig {
101
- return this.configurationService.resolveModelConfig(
102
- undefined,
103
- undefined,
104
- undefined,
105
- this.getPermissionMode(),
106
- );
107
- }
108
-
109
- public getMaxInputTokens(): number {
110
- return this.configurationService.resolveMaxInputTokens();
111
- }
112
-
113
- public getLanguage(): string | undefined {
114
- return this.configurationService.resolveLanguage();
115
- }
116
-
117
- /**
118
- * Set the active model for the agent session
119
- * @param model - The ID of the model to use
120
- */
121
- public setModel(model: string): void {
122
- this.configurationService.setModel(model);
123
- this.options.callbacks?.onModelChange?.(model);
124
- }
125
-
126
- /**
127
- * Get all configured models from settings.json and defaults
128
- * @returns Array of model IDs
129
- */
130
- public getConfiguredModels(): string[] {
131
- return this.configurationService.getConfiguredModels();
132
- }
133
-
134
- /**
135
- * Agent constructor - handles configuration resolution and validation
136
- *
137
- * IMPORTANT: This constructor is private. Use Agent.create() instead for proper
138
- * async initialization. Keep this constructor's signature exactly the same as
139
- * Agent.create() to maintain API consistency.
140
- *
141
- * @param options - Configuration options for the Agent instance
142
- */
143
- private constructor(options: AgentOptions) {
144
- const { logger, workdir, systemPrompt, stream = true } = options;
145
-
146
- // Set working directory early as we need it for loading configuration
147
- this.workdir = workdir || process.cwd();
148
-
149
- // Initialize configuration service
150
- this.configurationService = new ConfigurationService();
151
- this.configurationService.setOptions(options);
152
-
153
- this.logger = logger; // Save the passed logger
154
- this.systemPrompt = systemPrompt; // Save custom system prompt
155
- this.stream = stream; // Save streaming mode flag
156
-
157
- // Store options for dynamic configuration resolution
158
- this.options = options;
159
-
160
- this.container = setupAgentContainer({
161
- options,
162
- workdir: this.workdir,
163
- configurationService: this.configurationService,
164
- systemPrompt: this.systemPrompt,
165
- stream: this.stream,
166
- onTasksChange: (tasks) => {
167
- this.options.callbacks?.onTasksChange?.(tasks);
168
- },
169
- onBackgroundTasksChange: (tasks) => {
170
- this.options.callbacks?.onBackgroundTasksChange?.(tasks);
171
- },
172
- onPermissionModeChange: (mode) => {
173
- this.options.callbacks?.onPermissionModeChange?.(mode);
174
- },
175
- handlePlanModeTransition: (mode) => {
176
- this.planManager.handlePlanModeTransition(mode);
177
- },
178
- setPermissionMode: (mode) => {
179
- this.setPermissionMode(mode);
180
- },
181
- addPermissionRule: (rule) => this.addPermissionRule(rule),
182
- addUsage: (usage) => this.messageManager.addUsage(usage),
183
- });
184
-
185
- // Retrieve managers from container
186
- this.foregroundTaskManager = this.container.get("ForegroundTaskManager")!;
187
- this.memoryRuleManager = this.container.get("MemoryRuleManager")!;
188
- this.messageManager = this.container.get("MessageManager")!;
189
- this.taskManager = this.container.get("TaskManager")!;
190
- this.backgroundTaskManager = this.container.get("BackgroundTaskManager")!;
191
- this.mcpManager = this.container.get("McpManager")!;
192
- this.lspManager = this.container.get("LspManager")!;
193
- this.permissionManager = this.container.get("PermissionManager")!;
194
- this.planManager = this.container.get("PlanManager")!;
195
- this.hookManager = this.container.get("HookManager")!;
196
- this.skillManager = this.container.get("SkillManager")!;
197
- this.reversionManager = this.container.get("ReversionManager")!;
198
- this.toolManager = this.container.get("ToolManager")!;
199
- this.liveConfigManager = this.container.get("LiveConfigManager")!;
200
- this.subagentManager = this.container.get("SubagentManager")!;
201
- this.forkedAgentManager = this.container.get("ForkedAgentManager")!;
202
- this.aiManager = this.container.get("AIManager")!;
203
- this.slashCommandManager = this.container.get("SlashCommandManager")!;
204
- this.pluginManager = this.container.get("PluginManager")!;
205
- this.bangManager = this.container.get("BangManager")!;
206
- this.cronManager = this.container.get("CronManager")!;
207
- this.messageQueue = this.container.get("MessageQueue")!;
208
-
209
- // Wire up CWD change callback from AIManager to sync Agent's workdir
210
- this.aiManager.setOnCwdChange((newCwd) => {
211
- this.workdir = newCwd;
212
- this.container.register("Workdir", newCwd);
213
- this.options.callbacks?.onWorkdirChange?.(newCwd);
214
- });
215
-
216
- // Wire up message queue to process when agent becomes idle.
217
- // Both user messages and background task notifications enqueue here,
218
- // unified through a single dispatch path (tryDispatch).
219
- this.messageQueue.onMessageEnqueued = () => this.tryDispatch();
220
-
221
- // Wire up AI loading changes to process queue when AI becomes idle
222
- this.aiManager.onLoadingChange = (loading: boolean) => {
223
- if (!loading) this.tryDispatch();
224
- };
225
-
226
- // Wire up bang manager callback for command running changes
227
- this.bangManager.onCommandRunningChange = (running: boolean) => {
228
- this.options.callbacks?.onCommandRunningChange?.(running);
229
- if (!running) this.tryDispatch();
230
- };
231
-
232
- // Set initial permission mode if provided
233
- if (options.permissionMode) {
234
- this.setPermissionMode(options.permissionMode);
235
- }
236
- }
237
-
238
- // Public getter methods
239
- public get sessionId(): string {
240
- return this.messageManager.getSessionId();
241
- }
242
-
243
- public get messages(): Message[] {
244
- return this.messageManager.getMessages();
245
- }
246
-
247
- public get usages(): Usage[] {
248
- return this.messageManager.getUsages();
249
- }
250
-
251
- public get sessionFilePath(): string {
252
- return this.messageManager.getTranscriptPath();
253
- }
254
-
255
- public get latestTotalTokens(): number {
256
- return this.messageManager.getLatestTotalTokens();
257
- }
258
-
259
- /** Get working directory */
260
- public get workingDirectory(): string {
261
- return this.workdir;
262
- }
263
-
264
- /**
265
- * Set the working directory
266
- * @param newCwd - The new working directory
267
- */
268
- public setWorkdir(newCwd: string): void {
269
- this.workdir = newCwd;
270
- this.container.register("Workdir", newCwd);
271
- this.options.callbacks?.onWorkdirChange?.(newCwd);
272
- }
273
-
274
- /**
275
- * Set this session's worktree session state (used by CLI -w startup to inject the
276
- * session created before the agent existed). Delegates to AIManager so the state is
277
- * stored in this agent's own DI container.
278
- */
279
- public setWorktreeSession(session: WorktreeSession | null): void {
280
- this.aiManager.setWorktreeSession(session);
281
- }
282
-
283
- /** Get project memory content */
284
- public get projectMemory(): string {
285
- const memoryService =
286
- this.container.get<import("./services/memory.js").MemoryService>(
287
- "MemoryService",
288
- );
289
- return memoryService?.cachedProjectMemory ?? "";
290
- }
291
-
292
- /** Get user memory content */
293
- public get userMemory(): string {
294
- const memoryService =
295
- this.container.get<import("./services/memory.js").MemoryService>(
296
- "MemoryService",
297
- );
298
- return memoryService?.cachedUserMemory ?? "";
299
- }
300
-
301
- /** Get combined memory content (project + user + modular rules) */
302
- public async getCombinedMemory(): Promise<string> {
303
- return this.messageManager.getCombinedMemory();
304
- }
305
-
306
- /** Get AI loading status */
307
- public get isLoading(): boolean {
308
- return this.aiManager.isLoading;
309
- }
310
-
311
- /** Get message compaction status */
312
- public get isCompacting(): boolean {
313
- return this.aiManager.getIsCompacting();
314
- }
315
-
316
- /** Get bash command execution status */
317
- public get isCommandRunning(): boolean {
318
- return this.bangManager?.isCommandRunning ?? false;
319
- }
320
-
321
- /** Get queued user-facing messages (excludes background notifications) */
322
- public get queuedMessages(): QueuedMessage[] {
323
- return this.messageQueue
324
- .getQueue()
325
- .filter((m) => m.type !== "notification");
326
- }
327
-
328
- /**
329
- * Remove a queued message by index
330
- * @param index - The index of the message to remove
331
- * @returns true if the message was removed, false if the index was out of bounds
332
- */
333
- public removeQueuedMessage(index: number): boolean {
334
- const removed = this.messageQueue.removeAt(index);
335
- if (removed) {
336
- this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
337
- }
338
- return removed;
339
- }
340
-
341
- /**
342
- * Recall the last editable queued message (for inline editing)
343
- * @returns The recalled message, or null if no editable message exists
344
- */
345
- public recallQueuedMessage(): QueuedMessage | null {
346
- const msg = this.messageQueue.popLastEditable();
347
- if (msg) {
348
- this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
349
- }
350
- return msg;
351
- }
352
-
353
- /**
354
- * Remove a queued message by its ID
355
- * @param id - The ID of the message to remove
356
- * @returns true if the message was removed, false if not found
357
- */
358
- public removeQueuedMessageById(id: string): boolean {
359
- const removed = this.messageQueue.removeById(id);
360
- if (removed) {
361
- this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
362
- }
363
- return removed;
364
- }
365
-
366
- /**
367
- * Update a queued message's content by its ID (does not change queue order)
368
- * @param id - The ID of the message to update
369
- * @param patch - The fields to update
370
- * @returns true if the message was updated, false if not found
371
- */
372
- public updateQueuedMessageById(
373
- id: string,
374
- patch: {
375
- content?: string;
376
- images?: Array<{ path: string; mimeType: string }>;
377
- type?: "message" | "bang";
378
- },
379
- ): boolean {
380
- const updated = this.messageQueue.updateById(id, patch);
381
- if (updated) {
382
- this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
383
- }
384
- return updated;
385
- }
386
-
387
- /**
388
- * Unified dispatch trigger — checks state machine before processing.
389
- * Handles user messages, bang commands, and background task notifications
390
- * through a single serialized path. Called from onMessageEnqueued,
391
- * onLoadingChange(false), and onCommandRunningChange(false).
392
- */
393
- private tryDispatch(): void {
394
- if (this.isAborting) return; // Suppress dispatch during abort to prevent queued notifications from being dispatched as a side-effect
395
- if (this.messageQueue.state !== "idle") return;
396
- if (!this.messageQueue.hasPending()) return;
397
- if (this.aiManager.isLoading || this.isCommandRunning) return;
398
-
399
- this.messageQueue.transitionTo("dispatching");
400
- this.dispatchPromise = this.processQueuedMessage()
401
- .catch((error) => {
402
- this.logger?.error("Failed to process queued message:", error);
403
- })
404
- .finally(() => {
405
- this.messageQueue.transitionTo("idle");
406
- this.dispatchPromise = null;
407
- this.tryDispatch(); // Re-check after processing
408
- });
409
- }
410
-
411
- /**
412
- * Process the next queued item when the agent becomes idle.
413
- * Handles three item types: user messages, bang commands, and notifications.
414
- */
415
- private async processQueuedMessage(): Promise<void> {
416
- const next = this.messageQueue.dequeue();
417
- if (!next) return;
418
-
419
- this.messageQueue.transitionTo("running");
420
- this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
421
-
422
- if (next.type === "bang") {
423
- await this.bangManager?.executeCommand(next.content);
424
- await this.messageManager.saveSession();
425
- } else if (next.type === "notification") {
426
- // Drain all pending notifications and batch them into a single AI turn
427
- const allNotifications = [
428
- next.content,
429
- ...this.messageQueue.drainNotifications(),
430
- ];
431
- for (const xml of allNotifications) {
432
- const block = parseTaskNotificationXml(xml);
433
- if (block) {
434
- this.messageManager.addNotificationMessage({
435
- taskId: block.taskId,
436
- taskType: block.taskType,
437
- status: block.status,
438
- summary: block.summary,
439
- outputFile: block.outputFile,
440
- });
441
- }
442
- }
443
- await this.aiManager.sendAIMessage({ recursionDepth: 0 });
444
- } else {
445
- await this.sendMessage(next.content, next.images);
446
- }
447
- }
448
-
449
- /** Get background bash shell output */
450
- public getBackgroundShellOutput(
451
- id: string,
452
- filter?: string,
453
- ): {
454
- stdout: string;
455
- stderr: string;
456
- status: string;
457
- outputPath?: string;
458
- type: string;
459
- exitCode?: number;
460
- } | null {
461
- return this.backgroundTaskManager.getOutput(id, filter);
462
- }
463
-
464
- /** Kill background bash shell */
465
- public killBackgroundShell(id: string): boolean {
466
- return this.backgroundTaskManager.stopTask(id);
467
- }
468
-
469
- /** Get background task output */
470
- public getBackgroundTaskOutput(
471
- id: string,
472
- filter?: string,
473
- ): {
474
- stdout: string;
475
- stderr: string;
476
- status: string;
477
- outputPath?: string;
478
- type: string;
479
- exitCode?: number;
480
- } | null {
481
- return this.backgroundTaskManager.getOutput(id, filter);
482
- }
483
-
484
- /** Stop background task */
485
- public stopBackgroundTask(id: string): boolean {
486
- return this.backgroundTaskManager.stopTask(id);
487
- }
488
-
489
- /** Get all workflow runs */
490
- public async getWorkflowRuns(): Promise<
491
- import("./workflow/types.js").WorkflowRun[]
492
- > {
493
- if (!this.container.has("WorkflowManager")) return [];
494
- return this.container
495
- .get<
496
- import("./managers/workflowManager.js").WorkflowManager
497
- >("WorkflowManager")!
498
- .listRuns();
499
- }
500
-
501
- /** Get a specific workflow run by ID */
502
- public getWorkflowRun(
503
- runId: string,
504
- ): import("./workflow/types.js").WorkflowRun | undefined {
505
- if (!this.container.has("WorkflowManager")) return undefined;
506
- return this.container
507
- .get<
508
- import("./managers/workflowManager.js").WorkflowManager
509
- >("WorkflowManager")!
510
- .getRun(runId);
511
- }
512
-
513
- /** Stop a workflow run */
514
- public stopWorkflowRun(runId: string): void {
515
- if (!this.container.has("WorkflowManager")) return;
516
- this.container
517
- .get<
518
- import("./managers/workflowManager.js").WorkflowManager
519
- >("WorkflowManager")!
520
- .stopRun(runId);
521
- }
522
-
523
- /**
524
- * Static async factory method for creating Agent instances
525
- *
526
- * IMPORTANT: Keep this method's signature exactly the same as the constructor
527
- * to maintain consistency and avoid confusion for users of the API.
528
- *
529
- * @param options - Same AgentOptions interface used by constructor
530
- * @returns Promise<Agent> - Fully initialized Agent instance
531
- */
532
- /**
533
- * Create a new Agent instance with async initialization
534
- *
535
- * This is the recommended way to create Agent instances. The constructor is private
536
- * to ensure proper async initialization of all components.
537
- *
538
- * @param options - Configuration options for the Agent instance
539
- * @param options.apiKey - API key for the AI service (or set WAVE_API_KEY env var)
540
- * @param options.baseURL - Base URL for the AI service (or set WAVE_BASE_URL env var)
541
- * @param options.defaultHeaders - Optional HTTP headers to pass to the AI service
542
- * @param options.fetchOptions - Optional fetch options to pass to the AI service
543
- * @param options.fetch - Optional custom fetch implementation
544
- * @param options.callbacks - Optional callbacks for various Agent events
545
- * @param options.restoreSessionId - Optional session ID to restore from
546
- * @param options.continueLastSession - Whether to continue the last session automatically
547
- * @param options.logger - Optional custom logger implementation
548
- * @param options.messages - Optional initial messages for testing convenience
549
- * @param options.workdir - Working directory (defaults to process.cwd())
550
- * @param options.systemPrompt - Optional custom system prompt
551
- * @param options.mcpServers - Optional MCP server configs to connect at startup
552
- * @returns Promise that resolves to initialized Agent instance
553
- *
554
- * @example
555
- * ```typescript
556
- * // Basic usage
557
- * const agent = await Agent.create({
558
- * apiKey: 'your-api-key',
559
- * baseURL: 'https://api.example.com'
560
- * });
561
- * ```
562
- */
563
- static async create(options: AgentOptions): Promise<Agent> {
564
- // Create Agent instance
565
- const instance = new Agent(options);
566
- await instance.initialize({
567
- restoreSessionId: options.restoreSessionId,
568
- continueLastSession: options.continueLastSession,
569
- messages: options.messages,
570
- });
571
- return instance;
572
- }
573
-
574
- /**
575
- * Resolve and validate configuration from constructor args, environment variables,
576
- * and loaded settings.json.
577
- *
578
- * This is called during initialization after settings.json has been loaded.
579
- */
580
- private resolveAndValidateConfig(): void {
581
- // Resolve configuration from constructor args and environment variables (including settings.json)
582
- const gatewayConfig = this.getGatewayConfig();
583
- const maxInputTokens = this.getMaxInputTokens();
584
-
585
- // Validate resolved configuration
586
- configValidator.validateGatewayConfig(gatewayConfig);
587
- configValidator.validateMaxInputTokens(maxInputTokens);
588
- }
589
-
590
- /** Private initialization method, handles async initialization logic */
591
- private async initialize(options?: {
592
- restoreSessionId?: string;
593
- continueLastSession?: boolean;
594
- messages?: Message[];
595
- }): Promise<void> {
596
- await InitializationService.initialize(
597
- {
598
- skillManager: this.skillManager,
599
- subagentManager: this.subagentManager,
600
- container: this.container,
601
- toolManager: this.toolManager,
602
- pluginManager: this.pluginManager,
603
- options: this.options,
604
- slashCommandManager: this.slashCommandManager,
605
- logger: this.logger,
606
- mcpManager: this.mcpManager,
607
- workdir: this.workdir,
608
- lspManager: this.lspManager,
609
- configurationService: this.configurationService,
610
- hookManager: this.hookManager,
611
- messageManager: this.messageManager,
612
- memoryRuleManager: this.memoryRuleManager,
613
- liveConfigManager: this.liveConfigManager,
614
- taskManager: this.taskManager,
615
- resolveAndValidateConfig: () => this.resolveAndValidateConfig(),
616
- },
617
- options,
618
- );
619
-
620
- // Initialize OpenTelemetry (awaited to ensure ALS context is ready before
621
- // startInteractionSpan is called, preventing trace context fragmentation)
622
- const telemetryConfig = this.configurationService.resolveTelemetryConfig();
623
- try {
624
- await initializeTelemetry(telemetryConfig);
625
- } catch (error) {
626
- this.logger?.warn("Telemetry initialization failed:", error);
627
- }
628
-
629
- // Log session_start event
630
- try {
631
- const modelConfig = this.getModelConfig();
632
- if (modelConfig.model) {
633
- await logOTelEvent("session_start", {
634
- sessionId: this.messageManager.getSessionId(),
635
- model: modelConfig.model,
636
- workdir: this.workdir,
637
- }).catch(() => {}); // Non-blocking
638
- }
639
- } catch {
640
- // Model not configured yet - will be available after SSO login
641
- }
642
- }
643
-
644
- /**
645
- * Restore a session by ID, switching to the target session without destroying the Agent instance
646
- * @param sessionId - The ID of the session to restore
647
- */
648
- public async restoreSession(sessionId: string): Promise<void> {
649
- await InteractionService.restoreSession(
650
- {
651
- messageManager: this.messageManager,
652
- slashCommandManager: this.slashCommandManager,
653
- hookManager: this.hookManager,
654
- workdir: this.workdir,
655
- configurationService: this.configurationService,
656
- logger: this.logger,
657
- aiManager: this.aiManager,
658
- subagentManager: this.subagentManager,
659
- taskManager: this.taskManager,
660
- options: this.options,
661
- abortMessage: () => this.abortMessage(),
662
- },
663
- sessionId,
664
- );
665
- }
666
-
667
- public abortAIMessage(): void {
668
- this.aiManager.abortAIMessage();
669
- }
670
-
671
- /** Execute bash command (bang command) */
672
- public async bang(command: string): Promise<void> {
673
- // If the agent is busy, enqueue the bang command
674
- if (this.aiManager.isLoading || this.isCommandRunning) {
675
- this.messageQueue.enqueue({ type: "bang", content: command });
676
- this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
677
- return;
678
- }
679
-
680
- await this.bangManager?.executeCommand(command);
681
- await this.messageManager.saveSession();
682
- }
683
-
684
- public async clearMessages(): Promise<void> {
685
- // Aligned with webview (ChatApp handleClearChat): /clear is ignored
686
- // while the agent is running, instead of aborting the AI mid-turn
687
- // (which used to inject a late "Request was aborted" error into the
688
- // newly cleared session).
689
- if (this.aiManager.isLoading) {
690
- return;
691
- }
692
-
693
- this.aiManager.abortAIMessage();
694
-
695
- // Capture old session info before clearing
696
- const oldSessionId = this.messageManager.getSessionId();
697
- const transcriptPath = this.messageManager.getTranscriptPath();
698
-
699
- // Run SessionEnd hooks (cleanup before clear)
700
- try {
701
- await this.hookManager.executeSessionEndHooks(
702
- "clear",
703
- oldSessionId,
704
- transcriptPath,
705
- );
706
- } catch (error) {
707
- this.logger?.warn(
708
- `SessionEnd hooks on clear failed: ${(error as Error).message}`,
709
- );
710
- }
711
-
712
- // Clear messages and generate new session
713
- this.messageManager.clearMessages();
714
- await this.taskManager.syncWithSession();
715
-
716
- // Run SessionStart hooks (restore context for new session)
717
- try {
718
- const newSessionId = this.messageManager.getSessionId();
719
- const sessionStartResult =
720
- await this.hookManager.executeSessionStartHooks(
721
- "clear",
722
- newSessionId,
723
- this.messageManager.getTranscriptPath(),
724
- );
725
-
726
- // Inject additionalContext as a meta user message
727
- if (sessionStartResult.additionalContext) {
728
- this.messageManager.addUserMessage({
729
- content: `<system-reminder>\nSessionStart hook additional context: ${sessionStartResult.additionalContext}\n</system-reminder>`,
730
- isMeta: true,
731
- });
732
- }
733
-
734
- // Inject initialUserMessage as a meta user message
735
- if (sessionStartResult.initialUserMessage) {
736
- this.messageManager.addUserMessage({
737
- content: sessionStartResult.initialUserMessage,
738
- isMeta: true,
739
- });
740
- }
741
- } catch (error) {
742
- this.logger?.warn(
743
- `SessionStart hooks on clear failed: ${(error as Error).message}`,
744
- );
745
- }
746
-
747
- await this.messageManager.saveSession();
748
- }
749
-
750
- /**
751
- * Compact conversation history to reduce context usage
752
- * @param customInstructions - Optional custom instructions for compaction
753
- */
754
- public async compact(customInstructions?: string): Promise<void> {
755
- // Aligned with webview: /compact is ignored while the agent is running,
756
- // instead of aborting the AI mid-turn before compacting.
757
- if (this.aiManager.isLoading) {
758
- return;
759
- }
760
-
761
- this.aiManager.abortAIMessage();
762
-
763
- await this.aiManager.compactConversation({
764
- customInstructions,
765
- });
766
-
767
- await this.messageManager.saveSession();
768
- }
769
-
770
- /** Unified interrupt method, interrupts both AI messages and command execution */
771
- public abortMessage(): void {
772
- // Guard: prevent tryDispatch (triggered by abortAIMessage → setIsLoading(false))
773
- // from dispatching preserved notifications as a new AI turn during the abort.
774
- this.isAborting = true;
775
- try {
776
- if (this.aiManager.isLoading || this.isCommandRunning) {
777
- // Clear user-facing queue items first to prevent processQueuedMessage
778
- // from dequeuing when abortAIMessage triggers onLoadingChange(false).
779
- // Notifications are preserved so background task results aren't lost.
780
- this.messageQueue.clear();
781
- this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
782
- }
783
- this.messageQueue.transitionTo("idle"); // Reset state on abort
784
- this.abortAIMessage(); // This will abort tools including Agent tool (subagents)
785
- this.abortBashCommand();
786
- this.abortSlashCommand();
787
- } finally {
788
- this.isAborting = false;
789
- }
790
- }
791
-
792
- /** Interrupt bash command execution */
793
- public abortBashCommand(): void {
794
- this.bangManager?.abortCommand();
795
- }
796
-
797
- /** Interrupt slash command execution */
798
- public abortSlashCommand(): void {
799
- this.slashCommandManager.abortCurrentCommand();
800
- }
801
-
802
- /**
803
- * Register a foreground task that can be backgrounded
804
- */
805
- public registerForegroundTask(task: ForegroundTask): void {
806
- this.foregroundTaskManager.registerForegroundTask(task);
807
- }
808
-
809
- /**
810
- * Unregister a foreground task
811
- */
812
- public unregisterForegroundTask(id: string): void {
813
- this.foregroundTaskManager.unregisterForegroundTask(id);
814
- }
815
-
816
- /**
817
- * Background the current foreground task
818
- */
819
- public async backgroundCurrentTask(): Promise<void> {
820
- await this.foregroundTaskManager.backgroundCurrentTask();
821
- this.options.callbacks?.onBackgroundCurrentTask?.();
822
- }
823
-
824
- /**
825
- * Trigger WorktreeRemove hook before agent destruction.
826
- * Called from CLI exit dialog when user chooses to remove the worktree.
827
- * Non-blocking: errors logged but don't prevent removal.
828
- */
829
- public async triggerWorktreeRemoveHook(worktreePath: string): Promise<void> {
830
- if (!this.hookManager.hasHooks("WorktreeRemove")) {
831
- return;
832
- }
833
- try {
834
- const sessionId = this.messageManager.getSessionId();
835
- const transcriptPath = this.messageManager.getTranscriptPath();
836
- const hookResults = await this.hookManager.executeHooks(
837
- "WorktreeRemove",
838
- {
839
- event: "WorktreeRemove",
840
- projectDir: this.workdir,
841
- timestamp: new Date(),
842
- sessionId,
843
- transcriptPath,
844
- cwd: this.workdir,
845
- worktreePath,
846
- env: Object.fromEntries(
847
- Object.entries(this.configurationService.getMergedEnv()).filter(
848
- (e) => e[1] !== undefined,
849
- ),
850
- ) as Record<string, string>,
851
- },
852
- );
853
- // Process results via messageManager (may not be visible during shutdown)
854
- this.hookManager.processHookResults(
855
- "WorktreeRemove",
856
- hookResults,
857
- this.messageManager,
858
- );
859
- } catch (error) {
860
- this.logger?.warn("WorktreeRemove hooks execution failed:", error);
861
- }
862
- }
863
-
864
- /** Destroy managers, clean up resources */
865
- public async destroy(): Promise<void> {
866
- // Log session_end event and shutdown telemetry
867
- await logOTelEvent("session_end", {
868
- duration: String(Math.round((Date.now() - this.sessionStartTime) / 1000)),
869
- totalTokens: String(this.messageManager.getLatestTotalTokens()),
870
- exitReason: "destroy",
871
- }).catch(() => {});
872
- await shutdownTelemetry();
873
-
874
- // Clear message queue callback first to prevent any late triggers from
875
- // starting async work during teardown
876
- this.messageQueue.onMessageEnqueued = undefined;
877
-
878
- // Await any pending dispatch to prevent race conditions
879
- // with test teardown (e.g., V8 coverage stream cleanup)
880
- if (this.dispatchPromise) {
881
- await this.dispatchPromise.catch(() => {});
882
- }
883
-
884
- // Fire SessionEnd hooks (fire-and-forget, don't block shutdown)
885
- try {
886
- const sessionId = this.messageManager.getSessionId();
887
- const transcriptPath = this.messageManager.getTranscriptPath();
888
- await this.hookManager.executeSessionEndHooks(
889
- "stop",
890
- sessionId,
891
- transcriptPath,
892
- );
893
- } catch (error) {
894
- this.logger?.warn(`SessionEnd hooks failed: ${(error as Error).message}`);
895
- }
896
-
897
- await this.messageManager.saveSession();
898
- this.abortAIMessage(); // This will abort tools including Agent tool (subagents)
899
- this.abortBashCommand();
900
- this.abortSlashCommand();
901
- this.cronManager.stop();
902
- // Cleanup background task manager
903
- this.backgroundTaskManager.cleanup();
904
- // Cleanup MCP connections
905
- await this.mcpManager.cleanup();
906
- // Cleanup LSP connections
907
- if (this.lspManager instanceof LspManager) {
908
- await this.lspManager.cleanup();
909
- }
910
- // Cleanup subagent manager
911
- this.subagentManager.cleanup();
912
- // Cleanup forked agent manager
913
- await this.forkedAgentManager.cleanup();
914
- // Drain an in-flight auto-memory extraction fork so the process doesn't
915
- // exit mid-extraction
916
- try {
917
- const autoMemoryService =
918
- this.container.get<
919
- import("./services/autoMemoryService.js").AutoMemoryService
920
- >("AutoMemoryService");
921
- if (autoMemoryService) {
922
- await autoMemoryService.drain();
923
- }
924
- } catch (error) {
925
- this.logger?.warn(
926
- `Auto-memory extraction drain failed: ${(error as Error).message}`,
927
- );
928
- }
929
- // Cleanup skill manager
930
- await this.skillManager.destroy();
931
- // Cleanup live configuration reload
932
- try {
933
- await this.liveConfigManager.shutdown();
934
- } catch (error) {
935
- this.logger?.error(
936
- "Error shutting down live configuration reload:",
937
- error,
938
- );
939
- }
940
- // Cleanup remote settings polling
941
- remoteSettingsService.shutdown();
942
- // Cleanup memory store
943
- }
944
-
945
- /**
946
- * Trigger the rewind UI callback
947
- */
948
- public triggerShowRewind(): void {
949
- this.messageManager.triggerShowRewind();
950
- }
951
-
952
- /**
953
- * Ask a side question without interrupting the main agent.
954
- *
955
- * Runs a single-turn fork of the conversation that reuses the main loop's
956
- * system prompt, tools, and memory injection (prompt-cache friendly) and
957
- * never executes tools. The abort signal lets the UI cancel a pending
958
- * question (loading dismiss); aborts propagate as thrown errors.
959
- *
960
- * @param question - The side question to ask
961
- * @param abortSignal - Optional signal to abort the pending request
962
- * @returns Promise that resolves to the AI's answer
963
- */
964
- public async askBtw(
965
- question: string,
966
- abortSignal?: AbortSignal,
967
- onContent?: (content: string) => void,
968
- onReasoning?: (content: string) => void,
969
- ): Promise<string> {
970
- const result = await this.aiManager.runBtwFork(
971
- question,
972
- abortSignal,
973
- onContent,
974
- onReasoning,
975
- );
976
- return result.content ?? result.error ?? "No response received";
977
- }
978
-
979
- /**
980
- * Send a message to the AI agent with optional images
981
- *
982
- * @param content - The text content of the message to send
983
- * @param images - Optional array of images to include with the message
984
- * @param images[].path - File path to the image or base64 encoded image data
985
- * @param images[].mimeType - MIME type of the image (e.g., 'image/png', 'image/jpeg')
986
- * @returns Promise that resolves when the message has been processed
987
- *
988
- * @example
989
- * ```typescript
990
- * // Send a text message
991
- * await agent.sendMessage("Hello, how are you?");
992
- *
993
- * // Send a message with images using file paths
994
- * await agent.sendMessage("What do you see in these images?", [
995
- * { path: "/path/to/image.png", mimeType: "image/png" },
996
- * { path: "/path/to/photo.jpg", mimeType: "image/jpeg" }
997
- * ]);
998
- *
999
- * // Send a message with base64 encoded image
1000
- * await agent.sendMessage("Analyze this image", [
1001
- * { path: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...", mimeType: "image/png" }
1002
- * ]);
1003
- * ```
1004
- */
1005
- public async sendMessage(
1006
- content: string,
1007
- images?: Array<{ path: string; mimeType: string }>,
1008
- ): Promise<void> {
1009
- // If the agent is busy, enqueue the message — unless it's an immediate
1010
- // slash command (e.g., /clear, /compact) that should execute
1011
- // right away even while AI is processing
1012
- if (this.aiManager.isLoading || this.isCommandRunning) {
1013
- const trimmed = content.trim();
1014
- const isImmediate =
1015
- trimmed.startsWith("/") &&
1016
- trimmed !== "/" &&
1017
- this.slashCommandManager.isImmediateCommand(trimmed);
1018
-
1019
- if (!isImmediate) {
1020
- this.messageQueue.enqueue({
1021
- content,
1022
- images,
1023
- });
1024
- this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
1025
- return;
1026
- }
1027
- // Immediate slash command: fall through to InteractionService
1028
- }
1029
-
1030
- await InteractionService.sendMessage(
1031
- {
1032
- messageManager: this.messageManager,
1033
- slashCommandManager: this.slashCommandManager,
1034
- hookManager: this.hookManager,
1035
- workdir: this.workdir,
1036
- configurationService: this.configurationService,
1037
- logger: this.logger,
1038
- aiManager: this.aiManager,
1039
- subagentManager: this.subagentManager,
1040
- taskManager: this.taskManager,
1041
- options: this.options,
1042
- abortMessage: () => this.abortMessage(),
1043
- },
1044
- content,
1045
- images,
1046
- );
1047
- }
1048
-
1049
- // ========== Additional Directories ==========
1050
-
1051
- /**
1052
- * Add an additional directory to the Safe Zone for this session.
1053
- *
1054
- * The directory immediately becomes part of the Safe Zone (permission
1055
- * checks and the system prompt), and with `remember: true` it is also
1056
- * persisted to `.wave/settings.local.json` `permissions.additionalDirectories`
1057
- * so future sessions pick it up automatically.
1058
- *
1059
- * @param directory Path of the directory to add (relative paths resolve against the workdir).
1060
- * @param options.remember Whether to persist the directory across sessions.
1061
- */
1062
- public async addAdditionalDirectory(
1063
- directory: string,
1064
- options?: { remember?: boolean },
1065
- ): Promise<void> {
1066
- const resolvedPath = path.isAbsolute(directory)
1067
- ? directory
1068
- : path.resolve(this.workdir, directory);
1069
- this.permissionManager.addInstanceAdditionalDirectory(resolvedPath);
1070
- if (options?.remember) {
1071
- await this.configurationService.addAdditionalDirectory(
1072
- this.workdir,
1073
- resolvedPath,
1074
- );
1075
- }
1076
- }
1077
-
1078
- /**
1079
- * Get the effective additional directories for this session (config +
1080
- * session-level union), matching what is listed in the system prompt.
1081
- */
1082
- public getAdditionalDirectories(): string[] {
1083
- return this.permissionManager.getEffectiveAdditionalDirectories();
1084
- }
1085
-
1086
- // ========== MCP Management Methods ==========
1087
-
1088
- /** Get all MCP server status */
1089
- public getMcpServers(): McpServerStatus[] {
1090
- return this.mcpManager.getAllServers();
1091
- }
1092
-
1093
- /** Connect MCP server */
1094
- public async connectMcpServer(serverName: string): Promise<boolean> {
1095
- return await this.mcpManager.connectServer(serverName);
1096
- }
1097
-
1098
- /** Disconnect MCP server */
1099
- public async disconnectMcpServer(serverName: string): Promise<boolean> {
1100
- return await this.mcpManager.disconnectServer(serverName);
1101
- }
1102
-
1103
- // ========== Slash Command Management Methods ==========
1104
-
1105
- /** Get all available slash commands */
1106
- public getSlashCommands(): SlashCommand[] {
1107
- return this.slashCommandManager.getCommands();
1108
- }
1109
-
1110
- /** Check if slash command exists */
1111
- public hasSlashCommand(commandId: string): boolean {
1112
- return this.slashCommandManager.hasCommand(commandId);
1113
- }
1114
-
1115
- /** Reload custom commands */
1116
- public async reloadCustomCommands(): Promise<void> {
1117
- await this.skillManager.initialize();
1118
- this.slashCommandManager.reloadCustomCommands();
1119
- this.slashCommandManager.registerSkillCommands(
1120
- this.skillManager.getAvailableSkills(),
1121
- );
1122
- }
1123
-
1124
- /** Get custom command details */
1125
- public getCustomCommand(commandId: string): CustomSlashCommand | undefined {
1126
- return this.slashCommandManager.getCustomCommand(commandId);
1127
- }
1128
-
1129
- /** Get all custom commands */
1130
- public getCustomCommands(): CustomSlashCommand[] {
1131
- return this.slashCommandManager.getCustomCommands();
1132
- }
1133
-
1134
- /**
1135
- * Register a custom slash command
1136
- */
1137
- public registerSlashCommand(command: SlashCommand): void {
1138
- this.slashCommandManager.registerCommand(command);
1139
- }
1140
-
1141
- /**
1142
- * Get the current permission mode
1143
- */
1144
- public getPermissionMode(): PermissionMode {
1145
- return this.toolManager.getPermissionMode();
1146
- }
1147
-
1148
- /**
1149
- * Get the names of tools currently visible to the AI model (filtered by
1150
- * permission mode, denied rules, etc.)
1151
- */
1152
- public getAvailableToolNames(): string[] {
1153
- return this.toolManager.getToolsConfig().map((t) => t.function.name);
1154
- }
1155
-
1156
- /**
1157
- * Set the permission mode
1158
- * @param mode - The new permission mode
1159
- */
1160
- public setPermissionMode(mode: PermissionMode): void {
1161
- this.logger?.debug("Setting permission mode", { mode });
1162
- this.toolManager.setPermissionMode(mode);
1163
-
1164
- this.planManager.handlePlanModeTransition(mode);
1165
-
1166
- this.options.callbacks?.onPermissionModeChange?.(mode);
1167
- }
1168
-
1169
- /**
1170
- * Truncate history to a specific index and revert file changes.
1171
- * @param index - The index of the user message to truncate to.
1172
- */
1173
- public async truncateHistory(index: number): Promise<void> {
1174
- await this.messageManager.truncateHistory(index, this.reversionManager);
1175
- // After truncating history, the task list might have changed, so refresh it.
1176
- // We explicitly load tasks and trigger the callback to ensure UI updates immediately and in order.
1177
- const tasks = await this.taskManager.listTasks();
1178
- this.options.callbacks?.onTasksChange?.(tasks);
1179
- }
1180
-
1181
- /**
1182
- * Get the full message thread including parent sessions
1183
- */
1184
- public async getFullMessageThread(): Promise<{
1185
- messages: Message[];
1186
- sessionIds: string[];
1187
- }> {
1188
- return this.messageManager.getFullMessageThread();
1189
- }
1190
-
1191
- /**
1192
- * Get the current plan file path (for testing and UI)
1193
- */
1194
- public getPlanFilePath(): string | undefined {
1195
- return this.permissionManager.getPlanFilePath();
1196
- }
1197
-
1198
- /**
1199
- * Get all currently allowed rules (user-defined and default)
1200
- */
1201
- public getAllowedRules(): string[] {
1202
- return [
1203
- ...this.permissionManager.getAllowedRules(),
1204
- ...this.permissionManager.getDefaultAllowedRules(),
1205
- ];
1206
- }
1207
-
1208
- /**
1209
- * Get only user-defined allowed rules
1210
- */
1211
- public getUserAllowedRules(): string[] {
1212
- return this.permissionManager.getAllowedRules();
1213
- }
1214
-
1215
- /**
1216
- * Check permission for a tool call (for testing)
1217
- */
1218
- public async checkPermission(
1219
- context: import("./types/permissions.js").ToolPermissionContext,
1220
- ): Promise<import("./types/permissions.js").PermissionDecision> {
1221
- return this.permissionManager.checkPermission(context);
1222
- }
1223
-
1224
- /**
1225
- * Add a persistent permission rule
1226
- * @param rule - The rule to add (e.g., "Bash(ls)")
1227
- */
1228
- public async addPermissionRule(rule: string): Promise<void> {
1229
- await this.permissionManager.addPermissionRule(rule);
1230
- }
1231
-
1232
- /**
1233
- * Get subagent instance by ID
1234
- * @param subagentId - The ID of the subagent instance
1235
- * @returns The subagent instance or null if not found
1236
- */
1237
- public getSubagentInstance(
1238
- subagentId: string,
1239
- ): import("./managers/subagentManager.js").SubagentInstance | null {
1240
- return this.subagentManager.getInstance(subagentId);
1241
- }
1242
-
1243
- /**
1244
- * Get the current task list ID
1245
- */
1246
- public get taskListId(): string {
1247
- return this.taskManager.getTaskListId();
1248
- }
1249
-
1250
- /**
1251
- * Check if there are any running background tasks or active subagents
1252
- */
1253
- public get hasRunningBackgroundWork(): boolean {
1254
- const runningTasks = this.backgroundTaskManager
1255
- .getAllTasks()
1256
- .some((t) => t.status === "running");
1257
- const activeSubagents =
1258
- this.subagentManager.getActiveInstances().length > 0;
1259
- return runningTasks || activeSubagents;
1260
- }
1261
-
1262
- /**
1263
- * Check if there are pending items (messages, bang commands, or background
1264
- * task notifications) in the message queue. Background task completion
1265
- * notifications are enqueued before the main agent's dispatch consumes them,
1266
- * so callers waiting for the agent to fully settle must also wait on this.
1267
- */
1268
- public get hasPendingMessages(): boolean {
1269
- return this.messageQueue.hasPending();
1270
- }
1271
- }