wave-agent-sdk 0.0.8 → 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 (236) hide show
  1. package/dist/agent.d.ts +92 -23
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +340 -137
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -0
  7. package/dist/managers/aiManager.d.ts +14 -36
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +74 -77
  10. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  11. package/dist/managers/backgroundBashManager.js +4 -3
  12. package/dist/managers/hookManager.d.ts +3 -8
  13. package/dist/managers/hookManager.d.ts.map +1 -1
  14. package/dist/managers/hookManager.js +39 -29
  15. package/dist/managers/liveConfigManager.d.ts +55 -18
  16. package/dist/managers/liveConfigManager.d.ts.map +1 -1
  17. package/dist/managers/liveConfigManager.js +372 -90
  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 +8 -16
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +52 -74
  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 +0 -1
  32. package/dist/managers/subagentManager.d.ts +8 -23
  33. package/dist/managers/subagentManager.d.ts.map +1 -1
  34. package/dist/managers/subagentManager.js +97 -117
  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 +3 -1
  39. package/dist/services/aiService.d.ts.map +1 -1
  40. package/dist/services/aiService.js +123 -30
  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.map +1 -1
  45. package/dist/services/fileWatcher.js +5 -6
  46. package/dist/services/hook.d.ts +7 -124
  47. package/dist/services/hook.d.ts.map +1 -1
  48. package/dist/services/hook.js +46 -458
  49. package/dist/services/jsonlHandler.d.ts +24 -15
  50. package/dist/services/jsonlHandler.d.ts.map +1 -1
  51. package/dist/services/jsonlHandler.js +67 -88
  52. package/dist/services/memory.d.ts +0 -9
  53. package/dist/services/memory.d.ts.map +1 -1
  54. package/dist/services/memory.js +2 -49
  55. package/dist/services/session.d.ts +82 -33
  56. package/dist/services/session.d.ts.map +1 -1
  57. package/dist/services/session.js +275 -181
  58. package/dist/tools/bashTool.d.ts.map +1 -1
  59. package/dist/tools/bashTool.js +72 -13
  60. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  61. package/dist/tools/deleteFileTool.js +25 -0
  62. package/dist/tools/editTool.d.ts.map +1 -1
  63. package/dist/tools/editTool.js +30 -6
  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 +26 -7
  69. package/dist/tools/readTool.d.ts.map +1 -1
  70. package/dist/tools/readTool.js +111 -2
  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 +25 -9
  78. package/dist/types/commands.d.ts +0 -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 +10 -0
  86. package/dist/types/core.d.ts.map +1 -1
  87. package/dist/types/environment.d.ts +41 -0
  88. package/dist/types/environment.d.ts.map +1 -1
  89. package/dist/types/fileSearch.d.ts +5 -0
  90. package/dist/types/fileSearch.d.ts.map +1 -0
  91. package/dist/types/fileSearch.js +1 -0
  92. package/dist/types/hooks.d.ts +11 -2
  93. package/dist/types/hooks.d.ts.map +1 -1
  94. package/dist/types/hooks.js +1 -7
  95. package/dist/types/index.d.ts +5 -0
  96. package/dist/types/index.d.ts.map +1 -1
  97. package/dist/types/index.js +5 -0
  98. package/dist/types/lsp.d.ts +90 -0
  99. package/dist/types/lsp.d.ts.map +1 -0
  100. package/dist/types/lsp.js +4 -0
  101. package/dist/types/messaging.d.ts +6 -11
  102. package/dist/types/messaging.d.ts.map +1 -1
  103. package/dist/types/permissions.d.ts +35 -0
  104. package/dist/types/permissions.d.ts.map +1 -0
  105. package/dist/types/permissions.js +12 -0
  106. package/dist/types/session.d.ts +1 -6
  107. package/dist/types/session.d.ts.map +1 -1
  108. package/dist/types/skills.d.ts +1 -0
  109. package/dist/types/skills.d.ts.map +1 -1
  110. package/dist/types/tools.d.ts +35 -0
  111. package/dist/types/tools.d.ts.map +1 -0
  112. package/dist/types/tools.js +4 -0
  113. package/dist/utils/abortUtils.d.ts +34 -0
  114. package/dist/utils/abortUtils.d.ts.map +1 -0
  115. package/dist/utils/abortUtils.js +92 -0
  116. package/dist/utils/bashHistory.d.ts +4 -0
  117. package/dist/utils/bashHistory.d.ts.map +1 -1
  118. package/dist/utils/bashHistory.js +21 -4
  119. package/dist/utils/builtinSubagents.d.ts +7 -0
  120. package/dist/utils/builtinSubagents.d.ts.map +1 -0
  121. package/dist/utils/builtinSubagents.js +65 -0
  122. package/dist/utils/cacheControlUtils.d.ts +8 -33
  123. package/dist/utils/cacheControlUtils.d.ts.map +1 -1
  124. package/dist/utils/cacheControlUtils.js +83 -126
  125. package/dist/utils/constants.d.ts +0 -12
  126. package/dist/utils/constants.d.ts.map +1 -1
  127. package/dist/utils/constants.js +1 -13
  128. package/dist/utils/convertMessagesForAPI.d.ts +2 -1
  129. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  130. package/dist/utils/convertMessagesForAPI.js +33 -14
  131. package/dist/utils/fileSearch.d.ts +14 -0
  132. package/dist/utils/fileSearch.d.ts.map +1 -0
  133. package/dist/utils/fileSearch.js +88 -0
  134. package/dist/utils/fileUtils.d.ts +14 -2
  135. package/dist/utils/fileUtils.d.ts.map +1 -1
  136. package/dist/utils/fileUtils.js +101 -17
  137. package/dist/utils/globalLogger.d.ts +0 -14
  138. package/dist/utils/globalLogger.d.ts.map +1 -1
  139. package/dist/utils/globalLogger.js +0 -16
  140. package/dist/utils/largeOutputHandler.d.ts +15 -0
  141. package/dist/utils/largeOutputHandler.d.ts.map +1 -0
  142. package/dist/utils/largeOutputHandler.js +40 -0
  143. package/dist/utils/markdownParser.d.ts.map +1 -1
  144. package/dist/utils/markdownParser.js +1 -17
  145. package/dist/utils/messageOperations.d.ts +1 -11
  146. package/dist/utils/messageOperations.d.ts.map +1 -1
  147. package/dist/utils/messageOperations.js +7 -24
  148. package/dist/utils/pathEncoder.d.ts +4 -0
  149. package/dist/utils/pathEncoder.d.ts.map +1 -1
  150. package/dist/utils/pathEncoder.js +16 -9
  151. package/dist/utils/subagentParser.d.ts +2 -2
  152. package/dist/utils/subagentParser.d.ts.map +1 -1
  153. package/dist/utils/subagentParser.js +10 -7
  154. package/dist/utils/tokenEstimator.d.ts +39 -0
  155. package/dist/utils/tokenEstimator.d.ts.map +1 -0
  156. package/dist/utils/tokenEstimator.js +55 -0
  157. package/package.json +5 -8
  158. package/src/agent.ts +460 -216
  159. package/src/index.ts +2 -0
  160. package/src/managers/aiManager.ts +107 -111
  161. package/src/managers/backgroundBashManager.ts +4 -3
  162. package/src/managers/hookManager.ts +44 -39
  163. package/src/managers/liveConfigManager.ts +524 -138
  164. package/src/managers/lspManager.ts +434 -0
  165. package/src/managers/messageManager.ts +73 -103
  166. package/src/managers/permissionManager.ts +276 -0
  167. package/src/managers/skillManager.ts +3 -1
  168. package/src/managers/slashCommandManager.ts +1 -2
  169. package/src/managers/subagentManager.ts +116 -159
  170. package/src/managers/toolManager.ts +95 -3
  171. package/src/services/aiService.ts +207 -26
  172. package/src/services/configurationService.ts +762 -0
  173. package/src/services/fileWatcher.ts +5 -6
  174. package/src/services/hook.ts +50 -631
  175. package/src/services/jsonlHandler.ts +84 -100
  176. package/src/services/memory.ts +2 -59
  177. package/src/services/session.ts +338 -213
  178. package/src/tools/bashTool.ts +89 -16
  179. package/src/tools/deleteFileTool.ts +36 -0
  180. package/src/tools/editTool.ts +41 -7
  181. package/src/tools/lspTool.ts +760 -0
  182. package/src/tools/multiEditTool.ts +37 -8
  183. package/src/tools/readTool.ts +125 -2
  184. package/src/tools/skillTool.ts +2 -2
  185. package/src/tools/todoWriteTool.ts +33 -1
  186. package/src/tools/types.ts +15 -9
  187. package/src/tools/writeTool.ts +36 -10
  188. package/src/types/commands.ts +0 -1
  189. package/src/types/config.ts +5 -0
  190. package/src/types/configuration.ts +73 -0
  191. package/src/types/core.ts +11 -0
  192. package/src/types/environment.ts +44 -0
  193. package/src/types/fileSearch.ts +4 -0
  194. package/src/types/hooks.ts +14 -11
  195. package/src/types/index.ts +5 -0
  196. package/src/types/lsp.ts +96 -0
  197. package/src/types/messaging.ts +8 -13
  198. package/src/types/permissions.ts +48 -0
  199. package/src/types/session.ts +3 -8
  200. package/src/types/skills.ts +1 -0
  201. package/src/types/tools.ts +38 -0
  202. package/src/utils/abortUtils.ts +118 -0
  203. package/src/utils/bashHistory.ts +28 -4
  204. package/src/utils/builtinSubagents.ts +71 -0
  205. package/src/utils/cacheControlUtils.ts +106 -171
  206. package/src/utils/constants.ts +1 -16
  207. package/src/utils/convertMessagesForAPI.ts +38 -14
  208. package/src/utils/fileSearch.ts +107 -0
  209. package/src/utils/fileUtils.ts +114 -19
  210. package/src/utils/globalLogger.ts +0 -17
  211. package/src/utils/largeOutputHandler.ts +55 -0
  212. package/src/utils/markdownParser.ts +1 -19
  213. package/src/utils/messageOperations.ts +7 -35
  214. package/src/utils/pathEncoder.ts +24 -9
  215. package/src/utils/subagentParser.ts +11 -8
  216. package/src/utils/tokenEstimator.ts +68 -0
  217. package/dist/constants/events.d.ts +0 -28
  218. package/dist/constants/events.d.ts.map +0 -1
  219. package/dist/constants/events.js +0 -27
  220. package/dist/services/configurationWatcher.d.ts +0 -120
  221. package/dist/services/configurationWatcher.d.ts.map +0 -1
  222. package/dist/services/configurationWatcher.js +0 -439
  223. package/dist/services/memoryStore.d.ts +0 -81
  224. package/dist/services/memoryStore.d.ts.map +0 -1
  225. package/dist/services/memoryStore.js +0 -200
  226. package/dist/types/memoryStore.d.ts +0 -82
  227. package/dist/types/memoryStore.d.ts.map +0 -1
  228. package/dist/types/memoryStore.js +0 -7
  229. package/dist/utils/configResolver.d.ts +0 -65
  230. package/dist/utils/configResolver.d.ts.map +0 -1
  231. package/dist/utils/configResolver.js +0 -210
  232. package/src/constants/events.ts +0 -38
  233. package/src/services/configurationWatcher.ts +0 -622
  234. package/src/services/memoryStore.ts +0 -279
  235. package/src/types/memoryStore.ts +0 -94
  236. package/src/utils/configResolver.ts +0 -302
