wave-agent-sdk 0.0.7 → 0.0.10

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 (240) hide show
  1. package/dist/agent.d.ts +105 -24
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +438 -53
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +4 -0
  7. package/dist/managers/aiManager.d.ts +18 -7
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +254 -142
  10. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  11. package/dist/managers/backgroundBashManager.js +11 -9
  12. package/dist/managers/hookManager.d.ts +6 -6
  13. package/dist/managers/hookManager.d.ts.map +1 -1
  14. package/dist/managers/hookManager.js +81 -39
  15. package/dist/managers/liveConfigManager.d.ts +95 -0
  16. package/dist/managers/liveConfigManager.d.ts.map +1 -0
  17. package/dist/managers/liveConfigManager.js +442 -0
  18. package/dist/managers/lspManager.d.ts +43 -0
  19. package/dist/managers/lspManager.d.ts.map +1 -0
  20. package/dist/managers/lspManager.js +326 -0
  21. package/dist/managers/messageManager.d.ts +41 -24
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +184 -73
  24. package/dist/managers/permissionManager.d.ts +66 -0
  25. package/dist/managers/permissionManager.d.ts.map +1 -0
  26. package/dist/managers/permissionManager.js +208 -0
  27. package/dist/managers/skillManager.d.ts +1 -0
  28. package/dist/managers/skillManager.d.ts.map +1 -1
  29. package/dist/managers/skillManager.js +2 -1
  30. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  31. package/dist/managers/slashCommandManager.js +4 -2
  32. package/dist/managers/subagentManager.d.ts +42 -6
  33. package/dist/managers/subagentManager.d.ts.map +1 -1
  34. package/dist/managers/subagentManager.js +213 -62
  35. package/dist/managers/toolManager.d.ts +38 -1
  36. package/dist/managers/toolManager.d.ts.map +1 -1
  37. package/dist/managers/toolManager.js +66 -2
  38. package/dist/services/aiService.d.ts +15 -5
  39. package/dist/services/aiService.d.ts.map +1 -1
  40. package/dist/services/aiService.js +446 -77
  41. package/dist/services/configurationService.d.ts +116 -0
  42. package/dist/services/configurationService.d.ts.map +1 -0
  43. package/dist/services/configurationService.js +585 -0
  44. package/dist/services/fileWatcher.d.ts +69 -0
  45. package/dist/services/fileWatcher.d.ts.map +1 -0
  46. package/dist/services/fileWatcher.js +212 -0
  47. package/dist/services/hook.d.ts +5 -40
  48. package/dist/services/hook.d.ts.map +1 -1
  49. package/dist/services/hook.js +47 -109
  50. package/dist/services/jsonlHandler.d.ts +71 -0
  51. package/dist/services/jsonlHandler.d.ts.map +1 -0
  52. package/dist/services/jsonlHandler.js +236 -0
  53. package/dist/services/memory.d.ts.map +1 -1
  54. package/dist/services/memory.js +33 -11
  55. package/dist/services/session.d.ts +116 -52
  56. package/dist/services/session.d.ts.map +1 -1
  57. package/dist/services/session.js +415 -143
  58. package/dist/tools/bashTool.d.ts.map +1 -1
  59. package/dist/tools/bashTool.js +77 -17
  60. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  61. package/dist/tools/deleteFileTool.js +27 -1
  62. package/dist/tools/editTool.d.ts.map +1 -1
  63. package/dist/tools/editTool.js +33 -8
  64. package/dist/tools/lspTool.d.ts +6 -0
  65. package/dist/tools/lspTool.d.ts.map +1 -0
  66. package/dist/tools/lspTool.js +589 -0
  67. package/dist/tools/multiEditTool.d.ts.map +1 -1
  68. package/dist/tools/multiEditTool.js +30 -10
  69. package/dist/tools/readTool.d.ts.map +1 -1
  70. package/dist/tools/readTool.js +113 -3
  71. package/dist/tools/skillTool.js +2 -2
  72. package/dist/tools/todoWriteTool.d.ts.map +1 -1
  73. package/dist/tools/todoWriteTool.js +23 -0
  74. package/dist/tools/types.d.ts +11 -8
  75. package/dist/tools/types.d.ts.map +1 -1
  76. package/dist/tools/writeTool.d.ts.map +1 -1
  77. package/dist/tools/writeTool.js +30 -15
  78. package/dist/types/commands.d.ts +4 -1
  79. package/dist/types/commands.d.ts.map +1 -1
  80. package/dist/types/config.d.ts +4 -0
  81. package/dist/types/config.d.ts.map +1 -1
  82. package/dist/types/configuration.d.ts +69 -0
  83. package/dist/types/configuration.d.ts.map +1 -0
  84. package/dist/types/configuration.js +8 -0
  85. package/dist/types/core.d.ts +45 -0
  86. package/dist/types/core.d.ts.map +1 -1
  87. package/dist/types/environment.d.ts +83 -0
  88. package/dist/types/environment.d.ts.map +1 -0
  89. package/dist/types/environment.js +21 -0
  90. package/dist/types/fileSearch.d.ts +5 -0
  91. package/dist/types/fileSearch.d.ts.map +1 -0
  92. package/dist/types/fileSearch.js +1 -0
  93. package/dist/types/hooks.d.ts +18 -3
  94. package/dist/types/hooks.d.ts.map +1 -1
  95. package/dist/types/hooks.js +8 -8
  96. package/dist/types/index.d.ts +7 -0
  97. package/dist/types/index.d.ts.map +1 -1
  98. package/dist/types/index.js +7 -0
  99. package/dist/types/lsp.d.ts +90 -0
  100. package/dist/types/lsp.d.ts.map +1 -0
  101. package/dist/types/lsp.js +4 -0
  102. package/dist/types/messaging.d.ts +19 -12
  103. package/dist/types/messaging.d.ts.map +1 -1
  104. package/dist/types/permissions.d.ts +35 -0
  105. package/dist/types/permissions.d.ts.map +1 -0
  106. package/dist/types/permissions.js +12 -0
  107. package/dist/types/session.d.ts +15 -0
  108. package/dist/types/session.d.ts.map +1 -0
  109. package/dist/types/session.js +7 -0
  110. package/dist/types/skills.d.ts +1 -0
  111. package/dist/types/skills.d.ts.map +1 -1
  112. package/dist/types/tools.d.ts +35 -0
  113. package/dist/types/tools.d.ts.map +1 -0
  114. package/dist/types/tools.js +4 -0
  115. package/dist/utils/abortUtils.d.ts +34 -0
  116. package/dist/utils/abortUtils.d.ts.map +1 -0
  117. package/dist/utils/abortUtils.js +92 -0
  118. package/dist/utils/bashHistory.d.ts +4 -0
  119. package/dist/utils/bashHistory.d.ts.map +1 -1
  120. package/dist/utils/bashHistory.js +48 -30
  121. package/dist/utils/builtinSubagents.d.ts +7 -0
  122. package/dist/utils/builtinSubagents.d.ts.map +1 -0
  123. package/dist/utils/builtinSubagents.js +65 -0
  124. package/dist/utils/cacheControlUtils.d.ts +96 -0
  125. package/dist/utils/cacheControlUtils.d.ts.map +1 -0
  126. package/dist/utils/cacheControlUtils.js +324 -0
  127. package/dist/utils/commandPathResolver.d.ts +52 -0
  128. package/dist/utils/commandPathResolver.d.ts.map +1 -0
  129. package/dist/utils/commandPathResolver.js +145 -0
  130. package/dist/utils/configPaths.d.ts +85 -0
  131. package/dist/utils/configPaths.d.ts.map +1 -0
  132. package/dist/utils/configPaths.js +121 -0
  133. package/dist/utils/constants.d.ts +1 -13
  134. package/dist/utils/constants.d.ts.map +1 -1
  135. package/dist/utils/constants.js +2 -14
  136. package/dist/utils/convertMessagesForAPI.d.ts +2 -1
  137. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  138. package/dist/utils/convertMessagesForAPI.js +39 -18
  139. package/dist/utils/customCommands.d.ts.map +1 -1
  140. package/dist/utils/customCommands.js +66 -21
  141. package/dist/utils/fileSearch.d.ts +14 -0
  142. package/dist/utils/fileSearch.d.ts.map +1 -0
  143. package/dist/utils/fileSearch.js +88 -0
  144. package/dist/utils/fileUtils.d.ts +27 -0
  145. package/dist/utils/fileUtils.d.ts.map +1 -0
  146. package/dist/utils/fileUtils.js +145 -0
  147. package/dist/utils/globalLogger.d.ts +88 -0
  148. package/dist/utils/globalLogger.d.ts.map +1 -0
  149. package/dist/utils/globalLogger.js +120 -0
  150. package/dist/utils/largeOutputHandler.d.ts +15 -0
  151. package/dist/utils/largeOutputHandler.d.ts.map +1 -0
  152. package/dist/utils/largeOutputHandler.js +40 -0
  153. package/dist/utils/markdownParser.d.ts.map +1 -1
  154. package/dist/utils/markdownParser.js +1 -17
  155. package/dist/utils/mcpUtils.d.ts.map +1 -1
  156. package/dist/utils/mcpUtils.js +25 -3
  157. package/dist/utils/messageOperations.d.ts +20 -18
  158. package/dist/utils/messageOperations.d.ts.map +1 -1
  159. package/dist/utils/messageOperations.js +30 -38
  160. package/dist/utils/pathEncoder.d.ts +108 -0
  161. package/dist/utils/pathEncoder.d.ts.map +1 -0
  162. package/dist/utils/pathEncoder.js +279 -0
  163. package/dist/utils/subagentParser.d.ts +2 -2
  164. package/dist/utils/subagentParser.d.ts.map +1 -1
  165. package/dist/utils/subagentParser.js +12 -8
  166. package/dist/utils/tokenCalculation.d.ts +26 -0
  167. package/dist/utils/tokenCalculation.d.ts.map +1 -0
  168. package/dist/utils/tokenCalculation.js +36 -0
  169. package/dist/utils/tokenEstimator.d.ts +39 -0
  170. package/dist/utils/tokenEstimator.d.ts.map +1 -0
  171. package/dist/utils/tokenEstimator.js +55 -0
  172. package/package.json +6 -6
  173. package/src/agent.ts +586 -78
  174. package/src/index.ts +4 -0
  175. package/src/managers/aiManager.ts +341 -192
  176. package/src/managers/backgroundBashManager.ts +11 -9
  177. package/src/managers/hookManager.ts +102 -54
  178. package/src/managers/liveConfigManager.ts +634 -0
  179. package/src/managers/lspManager.ts +434 -0
  180. package/src/managers/messageManager.ts +258 -121
  181. package/src/managers/permissionManager.ts +276 -0
  182. package/src/managers/skillManager.ts +3 -1
  183. package/src/managers/slashCommandManager.ts +5 -3
  184. package/src/managers/subagentManager.ts +295 -76
  185. package/src/managers/toolManager.ts +95 -3
  186. package/src/services/aiService.ts +656 -84
  187. package/src/services/configurationService.ts +762 -0
  188. package/src/services/fileWatcher.ts +300 -0
  189. package/src/services/hook.ts +54 -144
  190. package/src/services/jsonlHandler.ts +303 -0
  191. package/src/services/memory.ts +34 -11
  192. package/src/services/session.ts +522 -173
  193. package/src/tools/bashTool.ts +94 -20
  194. package/src/tools/deleteFileTool.ts +38 -1
  195. package/src/tools/editTool.ts +44 -9
  196. package/src/tools/lspTool.ts +760 -0
  197. package/src/tools/multiEditTool.ts +41 -11
  198. package/src/tools/readTool.ts +127 -3
  199. package/src/tools/skillTool.ts +2 -2
  200. package/src/tools/todoWriteTool.ts +33 -1
  201. package/src/tools/types.ts +15 -9
  202. package/src/tools/writeTool.ts +43 -16
  203. package/src/types/commands.ts +6 -1
  204. package/src/types/config.ts +5 -0
  205. package/src/types/configuration.ts +73 -0
  206. package/src/types/core.ts +55 -0
  207. package/src/types/environment.ts +104 -0
  208. package/src/types/fileSearch.ts +4 -0
  209. package/src/types/hooks.ts +32 -16
  210. package/src/types/index.ts +7 -0
  211. package/src/types/lsp.ts +96 -0
  212. package/src/types/messaging.ts +21 -14
  213. package/src/types/permissions.ts +48 -0
  214. package/src/types/session.ts +20 -0
  215. package/src/types/skills.ts +1 -0
  216. package/src/types/tools.ts +38 -0
  217. package/src/utils/abortUtils.ts +118 -0
  218. package/src/utils/bashHistory.ts +55 -31
  219. package/src/utils/builtinSubagents.ts +71 -0
  220. package/src/utils/cacheControlUtils.ts +475 -0
  221. package/src/utils/commandPathResolver.ts +189 -0
  222. package/src/utils/configPaths.ts +163 -0
  223. package/src/utils/constants.ts +2 -17
  224. package/src/utils/convertMessagesForAPI.ts +44 -18
  225. package/src/utils/customCommands.ts +90 -22
  226. package/src/utils/fileSearch.ts +107 -0
  227. package/src/utils/fileUtils.ts +160 -0
  228. package/src/utils/globalLogger.ts +128 -0
  229. package/src/utils/largeOutputHandler.ts +55 -0
  230. package/src/utils/markdownParser.ts +1 -19
  231. package/src/utils/mcpUtils.ts +34 -3
  232. package/src/utils/messageOperations.ts +47 -53
  233. package/src/utils/pathEncoder.ts +394 -0
  234. package/src/utils/subagentParser.ts +13 -9
  235. package/src/utils/tokenCalculation.ts +43 -0
  236. package/src/utils/tokenEstimator.ts +68 -0
  237. package/dist/utils/configResolver.d.ts +0 -38
  238. package/dist/utils/configResolver.d.ts.map +0 -1
  239. package/dist/utils/configResolver.js +0 -106
  240. package/src/utils/configResolver.ts +0 -142
