wave-agent-sdk 0.0.6 → 0.0.8

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 (180) hide show
  1. package/dist/agent.d.ts +32 -20
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +209 -24
  4. package/dist/constants/events.d.ts +28 -0
  5. package/dist/constants/events.d.ts.map +1 -0
  6. package/dist/constants/events.js +27 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +2 -0
  10. package/dist/managers/aiManager.d.ts +34 -1
  11. package/dist/managers/aiManager.d.ts.map +1 -1
  12. package/dist/managers/aiManager.js +248 -132
  13. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  14. package/dist/managers/backgroundBashManager.js +7 -6
  15. package/dist/managers/hookManager.d.ts +13 -16
  16. package/dist/managers/hookManager.d.ts.map +1 -1
  17. package/dist/managers/hookManager.js +81 -44
  18. package/dist/managers/liveConfigManager.d.ts +58 -0
  19. package/dist/managers/liveConfigManager.d.ts.map +1 -0
  20. package/dist/managers/liveConfigManager.js +160 -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 +168 -49
  24. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  25. package/dist/managers/slashCommandManager.js +9 -3
  26. package/dist/managers/subagentManager.d.ts +51 -0
  27. package/dist/managers/subagentManager.d.ts.map +1 -1
  28. package/dist/managers/subagentManager.js +190 -19
  29. package/dist/services/aiService.d.ts +13 -5
  30. package/dist/services/aiService.d.ts.map +1 -1
  31. package/dist/services/aiService.js +350 -74
  32. package/dist/services/configurationWatcher.d.ts +120 -0
  33. package/dist/services/configurationWatcher.d.ts.map +1 -0
  34. package/dist/services/configurationWatcher.js +439 -0
  35. package/dist/services/fileWatcher.d.ts +69 -0
  36. package/dist/services/fileWatcher.d.ts.map +1 -0
  37. package/dist/services/fileWatcher.js +213 -0
  38. package/dist/services/hook.d.ts +91 -9
  39. package/dist/services/hook.d.ts.map +1 -1
  40. package/dist/services/hook.js +393 -43
  41. package/dist/services/jsonlHandler.d.ts +62 -0
  42. package/dist/services/jsonlHandler.d.ts.map +1 -0
  43. package/dist/services/jsonlHandler.js +257 -0
  44. package/dist/services/memory.d.ts +9 -0
  45. package/dist/services/memory.d.ts.map +1 -1
  46. package/dist/services/memory.js +81 -12
  47. package/dist/services/memoryStore.d.ts +81 -0
  48. package/dist/services/memoryStore.d.ts.map +1 -0
  49. package/dist/services/memoryStore.js +200 -0
  50. package/dist/services/session.d.ts +64 -49
  51. package/dist/services/session.d.ts.map +1 -1
  52. package/dist/services/session.js +310 -132
  53. package/dist/tools/bashTool.d.ts.map +1 -1
  54. package/dist/tools/bashTool.js +5 -4
  55. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  56. package/dist/tools/deleteFileTool.js +2 -1
  57. package/dist/tools/editTool.d.ts.map +1 -1
  58. package/dist/tools/editTool.js +3 -2
  59. package/dist/tools/multiEditTool.d.ts.map +1 -1
  60. package/dist/tools/multiEditTool.js +4 -3
  61. package/dist/tools/readTool.d.ts.map +1 -1
  62. package/dist/tools/readTool.js +2 -1
  63. package/dist/tools/todoWriteTool.d.ts.map +1 -1
  64. package/dist/tools/todoWriteTool.js +3 -10
  65. package/dist/tools/writeTool.d.ts.map +1 -1
  66. package/dist/tools/writeTool.js +5 -6
  67. package/dist/types/commands.d.ts +4 -0
  68. package/dist/types/commands.d.ts.map +1 -1
  69. package/dist/types/core.d.ts +35 -0
  70. package/dist/types/core.d.ts.map +1 -1
  71. package/dist/types/environment.d.ts +42 -0
  72. package/dist/types/environment.d.ts.map +1 -0
  73. package/dist/types/environment.js +21 -0
  74. package/dist/types/hooks.d.ts +8 -2
  75. package/dist/types/hooks.d.ts.map +1 -1
  76. package/dist/types/hooks.js +8 -2
  77. package/dist/types/index.d.ts +2 -0
  78. package/dist/types/index.d.ts.map +1 -1
  79. package/dist/types/index.js +2 -0
  80. package/dist/types/memoryStore.d.ts +82 -0
  81. package/dist/types/memoryStore.d.ts.map +1 -0
  82. package/dist/types/memoryStore.js +7 -0
  83. package/dist/types/messaging.d.ts +21 -9
  84. package/dist/types/messaging.d.ts.map +1 -1
  85. package/dist/types/messaging.js +5 -1
  86. package/dist/types/session.d.ts +20 -0
  87. package/dist/types/session.d.ts.map +1 -0
  88. package/dist/types/session.js +7 -0
  89. package/dist/utils/bashHistory.d.ts.map +1 -1
  90. package/dist/utils/bashHistory.js +27 -26
  91. package/dist/utils/cacheControlUtils.d.ts +121 -0
  92. package/dist/utils/cacheControlUtils.d.ts.map +1 -0
  93. package/dist/utils/cacheControlUtils.js +367 -0
  94. package/dist/utils/commandPathResolver.d.ts +52 -0
  95. package/dist/utils/commandPathResolver.d.ts.map +1 -0
  96. package/dist/utils/commandPathResolver.js +145 -0
  97. package/dist/utils/configPaths.d.ts +85 -0
  98. package/dist/utils/configPaths.d.ts.map +1 -0
  99. package/dist/utils/configPaths.js +121 -0
  100. package/dist/utils/configResolver.d.ts +37 -10
  101. package/dist/utils/configResolver.d.ts.map +1 -1
  102. package/dist/utils/configResolver.js +127 -23
  103. package/dist/utils/constants.d.ts +1 -1
  104. package/dist/utils/constants.js +1 -1
  105. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  106. package/dist/utils/convertMessagesForAPI.js +8 -13
  107. package/dist/utils/customCommands.d.ts.map +1 -1
  108. package/dist/utils/customCommands.js +66 -21
  109. package/dist/utils/fileUtils.d.ts +15 -0
  110. package/dist/utils/fileUtils.d.ts.map +1 -0
  111. package/dist/utils/fileUtils.js +61 -0
  112. package/dist/utils/globalLogger.d.ts +102 -0
  113. package/dist/utils/globalLogger.d.ts.map +1 -0
  114. package/dist/utils/globalLogger.js +136 -0
  115. package/dist/utils/hookMatcher.d.ts +1 -6
  116. package/dist/utils/hookMatcher.d.ts.map +1 -1
  117. package/dist/utils/mcpUtils.d.ts.map +1 -1
  118. package/dist/utils/mcpUtils.js +25 -3
  119. package/dist/utils/messageOperations.d.ts +27 -27
  120. package/dist/utils/messageOperations.d.ts.map +1 -1
  121. package/dist/utils/messageOperations.js +46 -36
  122. package/dist/utils/pathEncoder.d.ts +104 -0
  123. package/dist/utils/pathEncoder.d.ts.map +1 -0
  124. package/dist/utils/pathEncoder.js +272 -0
  125. package/dist/utils/subagentParser.d.ts.map +1 -1
  126. package/dist/utils/subagentParser.js +2 -1
  127. package/dist/utils/tokenCalculation.d.ts +26 -0
  128. package/dist/utils/tokenCalculation.d.ts.map +1 -0
  129. package/dist/utils/tokenCalculation.js +36 -0
  130. package/package.json +6 -3
  131. package/src/agent.ts +301 -37
  132. package/src/constants/events.ts +38 -0
  133. package/src/index.ts +2 -0
  134. package/src/managers/aiManager.ts +325 -173
  135. package/src/managers/backgroundBashManager.ts +7 -6
  136. package/src/managers/hookManager.ts +106 -84
  137. package/src/managers/liveConfigManager.ts +248 -0
  138. package/src/managers/messageManager.ts +237 -100
  139. package/src/managers/slashCommandManager.ts +9 -7
  140. package/src/managers/subagentManager.ts +284 -22
  141. package/src/services/aiService.ts +474 -83
  142. package/src/services/configurationWatcher.ts +622 -0
  143. package/src/services/fileWatcher.ts +301 -0
  144. package/src/services/hook.ts +538 -47
  145. package/src/services/jsonlHandler.ts +319 -0
  146. package/src/services/memory.ts +92 -12
  147. package/src/services/memoryStore.ts +279 -0
  148. package/src/services/session.ts +381 -157
  149. package/src/tools/bashTool.ts +5 -4
  150. package/src/tools/deleteFileTool.ts +2 -1
  151. package/src/tools/editTool.ts +3 -2
  152. package/src/tools/multiEditTool.ts +4 -3
  153. package/src/tools/readTool.ts +2 -1
  154. package/src/tools/todoWriteTool.ts +3 -11
  155. package/src/tools/writeTool.ts +7 -6
  156. package/src/types/commands.ts +6 -0
  157. package/src/types/core.ts +44 -0
  158. package/src/types/environment.ts +60 -0
  159. package/src/types/hooks.ts +21 -8
  160. package/src/types/index.ts +2 -0
  161. package/src/types/memoryStore.ts +94 -0
  162. package/src/types/messaging.ts +21 -10
  163. package/src/types/session.ts +25 -0
  164. package/src/utils/bashHistory.ts +27 -27
  165. package/src/utils/cacheControlUtils.ts +540 -0
  166. package/src/utils/commandPathResolver.ts +189 -0
  167. package/src/utils/configPaths.ts +163 -0
  168. package/src/utils/configResolver.ts +182 -22
  169. package/src/utils/constants.ts +1 -1
  170. package/src/utils/convertMessagesForAPI.ts +8 -14
  171. package/src/utils/customCommands.ts +90 -22
  172. package/src/utils/fileUtils.ts +65 -0
  173. package/src/utils/globalLogger.ts +145 -0
  174. package/src/utils/hookMatcher.ts +1 -12
  175. package/src/utils/mcpUtils.ts +34 -3
  176. package/src/utils/messageOperations.ts +77 -60
  177. package/src/utils/pathEncoder.ts +379 -0
  178. package/src/utils/subagentParser.ts +2 -1
  179. package/src/utils/tokenCalculation.ts +43 -0
  180. package/src/types/index.ts.backup +0 -357
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "child_process";
2
+ import { logger } from "../utils/globalLogger.js";
2
3
  import type { BackgroundShell } from "../types/index.js";
