wave-agent-sdk 1.0.4 → 1.0.6

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