package/src/agent.ts CHANGED
@@ -4,16 +4,25 @@ import {
4
4
  } from "./managers/messageManager.js";
5
5
  import { AIManager } from "./managers/aiManager.js";
6
6
  import { ToolManager } from "./managers/toolManager.js";
7
- import { SubagentManager } from "./managers/subagentManager.js";
7
+ import {
8
+ SubagentManager,
9
+ type SubagentManagerCallbacks,
10
+ } from "./managers/subagentManager.js";
8
11
  import * as memory from "./services/memory.js";
9
12
  import { McpManager, type McpManagerCallbacks } from "./managers/mcpManager.js";
13
+ import { LspManager } from "./managers/lspManager.js";
10
14
  import { BashManager } from "./managers/bashManager.js";
11
15
  import {
12
16
  BackgroundBashManager,
13
17
  type BackgroundBashManagerCallbacks,
14
18
  } from "./managers/backgroundBashManager.js";
15
19
  import { SlashCommandManager } from "./managers/slashCommandManager.js";
16
- import type { SlashCommand, CustomSlashCommand } from "./types/index.js";
20
+ import { PermissionManager } from "./managers/permissionManager.js";
21
+ import type {
22
+ SlashCommand,
23
+ CustomSlashCommand,
24
+ ILspManager,
25
+ } from "./types/index.js";
17
26
  import type {
18
27
  Message,
19
28
  Logger,
@@ -21,11 +30,24 @@ import type {
21
30
  GatewayConfig,
22
31
  ModelConfig,
23
32
  Usage,
33
+ PermissionMode,
34
+ PermissionCallback,
24
35
  } from "./types/index.js";
25
36
  import { HookManager } from "./managers/hookManager.js";
26
- import { configResolver } from "./utils/configResolver.js";
37
+ import { LiveConfigManager } from "./managers/liveConfigManager.js";
27
38
  import { configValidator } from "./utils/configValidator.js";
28
39
  import { SkillManager } from "./managers/skillManager.js";
40
+ import {
41
+ loadSessionFromJsonl,
42
+ handleSessionRestoration,
43
+ } from "./services/session.js";
44
+ import type { SubagentConfiguration } from "./utils/subagentParser.js";
45
+ import { setGlobalLogger } from "./utils/globalLogger.js";
46
+ import { ConfigurationService } from "./services/configurationService.js";
47
+ import * as fs from "fs/promises";
48
+ import path from "path";
49
+ import os from "os";
50
+ import { ClientOptions } from "openai";
29
51
 
30
52
  /**
31
53
  * Configuration options for Agent instances
@@ -37,6 +59,9 @@ export interface AgentOptions {
37
59
  // Optional configuration with environment fallbacks
38
60
  apiKey?: string;
39
61
  baseURL?: string;
62
+ defaultHeaders?: Record<string, string>;
63
+ fetchOptions?: ClientOptions["fetchOptions"];
64
+ fetch?: ClientOptions["fetch"];
40
65
  agentModel?: string;
41
66
  fastModel?: string;
42
67
  tokenLimit?: number;
@@ -52,20 +77,23 @@ export interface AgentOptions {
52
77
  workdir?: string;
53
78
  /**Optional custom system prompt - if provided, replaces default system prompt */
54
79
  systemPrompt?: string;
55
-
56
- // New: Session directory configuration
57
- /**
58
- * Optional custom directory for session file storage
59
- * @default join(homedir(), ".wave", "sessions")
60
- * @example "/path/to/custom/sessions"
61
- */
62
- sessionDir?: string;
80
+ /**Permission mode - defaults to "default" */
81
+ permissionMode?: PermissionMode;
82
+ /**Custom permission callback */
83
+ canUseTool?: PermissionCallback;
84
+ /**Whether to use streaming mode for AI responses - defaults to true */
85
+ stream?: boolean;
86
+ /**Optional custom LSP manager - if not provided, a standalone one will be created */
87
+ lspManager?: ILspManager;
63
88
  }