3
4
 
4
5
  export interface BackgroundBashManagerCallbacks {
@@ -127,8 +128,8 @@ export class BackgroundBashManager {
127
128
  .split("\n")
128
129
  .filter((line) => regex.test(line))
129
130
  .join("\n");
130
- } catch {
131
- // logger.warn(`Invalid filter regex: ${filter}`, error);
131
+ } catch (error) {
132
+ logger.warn(`Invalid filter regex: ${filter}`, error);
132
133
  }
133
134
  }
134
135
 
@@ -159,8 +160,8 @@ export class BackgroundBashManager {
159
160
  ) {
160
161
  try {
161
162
  process.kill(-shell.process.pid, "SIGKILL");
162
- } catch {
163
- // logger.error("Failed to force kill process:", error);
163
+ } catch (error) {
164
+ logger.error("Failed to force kill process:", error);
164
165
  }
165
166
  }
166
167
  }, 1000);
@@ -183,8 +184,8 @@ export class BackgroundBashManager {
183
184
  shell.runtime = Date.now() - shell.startTime;
184
185
  this.notifyShellsChange();
185
186
  return true;
186
- } catch {
187
- // logger.error("Failed to kill child process:", directKillError);
187
+ } catch (directKillError) {
188
+ logger.error("Failed to kill child process:", directKillError);
188
189
  return false;
189
190
  }
