wave-agent-sdk 0.0.7 → 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 (172) hide show
  1. package/dist/agent.d.ts +32 -20
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +202 -20
  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 +243 -128
  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 +9 -4
  16. package/dist/managers/hookManager.d.ts.map +1 -1
  17. package/dist/managers/hookManager.js +62 -30
  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 +38 -13
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +163 -30
  24. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  25. package/dist/managers/slashCommandManager.js +4 -1
  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 +189 -18
  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/writeTool.d.ts.map +1 -1
  64. package/dist/tools/writeTool.js +5 -6
  65. package/dist/types/commands.d.ts +4 -0
  66. package/dist/types/commands.d.ts.map +1 -1
  67. package/dist/types/core.d.ts +35 -0
  68. package/dist/types/core.d.ts.map +1 -1
  69. package/dist/types/environment.d.ts +42 -0
  70. package/dist/types/environment.d.ts.map +1 -0
  71. package/dist/types/environment.js +21 -0
  72. package/dist/types/hooks.d.ts +8 -2
  73. package/dist/types/hooks.d.ts.map +1 -1
  74. package/dist/types/hooks.js +8 -2
  75. package/dist/types/index.d.ts +2 -0
  76. package/dist/types/index.d.ts.map +1 -1
  77. package/dist/types/index.js +2 -0
  78. package/dist/types/memoryStore.d.ts +82 -0
  79. package/dist/types/memoryStore.d.ts.map +1 -0
  80. package/dist/types/memoryStore.js +7 -0
  81. package/dist/types/messaging.d.ts +14 -2
  82. package/dist/types/messaging.d.ts.map +1 -1
  83. package/dist/types/session.d.ts +20 -0
  84. package/dist/types/session.d.ts.map +1 -0
  85. package/dist/types/session.js +7 -0
  86. package/dist/utils/bashHistory.d.ts.map +1 -1
  87. package/dist/utils/bashHistory.js +27 -26
  88. package/dist/utils/cacheControlUtils.d.ts +121 -0
  89. package/dist/utils/cacheControlUtils.d.ts.map +1 -0
  90. package/dist/utils/cacheControlUtils.js +367 -0
  91. package/dist/utils/commandPathResolver.d.ts +52 -0
  92. package/dist/utils/commandPathResolver.d.ts.map +1 -0
  93. package/dist/utils/commandPathResolver.js +145 -0
  94. package/dist/utils/configPaths.d.ts +85 -0
  95. package/dist/utils/configPaths.d.ts.map +1 -0
  96. package/dist/utils/configPaths.js +121 -0
  97. package/dist/utils/configResolver.d.ts +37 -10
  98. package/dist/utils/configResolver.d.ts.map +1 -1
  99. package/dist/utils/configResolver.js +127 -23
  100. package/dist/utils/constants.d.ts +1 -1
  101. package/dist/utils/constants.js +1 -1
  102. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  103. package/dist/utils/convertMessagesForAPI.js +7 -5
  104. package/dist/utils/customCommands.d.ts.map +1 -1
  105. package/dist/utils/customCommands.js +66 -21
  106. package/dist/utils/fileUtils.d.ts +15 -0
  107. package/dist/utils/fileUtils.d.ts.map +1 -0
  108. package/dist/utils/fileUtils.js +61 -0
  109. package/dist/utils/globalLogger.d.ts +102 -0
  110. package/dist/utils/globalLogger.d.ts.map +1 -0
  111. package/dist/utils/globalLogger.js +136 -0
  112. package/dist/utils/mcpUtils.d.ts.map +1 -1
  113. package/dist/utils/mcpUtils.js +25 -3
  114. package/dist/utils/messageOperations.d.ts +20 -8
  115. package/dist/utils/messageOperations.d.ts.map +1 -1
  116. package/dist/utils/messageOperations.js +25 -16
  117. package/dist/utils/pathEncoder.d.ts +104 -0
  118. package/dist/utils/pathEncoder.d.ts.map +1 -0
  119. package/dist/utils/pathEncoder.js +272 -0
  120. package/dist/utils/subagentParser.d.ts.map +1 -1
  121. package/dist/utils/subagentParser.js +2 -1
  122. package/dist/utils/tokenCalculation.d.ts +26 -0
  123. package/dist/utils/tokenCalculation.d.ts.map +1 -0
  124. package/dist/utils/tokenCalculation.js +36 -0
  125. package/package.json +6 -3
  126. package/src/agent.ts +298 -34
  127. package/src/constants/events.ts +38 -0
  128. package/src/index.ts +2 -0
  129. package/src/managers/aiManager.ts +323 -170
  130. package/src/managers/backgroundBashManager.ts +7 -6
  131. package/src/managers/hookManager.ts +83 -40
  132. package/src/managers/liveConfigManager.ts +248 -0
  133. package/src/managers/messageManager.ts +230 -63
  134. package/src/managers/slashCommandManager.ts +4 -1
  135. package/src/managers/subagentManager.ts +283 -21
  136. package/src/services/aiService.ts +474 -83
  137. package/src/services/configurationWatcher.ts +622 -0
  138. package/src/services/fileWatcher.ts +301 -0
  139. package/src/services/hook.ts +538 -47
  140. package/src/services/jsonlHandler.ts +319 -0
  141. package/src/services/memory.ts +92 -12
  142. package/src/services/memoryStore.ts +279 -0
  143. package/src/services/session.ts +381 -157
  144. package/src/tools/bashTool.ts +5 -4
  145. package/src/tools/deleteFileTool.ts +2 -1
  146. package/src/tools/editTool.ts +3 -2
  147. package/src/tools/multiEditTool.ts +4 -3
  148. package/src/tools/readTool.ts +2 -1
  149. package/src/tools/writeTool.ts +7 -6
  150. package/src/types/commands.ts +6 -0
  151. package/src/types/core.ts +44 -0
  152. package/src/types/environment.ts +60 -0
  153. package/src/types/hooks.ts +21 -8
  154. package/src/types/index.ts +2 -0
  155. package/src/types/memoryStore.ts +94 -0
  156. package/src/types/messaging.ts +14 -2
  157. package/src/types/session.ts +25 -0
  158. package/src/utils/bashHistory.ts +27 -27
  159. package/src/utils/cacheControlUtils.ts +540 -0
  160. package/src/utils/commandPathResolver.ts +189 -0
  161. package/src/utils/configPaths.ts +163 -0
  162. package/src/utils/configResolver.ts +182 -22
  163. package/src/utils/constants.ts +1 -1
  164. package/src/utils/convertMessagesForAPI.ts +7 -5
  165. package/src/utils/customCommands.ts +90 -22
  166. package/src/utils/fileUtils.ts +65 -0
  167. package/src/utils/globalLogger.ts +145 -0
  168. package/src/utils/mcpUtils.ts +34 -3
  169. package/src/utils/messageOperations.ts +42 -20
  170. package/src/utils/pathEncoder.ts +379 -0
  171. package/src/utils/subagentParser.ts +2 -1
  172. package/src/utils/tokenCalculation.ts +43 -0