@@ -1,622 +0,0 @@
1
- /**
2
- * Configuration Watcher Service
3
- *
4
- * Orchestrates live configuration reload functionality by coordinating file watching,
5
- * configuration validation, and error recovery. Provides automatic reload of settings.json
6
- * changes without restart.
7
- */
8
-
9
- import { EventEmitter } from "events";
10
- import { existsSync } from "fs";
11
- import { FileWatcherService, type FileWatchEvent } from "./fileWatcher.js";
12
- import { loadMergedWaveConfigWithFallback } from "./hook.js";
13
- import type { WaveConfiguration, ValidationResult } from "../types/hooks.js";
14
- import { isValidHookEvent, isValidHookEventConfig } from "../types/hooks.js";
15
- import type { Logger } from "../types/index.js";
16
- import {
17
- CONFIGURATION_EVENTS,
18
- FILE_WATCHER_EVENTS,
19
- } from "../constants/events.js";
20
-
21
- export interface ConfigurationChangeEvent {
22
- type: "settings_changed" | "memory_changed" | "env_changed";
23
- path: string;
24
- timestamp: number;
25
- changes: {
26
- added: string[];
27
- modified: string[];
28
- removed: string[];
29
- };
30
- isValid: boolean;
31
- errorMessage?: string;
32
- }
33
-
34
- export interface ConfigurationReloadService {
35
- initializeWatching(
36
- userPaths: string[],
37
- projectPaths?: string[],
38
- ): Promise<void>;
39
- reloadConfiguration(): Promise<WaveConfiguration>;
40
- getCurrentConfiguration(): WaveConfiguration | null;
41
- validateEnvironmentVariables(env: Record<string, string>): {
42
- isValid: boolean;
43
- errors: string[];
44
- warnings: string[];
45
- };
46
- shutdown(): Promise<void>;
47
- }
48
-
49
- export class ConfigurationWatcher
50
- extends EventEmitter
51
- implements ConfigurationReloadService
52
- {
53
- private fileWatcher: FileWatcherService;
54
- private logger?: Logger;
55
- private currentConfiguration: WaveConfiguration | null = null;
56
- private lastValidConfiguration: WaveConfiguration | null = null;
57
- private userConfigPaths?: string[];
58
- private projectConfigPaths?: string[];
59
- private workdir: string;
60
- private isWatching: boolean = false;
61
- private reloadInProgress: boolean = false;
62
-
63
- constructor(workdir: string, logger?: Logger) {
64
- super();
65
- this.workdir = workdir;
66
- this.logger = logger;
67
- this.fileWatcher = new FileWatcherService(logger);
68
- this.setupFileWatcherEvents();
69
- }
70
-
71
- /**
72
- * Initialize configuration watching
73
- * Maps to FR-004: System MUST watch settings.json files
74
- * Supports watching multiple file paths (e.g., settings.local.json and settings.json)
75
- */
76
- async initializeWatching(
77
- userPaths: string[],
78
- projectPaths?: string[],
79
- ): Promise<void> {
80
- try {
81
- this.logger?.info("Live Config: Initializing configuration watching...");
82
-
83
- this.userConfigPaths = userPaths;
84
- this.projectConfigPaths = projectPaths;
85
-
86
- // Load initial configuration
87
- await this.reloadConfiguration();
88
-
89
- // Start watching user configs that exist
90
- for (const userPath of userPaths) {
91
- if (existsSync(userPath)) {
92
- this.logger?.debug(
93
- `Live Config: Starting to watch user config: ${userPath}`,
94
- );
95
- await this.fileWatcher.watchFile(userPath, (event) =>
96
- this.handleFileChange(event, "user"),
97
- );
98
- } else {
99
- this.logger?.debug(
100
- `Live Config: User config file does not exist: ${userPath}`,
101
- );
102
- }
103
- }
104
-
105
- // Start watching project configs that exist
106
- if (projectPaths) {
107
- for (const projectPath of projectPaths) {
108
- if (existsSync(projectPath)) {
109
- this.logger?.debug(
110
- `Live Config: Starting to watch project config: ${projectPath}`,
111
- );
112
- await this.fileWatcher.watchFile(projectPath, (event) =>
113
- this.handleFileChange(event, "project"),
114
- );
115
- } else {
116
- this.logger?.debug(
117
- `Live Config: Project config file does not exist: ${projectPath}`,
118
- );
119
- }
120
- }
121
- }
122
-
123
- this.isWatching = true;
124
- this.logger?.info(
125
- "Live Config: Configuration watching initialized successfully",
126
- );
127
- } catch (error) {
128
- const errorMessage = `Failed to initialize configuration watching: ${(error as Error).message}`;
129
- this.logger?.error(`Live Config: ${errorMessage}`);
130
- throw new Error(errorMessage);
131
- }
132
- }
133
-
134
- /**
135
- * Reload configuration from files
136
- * Maps to FR-008: Continue with previous valid configuration on errors
137
- */
138
- async reloadConfiguration(): Promise<WaveConfiguration> {
139
- if (this.reloadInProgress) {
140
- this.logger?.debug("Live Config: Reload already in progress, skipping");
141
- return this.currentConfiguration || {};
142
- }
143
-
144
- this.reloadInProgress = true;
145
-
146
- try {
147
- this.logger?.debug("Live Config: Reloading configuration from files...");
148
-
149
- // Load merged configuration with fallback support
150
- const loadResult = loadMergedWaveConfigWithFallback(
151
- this.workdir,
152
- this.lastValidConfiguration,
153
- );
154
- const newConfig = loadResult.config;
155
- const timestamp = Date.now();
156
-
157
- // Check for errors during loading
158
- if (loadResult.errors.length > 0) {
159
- const errorMessage = `Configuration load errors: ${loadResult.errors.join("; ")}`;
160
- this.logger?.error(`Live Config: ${errorMessage}`);
161
-
162
- // Emit error event
163
- this.emit(CONFIGURATION_EVENTS.CONFIGURATION_CHANGE, {
164
- type: "settings_changed",
165
- path:
166
- this.getFirstExistingProjectPath() ||
167
- this.getFirstExistingUserPath() ||
168
- "unknown",
169
- timestamp,
170
- changes: { added: [], modified: [], removed: [] },
171
- isValid: false,
172
- errorMessage,
173
- } as ConfigurationChangeEvent);
174
-
175
- // Use fallback configuration if available
176
- if (loadResult.usedFallback && this.lastValidConfiguration) {
177
- this.logger?.warn(
178
- "Live Config: Using previous valid configuration due to load errors",
179
- );
180
- this.currentConfiguration = this.lastValidConfiguration;
181
- return this.currentConfiguration;
182
- } else {
183
- this.logger?.warn(
184
- "Live Config: No previous valid configuration available, using empty config",
185
- );
186
- this.currentConfiguration = {};
187
- return this.currentConfiguration;
188
- }
189
- }
190
-
191
- // Validate new configuration if it exists
192
- if (newConfig) {
193
- const validation = this.validateConfiguration(newConfig);
194
- if (!validation.valid) {
195
- const errorMessage = `Invalid configuration: ${validation.errors.join(", ")}`;
196
- this.logger?.error(`Live Config: ${errorMessage}`);
197
-
198
- // Emit error event but continue with previous valid config
199
- this.emit(CONFIGURATION_EVENTS.CONFIGURATION_CHANGE, {
200
- type: "settings_changed",
201
- path:
202
- this.getFirstExistingProjectPath() ||
203
- this.getFirstExistingUserPath() ||
204
- "unknown",
205
- timestamp,
206
- changes: { added: [], modified: [], removed: [] },
207
- isValid: false,
208
- errorMessage,
209
- } as ConfigurationChangeEvent);
210
-
211
- // Use previous valid configuration for error recovery
212
- if (this.lastValidConfiguration) {
213
- this.logger?.warn(
214
- "Live Config: Using previous valid configuration due to validation errors",
215
- );
216
- this.currentConfiguration = this.lastValidConfiguration;
217
- return this.currentConfiguration;
218
- } else {
219
- this.logger?.warn(
220
- "Live Config: No previous valid configuration available, using empty config",
221
- );
222
- this.currentConfiguration = {};
223
- return this.currentConfiguration;
224
- }
225
- }
226
- }
227
-
228
- // Detect changes between old and new configuration
229
- const changes = this.detectChanges(this.currentConfiguration, newConfig);
230
-
231
- // Update current configuration
232
- this.currentConfiguration = newConfig || {};
233
-
234
- // Save as last valid configuration if it's valid and not empty
235
- if (newConfig && (newConfig.hooks || newConfig.env)) {
236
- this.lastValidConfiguration = { ...newConfig };
237
- }
238
-
239
- this.logger?.info(
240
- `Live Config: Configuration reloaded successfully with ${Object.keys(newConfig?.hooks || {}).length} event types and ${Object.keys(newConfig?.env || {}).length} environment variables`,
241
- );
242
-
243
- // Emit configuration change event
244
- this.emit(CONFIGURATION_EVENTS.CONFIGURATION_CHANGE, {
245
- type: "settings_changed",
246
- path:
247
- this.getFirstExistingProjectPath() ||
248
- this.getFirstExistingUserPath() ||
249
- "merged",
250
- timestamp,
251
- changes,
252
- isValid: true,
253
- } as ConfigurationChangeEvent);
254
-
255
- return this.currentConfiguration;
256
- } catch (error) {
257
- const errorMessage = `Failed to reload configuration: ${(error as Error).message}`;
258
- this.logger?.error(`Live Config: ${errorMessage}`);
259
-
260
- // Use previous valid configuration for error recovery
261
- if (this.lastValidConfiguration) {
262
- this.logger?.warn(
263
- "Live Config: Using previous valid configuration due to reload error",
264
- );
265
- this.currentConfiguration = this.lastValidConfiguration;
266
- } else {
267
- this.logger?.warn(
268
- "Live Config: No previous valid configuration available, using empty config",
269
- );
270
- this.currentConfiguration = {};
271
- }
272
-
273
- // Emit error event
274
- this.emit(CONFIGURATION_EVENTS.CONFIGURATION_CHANGE, {
275
- type: "settings_changed",
276
- path: "reload-error",
277
- timestamp: Date.now(),
278
- changes: { added: [], modified: [], removed: [] },
279
- isValid: false,
280
- errorMessage,
281
- } as ConfigurationChangeEvent);
282
-
283
- return this.currentConfiguration;
284
- } finally {
285
- this.reloadInProgress = false;
286
- }
287
- }
288
-
289
- /**
290
- * Get current effective configuration
291
- * Maps to FR-002: Merged configuration with project precedence
292
- */
293
- getCurrentConfiguration(): WaveConfiguration | null {
294
- return this.currentConfiguration ? { ...this.currentConfiguration } : null;
295
- }
296
-
297
- /**
298
- * Validate environment variables
299
- * Maps to FR-003: Validate env field format
300
- */
301
- validateEnvironmentVariables(env: Record<string, string>): {
302
- isValid: boolean;
303
- errors: string[];
304
- warnings: string[];
305
- } {
306
- const errors: string[] = [];
307
- const warnings: string[] = [];
308
-
309
- if (typeof env !== "object" || env === null) {
310
- errors.push("Environment variables must be an object");
311
- return { isValid: false, errors, warnings };
312
- }
313
-
314
- if (Array.isArray(env)) {
315
- errors.push("Environment variables cannot be an array");
316
- return { isValid: false, errors, warnings };
317
- }
318
-
319
- for (const [key, value] of Object.entries(env)) {
320
- // Validate key format
321
- if (typeof key !== "string" || key.trim() === "") {
322
- errors.push(
323
- `Invalid environment variable key: '${key}' (must be non-empty string)`,
324
- );
325
- continue;
326
- }
327
-
328
- // Validate key naming convention (optional warning)
329
- if (!/^[A-Z_][A-Z0-9_]*$/i.test(key)) {
330
- warnings.push(
331
- `Environment variable '${key}' doesn't follow conventional naming (A-Z, 0-9, underscore)`,
332
- );
333
- }
334
-
335
- // Validate value type
336
- if (typeof value !== "string") {
337
- errors.push(
338
- `Environment variable '${key}' must have a string value (got ${typeof value})`,
339
- );
340
- }
341
- }
342
-
343
- return {
344
- isValid: errors.length === 0,
345
- errors,
346
- warnings,
347
- };
348
- }
349
-
350
- /**
351
- * Stop watching and cleanup resources
352
- * Maps to cleanup requirements
353
- */
354
- async shutdown(): Promise<void> {
355
- this.logger?.info("Live Config: Shutting down configuration watcher...");
356
-
357
- this.isWatching = false;
358
-
359
- try {
360
- await this.fileWatcher.cleanup();
361
- this.removeAllListeners();
362
- this.logger?.info(
363
- "Live Config: Configuration watcher shutdown completed",
364
- );
365
- } catch (error) {
366
- this.logger?.error(
367
- `Live Config: Error during shutdown: ${(error as Error).message}`,
368
- );
369
- throw error;
370
- }
371
- }
372
-
373
- /**
374
- * Watch an additional file (like AGENTS.md) for changes
375
- * Maps to T033: Add AGENTS.md file watching to HookManager
376
- */
377
- async watchAdditionalFile(
378
- filePath: string,
379
- callback: (event: FileWatchEvent) => void,
380
- ): Promise<void> {
381
- try {
382
- await this.fileWatcher.watchFile(filePath, callback);
383
- this.logger?.info(
384
- `Live Config: Started watching additional file: ${filePath}`,
385
- );
386
- } catch (error) {
387
- this.logger?.error(
388
- `Live Config: Failed to watch additional file ${filePath}: ${(error as Error).message}`,
389
- );
390
- throw error;
391
- }
392
- }
393
-
394
- /**
395
- * Stop watching an additional file
396
- */
397
- async unwatchAdditionalFile(filePath: string): Promise<void> {
398
- try {
399
- await this.fileWatcher.unwatchFile(filePath);
400
- this.logger?.info(
401
- `Live Config: Stopped watching additional file: ${filePath}`,
402
- );
403
- } catch (error) {
404
- this.logger?.warn(
405
- `Live Config: Failed to stop watching file ${filePath}: ${(error as Error).message}`,
406
- );
407
- }
408
- }
409
-
410
- /**
411
- * Check if watching is active
412
- */
413
- isWatchingActive(): boolean {
414
- return this.isWatching;
415
- }
416
-
417
- /**
418
- * Get watcher status for monitoring
419
- */
420
- getWatcherStatus() {
421
- const statuses = this.fileWatcher.getAllWatcherStatuses();
422
- return {
423
- isActive: this.isWatching,
424
- configurationLoaded: this.currentConfiguration !== null,
425
- hasValidConfiguration: this.lastValidConfiguration !== null,
426
- reloadInProgress: this.reloadInProgress,
427
- watchedFiles: statuses.map((s) => ({
428
- path: s.path,
429
- isActive: s.isActive,
430
- method: s.method,
431
- errorCount: s.errorCount,
432
- })),
433
- };
434
- }
435
-
436
- private setupFileWatcherEvents(): void {
437
- this.fileWatcher.on("watcherError", (error: Error) => {
438
- this.logger?.error(`Live Config: File watcher error: ${error.message}`);
439
- this.emit(CONFIGURATION_EVENTS.WATCHER_ERROR, error);
440
- });
441
- }
442
-
443
- private async handleFileChange(
444
- event: FileWatchEvent,
445
- source: "user" | "project",
446
- ): Promise<void> {
447
- this.logger?.debug(
448
- `Live Config: File ${event.type} detected for ${source} config: ${event.path}`,
449
- );
450
-
451
- try {
452
- // Handle file deletion
453
- if (event.type === FILE_WATCHER_EVENTS.DELETE) {
454
- this.logger?.info(
455
- `Live Config: ${source} config file deleted: ${event.path}`,
456
- );
457
- // Reload configuration without the deleted file
458
- await this.reloadConfiguration();
459
- return;
460
- }
461
-
462
- // Handle file creation or modification
463
- if (
464
- event.type === FILE_WATCHER_EVENTS.CHANGE ||
465
- event.type === FILE_WATCHER_EVENTS.CREATE
466
- ) {
467
- this.logger?.info(
468
- `Live Config: ${source} config file ${event.type}: ${event.path}`,
469
- );
470
-
471
- // Add small delay to ensure file write is complete
472
- await new Promise((resolve) => setTimeout(resolve, 50));
473
-
474
- // Reload configuration
475
- await this.reloadConfiguration();
476
- }
477
- } catch (error) {
478
- this.logger?.error(
479
- `Live Config: Error handling file change for ${source} config: ${(error as Error).message}`,
480
- );
481
- }
482
- }
483
-
484
- /**
485
- * Validate configuration structure and content
486
- */
487
- private validateConfiguration(config: WaveConfiguration): ValidationResult {
488
- const errors: string[] = [];
489
-
490
- if (!config || typeof config !== "object") {
491
- return { valid: false, errors: ["Configuration must be an object"] };
492
- }
493
-
494
- // Validate hooks if present
495
- if (config.hooks) {
496
- if (typeof config.hooks !== "object") {
497
- errors.push("hooks property must be an object");
498
- } else {
499
- // Validate each hook event
500
- for (const [eventName, eventConfigs] of Object.entries(config.hooks)) {
501
- // Validate event name
502
- if (!isValidHookEvent(eventName)) {
503
- errors.push(`Invalid hook event: ${eventName}`);
504
- continue;
505
- }
506
-
507
- // Validate event configurations
508
- if (!Array.isArray(eventConfigs)) {
509
- errors.push(
510
- `Hook event ${eventName} must be an array of configurations`,
511
- );
512
- continue;
513
- }
514
-
515
- eventConfigs.forEach((eventConfig, index) => {
516
- if (!isValidHookEventConfig(eventConfig)) {
517
- errors.push(
518
- `Invalid hook event configuration at ${eventName}[${index}]`,
519
- );
520
- }
521
- });
522
- }
523
- }
524
- }
525
-
526
- // Validate environment variables if present
527
- if (config.env) {
528
- if (typeof config.env !== "object" || Array.isArray(config.env)) {
529
- errors.push("env property must be an object");
530
- } else {
531
- for (const [key, value] of Object.entries(config.env)) {
532
- if (typeof key !== "string" || key.trim() === "") {
533
- errors.push(`Invalid environment variable key: ${key}`);
534
- }
535
- if (typeof value !== "string") {
536
- errors.push(`Environment variable ${key} must have a string value`);
537
- }
538
- }
539
- }
540
- }
541
-
542
- return {
543
- valid: errors.length === 0,
544
- errors,
545
- };
546
- }
547
-
548
- private detectChanges(
549
- oldConfig: WaveConfiguration | null,
550
- newConfig: WaveConfiguration | null,
551
- ): {
552
- added: string[];
553
- modified: string[];
554
- removed: string[];
555
- } {
556
- const added: string[] = [];
557
- const modified: string[] = [];
558
- const removed: string[] = [];
559
-
560
- // Handle environment variables changes
561
- const oldEnv = oldConfig?.env || {};
562
- const newEnv = newConfig?.env || {};
563
-
564
- for (const key of Object.keys(newEnv)) {
565
- if (!(key in oldEnv)) {
566
- added.push(`env.${key}`);
567
- } else if (oldEnv[key] !== newEnv[key]) {
568
- modified.push(`env.${key}`);
569
- }
570
- }
571
-
572
- for (const key of Object.keys(oldEnv)) {
573
- if (!(key in newEnv)) {
574
- removed.push(`env.${key}`);
575
- }
576
- }
577
-
578
- // Handle hooks changes (simplified)
579
- const oldHooks = oldConfig?.hooks || {};
580
- const newHooks = newConfig?.hooks || {};
581
-
582
- for (const event of Object.keys(newHooks)) {
583
- if (isValidHookEvent(event)) {
584
- if (!(event in oldHooks)) {
585
- added.push(`hooks.${event}`);
586
- } else if (
587
- JSON.stringify(oldHooks[event]) !== JSON.stringify(newHooks[event])
588
- ) {
589
- modified.push(`hooks.${event}`);
590
- }
591
- }
592
- }
593
-
594
- for (const event of Object.keys(oldHooks)) {
595
- if (isValidHookEvent(event) && !(event in newHooks)) {
596
- removed.push(`hooks.${event}`);
597
- }
598
- }
599
-
600
- return { added, modified, removed };
601
- }
602
-
603
- /**
604
- * Get the first existing user config path for error reporting
605
- */
606
- private getFirstExistingUserPath(): string | undefined {
607
- return (
608
- this.userConfigPaths?.find((path) => existsSync(path)) ||
609
- this.userConfigPaths?.[0]
610
- );
611
- }
612
-
613
- /**
614
- * Get the first existing project config path for error reporting
615
- */
616
- private getFirstExistingProjectPath(): string | undefined {
617
- return (
618
- this.projectConfigPaths?.find((path) => existsSync(path)) ||
619
- this.projectConfigPaths?.[0]
620
- );
621
- }
622
- }