190
191
  }
@@ -8,7 +8,7 @@
8
8
  import {
9
9
  type HookEvent,
10
10
  type HookEventConfig,
11
- type HookConfiguration,
11
+ type WaveConfiguration,
12
12
  type PartialHookConfiguration,
13
13
  type HookExecutionContext,
14
14
  type ExtendedHookExecutionContext,
@@ -18,50 +18,26 @@ import {
18
18
  isValidHookEvent,
19
19
  isValidHookEventConfig,
20
20
  } from "../types/hooks.js";
21
- import { type IHookMatcher, HookMatcher } from "../utils/hookMatcher.js";
21
+ import { HookMatcher } from "../utils/hookMatcher.js";
22
22
  import {
23
23
  executeCommand,
24
24
  isCommandSafe,
25
- loadMergedHooksConfig,
25
+ loadMergedWaveConfig,
26
26
  } from "../services/hook.js";
27
27
  import type { Logger } from "../types/index.js";
28
+ import { MessageSource } from "../types/index.js";
28
29
  import type { MessageManager } from "./messageManager.js";
29
30
 
30
- export interface IHookManager {
31
- // Load configuration from settings
32
- loadConfiguration(
33
- userHooks?: PartialHookConfiguration,
34
- projectHooks?: PartialHookConfiguration,
35
- ): void;
36
-
37
- // Load configuration from filesystem settings
38
- loadConfigurationFromSettings(): void;
39
-
40
- // Execute hooks for specific event
41
- executeHooks(
42
- event: HookEvent,
43
- context: HookExecutionContext | ExtendedHookExecutionContext,
44
- ): Promise<HookExecutionResult[]>;
45
-
46
- // Check if hooks are configured for event
47
- hasHooks(event: HookEvent, toolName?: string): boolean;
48
-
49
- // Validate hook configuration
50
- validateConfiguration(config: HookConfiguration): ValidationResult;
51
-
52
- // Get current configuration
53
- getConfiguration(): PartialHookConfiguration | undefined;
54
- }
55
-
56
- export class HookManager implements IHookManager {
31
+ export class HookManager {
57
32
  private configuration: PartialHookConfiguration | undefined;
58
- private readonly matcher: IHookMatcher;
33
+ private environmentVars: Record<string, string> | undefined;
34
+ private readonly matcher: HookMatcher;
59
35
  private readonly logger?: Logger;
60
36
  private readonly workdir: string;
61
37
 
62
38
  constructor(
63
39
  workdir: string,
64
- matcher: IHookMatcher = new HookMatcher(),
40
+ matcher: HookMatcher = new HookMatcher(),
65
41
  logger?: Logger,
66
42
  ) {
67
43
  this.workdir = workdir;
@@ -103,18 +79,25 @@ export class HookManager implements IHookManager {
103
79
 
104
80
  /**
105
81
  * Load configuration from filesystem settings
106
- * Automatically loads and merges user and project hooks configuration
82
+ * Automatically loads and merges user and project Wave configuration (hooks + environment)
107
83
  */
108
84
  loadConfigurationFromSettings(): void {
109
85
  try {
110
86
  this.logger?.debug(`[HookManager] Loading configuration...`);
111
- const mergedConfig = loadMergedHooksConfig(this.workdir);
112
- this.logger?.debug(`[HookManager] Merged config result:`, mergedConfig);
113
- this.configuration = mergedConfig || undefined;
87
+ const mergedWaveConfig = loadMergedWaveConfig(this.workdir);
88
+ this.logger?.debug(
89
+ `[HookManager] Merged config result:`,
90
+ mergedWaveConfig,
91
+ );
92
+
93
+ this.configuration = mergedWaveConfig?.hooks || undefined;
94
+ this.environmentVars = mergedWaveConfig?.env || undefined;
114
95
 
115
96
  // Validate the loaded configuration if it exists
116
- if (mergedConfig) {
117
- const validation = this.validatePartialConfiguration(mergedConfig);
97
+ if (mergedWaveConfig?.hooks) {
98
+ const validation = this.validatePartialConfiguration(
99
+ mergedWaveConfig.hooks,
100
+ );
118
101
  if (!validation.valid) {
119
102
  throw new HookConfigurationError(
120
103
  "filesystem settings",
@@ -124,11 +107,12 @@ export class HookManager implements IHookManager {
124
107
  }
125
108
 
126
109
  this.logger?.debug(
127
- `[HookManager] Configuration loaded successfully with ${Object.keys(mergedConfig || {}).length} event types`,
110
+ `[HookManager] Configuration loaded successfully with ${Object.keys(mergedWaveConfig?.hooks || {}).length} event types and ${Object.keys(this.environmentVars || {}).length} environment variables`,
128
111
  );
129
112
  } catch (error) {
130
113
  // If loading fails, start with undefined configuration (no hooks)
131
114
  this.configuration = undefined;
115
+ this.environmentVars = undefined;
132
116
 
133
117
  // Re-throw configuration errors, but handle file system errors gracefully
134
118
  if (error instanceof HookConfigurationError) {
@@ -219,7 +203,12 @@ export class HookManager implements IHookManager {
219
203
  `[HookManager] Executing command ${commandIndex + 1}/${config.hooks.length} in configuration ${configIndex + 1}`,
220
204
  );
221
205
 
222
- const result = await executeCommand(hookCommand.command, context);
206
+ const result = await executeCommand(
207
+ hookCommand.command,
208
+ context,
209
+ undefined,
210
+ this.environmentVars,
211
+ );
223
212
  results.push(result);
224
213
 
225
214
  // Report individual command result
@@ -228,7 +217,7 @@ export class HookManager implements IHookManager {
228
217
  `[HookManager] Command ${commandIndex + 1} completed successfully in ${result.duration}ms`,
229
218
  );
230
219
  } else {
231
- this.logger?.warn(
220
+ this.logger?.debug(
232
221
  `[HookManager] Command ${commandIndex + 1} failed in ${result.duration}ms (exit code: ${result.exitCode}, timed out: ${result.timedOut})`,
233
222
  );
234
223
  }
@@ -274,7 +263,7 @@ export class HookManager implements IHookManager {
274
263
  results: HookExecutionResult[],
275
264
  messageManager?: MessageManager,
276
265
  toolId?: string,
277
- originalToolResult?: string,
266
+ toolParameters?: string,
278
267
  ): {
279
268
  shouldBlock: boolean;
280
269
  errorMessage?: string;
@@ -293,7 +282,7 @@ export class HookManager implements IHookManager {
293
282
  result,
294
283
  messageManager,
295
284
  toolId,
296
- originalToolResult,
285
+ toolParameters,
297
286
  );
298
287
  }
299
288
  }
@@ -327,7 +316,10 @@ export class HookManager implements IHookManager {
327
316
  ): void {
328
317
  if (event === "UserPromptSubmit" && result.stdout?.trim()) {
329
318
  // Inject stdout as user message context for UserPromptSubmit
330
- messageManager.addUserMessage(result.stdout.trim());
319
+ messageManager.addUserMessage({
320
+ content: result.stdout.trim(),
321
+ source: MessageSource.HOOK,
322
+ });
331
323
  }
332
324
  // For other hook types (PreToolUse, PostToolUse, Stop), ignore stdout
333
325
  }
@@ -340,7 +332,7 @@ export class HookManager implements IHookManager {
340
332
  result: HookExecutionResult,
341
333
  messageManager: MessageManager,
342
334
  toolId?: string,
343
- originalToolResult?: string,
335
+ toolParameters?: string,
344
336
  ): {
345
337
  shouldBlock: boolean;
346
338
  errorMessage?: string;
@@ -361,28 +353,30 @@ export class HookManager implements IHookManager {
361
353
  // Block tool execution and show error to Wave Agent via tool block
362
354
  if (toolId) {
363
355
  messageManager.updateToolBlock({
364
- toolId,
356
+ id: toolId,
357
+ parameters: toolParameters || "",
365
358
  result: errorMessage,
366
359
  success: false,
367
360
  error: "Hook blocked tool execution",
361
+ stage: "end", // Hook blocking results in end stage with error
368
362
  });
369
363
  }
370
364
  return { shouldBlock: true };
371
365
 
372
366
  case "PostToolUse":
373
- // Show error to Wave Agent via tool block, execution continues
374
- if (toolId && originalToolResult !== undefined) {
375
- messageManager.updateToolBlock({
376
- toolId,
377
- result: `${originalToolResult}\n\nHook feedback: ${errorMessage}`,
378
- success: false,
379
- });
380
- }
367
+ // Show error to Wave Agent via user message and allow AI to continue
368
+ messageManager.addUserMessage({
369
+ content: errorMessage,
370
+ source: MessageSource.HOOK,
371
+ });
381
372
  return { shouldBlock: false };
382
373
 
383
374
  case "Stop":
384
375
  // Show error to Wave Agent via user message and block stopping to continue conversation
385
- messageManager.addUserMessage(errorMessage);
376
+ messageManager.addUserMessage({
377
+ content: errorMessage,
378
+ source: MessageSource.HOOK,
379
+ });
386
380
  return { shouldBlock: true, errorMessage };
387
381
 
388
382
  default:
@@ -416,46 +410,62 @@ export class HookManager implements IHookManager {
416
410
  }
417
411
 
418
412
  /**
419
- * Validate hook configuration structure and content
413
+ * Validate Wave configuration structure and content
420
414
  */
421
- validateConfiguration(config: HookConfiguration): ValidationResult {
415
+ validateConfiguration(config: WaveConfiguration): ValidationResult {
422
416
  const errors: string[] = [];
423
417
 
424
418
  if (!config || typeof config !== "object") {
425
419
  return { valid: false, errors: ["Configuration must be an object"] };
426
420
  }
427
421
 
428
- if (!config.hooks || typeof config.hooks !== "object") {
429
- return {
430
- valid: false,
431
- errors: ["Configuration must have a hooks property"],
432
- };
433
- }
422
+ // Validate hooks if present
423
+ if (config.hooks) {
424
+ if (typeof config.hooks !== "object") {
425
+ errors.push("hooks property must be an object");
426
+ } else {
427
+ // Validate each hook event
428
+ for (const [eventName, eventConfigs] of Object.entries(config.hooks)) {
429
+ // Validate event name
430
+ if (!isValidHookEvent(eventName)) {
431
+ errors.push(`Invalid hook event: ${eventName}`);
432
+ continue;
433
+ }
434
434
 
435
- // Validate each hook event
436
- for (const [eventName, eventConfigs] of Object.entries(config.hooks)) {
437
- // Validate event name
438
- if (!isValidHookEvent(eventName)) {
439
- errors.push(`Invalid hook event: ${eventName}`);
440
- continue;
441
- }
435
+ // Validate event configurations
436
+ if (!Array.isArray(eventConfigs)) {
437
+ errors.push(
438
+ `Hook event ${eventName} must be an array of configurations`,
439
+ );
440
+ continue;
441
+ }
442
442
 
443
- // Validate event configurations
444
- if (!Array.isArray(eventConfigs)) {
445
- errors.push(
446
- `Hook event ${eventName} must be an array of configurations`,
447
- );
448
- continue;
443
+ eventConfigs.forEach((eventConfig, index) => {
444
+ const configErrors = this.validateEventConfig(
445
+ eventName as HookEvent,
446
+ eventConfig,
447
+ index,
448
+ );
449
+ errors.push(...configErrors);
450
+ });
451
+ }
449
452
  }
453
+ }
450
454
 
451
- eventConfigs.forEach((eventConfig, index) => {
452
- const configErrors = this.validateEventConfig(
453
- eventName as HookEvent,
454
- eventConfig,
455
- index,
456
- );
457
- errors.push(...configErrors);
458
- });
455
+ // Validate environment variables if present
456
+ if (config.env) {
457
+ if (typeof config.env !== "object" || Array.isArray(config.env)) {
458
+ errors.push("env property must be an object");
459
+ } else {
460
+ for (const [key, value] of Object.entries(config.env)) {
461
+ if (typeof key !== "string" || key.trim() === "") {
462
+ errors.push(`Invalid environment variable key: ${key}`);
463
+ }
464
+ if (typeof value !== "string") {
465
+ errors.push(`Environment variable ${key} must have a string value`);
466
+ }
467
+ }
468
+ }
459
469
  }
460
470
 
461
471
  return {
@@ -518,6 +528,16 @@ export class HookManager implements IHookManager {
518
528
  return JSON.parse(JSON.stringify(this.configuration));
519
529
  }
520
530
 
531
+ /**
532
+ * Get current environment variables
533
+ */
534
+ getEnvironmentVars(): Record<string, string> | undefined {
535
+ if (!this.environmentVars) return undefined;
536
+
537
+ // Deep clone to prevent external modification
538
+ return JSON.parse(JSON.stringify(this.environmentVars));
539
+ }
540
+
521
541
  /**
522
542
  * Clear current configuration
523
543
  */
@@ -718,6 +738,7 @@ export class HookManager implements IHookManager {
718
738
  PostToolUse: 0,
719
739
  UserPromptSubmit: 0,
720
740
  Stop: 0,
741
+ SubagentStop: 0,
721
742
  },
722
743
  };
723
744
  }
@@ -727,6 +748,7 @@ export class HookManager implements IHookManager {
727
748
  PostToolUse: 0,
728
749
  UserPromptSubmit: 0,
729
750
  Stop: 0,
751
+ SubagentStop: 0,
730
752
  };
731
753
 
732
754
  let totalConfigs = 0;
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Live Configuration Manager
3
+ *
4
+ * Orchestrates live configuration reload functionality including:
5
+ * - Hook configuration watching and reloading
6
+ * - Memory store management for AGENTS.md files
7
+ * - Coordination between file watchers and configuration updates
8
+ */
9
+
10
+ import type { Logger } from "../types/index.js";
11
+ import {
12
+ ConfigurationWatcher,
13
+ type ConfigurationChangeEvent,
14
+ } from "../services/configurationWatcher.js";
15
+
16
+ import { type FileWatchEvent } from "../services/fileWatcher.js";
17
+ import { configResolver } from "../utils/configResolver.js";
18
+ import { join } from "path";
19
+ import {
20
+ getUserConfigPaths,
21
+ getProjectConfigPaths,
22
+ } from "../utils/configPaths.js";
23
+ import { CONFIGURATION_EVENTS } from "../constants/events.js";
24
+
25
+ export interface LiveConfigManagerOptions {
26
+ workdir: string;
27
+ logger?: Logger;
28
+ onConfigurationChanged?: () => void; // Callback for when configuration changes
29
+ onMemoryStoreFileChanged?: (
30
+ filePath: string,
31
+ changeType: "add" | "change" | "unlink",
32
+ ) => Promise<void>; // Callback for memory store file changes
33
+ }
34
+
35
+ export class LiveConfigManager {
36
+ private readonly workdir: string;
37
+ private readonly logger?: Logger;
38
+ private readonly onConfigurationChanged?: () => void;
39
+ private readonly onMemoryStoreFileChanged?: (
40
+ filePath: string,
41
+ changeType: "add" | "change" | "unlink",
42
+ ) => Promise<void>;
43
+ private configurationWatcher?: ConfigurationWatcher;
44
+ private isInitialized: boolean = false;
45
+
46
+ constructor(options: LiveConfigManagerOptions) {
47
+ this.workdir = options.workdir;
48
+ this.logger = options.logger;
49
+ this.onConfigurationChanged = options.onConfigurationChanged;
50
+ this.onMemoryStoreFileChanged = options.onMemoryStoreFileChanged;
51
+ }
52
+
53
+ /**
54
+ * Initialize live configuration management
55
+ */
56
+ async initialize(): Promise<void> {
57
+ if (this.isInitialized) {
58
+ this.logger?.debug("[LiveConfigManager] Already initialized");
59
+ return;
60
+ }
61
+
62
+ try {
63
+ // Initialize configuration watcher for hook settings
64
+ await this.initializeConfigurationWatcher();
65
+
66
+ // Initialize memory store watching for AGENTS.md if callback is available
67
+ if (this.onMemoryStoreFileChanged) {
68
+ await this.initializeMemoryStoreWatching();
69
+ }
70
+
71
+ this.isInitialized = true;
72
+ this.logger?.info(
73
+ "Live Config: Live configuration management initialized successfully",
74
+ );
75
+ } catch (error) {
76
+ this.logger?.error(
77
+ `Live Config: Failed to initialize: ${(error as Error).message}`,
78
+ );
79
+ throw error;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Shutdown live configuration management
85
+ */
86
+ async shutdown(): Promise<void> {
87
+ if (!this.isInitialized) {
88
+ return;
89
+ }
90
+
91
+ try {
92
+ if (this.configurationWatcher) {
93
+ await this.configurationWatcher.shutdown();
94
+ this.configurationWatcher = undefined;
95
+ }
96
+
97
+ this.isInitialized = false;
98
+ this.logger?.info(
99
+ "Live Config: Live configuration management shutdown completed",
100
+ );
101
+ } catch (error) {
102
+ this.logger?.error(
103
+ `Live Config: Error during shutdown: ${(error as Error).message}`,
104
+ );
105
+ throw error;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Initialize configuration watcher for hook settings
111
+ */
112
+ private async initializeConfigurationWatcher(): Promise<void> {
113
+ this.configurationWatcher = new ConfigurationWatcher(
114
+ this.workdir,
115
+ this.logger,
116
+ );
117
+
118
+ // Set up configuration change handler using EventEmitter pattern
119
+ this.configurationWatcher.on(
120
+ CONFIGURATION_EVENTS.CONFIGURATION_CHANGE,
121
+ (event: ConfigurationChangeEvent) => {
122
+ this.handleConfigurationChange(event);
123
+ },
124
+ );
125
+
126
+ // Initialize watching for user and project settings
127
+ const { userPaths, projectPaths } = this.getConfigurationPaths();
128
+ await this.configurationWatcher.initializeWatching(userPaths, projectPaths);
129
+ this.logger?.info("Live Config: Configuration watching initialized");
130
+ }
131
+
132
+ /**
133
+ * Initialize memory store watching for AGENTS.md files
134
+ */
135
+ private async initializeMemoryStoreWatching(): Promise<void> {
136
+ if (!this.onMemoryStoreFileChanged || !this.configurationWatcher) {
137
+ this.logger?.debug(
138
+ "Live Config: Memory store callback or configuration watcher not available, skipping AGENTS.md watching",
139
+ );
140
+ return;
141
+ }
142
+
143
+ try {
144
+ const agentsFilePath = join(this.workdir, "AGENTS.md");
145
+
146
+ // Add AGENTS.md to file watcher
147
+ await this.configurationWatcher.watchAdditionalFile(
148
+ agentsFilePath,
149
+ async (event: FileWatchEvent) => {
150
+ await this.handleMemoryStoreFileChange(event);
151
+ },
152
+ );
153
+
154
+ this.logger?.info("Live Config: AGENTS.md file watching initialized");
155
+ } catch (error) {
156
+ this.logger?.warn(
157
+ `Live Config: Failed to initialize AGENTS.md watching: ${(error as Error).message}`,
158
+ );
159
+ // Don't throw - memory optimization is not critical for core functionality
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Handle configuration change events
165
+ */
166
+ private handleConfigurationChange(event: ConfigurationChangeEvent): void {
167
+ this.logger?.info(
168
+ `Live Config: Configuration change detected: ${event.type} at ${event.path}`,
169
+ );
170
+
171
+ // Invalidate and refresh configuration cache for live environment variable updates
172
+ configResolver.invalidateCache(this.workdir);
173
+ configResolver.refreshCache(this.workdir);
174
+
175
+ // Trigger Agent configuration update callback if provided
176
+ if (this.onConfigurationChanged) {
177
+ try {
178
+ this.logger?.info("Live Config: Triggering Agent configuration update");
179
+ this.onConfigurationChanged();
180
+ } catch (error) {
181
+ this.logger?.error(
182
+ `Live Config: Error in configuration change callback: ${(error as Error).message}`,
183
+ );
184
+ }
185
+ }
186
+
187
+ // Log cache status after refresh
188
+ const cacheStatus = configResolver.getCacheStatus();
189
+ if (cacheStatus) {
190
+ this.logger?.info(
191
+ `Live Config: Configuration cache refreshed - ${cacheStatus.envVarCount} environment variables loaded`,
192
+ );
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Handle AGENTS.md file change events
198
+ */
199
+ private async handleMemoryStoreFileChange(
200
+ event: FileWatchEvent,
201
+ ): Promise<void> {
202
+ if (!this.onMemoryStoreFileChanged) {
203
+ return;
204
+ }
205
+
206
+ try {
207
+ this.logger?.info(
208
+ `Live Config: AGENTS.md ${event.type} detected: ${event.path}`,
209
+ );
210
+
211
+ const changeType: "add" | "change" | "unlink" =
212
+ event.type === "delete"
213
+ ? "unlink"
214
+ : event.type === "create"
215
+ ? "add"
216
+ : "change";
217
+ await this.onMemoryStoreFileChanged(event.path, changeType);
218
+
219
+ this.logger?.info(
220
+ `Live Config: Memory store updated for AGENTS.md ${event.type}`,
221
+ );
222
+ } catch (error) {
223
+ this.logger?.error(
224
+ `Live Config: Failed to handle AGENTS.md file change: ${(error as Error).message}`,
225
+ );
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Get initialization status
231
+ */
232
+ get initialized(): boolean {
233
+ return this.isInitialized;
234
+ }
235
+
236
+ /**
237
+ * Get configuration file paths for user and project settings
238
+ * Returns paths in priority order (local.json first, then .json)
239
+ */
240
+ private getConfigurationPaths(): {
241
+ userPaths: string[];
242
+ projectPaths: string[];
243
+ } {
244
+ const userPaths = getUserConfigPaths();
245
+ const projectPaths = getProjectConfigPaths(this.workdir);
246
+ return { userPaths, projectPaths };
247
+ }
248
+ }