@@ -0,0 +1,189 @@
1
+ import { relative, basename } from "path";
2
+
3
+ /**
4
+ * Command path resolver utilities for nested command discovery
5
+ * Handles conversion between file paths and command IDs with colon syntax
6
+ */
7
+
8
+ export interface CommandIdParts {
9
+ namespace?: string; // e.g., "openspec" for "openspec:apply"
10
+ commandName: string; // e.g., "apply" for "openspec:apply"
11
+ isNested: boolean; // true if command has namespace
12
+ depth: number; // 0 for root, 1 for nested
13
+ segments: string[]; // Path components array
14
+ }
15
+
16
+ /**
17
+ * Generate command ID from file path
18
+ * @param filePath - Absolute path to markdown file
19
+ * @param rootDir - Root commands directory path
20
+ * @returns Command identifier string (e.g., "openspec:apply")
21
+ * @throws Error on invalid path structure
22
+ */
23
+ export function generateCommandId(filePath: string, rootDir: string): string {
24
+ // Handle null/undefined inputs
25
+ if (filePath == null || rootDir == null) {
26
+ throw new Error("File path and root directory must be provided");
27
+ }
28
+
29
+ // Handle empty root directory (for root level commands)
30
+ const relativePath = rootDir === "" ? filePath : relative(rootDir, filePath);
31
+
32
+ // Handle edge cases
33
+ if (!relativePath || relativePath === ".") {
34
+ throw new Error("Command filename cannot be empty");
35
+ }
36
+
37
+ const segments = relativePath.split("/").filter((segment) => segment !== "");
38
+
39
+ // Remove .md extension from the last segment
40
+ const lastSegment = segments[segments.length - 1];
41
+ if (!lastSegment.endsWith(".md")) {
42
+ throw new Error(`Command files must have .md extension`);
43
+ }
44
+
45
+ segments[segments.length - 1] = basename(lastSegment, ".md");
46
+
47
+ // Handle empty filename after removing extension
48
+ if (segments[segments.length - 1] === "") {
49
+ throw new Error("Command filename cannot be empty");
50
+ }
51
+
52
+ // Validate depth (max 1 level of nesting)
53
+ if (segments.length > 2) {
54
+ throw new Error(
55
+ `Command nesting too deep: ${relativePath}. Maximum depth is 1 level.`,
56
+ );
57
+ }
58
+
59
+ // Validate segments
60
+ for (const segment of segments) {
61
+ if (!validateSegment(segment)) {
62
+ throw new Error(
63
+ `Invalid command path segment: "${segment}" in ${relativePath}. Must match pattern /^[a-zA-Z][a-zA-Z0-9_.-]*$/`,
64
+ );
65
+ }
66
+ }
67
+
68
+ // Generate command ID
69
+ if (segments.length === 1) {
70
+ return segments[0]; // Flat command
71
+ } else {
72
+ return segments.join(":"); // Nested command with colon syntax
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Parse command ID into components
78
+ * @param commandId - Command identifier (e.g., "openspec:apply")
79
+ * @returns Object with namespace and command name
80
+ * @throws Error on malformed command ID
81
+ */
82
+ export function parseCommandId(commandId: string): CommandIdParts {
83
+ // Handle null/undefined inputs
84
+ if (commandId == null) {
85
+ throw new Error("Command ID cannot be null or undefined");
86
+ }
87
+
88
+ if (commandId === "") {
89
+ throw new Error("Command ID cannot be empty");
90
+ }
91
+
92
+ if (!validateCommandId(commandId)) {
93
+ throw new Error(
94
+ `Invalid command ID format: "${commandId}". Must match pattern /^[a-zA-Z0-9_-]+(?::[a-zA-Z0-9_-]+)?$/`,
95
+ );
96
+ }
97
+
98
+ const parts = commandId.split(":");
99
+
100
+ if (parts.length === 1) {
101
+ // Flat command
102
+ return {
103
+ namespace: undefined,
104
+ commandName: parts[0],
105
+ isNested: false,
106
+ depth: 0,
107
+ segments: [parts[0]],
108
+ };
109
+ } else if (parts.length === 2) {
110
+ // Nested command
111
+ return {
112
+ namespace: parts[0],
113
+ commandName: parts[1],
114
+ isNested: true,
115
+ depth: 1,
116
+ segments: [parts[0], parts[1]],
117
+ };
118
+ } else {
119
+ throw new Error(
120
+ `Invalid command ID format: "${commandId}". Too many colon separators.`,
121
+ );
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Validate command ID format
127
+ * @param commandId - Command identifier to validate
128
+ * @returns Boolean indicating validity
129
+ */
130
+ export function validateCommandId(commandId: string): boolean {
131
+ // Handle null/undefined inputs
132
+ if (commandId == null) {
133
+ return false;
134
+ }
135
+
136
+ // Command ID can have multiple colons (though generateCommandId enforces max 1 level)
137
+ // This validates the format but doesn't enforce depth limits
138
+ const pattern = /^[a-zA-Z][a-zA-Z0-9_-]*(?::[a-zA-Z][a-zA-Z0-9_-]*)*$/;
139
+ return pattern.test(commandId);
140
+ }
141
+
142
+ /**
143
+ * Validate individual path segment
144
+ * @param segment - Path segment to validate
145
+ * @returns Boolean indicating validity
146
+ */
147
+ function validateSegment(segment: string): boolean {
148
+ // Segments should start with letters and can contain letters, numbers, dashes, underscores, dots
149
+ const pattern = /^[a-zA-Z][a-zA-Z0-9_.-]*$/;
150
+ return pattern.test(segment);
151
+ }
152
+
153
+ /**
154
+ * Convert file path to command segments array
155
+ * @param filePath - Absolute path to markdown file
156
+ * @param rootDir - Root commands directory path
157
+ * @returns Array of path segments
158
+ */
159
+ export function getCommandSegments(
160
+ filePath: string,
161
+ rootDir: string,
162
+ ): string[] {
163
+ const relativePath = relative(rootDir, filePath);
164
+ const segments = relativePath.split("/").filter((segment) => segment !== "");
165
+
166
+ // Remove .md extension from the last segment
167
+ const lastSegment = segments[segments.length - 1];
168
+ segments[segments.length - 1] = basename(lastSegment, ".md");
169
+
170
+ return segments;
171
+ }
172
+
173
+ /**
174
+ * Get namespace from command segments
175
+ * @param segments - Command path segments
176
+ * @returns Namespace string or undefined for flat commands
177
+ */
178
+ export function getNamespace(segments: string[]): string | undefined {
179
+ return segments.length > 1 ? segments[0] : undefined;
180
+ }
181
+
182
+ /**
183
+ * Get command depth from segments
184
+ * @param segments - Command path segments
185
+ * @returns Depth number (0 for root, 1 for nested)
186
+ */
187
+ export function getDepth(segments: string[]): number {
188
+ return Math.max(0, segments.length - 1);
189
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Configuration Path Utilities
3
+ *
4
+ * Centralized utilities for resolving Wave configuration file paths.
5
+ * Supports both regular settings.json and settings.local.json with proper priority.
6
+ *
7
+ * Priority system:
8
+ * - User configs: ~/.wave/settings.local.json > ~/.wave/settings.json
9
+ * - Project configs: {workdir}/.wave/settings.local.json > {workdir}/.wave/settings.json
10
+ * - Project configs override user configs (existing behavior)
11
+ */
12
+
13
+ import { join } from "path";
14
+ import { homedir } from "os";
15
+ import { existsSync } from "fs";
16
+
17
+ /**
18
+ * Get the user-specific configuration file path (legacy function)
19
+ * @deprecated Use getUserConfigPaths() for better priority support
20
+ */
21
+ export function getUserConfigPath(): string {
22
+ return join(homedir(), ".wave", "settings.json");
23
+ }
24
+
25
+ /**
26
+ * Get the project-specific configuration file path (legacy function)
27
+ * @deprecated Use getProjectConfigPaths() for better priority support
28
+ */
29
+ export function getProjectConfigPath(workdir: string): string {
30
+ return join(workdir, ".wave", "settings.json");
31
+ }
32
+
33
+ /**
34
+ * Get the user-specific configuration file paths in priority order
35
+ * Returns array with .local.json first, then .json
36
+ */
37
+ export function getUserConfigPaths(): string[] {
38
+ const baseDir = join(homedir(), ".wave");
39
+ return [join(baseDir, "settings.local.json"), join(baseDir, "settings.json")];
40
+ }
41
+
42
+ /**
43
+ * Get the project-specific configuration file paths in priority order
44
+ * Returns array with .local.json first, then .json
45
+ */
46
+ export function getProjectConfigPaths(workdir: string): string[] {
47
+ const baseDir = join(workdir, ".wave");
48
+ return [join(baseDir, "settings.local.json"), join(baseDir, "settings.json")];
49
+ }
50
+
51
+ /**
52
+ * Get all configuration file paths (user and project) in priority order
53
+ * Useful for comprehensive configuration detection
54
+ */
55
+ export function getAllConfigPaths(workdir: string): {
56
+ userPaths: string[];
57
+ projectPaths: string[];
58
+ allPaths: string[];
59
+ } {
60
+ const userPaths = getUserConfigPaths();
61
+ const projectPaths = getProjectConfigPaths(workdir);
62
+
63
+ return {
64
+ userPaths,
65
+ projectPaths,
66
+ allPaths: [...userPaths, ...projectPaths],
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Get existing configuration file paths
72
+ * Returns only the paths that actually exist on the filesystem
73
+ */
74
+ export function getExistingConfigPaths(workdir: string): {
75
+ userPaths: string[];
76
+ projectPaths: string[];
77
+ existingPaths: string[];
78
+ } {
79
+ const allPaths = getAllConfigPaths(workdir);
80
+
81
+ const existingUserPaths = allPaths.userPaths.filter(existsSync);
82
+ const existingProjectPaths = allPaths.projectPaths.filter(existsSync);
83
+ const allExistingPaths = allPaths.allPaths.filter(existsSync);
84
+
85
+ return {
86
+ userPaths: existingUserPaths,
87
+ projectPaths: existingProjectPaths,
88
+ existingPaths: allExistingPaths,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Get the first existing configuration file path with the specified priority
94
+ * @param paths Array of paths in priority order
95
+ * @returns The first path that exists, or undefined if none exist
96
+ */
97
+ export function getFirstExistingPath(paths: string[]): string | undefined {
98
+ return paths.find((path) => existsSync(path));
99
+ }
100
+
101
+ /**
102
+ * Get effective configuration paths (the ones that would actually be used)
103
+ * Returns the highest priority existing path for each category
104
+ */
105
+ export function getEffectiveConfigPaths(workdir: string): {
106
+ userPath?: string;
107
+ projectPath?: string;
108
+ effectivePath?: string; // The path that takes final precedence
109
+ } {
110
+ const userPaths = getUserConfigPaths();
111
+ const projectPaths = getProjectConfigPaths(workdir);
112
+
113
+ const userPath = getFirstExistingPath(userPaths);
114
+ const projectPath = getFirstExistingPath(projectPaths);
115
+
116
+ // Project path takes precedence over user path if both exist
117
+ const effectivePath = projectPath || userPath;
118
+
119
+ return {
120
+ userPath,
121
+ projectPath,
122
+ effectivePath,
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Check if any configuration files exist
128
+ */
129
+ export function hasAnyConfig(workdir: string): boolean {
130
+ const { existingPaths } = getExistingConfigPaths(workdir);
131
+ return existingPaths.length > 0;
132
+ }
133
+
134
+ /**
135
+ * Get configuration information for debugging and monitoring
136
+ */
137
+ export function getConfigurationInfo(workdir: string): {
138
+ hasUser: boolean;
139
+ hasProject: boolean;
140
+ paths: string[];
141
+ userPaths: string[];
142
+ projectPaths: string[];
143
+ existingPaths: string[];
144
+ effectivePaths: {
145
+ userPath?: string;
146
+ projectPath?: string;
147
+ effectivePath?: string;
148
+ };
149
+ } {
150
+ const allPaths = getAllConfigPaths(workdir);
151
+ const existingPaths = getExistingConfigPaths(workdir);
152
+ const effectivePaths = getEffectiveConfigPaths(workdir);
153
+
154
+ return {
155
+ hasUser: existingPaths.userPaths.length > 0,
156
+ hasProject: existingPaths.projectPaths.length > 0,
157
+ paths: allPaths.allPaths,
158
+ userPaths: allPaths.userPaths,
159
+ projectPaths: allPaths.projectPaths,
160
+ existingPaths: existingPaths.existingPaths,
161
+ effectivePaths,
162
+ };
163
+ }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Configuration resolver utilities for Agent Constructor Configuration
3
3
  * Resolves configuration from constructor arguments with environment fallbacks
4
+ * Supports live configuration updates and cache invalidation
4
5
  */
5
6
 
6
7
  import {
@@ -9,32 +10,111 @@ import {
9
10
  ConfigurationError,
10
11
  CONFIG_ERRORS,
11
12
  } from "../types/index.js";
13
+ import { DEFAULT_TOKEN_LIMIT } from "./constants.js";
14
+ import { loadMergedWaveConfig } from "../services/hook.js";
15
+ import { getGlobalLogger } from "./globalLogger.js";
16
+
17
+ /**
18
+ * Live configuration cache and invalidation support
19
+ */
20
+ interface ConfigurationCache {
21
+ workdir?: string;
22
+ lastUpdated: number;
23
+ environmentVars: Record<string, string>;
24
+ isValid: boolean;
25
+ }
26
+
27
+ let configCache: ConfigurationCache | null = null;
28
+
29
+ /**
30
+ * Initialize configuration cache with current environment variables from settings.json
31
+ */
32
+ function initializeConfigurationCache(workdir?: string): void {
33
+ try {
34
+ const waveConfig = workdir ? loadMergedWaveConfig(workdir) : null;
35
+ const envVars = waveConfig?.env || {};
36
+
37
+ configCache = {
38
+ workdir,
39
+ lastUpdated: Date.now(),
40
+ environmentVars: envVars,
41
+ isValid: true,
42
+ };
43
+
44
+ const logger = getGlobalLogger();
45
+ logger?.debug(
46
+ `Live Config: Configuration cache initialized with ${Object.keys(envVars).length} environment variables`,
47
+ );
48
+ } catch (error) {
49
+ const logger = getGlobalLogger();
50
+ logger?.error(
51
+ `Live Config: Failed to initialize configuration cache: ${(error as Error).message}`,
52
+ );
53
+ configCache = {
54
+ workdir,
55
+ lastUpdated: Date.now(),
56
+ environmentVars: {},
57
+ isValid: false,
58
+ };
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Get current environment variable value with live configuration support
64
+ */
65
+ function getCurrentEnvironmentValue(
66
+ key: string,
67
+ workdir?: string,
68
+ ): string | undefined {
69
+ // Initialize cache if not present or workdir changed
70
+ if (!configCache || configCache.workdir !== workdir) {
71
+ initializeConfigurationCache(workdir);
72
+ }
73
+
74
+ // Use cached environment variables if available and valid
75
+ if (configCache && configCache.isValid) {
76
+ const cachedValue = configCache.environmentVars[key];
77
+ if (cachedValue !== undefined) {
78
+ const logger = getGlobalLogger();
79
+ logger?.debug(
80
+ `Live Config: Using cached environment variable ${key}=${cachedValue}`,
81
+ );
82
+ return cachedValue;
83
+ }
84
+ }
85
+
86
+ // Fallback to process environment
87
+ return process.env[key];
88
+ }
12
89
 
13
90
  export class ConfigResolver {
14
91
  /**
15
- * Resolves gateway configuration from constructor args and environment
92
+ * Resolves gateway configuration from constructor args and environment with live config support
16
93
  * @param apiKey - API key from constructor (optional)
17
94
  * @param baseURL - Base URL from constructor (optional)
95
+ * @param workdir - Working directory for loading live configuration (optional)
18
96
  * @returns Resolved gateway configuration
19
97
  * @throws ConfigurationError if required configuration is missing after fallbacks
20
98
  */
21
99
  static resolveGatewayConfig(
22
100
  apiKey?: string,
23
101
  baseURL?: string,
102
+ workdir?: string,
24
103
  ): GatewayConfig {
25
- // Resolve API key: constructor > environment variable
104
+ // Resolve API key: constructor > live configuration > environment variable
26
105
  // Note: Explicitly provided empty strings should be treated as invalid, not fall back to env
27
106
  let resolvedApiKey: string;
28
107
  if (apiKey !== undefined) {
29
108
  resolvedApiKey = apiKey;
30
109
  } else {
31
- resolvedApiKey = process.env.AIGW_TOKEN || "";
110
+ resolvedApiKey = getCurrentEnvironmentValue("AIGW_TOKEN", workdir) || "";
32
111
  }
33
112
 
34
113
  if (!resolvedApiKey && apiKey === undefined) {
114
+ const envValue = getCurrentEnvironmentValue("AIGW_TOKEN", workdir);
35
115
  throw new ConfigurationError(CONFIG_ERRORS.MISSING_API_KEY, "apiKey", {
36
116
  constructor: apiKey,
37
- environment: process.env.AIGW_TOKEN,
117
+ environment: envValue,
38
118
  });
39
119
  }
40
120
 
@@ -46,19 +126,20 @@ export class ConfigResolver {
46
126
  );
47
127
  }
48
128
 
49
- // Resolve base URL: constructor > environment variable
129
+ // Resolve base URL: constructor > live configuration > environment variable
50
130
  // Note: Explicitly provided empty strings should be treated as invalid, not fall back to env
51
131
  let resolvedBaseURL: string;
52
132
  if (baseURL !== undefined) {
53
133
  resolvedBaseURL = baseURL;
54
134
  } else {
55
- resolvedBaseURL = process.env.AIGW_URL || "";
135
+ resolvedBaseURL = getCurrentEnvironmentValue("AIGW_URL", workdir) || "";
56
136
  }
57
137
 
58
138
  if (!resolvedBaseURL && baseURL === undefined) {
139
+ const envValue = getCurrentEnvironmentValue("AIGW_URL", workdir);
59
140
  throw new ConfigurationError(CONFIG_ERRORS.MISSING_BASE_URL, "baseURL", {
60
141
  constructor: baseURL,
61
- environment: process.env.AIGW_URL,
142
+ environment: envValue,
62
143
  });
63
144
  }
64
145
 
@@ -77,26 +158,37 @@ export class ConfigResolver {
77
158
  }
78
159
 
79
160
  /**
80
- * Resolves model configuration with fallbacks
161
+ * Resolves model configuration with fallbacks and live config support
81
162
  * @param agentModel - Agent model from constructor (optional)
82
163
  * @param fastModel - Fast model from constructor (optional)
164
+ * @param workdir - Working directory for loading live configuration (optional)
83
165
  * @returns Resolved model configuration with defaults
84
166
  */
85
167
  static resolveModelConfig(
86
168
  agentModel?: string,
87
169
  fastModel?: string,
170
+ workdir?: string,
88
171
  ): ModelConfig {
89
172
  // Default values as per data-model.md
90
173
  const DEFAULT_AGENT_MODEL = "claude-sonnet-4-20250514";
91
174
  const DEFAULT_FAST_MODEL = "gemini-2.5-flash";
92
175
 
93
- // Resolve agent model: constructor > environment > default
176
+ // Resolve agent model: constructor > live configuration > environment > default
94
177
  const resolvedAgentModel =
95
- agentModel || process.env.AIGW_MODEL || DEFAULT_AGENT_MODEL;
178
+ agentModel ||
179
+ getCurrentEnvironmentValue("AIGW_MODEL", workdir) ||
180
+ DEFAULT_AGENT_MODEL;
96
181
 
97
- // Resolve fast model: constructor > environment > default
182
+ // Resolve fast model: constructor > live configuration > environment > default
98
183
  const resolvedFastModel =
99
- fastModel || process.env.AIGW_FAST_MODEL || DEFAULT_FAST_MODEL;
184
+ fastModel ||
185
+ getCurrentEnvironmentValue("AIGW_FAST_MODEL", workdir) ||
186
+ DEFAULT_FAST_MODEL;
187
+
188
+ const logger = getGlobalLogger();
189
+ logger?.debug(
190
+ `Live Config: Resolved models - agent: ${resolvedAgentModel}, fast: ${resolvedFastModel}`,
191
+ );
100
192
 
101
193
  return {
102
194
  agentModel: resolvedAgentModel,
@@ -105,23 +197,29 @@ export class ConfigResolver {
105
197
  }
106
198
 
107
199
  /**
108
- * Resolves token limit with fallbacks
200
+ * Resolves token limit with fallbacks and live config support
109
201
  * @param constructorLimit - Token limit from constructor (optional)
202
+ * @param workdir - Working directory for loading live configuration (optional)
110
203
  * @returns Resolved token limit
111
204
  */
112
- static resolveTokenLimit(constructorLimit?: number): number {
113
- const DEFAULT_TOKEN_LIMIT = 64000;
114
-
205
+ static resolveTokenLimit(
206
+ constructorLimit?: number,
207
+ workdir?: string,
208
+ ): number {
115
209
  // If constructor value provided, use it
116
210
  if (constructorLimit !== undefined) {
117
211
  return constructorLimit;
118
212
  }
119
213
 
120
- // Try environment variable
121
- const envTokenLimit = process.env.TOKEN_LIMIT;
214
+ // Try live configuration then environment variable
215
+ const envTokenLimit = getCurrentEnvironmentValue("TOKEN_LIMIT", workdir);
122
216
  if (envTokenLimit) {
123
217
  const parsed = parseInt(envTokenLimit, 10);
124
218
  if (!isNaN(parsed)) {
219
+ const logger = getGlobalLogger();
220
+ logger?.debug(
221
+ `Live Config: Resolved token limit from configuration: ${parsed}`,
222
+ );
125
223
  return parsed;
126
224
  }
127
225
  }
@@ -129,14 +227,76 @@ export class ConfigResolver {
129
227
  // Use default
130
228
  return DEFAULT_TOKEN_LIMIT;
131
229
  }
230
+
231
+ /**
232
+ * Invalidate configuration cache to force reload from settings.json
233
+ * @param workdir - Working directory to invalidate cache for (optional)
234
+ */
235
+ static invalidateCache(workdir?: string): void {
236
+ if (
237
+ configCache &&
238
+ (workdir === undefined || configCache.workdir === workdir)
239
+ ) {
240
+ const logger = getGlobalLogger();
241
+ logger?.info(
242
+ `Live Config: Configuration cache invalidated for workdir: ${workdir || "global"}`,
243
+ );
244
+ configCache = null;
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Refresh configuration cache by reloading from settings.json
250
+ * @param workdir - Working directory to refresh cache for (optional)
251
+ */
252
+ static refreshCache(workdir?: string): void {
253
+ const logger = getGlobalLogger();
254
+ logger?.info(
255
+ `Live Config: Refreshing configuration cache for workdir: ${workdir || "global"}`,
256
+ );
257
+ initializeConfigurationCache(workdir);
258
+ }
259
+
260
+ /**
261
+ * Get current cache status for monitoring
262
+ * @returns Cache information or null if no cache
263
+ */
264
+ static getCacheStatus(): {
265
+ workdir?: string;
266
+ lastUpdated: number;
267
+ envVarCount: number;
268
+ isValid: boolean;
269
+ } | null {
270
+ if (!configCache) {
271
+ return null;
272
+ }
273
+
274
+ return {
275
+ workdir: configCache.workdir,
276
+ lastUpdated: configCache.lastUpdated,
277
+ envVarCount: Object.keys(configCache.environmentVars).length,
278
+ isValid: configCache.isValid,
279
+ };
280
+ }
132
281
  }
133
282
 
134
283
  /**
135
284
  * Static configuration resolver instance
136
- * Implements ConfigurationResolver interface from types.ts
285
+ * Implements ConfigurationResolver interface from types.ts with backward compatibility
137
286
  */
138
287
  export const configResolver = {
139
- resolveGatewayConfig: ConfigResolver.resolveGatewayConfig,
140
- resolveModelConfig: ConfigResolver.resolveModelConfig,
141
- resolveTokenLimit: ConfigResolver.resolveTokenLimit,
288
+ resolveGatewayConfig: (apiKey?: string, baseURL?: string, workdir?: string) =>
289
+ ConfigResolver.resolveGatewayConfig(apiKey, baseURL, workdir),
290
+ resolveModelConfig: (
291
+ agentModel?: string,
292
+ fastModel?: string,
293
+ workdir?: string,
294
+ ) => ConfigResolver.resolveModelConfig(agentModel, fastModel, workdir),
295
+ resolveTokenLimit: (constructorLimit?: number, workdir?: string) =>
296
+ ConfigResolver.resolveTokenLimit(constructorLimit, workdir),
297
+
298
+ // Live configuration management methods
299
+ invalidateCache: ConfigResolver.invalidateCache,
300
+ refreshCache: ConfigResolver.refreshCache,
301
+ getCacheStatus: ConfigResolver.getCacheStatus,
142
302
  };
@@ -29,7 +29,7 @@ export const USER_MEMORY_FILE = path.join(DATA_DIRECTORY, "user-memory.md");
29
29
  /**
30
30
  * AI related constants
31
31
  */
32
- export const DEFAULT_TOKEN_LIMIT = 64000; // Default token limit
32
+ export const DEFAULT_TOKEN_LIMIT = 96000; // Default token limit
33
33
 
34
34
  /**
35
35
  * @deprecated These constants are now legacy. Use ModelConfig through Agent constructor instead.