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
@@ -0,0 +1,585 @@
1
+ /**
2
+ * Configuration Service
3
+ *
4
+ * Centralized service for loading, validating, and managing Wave configuration files.
5
+ * Replaces distributed configuration logic previously embedded in hook.ts.
6
+ */
7
+ import { readFileSync, existsSync, promises as fs } from "fs";
8
+ import * as path from "path";
9
+ import { isValidHookEvent } from "../types/hooks.js";
10
+ import { getAllConfigPaths, getExistingConfigPaths, getUserConfigPaths, getProjectConfigPaths, } from "../utils/configPaths.js";
11
+ import { isValidEnvironmentVars, } from "../types/environment.js";
12
+ import { ConfigurationError, CONFIG_ERRORS, } from "../types/index.js";
13
+ import { DEFAULT_TOKEN_LIMIT } from "../utils/constants.js";
14
+ /**
15
+ * Default ConfigurationService implementation
16
+ *
17
+ * Provides centralized configuration loading, validation, and management.
18
+ * Extracted from distributed logic in hook.ts with improved error handling.
19
+ */
20
+ export class ConfigurationService {
21
+ constructor() {
22
+ this.currentConfiguration = null;
23
+ this.env = {};
24
+ }
25
+ // Core loading operations
26
+ /**
27
+ * Load and merge configuration with comprehensive validation
28
+ */
29
+ async loadMergedConfiguration(workdir) {
30
+ try {
31
+ const userConfigPaths = getUserConfigPaths();
32
+ const projectConfigPaths = getProjectConfigPaths(workdir);
33
+ // Use the merged configuration function (this loads user and project configs internally)
34
+ const mergedConfig = loadMergedWaveConfig(workdir);
35
+ // Track loading context for better error messages by checking which files exist
36
+ const loadingContext = [];
37
+ const userPath = userConfigPaths.find((path) => existsSync(path));
38
+ if (userPath) {
39
+ loadingContext.push(`user config from ${userPath}`);
40
+ }
41
+ const projectPath = projectConfigPaths.find((path) => existsSync(path));
42
+ if (projectPath) {
43
+ loadingContext.push(`project config from ${projectPath}`);
44
+ }
45
+ if (!mergedConfig) {
46
+ const message = loadingContext.length > 0
47
+ ? `No valid configuration found despite attempting to load: ${loadingContext.join(", ")}`
48
+ : "No configuration files found in user or project directories";
49
+ return {
50
+ configuration: null,
51
+ success: true, // No config is valid
52
+ warnings: [message],
53
+ };
54
+ }
55
+ // Comprehensive validation
56
+ const validation = this.validateConfiguration(mergedConfig);
57
+ if (!validation.isValid) {
58
+ const sourcePaths = loadingContext.join(" and ");
59
+ return {
60
+ configuration: null,
61
+ success: false,
62
+ error: `Merged configuration validation failed (sources: ${sourcePaths}): ${validation.errors.join(", ")}`,
63
+ warnings: validation.warnings,
64
+ };
65
+ }
66
+ // Success case
67
+ this.currentConfiguration = mergedConfig;
68
+ // Set environment variables if present in the merged config
69
+ if (mergedConfig.env) {
70
+ this.setEnvironmentVars(mergedConfig.env);
71
+ }
72
+ const sourcePaths = loadingContext.join(" and ");
73
+ return {
74
+ configuration: mergedConfig,
75
+ success: true,
76
+ sourcePath: sourcePaths || "merged configuration",
77
+ warnings: validation.warnings,
78
+ };
79
+ }
80
+ catch (error) {
81
+ return {
82
+ configuration: null,
83
+ success: false,
84
+ error: `Failed to load merged configuration from ${workdir}: ${error.message}`,
85
+ warnings: [],
86
+ };
87
+ }
88
+ }
89
+ // Validation operations
90
+ /**
91
+ * Validate configuration object structure and values
92
+ */
93
+ validateConfiguration(config) {
94
+ const result = {
95
+ isValid: true,
96
+ errors: [],
97
+ warnings: [],
98
+ };
99
+ // Validate basic structure
100
+ if (!config || typeof config !== "object") {
101
+ result.isValid = false;
102
+ result.errors.push("Configuration must be a valid object");
103
+ return result;
104
+ }
105
+ // Validate hooks if present
106
+ if (config.hooks !== undefined) {
107
+ if (typeof config.hooks !== "object" || config.hooks === null) {
108
+ result.isValid = false;
109
+ result.errors.push("Hooks configuration must be an object");
110
+ }
111
+ else {
112
+ for (const [event, eventConfigs] of Object.entries(config.hooks)) {
113
+ if (!isValidHookEvent(event)) {
114
+ result.warnings.push(`Unknown hook event: ${event}`);
115
+ continue;
116
+ }
117
+ if (!Array.isArray(eventConfigs)) {
118
+ result.isValid = false;
119
+ result.errors.push(`Hook event '${event}' must be an array`);
120
+ continue;
121
+ }
122
+ // Validate individual hook configurations
123
+ for (let i = 0; i < eventConfigs.length; i++) {
124
+ const hookConfig = eventConfigs[i];
125
+ if (!hookConfig || typeof hookConfig !== "object") {
126
+ result.isValid = false;
127
+ result.errors.push(`Hook configuration ${i} for event '${event}' must be an object`);
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ // Validate environment variables if present
134
+ if (config.env !== undefined) {
135
+ const envValidation = validateEnvironmentConfig(config.env);
136
+ if (!envValidation.isValid) {
137
+ result.isValid = false;
138
+ result.errors.push(...envValidation.errors);
139
+ }
140
+ result.warnings.push(...envValidation.warnings);
141
+ }
142
+ // Validate defaultMode if present
143
+ if (config.defaultMode !== undefined) {
144
+ if (config.defaultMode !== "default" &&
145
+ config.defaultMode !== "bypassPermissions" &&
146
+ config.defaultMode !== "acceptEdits") {
147
+ result.isValid = false;
148
+ result.errors.push(`Invalid defaultMode: "${config.defaultMode}". Must be "default", "bypassPermissions" or "acceptEdits"`);
149
+ }
150
+ }
151
+ // Validate permissions if present
152
+ if (config.permissions !== undefined) {
153
+ if (typeof config.permissions !== "object" ||
154
+ config.permissions === null) {
155
+ result.isValid = false;
156
+ result.errors.push("Permissions configuration must be an object");
157
+ }
158
+ else if (config.permissions.allow !== undefined) {
159
+ if (!Array.isArray(config.permissions.allow)) {
160
+ result.isValid = false;
161
+ result.errors.push("Permissions allow must be an array of strings");
162
+ }
163
+ else if (!config.permissions.allow.every((rule) => typeof rule === "string")) {
164
+ result.isValid = false;
165
+ result.errors.push("All permission rules must be strings");
166
+ }
167
+ }
168
+ }
169
+ return result;
170
+ }
171
+ /**
172
+ * Validate configuration file without loading
173
+ */
174
+ validateConfigurationFile(filePath) {
175
+ const result = {
176
+ isValid: true,
177
+ errors: [],
178
+ warnings: [],
179
+ };
180
+ if (!existsSync(filePath)) {
181
+ result.isValid = false;
182
+ result.errors.push(`Configuration file not found: ${filePath}`);
183
+ return result;
184
+ }
185
+ try {
186
+ const content = readFileSync(filePath, "utf-8");
187
+ const config = JSON.parse(content);
188
+ // Use the main validation method
189
+ const configValidation = this.validateConfiguration(config);
190
+ result.isValid = configValidation.isValid;
191
+ result.errors = configValidation.errors;
192
+ result.warnings = configValidation.warnings;
193
+ }
194
+ catch (error) {
195
+ result.isValid = false;
196
+ if (error instanceof SyntaxError) {
197
+ result.errors.push(`Invalid JSON syntax in ${filePath}: ${error.message}`);
198
+ }
199
+ else {
200
+ result.errors.push(`Error reading configuration file ${filePath}: ${error.message}`);
201
+ }
202
+ }
203
+ return result;
204
+ }
205
+ // Utility operations
206
+ /**
207
+ * Get currently loaded configuration
208
+ */
209
+ getCurrentConfiguration() {
210
+ return this.currentConfiguration;
211
+ }
212
+ /**
213
+ * Set environment variables from configuration
214
+ * This replaces direct process.env modification
215
+ */
216
+ setEnvironmentVars(env) {
217
+ this.env = { ...env };
218
+ }
219
+ /**
220
+ * Get current environment variables
221
+ */
222
+ getEnvironmentVars() {
223
+ return { ...this.env };
224
+ }
225
+ // =============================================================================
226
+ // Configuration Resolution Methods (merged from configResolver.ts)
227
+ // =============================================================================
228
+ /**
229
+ * Resolves gateway configuration from constructor args and environment
230
+ * Resolution priority: options > env (from settings.json) > process.env > error
231
+ * @param apiKey - API key from constructor (optional)
232
+ * @param baseURL - Base URL from constructor (optional)
233
+ * @param defaultHeaders - HTTP headers from constructor (optional)
234
+ * @param fetchOptions - Fetch options from constructor (optional)
235
+ * @param fetch - Custom fetch implementation from constructor (optional)
236
+ * @returns Resolved gateway configuration
237
+ * @throws ConfigurationError if required configuration is missing after fallbacks
238
+ */
239
+ resolveGatewayConfig(apiKey, baseURL, defaultHeaders, fetchOptions, fetch) {
240
+ // Resolve API key: constructor > env (settings.json) > process.env
241
+ // Note: Explicitly provided empty strings should be treated as invalid, not fall back to env
242
+ let resolvedApiKey;
243
+ if (apiKey !== undefined) {
244
+ resolvedApiKey = apiKey;
245
+ }
246
+ else {
247
+ resolvedApiKey = this.env.AIGW_TOKEN || process.env.AIGW_TOKEN || "";
248
+ }
249
+ if (!resolvedApiKey && apiKey === undefined) {
250
+ throw new ConfigurationError(CONFIG_ERRORS.MISSING_API_KEY, "apiKey", {
251
+ constructor: apiKey,
252
+ environment: process.env.AIGW_TOKEN,
253
+ settings: this.env.AIGW_TOKEN,
254
+ });
255
+ }
256
+ if (resolvedApiKey.trim() === "") {
257
+ throw new ConfigurationError(CONFIG_ERRORS.EMPTY_API_KEY, "apiKey", resolvedApiKey);
258
+ }
259
+ // Resolve base URL: constructor > env (settings.json) > process.env
260
+ // Note: Explicitly provided empty strings should be treated as invalid, not fall back to env
261
+ let resolvedBaseURL;
262
+ if (baseURL !== undefined) {
263
+ resolvedBaseURL = baseURL;
264
+ }
265
+ else {
266
+ resolvedBaseURL = this.env.AIGW_URL || process.env.AIGW_URL || "";
267
+ }
268
+ if (!resolvedBaseURL && baseURL === undefined) {
269
+ throw new ConfigurationError(CONFIG_ERRORS.MISSING_BASE_URL, "baseURL", {
270
+ constructor: baseURL,
271
+ environment: process.env.AIGW_URL,
272
+ settings: this.env.AIGW_URL,
273
+ });
274
+ }
275
+ if (resolvedBaseURL.trim() === "") {
276
+ throw new ConfigurationError(CONFIG_ERRORS.EMPTY_BASE_URL, "baseURL", resolvedBaseURL);
277
+ }
278
+ return {
279
+ apiKey: resolvedApiKey,
280
+ baseURL: resolvedBaseURL,
281
+ defaultHeaders,
282
+ fetchOptions,
283
+ fetch,
284
+ };
285
+ }
286
+ /**
287
+ * Resolves model configuration with fallbacks
288
+ * Resolution priority: options > env (from settings.json) > process.env > default
289
+ * @param agentModel - Agent model from constructor (optional)
290
+ * @param fastModel - Fast model from constructor (optional)
291
+ * @returns Resolved model configuration with defaults
292
+ */
293
+ resolveModelConfig(agentModel, fastModel) {
294
+ // Default values as per data-model.md
295
+ const DEFAULT_AGENT_MODEL = "claude-sonnet-4-20250514";
296
+ const DEFAULT_FAST_MODEL = "gemini-2.5-flash";
297
+ // Resolve agent model: constructor > env (settings.json) > process.env > default
298
+ const resolvedAgentModel = agentModel ||
299
+ this.env.AIGW_MODEL ||
300
+ process.env.AIGW_MODEL ||
301
+ DEFAULT_AGENT_MODEL;
302
+ // Resolve fast model: constructor > env (settings.json) > process.env > default
303
+ const resolvedFastModel = fastModel ||
304
+ this.env.AIGW_FAST_MODEL ||
305
+ process.env.AIGW_FAST_MODEL ||
306
+ DEFAULT_FAST_MODEL;
307
+ return {
308
+ agentModel: resolvedAgentModel,
309
+ fastModel: resolvedFastModel,
310
+ };
311
+ }
312
+ /**
313
+ * Resolves token limit with fallbacks
314
+ * Resolution priority: options > env (from settings.json) > process.env > default
315
+ * @param constructorLimit - Token limit from constructor (optional)
316
+ * @returns Resolved token limit
317
+ */
318
+ resolveTokenLimit(constructorLimit) {
319
+ // If constructor value provided, use it
320
+ if (constructorLimit !== undefined) {
321
+ return constructorLimit;
322
+ }
323
+ // Try env (settings.json) first, then process.env
324
+ const envTokenLimit = this.env.TOKEN_LIMIT || process.env.TOKEN_LIMIT;
325
+ if (envTokenLimit) {
326
+ const parsed = parseInt(envTokenLimit, 10);
327
+ if (!isNaN(parsed)) {
328
+ return parsed;
329
+ }
330
+ }
331
+ // Use default
332
+ return DEFAULT_TOKEN_LIMIT;
333
+ }
334
+ /**
335
+ * Resolve all configuration file paths
336
+ */
337
+ getConfigurationPaths(workdir) {
338
+ const allPaths = getAllConfigPaths(workdir);
339
+ const existingPaths = getExistingConfigPaths(workdir);
340
+ return {
341
+ userPaths: allPaths.userPaths,
342
+ projectPaths: allPaths.projectPaths,
343
+ allPaths: allPaths.allPaths,
344
+ existingPaths: existingPaths.existingPaths,
345
+ };
346
+ }
347
+ /**
348
+ * Add a permission rule to the project's settings.local.json
349
+ */
350
+ async addAllowedRule(workdir, rule) {
351
+ const localConfigPath = path.join(workdir, ".wave", "settings.local.json");
352
+ // Ensure .wave directory exists
353
+ const waveDir = path.join(workdir, ".wave");
354
+ if (!existsSync(waveDir)) {
355
+ await fs.mkdir(waveDir, { recursive: true });
356
+ }
357
+ let config = {};
358
+ if (existsSync(localConfigPath)) {
359
+ try {
360
+ const content = await fs.readFile(localConfigPath, "utf-8");
361
+ config = JSON.parse(content);
362
+ }
363
+ catch {
364
+ // If file is corrupted, start with empty config
365
+ }
366
+ }
367
+ if (!config.permissions) {
368
+ config.permissions = {};
369
+ }
370
+ if (!config.permissions.allow) {
371
+ config.permissions.allow = [];
372
+ }
373
+ if (!config.permissions.allow.includes(rule)) {
374
+ config.permissions.allow.push(rule);
375
+ await fs.writeFile(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
376
+ }
377
+ }
378
+ }
379
+ // =============================================================================
380
+ // Extracted Configuration Functions
381
+ // =============================================================================
382
+ /**
383
+ * Validate environment variable configuration
384
+ */
385
+ export function validateEnvironmentConfig(env, configPath) {
386
+ const result = {
387
+ isValid: true,
388
+ errors: [],
389
+ warnings: [],
390
+ };
391
+ // Check if env is defined
392
+ if (env === undefined || env === null) {
393
+ return result; // undefined/null env is valid (means no env vars)
394
+ }
395
+ // Validate that env is a Record<string, string>
396
+ if (!isValidEnvironmentVars(env)) {
397
+ result.isValid = false;
398
+ result.errors.push(`Invalid env field format${configPath ? ` in ${configPath}` : ""}. Environment variables must be a Record<string, string>.`);
399
+ return result;
400
+ }
401
+ // Additional validation for environment variable names
402
+ const envVars = env;
403
+ for (const [key, value] of Object.entries(envVars)) {
404
+ // Check for valid environment variable naming convention
405
+ if (!/^[A-Z_][A-Z0-9_]*$/i.test(key)) {
406
+ result.warnings.push(`Environment variable '${key}' does not follow standard naming convention (alphanumeric and underscores only).`);
407
+ }
408
+ // Check for empty values
409
+ if (value === "") {
410
+ result.warnings.push(`Environment variable '${key}' has an empty value.`);
411
+ }
412
+ // Check for reserved variable names that might cause conflicts
413
+ const reservedNames = [
414
+ "PATH",
415
+ "HOME",
416
+ "USER",
417
+ "PWD",
418
+ "SHELL",
419
+ "TERM",
420
+ "NODE_ENV",
421
+ ];
422
+ if (reservedNames.includes(key.toUpperCase())) {
423
+ result.warnings.push(`Environment variable '${key}' overrides a system variable, which may cause unexpected behavior.`);
424
+ }
425
+ }
426
+ return result;
427
+ }
428
+ /**
429
+ * Merge environment configurations with project taking precedence over user
430
+ */
431
+ export function mergeEnvironmentConfig(userEnv, projectEnv, options = {}) {
432
+ const userVars = userEnv || {};
433
+ const projectVars = projectEnv || {};
434
+ const mergedVars = {};
435
+ const conflicts = [];
436
+ // Start with user environment variables
437
+ Object.assign(mergedVars, userVars);
438
+ // Override with project environment variables and track conflicts
439
+ for (const [key, projectValue] of Object.entries(projectVars)) {
440
+ const userValue = userVars[key];
441
+ if (userValue !== undefined &&
442
+ userValue !== projectValue &&
443
+ options.includeConflictWarnings !== false) {
444
+ // Conflict detected - project value takes precedence
445
+ conflicts.push({
446
+ key,
447
+ userValue,
448
+ projectValue,
449
+ resolvedValue: projectValue,
450
+ });
451
+ }
452
+ mergedVars[key] = projectValue;
453
+ }
454
+ return {
455
+ userVars,
456
+ projectVars,
457
+ mergedVars,
458
+ conflicts,
459
+ };
460
+ }
461
+ /**
462
+ * Load Wave configuration from a JSON file
463
+ * Supports both hooks and environment variables with proper validation
464
+ */
465
+ export function loadWaveConfigFromFile(filePath) {
466
+ if (!existsSync(filePath)) {
467
+ return null;
468
+ }
469
+ try {
470
+ const content = readFileSync(filePath, "utf-8");
471
+ const config = JSON.parse(content);
472
+ // Validate basic structure
473
+ if (!config || typeof config !== "object") {
474
+ throw new Error(`Invalid configuration structure in ${filePath}`);
475
+ }
476
+ // Validate environment variables if present
477
+ if (config.env !== undefined) {
478
+ const envValidation = validateEnvironmentConfig(config.env, filePath);
479
+ if (!envValidation.isValid) {
480
+ throw new Error(`Environment variable validation failed in ${filePath}: ${envValidation.errors.join(", ")}`);
481
+ }
482
+ // Log warnings if any
483
+ if (envValidation.warnings.length > 0) {
484
+ console.warn(`Environment variable warnings in ${filePath}:\n- ${envValidation.warnings.join("\n- ")}`);
485
+ }
486
+ }
487
+ return {
488
+ hooks: config.hooks || undefined,
489
+ env: config.env || undefined,
490
+ defaultMode: config.defaultMode,
491
+ permissions: config.permissions || undefined,
492
+ };
493
+ }
494
+ catch (error) {
495
+ if (error instanceof SyntaxError) {
496
+ throw new Error(`Invalid JSON syntax in ${filePath}: ${error.message}`);
497
+ }
498
+ // Re-throw validation errors and other errors as-is
499
+ throw error;
500
+ }
501
+ }
502
+ /**
503
+ * Load Wave configuration from multiple file paths in priority order
504
+ * Returns the first valid configuration found, or null if none exist
505
+ */
506
+ export function loadWaveConfigFromFiles(filePaths) {
507
+ for (const filePath of filePaths) {
508
+ const config = loadWaveConfigFromFile(filePath);
509
+ if (config !== null) {
510
+ return config;
511
+ }
512
+ }
513
+ return null;
514
+ }
515
+ /**
516
+ * Load user-specific Wave configuration
517
+ * Checks .local.json first, then falls back to .json
518
+ */
519
+ export function loadUserWaveConfig() {
520
+ return loadWaveConfigFromFiles(getUserConfigPaths());
521
+ }
522
+ /**
523
+ * Load project-specific Wave configuration
524
+ * Checks .local.json first, then falls back to .json
525
+ */
526
+ export function loadProjectWaveConfig(workdir) {
527
+ return loadWaveConfigFromFiles(getProjectConfigPaths(workdir));
528
+ }
529
+ /**
530
+ * Load and merge Wave configuration from both user and project sources
531
+ * Project configuration takes precedence over user configuration
532
+ * Checks .local.json files first, then falls back to .json files
533
+ */
534
+ export function loadMergedWaveConfig(workdir) {
535
+ const userConfig = loadUserWaveConfig();
536
+ const projectConfig = loadProjectWaveConfig(workdir);
537
+ // No configuration found
538
+ if (!userConfig && !projectConfig) {
539
+ return null;
540
+ }
541
+ // Only one configuration found
542
+ if (!userConfig)
543
+ return projectConfig;
544
+ if (!projectConfig)
545
+ return userConfig;
546
+ // Merge configurations (project overrides user)
547
+ const mergedHooks = {};
548
+ // Merge environment variables using the new mergeEnvironmentConfig function
549
+ const environmentContext = mergeEnvironmentConfig(userConfig.env, projectConfig.env, { includeConflictWarnings: true });
550
+ // Log environment variable conflicts if any
551
+ if (environmentContext.conflicts.length > 0) {
552
+ console.warn(`Environment variable conflicts detected (project values take precedence):\n${environmentContext.conflicts
553
+ .map((conflict) => `- ${conflict.key}: "${conflict.userValue}" → "${conflict.projectValue}"`)
554
+ .join("\n")}`);
555
+ }
556
+ // Merge hooks (combine arrays, project configs come after user configs)
557
+ const allEvents = new Set([
558
+ ...Object.keys(userConfig.hooks || {}),
559
+ ...Object.keys(projectConfig.hooks || {}),
560
+ ]);
561
+ for (const event of allEvents) {
562
+ if (!isValidHookEvent(event))
563
+ continue;
564
+ const userEventConfigs = userConfig.hooks?.[event] || [];
565
+ const projectEventConfigs = projectConfig.hooks?.[event] || [];
566
+ // Project configurations take precedence
567
+ mergedHooks[event] = [...userEventConfigs, ...projectEventConfigs];
568
+ }
569
+ // Merge permissions (combine allow arrays)
570
+ const mergedPermissions = {};
571
+ const userAllow = userConfig.permissions?.allow || [];
572
+ const projectAllow = projectConfig.permissions?.allow || [];
573
+ if (userAllow.length > 0 || projectAllow.length > 0) {
574
+ mergedPermissions.allow = [...new Set([...userAllow, ...projectAllow])];
575
+ }
576
+ return {
577
+ hooks: Object.keys(mergedHooks).length > 0 ? mergedHooks : undefined,
578
+ env: Object.keys(environmentContext.mergedVars).length > 0
579
+ ? environmentContext.mergedVars
580
+ : undefined,
581
+ // Project defaultMode takes precedence over user defaultMode
582
+ defaultMode: projectConfig.defaultMode ?? userConfig.defaultMode,
583
+ permissions: Object.keys(mergedPermissions).length > 0 ? mergedPermissions : undefined,
584
+ };
585
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"fileWatcher.d.ts","sourceRoot":"","sources":["../../src/services/fileWatcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAGhD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,cAAc,CAAC;CAC5B;AAaD,qBAAa,kBAAmB,SAAQ,YAAY;IAClD,OAAO,CAAC,QAAQ,CAA4C;IAC5D,OAAO,CAAC,aAAa,CAAmC;IACxD,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,MAAM,CAAC,CAAS;gBAEZ,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC;IAahE;;;OAGG;IACG,SAAS,CACb,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GACxC,OAAO,CAAC,IAAI,CAAC;IA+BhB;;;OAGG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB9C;;;OAGG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IA0BxD;;;OAGG;IACH,qBAAqB,IAAI,iBAAiB,EAAE;IAM5C;;;OAGG;IACH,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI;IAStD;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAUhB,iBAAiB;IAkD/B,OAAO,CAAC,wBAAwB;IA4BhC,OAAO,CAAC,eAAe;CA8BxB"}
1
+ {"version":3,"file":"fileWatcher.d.ts","sourceRoot":"","sources":["../../src/services/fileWatcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,cAAc,CAAC;CAC5B;AAaD,qBAAa,kBAAmB,SAAQ,YAAY;IAClD,OAAO,CAAC,QAAQ,CAA4C;IAC5D,OAAO,CAAC,aAAa,CAAmC;IACxD,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,MAAM,CAAC,CAAS;gBAEZ,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC;IAahE;;;OAGG;IACG,SAAS,CACb,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GACxC,OAAO,CAAC,IAAI,CAAC;IA+BhB;;;OAGG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB9C;;;OAGG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IA0BxD;;;OAGG;IACH,qBAAqB,IAAI,iBAAiB,EAAE;IAM5C;;;OAGG;IACH,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI;IAStD;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAUhB,iBAAiB;IAkD/B,OAAO,CAAC,wBAAwB;IA4BhC,OAAO,CAAC,eAAe;CA8BxB"}
@@ -6,7 +6,6 @@
6
6
  */
7
7
  import * as chokidar from "chokidar";
8
8
  import { EventEmitter } from "events";
9
- import { FILE_WATCHER_EVENTS } from "../constants/events.js";
10
9
  export class FileWatcherService extends EventEmitter {
11
10
  constructor(logger, config) {
12
11
  super();
@@ -92,7 +91,7 @@ export class FileWatcherService extends EventEmitter {
92
91
  lastError: entry.lastError,
93
92
  lastEvent: entry.lastEvent > 0
94
93
  ? {
95
- type: FILE_WATCHER_EVENTS.CHANGE,
94
+ type: "change",
96
95
  path: entry.path,
97
96
  timestamp: entry.lastEvent,
98
97
  }
@@ -173,14 +172,14 @@ export class FileWatcherService extends EventEmitter {
173
172
  setupGlobalWatcherEvents() {
174
173
  if (!this.globalWatcher)
175
174
  return;
176
- this.globalWatcher.on(FILE_WATCHER_EVENTS.CHANGE, (filePath, stats) => {
177
- this.handleFileEvent(FILE_WATCHER_EVENTS.CHANGE, filePath, stats);
175
+ this.globalWatcher.on("change", (filePath, stats) => {
176
+ this.handleFileEvent("change", filePath, stats);
178
177
  });
179
178
  this.globalWatcher.on("add", (filePath, stats) => {
180
- this.handleFileEvent(FILE_WATCHER_EVENTS.CREATE, filePath, stats);
179
+ this.handleFileEvent("create", filePath, stats);
181
180
  });
182
181
  this.globalWatcher.on("unlink", (filePath) => {
183
- this.handleFileEvent(FILE_WATCHER_EVENTS.DELETE, filePath);
182
+ this.handleFileEvent("delete", filePath);
184
183
  });
185
184
  this.globalWatcher.on("error", (err) => {
186
185
  const error = err instanceof Error ? err : new Error(String(err));