64
89
 
65
90
  export interface AgentCallbacks
66
91
  extends MessageManagerCallbacks,
67
92
  BackgroundBashManagerCallbacks,
68
- McpManagerCallbacks {}
93
+ McpManagerCallbacks,
94
+ SubagentManagerCallbacks {
95
+ onPermissionModeChange?: (mode: PermissionMode) => void;
96
+ }
69
97
 
70
98
  export class Agent {
71
99
  private messageManager: MessageManager;
@@ -76,17 +104,80 @@ export class Agent {
76
104
  private logger?: Logger; // Add optional logger property
77
105
  private toolManager: ToolManager; // Add tool registry instance
78
106
  private mcpManager: McpManager; // Add MCP manager instance
107
+ private lspManager: ILspManager; // Add LSP manager instance
108
+ private permissionManager: PermissionManager; // Add permission manager instance
79
109
  private subagentManager: SubagentManager; // Add subagent manager instance
80
110
  private slashCommandManager: SlashCommandManager; // Add slash command manager instance
81
111
  private hookManager: HookManager; // Add hooks manager instance
112
+ private liveConfigManager: LiveConfigManager; // Add live configuration manager
113
+ private configurationService: ConfigurationService; // Add configuration service
82
114
  private workdir: string; // Working directory
83
115
  private systemPrompt?: string; // Custom system prompt
84
116
  private _usages: Usage[] = []; // Usage tracking array
117
+ private stream: boolean; // Streaming mode flag
118
+
119
+ // Configuration options storage for dynamic resolution
120
+ private options: AgentOptions;
121
+
122
+ // Memory content storage
123
+ private _projectMemoryContent: string = "";
124
+ private _userMemoryContent: string = "";
125
+
126
+ // Dynamic configuration getter methods
127
+ public getGatewayConfig(): GatewayConfig {
128
+ return this.configurationService.resolveGatewayConfig(
129
+ this.options.apiKey,
130
+ this.options.baseURL,
131
+ this.options.defaultHeaders,
132
+ this.options.fetchOptions,
133
+ this.options.fetch,
134
+ );
135
+ }
136
+
137
+ public getModelConfig(): ModelConfig {
138
+ return this.configurationService.resolveModelConfig(
139
+ this.options.agentModel,
140
+ this.options.fastModel,
141
+ );
142
+ }
143
+
144
+ public getTokenLimit(): number {
145
+ return this.configurationService.resolveTokenLimit(this.options.tokenLimit);
146
+ }
147
+
148
+ /**
149
+ * Update agent configuration dynamically
150
+ *
151
+ * @param config - Configuration updates for gateway, model, and token limit
152
+ */
153
+ public updateConfig(config: {
154
+ gateway?: Partial<GatewayConfig>;
155
+ model?: Partial<ModelConfig>;
156
+ tokenLimit?: number;
157
+ }): void {
158
+ if (config.gateway) {
159
+ this.options.apiKey = config.gateway.apiKey ?? this.options.apiKey;
160
+ this.options.baseURL = config.gateway.baseURL ?? this.options.baseURL;
161
+ this.options.defaultHeaders =
162
+ config.gateway.defaultHeaders ?? this.options.defaultHeaders;
163
+ this.options.fetchOptions =
164
+ config.gateway.fetchOptions ?? this.options.fetchOptions;
165
+ this.options.fetch = config.gateway.fetch ?? this.options.fetch;
166
+ }
167
+
168
+ if (config.model) {
169
+ this.options.agentModel =
170
+ config.model.agentModel ?? this.options.agentModel;
171
+ this.options.fastModel = config.model.fastModel ?? this.options.fastModel;
172
+ }
85
173
 
86
- // Configuration properties
87
- private gatewayConfig: GatewayConfig;
88
- private modelConfig: ModelConfig;
89
- private tokenLimit: number;
174
+ if (config.tokenLimit !== undefined) {
175
+ this.options.tokenLimit = config.tokenLimit;
176
+ }
177
+
178
+ // Re-validate configuration after update
179
+ this.resolveAndValidateConfig();
180
+ }
90
181
 
91
182
  /**
92
183
  * Agent constructor - handles configuration resolution and validation
@@ -96,7 +187,6 @@ export class Agent {
96
187
  * Agent.create() to maintain API consistency.
97
188
  *
98
189
  * @param options - Configuration options for the Agent instance
99
- * @param options.sessionDir - Optional custom directory for session storage
100
190
  */
101
191
  private constructor(options: AgentOptions) {
102
192
  const {
@@ -104,47 +194,37 @@ export class Agent {
104
194
  logger,
105
195
  workdir,
106
196
  systemPrompt,
107
- sessionDir,
197
+ stream = true,
108
198
  } = options;
109
199
 
110
- // Resolve configuration from constructor args and environment variables
111
- const gatewayConfig = configResolver.resolveGatewayConfig(
112
- options.apiKey,
113
- options.baseURL,
114
- );
115
- const modelConfig = configResolver.resolveModelConfig(
116
- options.agentModel,
117
- options.fastModel,
118
- );
119
- const tokenLimit = configResolver.resolveTokenLimit(options.tokenLimit);
200
+ // Set working directory early as we need it for loading configuration
201
+ this.workdir = workdir || process.cwd();
120
202
 
121
- // Validate resolved configuration
122
- configValidator.validateGatewayConfig(gatewayConfig);
123
- configValidator.validateTokenLimit(tokenLimit);
124
- configValidator.validateModelConfig(
125
- modelConfig.agentModel,
126
- modelConfig.fastModel,
127
- );
203
+ // Initialize configuration service
204
+ this.configurationService = new ConfigurationService();
128
205
 
129
206
  this.logger = logger; // Save the passed logger
130
- this.workdir = workdir || process.cwd(); // Set working directory, default to current working directory
131
207
  this.systemPrompt = systemPrompt; // Save custom system prompt
208
+ this.stream = stream; // Save streaming mode flag
132
209
 
133
- // Store resolved configuration
134
- this.gatewayConfig = gatewayConfig;
135
- this.modelConfig = modelConfig;
136
- this.tokenLimit = tokenLimit;
210
+ // Store options for dynamic configuration resolution
211
+ this.options = options;
137
212
 
138
213
  this.backgroundBashManager = new BackgroundBashManager({
139
214
  callbacks,
140
215
  workdir: this.workdir,
141
216
  });
142
217
  this.mcpManager = new McpManager({ callbacks, logger: this.logger }); // Initialize MCP manager
143
- this.toolManager = new ToolManager({
144
- mcpManager: this.mcpManager,
145
- logger: this.logger,
146
- }); // Initialize tool registry, pass MCP manager
218
+ this.lspManager =
219
+ options.lspManager || new LspManager({ logger: this.logger }); // Initialize LSP manager
220
+
221
+ // Initialize permission manager
222
+ this.permissionManager = new PermissionManager({ logger: this.logger });
223
+ this.permissionManager.setOnConfiguredDefaultModeChange((mode) => {
224
+ this.options.callbacks?.onPermissionModeChange?.(mode);
225
+ });
147
226
 
227
+ // Initialize configuration service and hooks manager
148
228
  this.hookManager = new HookManager(this.workdir, undefined, this.logger); // Initialize hooks manager
149
229
 
150
230
  // Initialize MessageManager
@@ -152,19 +232,89 @@ export class Agent {
152
232
  callbacks,
153
233
  workdir: this.workdir,
154
234
  logger: this.logger,
155
- sessionDir,
156
235
  });
157
236
 
237
+ // Create a wrapper for canUseTool that triggers notification hooks
238
+ const canUseToolWithNotification: PermissionCallback | undefined =
239
+ options.canUseTool
240
+ ? async (context) => {
241
+ try {
242
+ // Trigger notification hooks before calling the original callback
243
+ const notificationMessage = `Claude needs your permission to use ${context.toolName}`;
244
+ await this.hookManager.executeHooks("Notification", {
245
+ event: "Notification",
246
+ projectDir: this.workdir,
247
+ timestamp: new Date(),
248
+ sessionId: this.sessionId,
249
+ transcriptPath: this.messageManager.getTranscriptPath(),
250
+ cwd: this.workdir,
251
+ message: notificationMessage,
252
+ notificationType: "permission_prompt",
253
+ env: this.configurationService.getEnvironmentVars(), // Include configuration environment variables
254
+ });
255
+ } catch (error) {
256
+ this.logger?.warn("Failed to execute notification hooks", {
257
+ toolName: context.toolName,
258
+ error: error instanceof Error ? error.message : String(error),
259
+ });
260
+ // Continue with permission check even if hooks fail
261
+ }
262
+
263
+ // Call the original callback
264
+ const decision = await options.canUseTool!(context);
265
+
266
+ // Handle state changes from decision
267
+ if (decision.newPermissionMode) {
268
+ this.setPermissionMode(decision.newPermissionMode);
269
+ }
270
+
271
+ if (decision.newPermissionRule) {
272
+ await this.addPermissionRule(decision.newPermissionRule);
273
+ }
274
+
275
+ return decision;
276
+ }
277
+ : undefined;
278
+
279
+ // Initialize tool manager with permission context
280
+ this.toolManager = new ToolManager({
281
+ mcpManager: this.mcpManager,
282
+ lspManager: this.lspManager,
283
+ logger: this.logger,
284
+ permissionManager: this.permissionManager,
285
+ permissionMode: options.permissionMode, // Let PermissionManager handle defaultMode resolution
286
+ canUseToolCallback: canUseToolWithNotification,
287
+ }); // Initialize tool registry with permission support
288
+ this.liveConfigManager = new LiveConfigManager({
289
+ workdir: this.workdir,
290
+ logger: this.logger,
291
+ hookManager: this.hookManager,
292
+ permissionManager: this.permissionManager,
293
+ configurationService: this.configurationService,
294
+ }); // Initialize live configuration manager
295
+
158
296
  // Initialize subagent manager with all dependencies in constructor
159
297
  // IMPORTANT: Must be initialized AFTER MessageManager
160
298
  this.subagentManager = new SubagentManager({
161
299
  workdir: this.workdir,
162
300
  parentToolManager: this.toolManager,
163
301
  parentMessageManager: this.messageManager,
302
+ callbacks: {
303
+ onSubagentUserMessageAdded: callbacks.onSubagentUserMessageAdded,
304
+ onSubagentAssistantMessageAdded:
305
+ callbacks.onSubagentAssistantMessageAdded,
306
+ onSubagentAssistantContentUpdated:
307
+ callbacks.onSubagentAssistantContentUpdated,
308
+ onSubagentAssistantReasoningUpdated:
309
+ callbacks.onSubagentAssistantReasoningUpdated,
310
+ onSubagentToolBlockUpdated: callbacks.onSubagentToolBlockUpdated,
311
+ onSubagentMessagesChange: callbacks.onSubagentMessagesChange,
312
+ }, // Pass subagent callbacks for forwarding
164
313
  logger: this.logger,
165
- gatewayConfig,
166
- modelConfig,
167
- tokenLimit,
314
+ getGatewayConfig: () => this.getGatewayConfig(),
315
+ getModelConfig: () => this.getModelConfig(),
316
+ getTokenLimit: () => this.getTokenLimit(),
317
+ hookManager: this.hookManager,
168
318
  onUsageAdded: (usage) => this.addUsage(usage),
169
319
  });
170
320
 
@@ -183,9 +333,11 @@ export class Agent {
183
333
  },
184
334
  workdir: this.workdir,
185
335
  systemPrompt: this.systemPrompt,
186
- gatewayConfig: this.gatewayConfig,
187
- modelConfig: this.modelConfig,
188
- tokenLimit: this.tokenLimit,
336
+ stream: this.stream, // Pass streaming mode flag
337
+ getGatewayConfig: () => this.getGatewayConfig(),
338
+ getModelConfig: () => this.getModelConfig(),
339
+ getTokenLimit: () => this.getTokenLimit(),
340
+ getEnvironmentVars: () => this.configurationService.getEnvironmentVars(), // Provide access to configuration environment variables
189
341
  });
190
342
 
191
343
  // Initialize command manager
@@ -224,9 +376,9 @@ export class Agent {
224
376
  * Rebuild usage array from messages containing usage metadata
225
377
  * Called during session restoration to reconstruct usage tracking
226
378
  */
227
- private rebuildUsageFromMessages(): void {
379
+ private rebuildUsageFromMessages(messages: Message[]): void {
228
380
  this._usages = [];
229
- this.messages.forEach((message) => {
381
+ messages.forEach((message) => {
230
382
  if (message.role === "assistant" && message.usage) {
231
383
  this._usages.push(message.usage);
232
384
  }
@@ -257,6 +409,31 @@ export class Agent {
257
409
  return this.workdir;
258
410
  }
259
411
 
412
+ /** Get project memory content */
413
+ public get projectMemory(): string {
414
+ return this._projectMemoryContent;
415
+ }
416
+
417
+ /** Get user memory content */
418
+ public get userMemory(): string {
419
+ return this._userMemoryContent;
420
+ }
421
+
422
+ /** Get combined memory content (project + user) */
423
+ public get combinedMemory(): string {
424
+ let combined = "";
425
+ if (this._projectMemoryContent.trim()) {
426
+ combined += this._projectMemoryContent;
427
+ }
428
+ if (this._userMemoryContent.trim()) {
429
+ if (combined) {
430
+ combined += "\n\n";
431
+ }
432
+ combined += this._userMemoryContent;
433
+ }
434
+ return combined;
435
+ }
436
+
260
437
  /** Get AI loading status */
261
438
  public get isLoading(): boolean {
262
439
  return this.aiManager.isLoading;
@@ -303,9 +480,9 @@ export class Agent {
303
480
  * @param options - Configuration options for the Agent instance
304
481
  * @param options.apiKey - API key for the AI service (or set WAVE_API_KEY env var)
305
482
  * @param options.baseURL - Base URL for the AI service (or set WAVE_BASE_URL env var)
306
- * @param options.sessionDir - Optional custom directory for session file storage.
307
- * If not provided, defaults to ~/.wave/sessions/. Can be relative or absolute path.
308
- * Examples: "./app-sessions", "/var/myapp/sessions", "~/Documents/sessions"
483
+ * @param options.defaultHeaders - Optional HTTP headers to pass to the AI service
484
+ * @param options.fetchOptions - Optional fetch options to pass to the AI service
485
+ * @param options.fetch - Optional custom fetch implementation
309
486
  * @param options.callbacks - Optional callbacks for various Agent events
310
487
  * @param options.restoreSessionId - Optional session ID to restore from
311
488
  * @param options.continueLastSession - Whether to continue the last session automatically
@@ -317,22 +494,15 @@ export class Agent {
317
494
  *
318
495
  * @example
319
496
  * ```typescript
320
- * // Basic usage with default session directory
497
+ * // Basic usage
321
498
  * const agent = await Agent.create({
322
499
  * apiKey: 'your-api-key',
323
500
  * baseURL: 'https://api.example.com'
324
501
  * });
325
- *
326
- * // Custom session directory
327
- * const agent = await Agent.create({
328
- * apiKey: 'your-api-key',
329
- * baseURL: 'https://api.example.com',
330
- * sessionDir: './app-sessions'
331
- * });
332
502
  * ```
333
503
  */
334
504
  static async create(options: AgentOptions): Promise<Agent> {
335
- // Create Agent instance - configuration resolution and validation now happens in constructor
505
+ // Create Agent instance
336
506
  const instance = new Agent(options);
337
507
  await instance.initialize({
338
508
  restoreSessionId: options.restoreSessionId,
@@ -342,6 +512,27 @@ export class Agent {
342
512
  return instance;
343
513
  }
344
514
 
515
+ /**
516
+ * Resolve and validate configuration from constructor args, environment variables,
517
+ * and loaded settings.json.
518
+ *
519
+ * This is called during initialization after settings.json has been loaded.
520
+ */
521
+ private resolveAndValidateConfig(): void {
522
+ // Resolve configuration from constructor args and environment variables (including settings.json)
523
+ const gatewayConfig = this.getGatewayConfig();
524
+ const modelConfig = this.getModelConfig();
525
+ const tokenLimit = this.getTokenLimit();
526
+
527
+ // Validate resolved configuration
528
+ configValidator.validateGatewayConfig(gatewayConfig);
529
+ configValidator.validateTokenLimit(tokenLimit);
530
+ configValidator.validateModelConfig(
531
+ modelConfig.agentModel,
532
+ modelConfig.fastModel,
533
+ );
534
+ }
535
+
345
536
  /** Private initialization method, handles async initialization logic */
346
537
  private async initialize(options?: {
347
538
  restoreSessionId?: string;
@@ -351,7 +542,10 @@ export class Agent {
351
542
  // Initialize managers first
352
543
  try {
353
544
  // Initialize SkillManager
354
- const skillManager = new SkillManager({ logger: this.logger });
545
+ const skillManager = new SkillManager({
546
+ logger: this.logger,
547
+ workdir: this.workdir,
548
+ });
355
549
  await skillManager.initialize();
356
550
 
357
551
  // Initialize SubagentManager (load and cache configurations)
@@ -370,6 +564,9 @@ export class Agent {
370
564
  // Initialize MCP servers with auto-connect
371
565
  try {
372
566
  await this.mcpManager.initialize(this.workdir, true);
567
+ if (this.lspManager instanceof LspManager) {
568
+ await this.lspManager.initialize(this.workdir);
569
+ }
373
570
  } catch (error) {
374
571
  this.logger?.error("Failed to initialize MCP servers:", error);
375
572
  // Don't throw error to prevent app startup failure
@@ -377,32 +574,253 @@ export class Agent {
377
574
 
378
575
  // Initialize hooks configuration
379
576
  try {
380
- // Load hooks configuration from user and project settings
577
+ // Load hooks configuration using ConfigurationService
381
578
  this.logger?.debug("Loading hooks configuration...");
382
- this.hookManager.loadConfigurationFromSettings();
579
+ const configResult =
580
+ await this.configurationService.loadMergedConfiguration(this.workdir);
581
+
582
+ this.hookManager.loadConfigurationFromWaveConfig(
583
+ configResult.configuration,
584
+ );
383
585
  this.logger?.debug("Hooks system initialized successfully");
384
586
  } catch (error) {
385
587
  this.logger?.error("Failed to initialize hooks system:", error);
386
588
  // Don't throw error to prevent app startup failure
387
589
  }
388
590
 
591
+ // Resolve and validate configuration after loading settings.json
592
+ this.resolveAndValidateConfig();
593
+
594
+ // Set global logger for SDK-wide access after validation
595
+ setGlobalLogger(this.logger || null);
596
+
597
+ // Initialize live configuration reload
598
+ try {
599
+ this.logger?.debug("Initializing live configuration reload...");
600
+ await this.liveConfigManager.initialize();
601
+ this.logger?.debug("Live configuration reload initialized successfully");
602
+
603
+ // Update permission manager with configuration-based defaultMode
604
+ const currentConfig = this.liveConfigManager.getCurrentConfiguration();
605
+ if (currentConfig?.defaultMode) {
606
+ this.logger?.debug(
607
+ "Applying configured defaultMode to PermissionManager",
608
+ {
609
+ defaultMode: currentConfig.defaultMode,
610
+ },
611
+ );
612
+ this.permissionManager.updateConfiguredDefaultMode(
613
+ currentConfig.defaultMode,
614
+ );
615
+ }
616
+
617
+ // Update permission manager with configuration-based allowed rules
618
+ if (currentConfig?.permissions?.allow) {
619
+ this.logger?.debug(
620
+ "Applying configured allowed rules to PermissionManager",
621
+ {
622
+ count: currentConfig.permissions.allow.length,
623
+ },
624
+ );
625
+ this.permissionManager.updateAllowedRules(
626
+ currentConfig.permissions.allow,
627
+ );
628
+ }
629
+ } catch (error) {
630
+ this.logger?.error(
631
+ "Failed to initialize live configuration reload:",
632
+ error,
633
+ );
634
+ // Don't throw error to prevent app startup failure - continue without live reload
635
+ }
636
+
637
+ // Load memory files during initialization
638
+ try {
639
+ this.logger?.debug("Loading memory files...");
640
+
641
+ // Load project memory from AGENTS.md (bypass memory store for direct file access)
642
+ try {
643
+ const projectMemoryPath = path.join(this.workdir, "AGENTS.md");
644
+ this._projectMemoryContent = await fs.readFile(
645
+ projectMemoryPath,
646
+ "utf-8",
647
+ );
648
+ this.logger?.debug("Project memory loaded successfully");
649
+ } catch (error) {
650
+ this._projectMemoryContent = "";
651
+ this.logger?.debug(
652
+ "Project memory file not found or unreadable, using empty content:",
653
+ error instanceof Error ? error.message : String(error),
654
+ );
655
+ }
656
+
657
+ // Load user memory (bypass memory store for direct file access)
658
+ try {
659
+ const userMemoryPath = path.join(os.homedir(), ".wave", "AGENTS.md");
660
+ this._userMemoryContent = await fs.readFile(userMemoryPath, "utf-8");
661
+ this.logger?.debug("User memory loaded successfully");
662
+ } catch (error) {
663
+ this._userMemoryContent = "";
664
+ this.logger?.debug(
665
+ "User memory file not found or unreadable, using empty content:",
666
+ error instanceof Error ? error.message : String(error),
667
+ );
668
+ }
669
+
670
+ this.logger?.debug("Memory initialization completed");
671
+ } catch (error) {
672
+ // Ensure memory is always initialized even if loading fails
673
+ this._projectMemoryContent = "";
674
+ this._userMemoryContent = "";
675
+ this.logger?.error("Failed to load memory files:", error);
676
+ // Don't throw error to prevent app startup failure
677
+ }
678
+
389
679
  // Handle session restoration or set provided messages
390
680
  if (options?.messages) {
391
681
  // If messages are provided, use them directly (useful for testing)
392
682
  this.messageManager.setMessages(options.messages);
393
683
  // Rebuild usage array from restored messages
394
- this.rebuildUsageFromMessages();
684
+ this.rebuildUsageFromMessages(options.messages);
395
685
  } else {
396
686
  // Otherwise, handle session restoration
397
- await this.messageManager.handleSessionRestoration(
687
+ const sessionToRestore = await handleSessionRestoration(
398
688
  options?.restoreSessionId,
399
689
  options?.continueLastSession,
690
+ this.messageManager.getWorkdir(),
400
691
  );
401
692
  // Rebuild usage array from restored messages
402
- this.rebuildUsageFromMessages();
693
+ this.rebuildUsageFromMessages(sessionToRestore?.messages || []);
694
+
695
+ // After main session is restored, restore any associated subagent sessions
696
+ await this.restoreSubagentSessions(sessionToRestore?.messages || []);
697
+
698
+ if (sessionToRestore)
699
+ this.messageManager.initializeFromSession(sessionToRestore);
700
+ }
701
+ }
702
+
703
+ /**
704
+ * Restore subagent sessions associated with the current main session
705
+ * This method is called after the main session is restored to load any subagent sessions
706
+ */
707
+ private async restoreSubagentSessions(messages: Message[]): Promise<void> {
708
+ try {
709
+ // Only attempt to restore subagent sessions if we have messages (session was restored)
710
+ if (messages.length === 0) {
711
+ return;
712
+ }
713
+
714
+ // Extract sessionId -> subagentId mapping from SubagentBlocks
715
+ const subagentBlockMap = new Map<
716
+ string,
717
+ { subagentId: string; configuration: SubagentConfiguration }
718
+ >(); // sessionId -> { subagentId, configuration }
719
+
720
+ for (const message of messages) {
721
+ if (message.role === "assistant" && message.blocks) {
722
+ for (const block of message.blocks) {
723
+ if (
724
+ block.type === "subagent" &&
725
+ block.sessionId &&
726
+ block.subagentId &&
727
+ block.configuration
728
+ ) {
729
+ subagentBlockMap.set(block.sessionId, {
730
+ subagentId: block.subagentId,
731
+ configuration: block.configuration,
732
+ });
733
+ }
734
+ }
735
+ }
736
+ }
737
+
738
+ if (subagentBlockMap.size === 0) {
739
+ return; // No subagent blocks found
740
+ }
741
+
742
+ // Load subagent sessions using sessionIds
743
+ const subagentSessions = [];
744
+ for (const [sessionId, blockData] of subagentBlockMap) {
745
+ try {
746
+ const sessionData = await loadSessionFromJsonl(
747
+ sessionId,
748
+ this.messageManager.getWorkdir(),
749
+ "subagent",
750
+ );
751
+ if (sessionData) {
752
+ subagentSessions.push({
753
+ sessionData,
754
+ subagentId: blockData.subagentId, // Use the subagentId from SubagentBlock
755
+ configuration: blockData.configuration, // Include configuration
756
+ });
757
+ }
758
+ } catch (error) {
759
+ this.logger?.warn(
760
+ `Failed to load subagent session ${sessionId}:`,
761
+ error,
762
+ );
763
+ }
764
+ }
765
+
766
+ if (subagentSessions.length > 0) {
767
+ this.logger?.debug(
768
+ `Found ${subagentSessions.length} subagent sessions to restore`,
769
+ );
770
+
771
+ // Restore subagent sessions through the SubagentManager
772
+ await this.subagentManager.restoreSubagentSessions(subagentSessions);
773
+
774
+ this.logger?.debug("Subagent sessions restored successfully");
775
+ }
776
+ } catch (error) {
777
+ this.logger?.warn("Failed to restore subagent sessions:", error);
778
+ // Don't throw error to prevent app startup failure
403
779
  }
404
780
  }
405
781
 
782
+ /**
783
+ * Restore a session by ID, switching to the target session without destroying the Agent instance
784
+ * @param sessionId - The ID of the session to restore
785
+ */
786
+ public async restoreSession(sessionId: string): Promise<void> {
787
+ // 1. Validation
788
+ if (!sessionId || sessionId === this.sessionId) {
789
+ return; // No-op if session ID is invalid or already current
790
+ }
791
+
792
+ // 2. Auto-save current session
793
+ try {
794
+ await this.messageManager.saveSession();
795
+ } catch (error) {
796
+ this.logger?.warn(
797
+ "Failed to save current session before restore:",
798
+ error,
799
+ );
800
+ // Continue with restoration even if save fails
801
+ }
802
+
803
+ // 3. Load target session
804
+ const sessionData = await loadSessionFromJsonl(
805
+ sessionId,
806
+ this.messageManager.getWorkdir(),
807
+ );
808
+ if (!sessionData) {
809
+ throw new Error(`Session not found: ${sessionId}`);
810
+ }
811
+
812
+ // 4. Clean current state
813
+ this.abortMessage(); // Abort any running operations
814
+ this.subagentManager.cleanup(); // Clean up active subagents
815
+
816
+ // 5. Rebuild usage and restore subagents (in correct order)
817
+ this.rebuildUsageFromMessages(sessionData.messages);
818
+ await this.restoreSubagentSessions(sessionData.messages);
819
+
820
+ // 6. Initialize session state last
821
+ this.messageManager.initializeFromSession(sessionData);
822
+ }
823
+
406
824
  public abortAIMessage(): void {
407
825
  this.aiManager.abortAIMessage();
408
826
  }
@@ -451,10 +869,50 @@ export class Agent {
451
869
  this.backgroundBashManager.cleanup();
452
870
  // Cleanup MCP connections
453
871
  await this.mcpManager.cleanup();
872
+ // Cleanup LSP connections
873
+ if (this.lspManager instanceof LspManager) {
874
+ await this.lspManager.cleanup();
875
+ }
454
876
  // Cleanup subagent manager
455
877
  this.subagentManager.cleanup();
878
+ // Cleanup live configuration reload
879
+ try {
880
+ await this.liveConfigManager.shutdown();
881
+ } catch (error) {
882
+ this.logger?.error(
883
+ "Error shutting down live configuration reload:",
884
+ error,
885
+ );
886
+ }
887
+ // Cleanup memory store
456
888
  }
457
889
 
890
+ /**
891
+ * Send a message to the AI agent with optional images
892
+ *
893
+ * @param content - The text content of the message to send
894
+ * @param images - Optional array of images to include with the message
895
+ * @param images[].path - File path to the image or base64 encoded image data
896
+ * @param images[].mimeType - MIME type of the image (e.g., 'image/png', 'image/jpeg')
897
+ * @returns Promise that resolves when the message has been processed
898
+ *
899
+ * @example
900
+ * ```typescript
901
+ * // Send a text message
902
+ * await agent.sendMessage("Hello, how are you?");
903
+ *
904
+ * // Send a message with images using file paths
905
+ * await agent.sendMessage("What do you see in these images?", [
906
+ * { path: "/path/to/image.png", mimeType: "image/png" },
907
+ * { path: "/path/to/photo.jpg", mimeType: "image/jpeg" }
908
+ * ]);
909
+ *
910
+ * // Send a message with base64 encoded image
911
+ * await agent.sendMessage("Analyze this image", [
912
+ * { path: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...", mimeType: "image/png" }
913
+ * ]);
914
+ * ```
915
+ */
458
916
  public async sendMessage(
459
917
  content: string,
460
918
  images?: Array<{ path: string; mimeType: string }>,
@@ -509,6 +967,7 @@ export class Agent {
509
967
  transcriptPath: this.messageManager.getTranscriptPath(),
510
968
  cwd: this.workdir,
511
969
  userPrompt: content,
970
+ env: this.configurationService.getEnvironmentVars(), // Include configuration environment variables
512
971
  },
513
972
  );
514
973
 
@@ -547,16 +1006,25 @@ export class Agent {
547
1006
  type: "project" | "user",
548
1007
  ): Promise<void> {
549
1008
  try {
1009
+ // Ensure the message starts with # for memory functions
1010
+ const formattedMessage = message.startsWith("#")
1011
+ ? message
1012
+ : `#${message}`;
1013
+
550
1014
  if (type === "project") {
551
- await memory.addMemory(message, this.workdir);
1015
+ await memory.addMemory(formattedMessage, this.workdir);
1016
+ // Update internal state after successful save
1017
+ this._projectMemoryContent = await memory.readMemoryFile(this.workdir);
552
1018
  } else {
553
- await memory.addUserMemory(message);
1019
+ await memory.addUserMemory(formattedMessage);
1020
+ // Update internal state after successful save
1021
+ this._userMemoryContent = await memory.getUserMemoryContent();
554
1022
  }
555
1023
 
556
1024
  // Add successful MemoryBlock to the last assistant message
557
1025
  const memoryText = message.substring(1).trim();
558
1026
  const typeLabel = type === "project" ? "Project Memory" : "User Memory";
559
- const storagePath = type === "project" ? "AGENTS.md" : "user-memory.md";
1027
+ const storagePath = "AGENTS.md";
560
1028
 
561
1029
  this.messageManager.addMemoryBlock(
562
1030
  `${typeLabel}: ${memoryText}`,
@@ -566,13 +1034,14 @@ export class Agent {
566
1034
  );
567
1035
  } catch (error) {
568
1036
  // Add failed MemoryBlock to the last assistant message
1037
+ const memoryText = message.substring(1).trim();
569
1038
  const typeLabel = type === "project" ? "Project Memory" : "User Memory";
570
- const storagePath = type === "project" ? "AGENTS.md" : "user-memory.md";
1039
+ const storagePath = "AGENTS.md";
1040
+ const errorMessage =
1041
+ error instanceof Error ? error.message : String(error);
571
1042
 
572
1043
  this.messageManager.addMemoryBlock(
573
- `${typeLabel} add failed: ${
574
- error instanceof Error ? error.message : String(error)
575
- }`,
1044
+ `${typeLabel}: ${memoryText} - Error: ${errorMessage}`,
576
1045
  false,
577
1046
  type,
578
1047
  storagePath,
@@ -623,4 +1092,43 @@ export class Agent {
623
1092
  public getCustomCommands(): CustomSlashCommand[] {
624
1093
  return this.slashCommandManager.getCustomCommands();
625
1094
  }
1095
+
1096
+ /**
1097
+ * Get the current permission mode
1098
+ */
1099
+ public getPermissionMode(): PermissionMode {
1100
+ return this.toolManager.getPermissionMode();
1101
+ }
1102
+
1103
+ /**
1104
+ * Set the permission mode
1105
+ * @param mode - The new permission mode
1106
+ */
1107
+ public setPermissionMode(mode: PermissionMode): void {
1108
+ this.toolManager.setPermissionMode(mode);
1109
+ this.options.callbacks?.onPermissionModeChange?.(mode);
1110
+ }
1111
+
1112
+ /**
1113
+ * Add a persistent permission rule
1114
+ * @param rule - The rule to add (e.g., "Bash(ls)")
1115
+ */
1116
+ private async addPermissionRule(rule: string): Promise<void> {
1117
+ // 1. Update PermissionManager state
1118
+ const currentRules = this.permissionManager.getAllowedRules();
1119
+ if (!currentRules.includes(rule)) {
1120
+ this.permissionManager.updateAllowedRules([...currentRules, rule]);
1121
+
1122
+ // 2. Persist to settings.local.json
1123
+ try {
1124
+ await this.configurationService.addAllowedRule(this.workdir, rule);
1125
+ this.logger?.debug("Persistent permission rule added", { rule });
1126
+ } catch (error) {
1127
+ this.logger?.error("Failed to persist permission rule", {
1128
+ rule,
1129
+ error: error instanceof Error ? error.message : String(error),
1130
+ });
1131
+ }
1132
+ }
1133
+ }
626
1134
  }