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
@@ -0,0 +1,300 @@
1
+ /**
2
+ * File Watcher Service
3
+ *
4
+ * Provides robust cross-platform file watching using Chokidar library.
5
+ * Handles file watching with debouncing, error recovery, and graceful fallbacks.
6
+ */
7
+
8
+ import * as chokidar from "chokidar";
9
+ import { EventEmitter } from "events";
10
+ import type { Logger } from "../types/index.js";
11
+
12
+ export interface FileWatchEvent {
13
+ type: "change" | "create" | "delete" | "rename";
14
+ path: string;
15
+ timestamp: number;
16
+ size?: number;
17
+ }
18
+
19
+ export interface FileWatcherConfig {
20
+ stabilityThreshold: number; // Chokidar awaitWriteFinish delay (ms)
21
+ pollInterval: number; // Chokidar polling interval (ms)
22
+ maxRetries: number; // Default: 3
23
+ fallbackPolling: boolean; // Default: false
24
+ ignoreTempFiles: boolean; // Default: true
25
+ }
26
+
27
+ export interface FileWatcherStatus {
28
+ isActive: boolean;
29
+ path: string;
30
+ method: "native" | "polling" | "failed";
31
+ errorCount: number;
32
+ lastError?: string;
33
+ lastEvent?: FileWatchEvent;
34
+ }
35
+
36
+ interface FileWatcherEntry {
37
+ path: string;
38
+ watcher: chokidar.FSWatcher | null;
39
+ isActive: boolean;
40
+ lastEvent: number;
41
+ errorCount: number;
42
+ lastError?: string;
43
+ callbacks: Set<(event: FileWatchEvent) => void>;
44
+ config: FileWatcherConfig;
45
+ }
46
+
47
+ export class FileWatcherService extends EventEmitter {
48
+ private watchers: Map<string, FileWatcherEntry> = new Map();
49
+ private globalWatcher: chokidar.FSWatcher | null = null;
50
+ private defaultConfig: FileWatcherConfig;
51
+ private logger?: Logger;
52
+
53
+ constructor(logger?: Logger, config?: Partial<FileWatcherConfig>) {
54
+ super();
55
+ this.logger = logger;
56
+ this.defaultConfig = {
57
+ stabilityThreshold: 300,
58
+ pollInterval: 100,
59
+ maxRetries: 3,
60
+ fallbackPolling: false,
61
+ ignoreTempFiles: true,
62
+ ...config,
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Start watching a file
68
+ * Maps to FR-010: Handle file deletion, creation, and modification
69
+ */
70
+ async watchFile(
71
+ path: string,
72
+ callback: (event: FileWatchEvent) => void,
73
+ ): Promise<void> {
74
+ try {
75
+ if (this.watchers.has(path)) {
76
+ // Add callback to existing watcher
77
+ const entry = this.watchers.get(path)!;
78
+ entry.callbacks.add(callback);
79
+ return;
80
+ }
81
+
82
+ // Create new watcher entry
83
+ const entry: FileWatcherEntry = {
84
+ path,
85
+ watcher: null,
86
+ isActive: false,
87
+ lastEvent: Date.now(),
88
+ errorCount: 0,
89
+ lastError: undefined,
90
+ callbacks: new Set([callback]),
91
+ config: { ...this.defaultConfig },
92
+ };
93
+
94
+ this.watchers.set(path, entry);
95
+ await this.initializeWatcher(entry);
96
+ } catch (error) {
97
+ this.logger?.error(
98
+ `Live Config: Failed to watch file ${path}: ${(error as Error).message}`,
99
+ );
100
+ throw error;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Stop watching a file
106
+ * Resource cleanup
107
+ */
108
+ async unwatchFile(path: string): Promise<void> {
109
+ const entry = this.watchers.get(path);
110
+ if (!entry) return;
111
+
112
+ try {
113
+ if (entry.watcher) {
114
+ entry.watcher.unwatch(path);
115
+ }
116
+
117
+ this.watchers.delete(path);
118
+ this.logger?.info(`Live Config: Stopped watching file: ${path}`);
119
+ } catch (error) {
120
+ this.logger?.warn(
121
+ `Live Config: Error unwatching file ${path}: ${(error as Error).message}`,
122
+ );
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Get watcher status
128
+ * Maps to FR-012: Handle watcher initialization failures
129
+ */
130
+ getWatcherStatus(path: string): FileWatcherStatus | null {
131
+ const entry = this.watchers.get(path);
132
+ if (!entry) return null;
133
+
134
+ return {
135
+ isActive: entry.isActive,
136
+ path: entry.path,
137
+ method:
138
+ entry.errorCount > 0
139
+ ? "failed"
140
+ : entry.config.fallbackPolling
141
+ ? "polling"
142
+ : "native",
143
+ errorCount: entry.errorCount,
144
+ lastError: entry.lastError,
145
+ lastEvent:
146
+ entry.lastEvent > 0
147
+ ? {
148
+ type: "change",
149
+ path: entry.path,
150
+ timestamp: entry.lastEvent,
151
+ }
152
+ : undefined,
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Get all watcher statuses
158
+ * For monitoring and debugging
159
+ */
160
+ getAllWatcherStatuses(): FileWatcherStatus[] {
161
+ return Array.from(this.watchers.keys())
162
+ .map((path) => this.getWatcherStatus(path))
163
+ .filter((status): status is FileWatcherStatus => status !== null);
164
+ }
165
+
166
+ /**
167
+ * Configure watcher behavior
168
+ * Runtime configuration updates
169
+ */
170
+ updateConfig(config: Partial<FileWatcherConfig>): void {
171
+ this.defaultConfig = { ...this.defaultConfig, ...config };
172
+
173
+ // Update existing watchers with new config
174
+ for (const entry of this.watchers.values()) {
175
+ entry.config = { ...entry.config, ...config };
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Cleanup all watchers
181
+ */
182
+ async cleanup(): Promise<void> {
183
+ const paths = Array.from(this.watchers.keys());
184
+ await Promise.all(paths.map((path) => this.unwatchFile(path)));
185
+
186
+ if (this.globalWatcher) {
187
+ await this.globalWatcher.close();
188
+ this.globalWatcher = null;
189
+ }
190
+ }
191
+
192
+ private async initializeWatcher(entry: FileWatcherEntry): Promise<void> {
193
+ try {
194
+ // Initialize global watcher if needed
195
+ if (!this.globalWatcher) {
196
+ this.globalWatcher = chokidar.watch([], {
197
+ persistent: true,
198
+ ignoreInitial: true,
199
+ awaitWriteFinish: {
200
+ stabilityThreshold: entry.config.stabilityThreshold,
201
+ pollInterval: entry.config.pollInterval,
202
+ },
203
+ usePolling: entry.config.fallbackPolling,
204
+ interval: entry.config.pollInterval,
205
+ });
206
+
207
+ this.setupGlobalWatcherEvents();
208
+ }
209
+
210
+ // Add path to global watcher
211
+ this.globalWatcher.add(entry.path);
212
+ entry.watcher = this.globalWatcher;
213
+ entry.isActive = true;
214
+ entry.errorCount = 0;
215
+
216
+ this.logger?.info(`Live Config: Started watching file: ${entry.path}`);
217
+ } catch (error) {
218
+ entry.errorCount++;
219
+ entry.isActive = false;
220
+ entry.lastError = (error as Error).message;
221
+
222
+ this.logger?.error(
223
+ `Live Config: Failed to initialize watcher for ${entry.path}: ${(error as Error).message}`,
224
+ );
225
+
226
+ // Try fallback polling if not already using it
227
+ if (
228
+ !entry.config.fallbackPolling &&
229
+ entry.errorCount < entry.config.maxRetries
230
+ ) {
231
+ this.logger?.info(
232
+ `Live Config: Attempting polling fallback for ${entry.path}`,
233
+ );
234
+ entry.config.fallbackPolling = true;
235
+ await this.initializeWatcher(entry);
236
+ } else {
237
+ throw error;
238
+ }
239
+ }
240
+ }
241
+
242
+ private setupGlobalWatcherEvents(): void {
243
+ if (!this.globalWatcher) return;
244
+
245
+ this.globalWatcher.on(
246
+ "change",
247
+ (filePath: string, stats?: { size?: number }) => {
248
+ this.handleFileEvent("change", filePath, stats);
249
+ },
250
+ );
251
+
252
+ this.globalWatcher.on(
253
+ "add",
254
+ (filePath: string, stats?: { size?: number }) => {
255
+ this.handleFileEvent("create", filePath, stats);
256
+ },
257
+ );
258
+
259
+ this.globalWatcher.on("unlink", (filePath: string) => {
260
+ this.handleFileEvent("delete", filePath);
261
+ });
262
+
263
+ this.globalWatcher.on("error", (err: unknown) => {
264
+ const error = err instanceof Error ? err : new Error(String(err));
265
+ this.logger?.error(`Live Config: File watcher error: ${error.message}`);
266
+ this.emit("watcherError", error);
267
+ });
268
+ }
269
+
270
+ private handleFileEvent(
271
+ type: FileWatchEvent["type"],
272
+ filePath: string,
273
+ stats?: { size?: number },
274
+ ): void {
275
+ const entry = this.watchers.get(filePath);
276
+ if (!entry) return;
277
+
278
+ const event: FileWatchEvent = {
279
+ type,
280
+ path: filePath,
281
+ timestamp: Date.now(),
282
+ size: stats?.size,
283
+ };
284
+
285
+ entry.lastEvent = event.timestamp;
286
+
287
+ // Notify all callbacks for this file
288
+ for (const callback of entry.callbacks) {
289
+ try {
290
+ callback(event);
291
+ } catch (error) {
292
+ this.logger?.error(
293
+ `Live Config: Error in file watch callback for ${filePath}: ${(error as Error).message}`,
294
+ );
295
+ }
296
+ }
297
+
298
+ this.logger?.debug(`Live Config: File ${type} event for ${filePath}`);
299
+ }
300
+ }
@@ -1,25 +1,20 @@
1
1
  /**
2
- * Hook Services
2
+ * Hook Execution Services
3
3
  *
4
- * Consolidated hook services providing both execution and configuration functionality.
5
- * Combines hook command execution and settings management into a single module.
4
+ * Provides hook command execution functionality and hook-specific configuration loading.
5
+ * This module focuses on hook execution while delegating general Wave configuration
6
+ * management to ConfigurationService.
6
7
  */
7
8
 
8
9
  import { spawn, type ChildProcess } from "child_process";
9
- import { existsSync, readFileSync } from "fs";
10
- import { join } from "path";
11
- import { homedir } from "os";
12
10
  import {
13
11
  type HookExecutionContext,
14
12
  type HookExecutionResult,
15
13
  type HookExecutionOptions,
16
14
  type ExtendedHookExecutionContext,
17
15
  type HookJsonInput,
18
- type HookConfiguration,
19
- type PartialHookConfiguration,
20
- getSessionFilePath,
21
- isValidHookEvent,
22
16
  } from "../types/hooks.js";
17
+ import { generateSessionFilePath } from "./session.js";
23
18
 
24
19
  // =============================================================================
25
20
  // Hook Execution Functions
@@ -28,15 +23,27 @@ import {
28
23
  /**
29
24
  * Build JSON input data for hook stdin
30
25
  */
31
- function buildHookJsonInput(
26
+ async function buildHookJsonInput(
32
27
  context: ExtendedHookExecutionContext,
33
- ): HookJsonInput {
28
+ ): Promise<HookJsonInput> {
29
+ const workdir = context.cwd || context.projectDir || process.cwd();
30
+
31
+ let transcriptPath = context.transcriptPath;
32
+ if (!transcriptPath && context.sessionId) {
33
+ try {
34
+ transcriptPath = await generateSessionFilePath(
35
+ context.sessionId,
36
+ workdir,
37
+ );
38
+ } catch {
39
+ transcriptPath = "";
40
+ }
41
+ }
42
+
34
43
  const jsonInput: HookJsonInput = {
35
44
  session_id: context.sessionId || "unknown",
36
- transcript_path:
37
- context.transcriptPath ||
38
- (context.sessionId ? getSessionFilePath(context.sessionId) : ""),
39
- cwd: context.cwd || context.projectDir,
45
+ transcript_path: transcriptPath || "",
46
+ cwd: workdir,
40
47
  hook_event_name: context.event,
41
48
  };
42
49
 
@@ -61,6 +68,21 @@ function buildHookJsonInput(
61
68
  jsonInput.user_prompt = context.userPrompt;
62
69
  }
63
70
 
71
+ // Add subagent_type if present
72
+ if (context.subagentType !== undefined) {
73
+ jsonInput.subagent_type = context.subagentType;
74
+ }
75
+
76
+ // Add notification fields for Notification events
77
+ if (context.event === "Notification") {
78
+ if (context.message !== undefined) {
79
+ jsonInput.message = context.message;
80
+ }
81
+ if (context.notificationType !== undefined) {
82
+ jsonInput.notification_type = context.notificationType;
83
+ }
84
+ }
85
+
64
86
  return jsonInput;
65
87
  }
66
88
 
@@ -93,6 +115,17 @@ export async function executeCommand(
93
115
  };
94
116
  }
95
117
 
118
+ // Prepare JSON input for hooks that need it
119
+ let jsonInput: string | null = null;
120
+ if ("sessionId" in context) {
121
+ try {
122
+ const hookJsonInput = await buildHookJsonInput(context);
123
+ jsonInput = JSON.stringify(hookJsonInput, null, 2);
124
+ } catch {
125
+ // Continue execution even if JSON input preparation fails
126
+ }
127
+ }
128
+
96
129
  return new Promise((resolve) => {
97
130
  let stdout = "";
98
131
  let stderr = "";
@@ -107,7 +140,8 @@ export async function executeCommand(
107
140
  stdio: ["pipe", "pipe", "pipe"],
108
141
  cwd: context.projectDir,
109
142
  env: {
110
- ...process.env,
143
+ ...process.env, // Environment variables from process.env
144
+ ...("env" in context ? context.env || {} : {}), // Additional environment variables from configuration (if ExtendedHookExecutionContext)
111
145
  HOOK_EVENT: context.event,
112
146
  HOOK_TOOL_NAME: context.toolName || "",
113
147
  HOOK_PROJECT_DIR: context.projectDir,
@@ -141,11 +175,10 @@ export async function executeCommand(
141
175
  });
142
176
  }
143
177
 
144
- // Send JSON input to stdin if we have extended context
145
- if (childProcess.stdin && "sessionId" in context) {
178
+ // Send JSON input to stdin if we have prepared it
179
+ if (childProcess.stdin && jsonInput) {
146
180
  try {
147
- const jsonInput = buildHookJsonInput(context);
148
- childProcess.stdin.write(JSON.stringify(jsonInput, null, 2));
181
+ childProcess.stdin.write(jsonInput);
149
182
  childProcess.stdin.end();
150
183
  } catch {
151
184
  // Continue execution even if JSON input fails
@@ -235,126 +268,3 @@ export function isCommandSafe(command: string): boolean {
235
268
  pattern.test(trimmed.toLowerCase()),
236
269
  );
237
270
  }
238
-
239
- // =============================================================================
240
- // Hook Settings Functions
241
- // =============================================================================
242
-
243
- /**
244
- * Get the user-specific hooks configuration file path
245
- */
246
- export function getUserHooksConfigPath(): string {
247
- return join(homedir(), ".wave", "settings.json");
248
- }
249
-
250
- /**
251
- * Get the project-specific hooks configuration file path
252
- */
253
- export function getProjectHooksConfigPath(workdir: string): string {
254
- return join(workdir, ".wave", "settings.json");
255
- }
256
-
257
- /**
258
- * Load hooks configuration from a JSON file
259
- */
260
- export function loadHooksConfigFromFile(
261
- filePath: string,
262
- ): PartialHookConfiguration | null {
263
- if (!existsSync(filePath)) {
264
- return null;
265
- }
266
-
267
- const content = readFileSync(filePath, "utf-8");
268
- const config = JSON.parse(content) as HookConfiguration;
269
-
270
- // Validate basic structure
271
- if (!config || typeof config !== "object" || !config.hooks) {
272
- throw new Error(`Invalid hooks configuration structure in ${filePath}`);
273
- }
274
-
275
- return config.hooks;
276
- }
277
-
278
- /**
279
- * Load user-specific hooks configuration
280
- */
281
- export function loadUserHooksConfig(): PartialHookConfiguration | null {
282
- return loadHooksConfigFromFile(getUserHooksConfigPath());
283
- }
284
-
285
- /**
286
- * Load project-specific hooks configuration
287
- */
288
- export function loadProjectHooksConfig(
289
- workdir: string,
290
- ): PartialHookConfiguration | null {
291
- return loadHooksConfigFromFile(getProjectHooksConfigPath(workdir));
292
- }
293
-
294
- /**
295
- * Load and merge hooks configuration from both user and project sources
296
- */
297
- export function loadMergedHooksConfig(
298
- workdir: string,
299
- ): PartialHookConfiguration | null {
300
- const userConfig = loadUserHooksConfig();
301
- const projectConfig = loadProjectHooksConfig(workdir);
302
-
303
- // No configuration found
304
- if (!userConfig && !projectConfig) {
305
- return null;
306
- }
307
-
308
- // Only one configuration found
309
- if (!userConfig) return projectConfig;
310
- if (!projectConfig) return userConfig;
311
-
312
- // Merge configurations (project overrides user)
313
- const merged: PartialHookConfiguration = {};
314
-
315
- // Combine all hook events
316
- const allEvents = new Set([
317
- ...Object.keys(userConfig),
318
- ...Object.keys(projectConfig),
319
- ]);
320
-
321
- for (const event of allEvents) {
322
- if (!isValidHookEvent(event)) continue;
323
-
324
- const userEventConfigs = userConfig[event] || [];
325
- const projectEventConfigs = projectConfig[event] || [];
326
-
327
- // Project configurations take precedence
328
- merged[event] = [...userEventConfigs, ...projectEventConfigs];
329
- }
330
-
331
- return merged;
332
- }
333
-
334
- /**
335
- * Check if hooks configuration exists (user or project)
336
- */
337
- export function hasHooksConfiguration(workdir: string): boolean {
338
- return (
339
- existsSync(getUserHooksConfigPath()) ||
340
- existsSync(getProjectHooksConfigPath(workdir))
341
- );
342
- }
343
-
344
- /**
345
- * Get hooks configuration information for debugging
346
- */
347
- export function getHooksConfigurationInfo(workdir: string): {
348
- hasUser: boolean;
349
- hasProject: boolean;
350
- paths: string[];
351
- } {
352
- const userPath = getUserHooksConfigPath();
353
- const projectPath = getProjectHooksConfigPath(workdir);
354
-
355
- return {
356
- hasUser: existsSync(userPath),
357
- hasProject: existsSync(projectPath),
358
- paths: [userPath, projectPath],
359
- };
360
- }