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.
- package/dist/agent.d.ts +105 -24
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +438 -53
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/managers/aiManager.d.ts +18 -7
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +254 -142
- package/dist/managers/backgroundBashManager.d.ts.map +1 -1
- package/dist/managers/backgroundBashManager.js +11 -9
- package/dist/managers/hookManager.d.ts +6 -6
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +81 -39
- package/dist/managers/liveConfigManager.d.ts +95 -0
- package/dist/managers/liveConfigManager.d.ts.map +1 -0
- package/dist/managers/liveConfigManager.js +442 -0
- package/dist/managers/lspManager.d.ts +43 -0
- package/dist/managers/lspManager.d.ts.map +1 -0
- package/dist/managers/lspManager.js +326 -0
- package/dist/managers/messageManager.d.ts +41 -24
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +184 -73
- package/dist/managers/permissionManager.d.ts +66 -0
- package/dist/managers/permissionManager.d.ts.map +1 -0
- package/dist/managers/permissionManager.js +208 -0
- package/dist/managers/skillManager.d.ts +1 -0
- package/dist/managers/skillManager.d.ts.map +1 -1
- package/dist/managers/skillManager.js +2 -1
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +4 -2
- package/dist/managers/subagentManager.d.ts +42 -6
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +213 -62
- package/dist/managers/toolManager.d.ts +38 -1
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +66 -2
- package/dist/services/aiService.d.ts +15 -5
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +446 -77
- package/dist/services/configurationService.d.ts +116 -0
- package/dist/services/configurationService.d.ts.map +1 -0
- package/dist/services/configurationService.js +585 -0
- package/dist/services/fileWatcher.d.ts +69 -0
- package/dist/services/fileWatcher.d.ts.map +1 -0
- package/dist/services/fileWatcher.js +212 -0
- package/dist/services/hook.d.ts +5 -40
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +47 -109
- package/dist/services/jsonlHandler.d.ts +71 -0
- package/dist/services/jsonlHandler.d.ts.map +1 -0
- package/dist/services/jsonlHandler.js +236 -0
- package/dist/services/memory.d.ts.map +1 -1
- package/dist/services/memory.js +33 -11
- package/dist/services/session.d.ts +116 -52
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +415 -143
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +77 -17
- package/dist/tools/deleteFileTool.d.ts.map +1 -1
- package/dist/tools/deleteFileTool.js +27 -1
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +33 -8
- package/dist/tools/lspTool.d.ts +6 -0
- package/dist/tools/lspTool.d.ts.map +1 -0
- package/dist/tools/lspTool.js +589 -0
- package/dist/tools/multiEditTool.d.ts.map +1 -1
- package/dist/tools/multiEditTool.js +30 -10
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +113 -3
- package/dist/tools/skillTool.js +2 -2
- package/dist/tools/todoWriteTool.d.ts.map +1 -1
- package/dist/tools/todoWriteTool.js +23 -0
- package/dist/tools/types.d.ts +11 -8
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/writeTool.d.ts.map +1 -1
- package/dist/tools/writeTool.js +30 -15
- package/dist/types/commands.d.ts +4 -1
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/config.d.ts +4 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/configuration.d.ts +69 -0
- package/dist/types/configuration.d.ts.map +1 -0
- package/dist/types/configuration.js +8 -0
- package/dist/types/core.d.ts +45 -0
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/environment.d.ts +83 -0
- package/dist/types/environment.d.ts.map +1 -0
- package/dist/types/environment.js +21 -0
- package/dist/types/fileSearch.d.ts +5 -0
- package/dist/types/fileSearch.d.ts.map +1 -0
- package/dist/types/fileSearch.js +1 -0
- package/dist/types/hooks.d.ts +18 -3
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +8 -8
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +7 -0
- package/dist/types/lsp.d.ts +90 -0
- package/dist/types/lsp.d.ts.map +1 -0
- package/dist/types/lsp.js +4 -0
- package/dist/types/messaging.d.ts +19 -12
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/permissions.d.ts +35 -0
- package/dist/types/permissions.d.ts.map +1 -0
- package/dist/types/permissions.js +12 -0
- package/dist/types/session.d.ts +15 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +7 -0
- package/dist/types/skills.d.ts +1 -0
- package/dist/types/skills.d.ts.map +1 -1
- package/dist/types/tools.d.ts +35 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +4 -0
- package/dist/utils/abortUtils.d.ts +34 -0
- package/dist/utils/abortUtils.d.ts.map +1 -0
- package/dist/utils/abortUtils.js +92 -0
- package/dist/utils/bashHistory.d.ts +4 -0
- package/dist/utils/bashHistory.d.ts.map +1 -1
- package/dist/utils/bashHistory.js +48 -30
- package/dist/utils/builtinSubagents.d.ts +7 -0
- package/dist/utils/builtinSubagents.d.ts.map +1 -0
- package/dist/utils/builtinSubagents.js +65 -0
- package/dist/utils/cacheControlUtils.d.ts +96 -0
- package/dist/utils/cacheControlUtils.d.ts.map +1 -0
- package/dist/utils/cacheControlUtils.js +324 -0
- package/dist/utils/commandPathResolver.d.ts +52 -0
- package/dist/utils/commandPathResolver.d.ts.map +1 -0
- package/dist/utils/commandPathResolver.js +145 -0
- package/dist/utils/configPaths.d.ts +85 -0
- package/dist/utils/configPaths.d.ts.map +1 -0
- package/dist/utils/configPaths.js +121 -0
- package/dist/utils/constants.d.ts +1 -13
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +2 -14
- package/dist/utils/convertMessagesForAPI.d.ts +2 -1
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +39 -18
- package/dist/utils/customCommands.d.ts.map +1 -1
- package/dist/utils/customCommands.js +66 -21
- package/dist/utils/fileSearch.d.ts +14 -0
- package/dist/utils/fileSearch.d.ts.map +1 -0
- package/dist/utils/fileSearch.js +88 -0
- package/dist/utils/fileUtils.d.ts +27 -0
- package/dist/utils/fileUtils.d.ts.map +1 -0
- package/dist/utils/fileUtils.js +145 -0
- package/dist/utils/globalLogger.d.ts +88 -0
- package/dist/utils/globalLogger.d.ts.map +1 -0
- package/dist/utils/globalLogger.js +120 -0
- package/dist/utils/largeOutputHandler.d.ts +15 -0
- package/dist/utils/largeOutputHandler.d.ts.map +1 -0
- package/dist/utils/largeOutputHandler.js +40 -0
- package/dist/utils/markdownParser.d.ts.map +1 -1
- package/dist/utils/markdownParser.js +1 -17
- package/dist/utils/mcpUtils.d.ts.map +1 -1
- package/dist/utils/mcpUtils.js +25 -3
- package/dist/utils/messageOperations.d.ts +20 -18
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +30 -38
- package/dist/utils/pathEncoder.d.ts +108 -0
- package/dist/utils/pathEncoder.d.ts.map +1 -0
- package/dist/utils/pathEncoder.js +279 -0
- package/dist/utils/subagentParser.d.ts +2 -2
- package/dist/utils/subagentParser.d.ts.map +1 -1
- package/dist/utils/subagentParser.js +12 -8
- package/dist/utils/tokenCalculation.d.ts +26 -0
- package/dist/utils/tokenCalculation.d.ts.map +1 -0
- package/dist/utils/tokenCalculation.js +36 -0
- package/dist/utils/tokenEstimator.d.ts +39 -0
- package/dist/utils/tokenEstimator.d.ts.map +1 -0
- package/dist/utils/tokenEstimator.js +55 -0
- package/package.json +6 -6
- package/src/agent.ts +586 -78
- package/src/index.ts +4 -0
- package/src/managers/aiManager.ts +341 -192
- package/src/managers/backgroundBashManager.ts +11 -9
- package/src/managers/hookManager.ts +102 -54
- package/src/managers/liveConfigManager.ts +634 -0
- package/src/managers/lspManager.ts +434 -0
- package/src/managers/messageManager.ts +258 -121
- package/src/managers/permissionManager.ts +276 -0
- package/src/managers/skillManager.ts +3 -1
- package/src/managers/slashCommandManager.ts +5 -3
- package/src/managers/subagentManager.ts +295 -76
- package/src/managers/toolManager.ts +95 -3
- package/src/services/aiService.ts +656 -84
- package/src/services/configurationService.ts +762 -0
- package/src/services/fileWatcher.ts +300 -0
- package/src/services/hook.ts +54 -144
- package/src/services/jsonlHandler.ts +303 -0
- package/src/services/memory.ts +34 -11
- package/src/services/session.ts +522 -173
- package/src/tools/bashTool.ts +94 -20
- package/src/tools/deleteFileTool.ts +38 -1
- package/src/tools/editTool.ts +44 -9
- package/src/tools/lspTool.ts +760 -0
- package/src/tools/multiEditTool.ts +41 -11
- package/src/tools/readTool.ts +127 -3
- package/src/tools/skillTool.ts +2 -2
- package/src/tools/todoWriteTool.ts +33 -1
- package/src/tools/types.ts +15 -9
- package/src/tools/writeTool.ts +43 -16
- package/src/types/commands.ts +6 -1
- package/src/types/config.ts +5 -0
- package/src/types/configuration.ts +73 -0
- package/src/types/core.ts +55 -0
- package/src/types/environment.ts +104 -0
- package/src/types/fileSearch.ts +4 -0
- package/src/types/hooks.ts +32 -16
- package/src/types/index.ts +7 -0
- package/src/types/lsp.ts +96 -0
- package/src/types/messaging.ts +21 -14
- package/src/types/permissions.ts +48 -0
- package/src/types/session.ts +20 -0
- package/src/types/skills.ts +1 -0
- package/src/types/tools.ts +38 -0
- package/src/utils/abortUtils.ts +118 -0
- package/src/utils/bashHistory.ts +55 -31
- package/src/utils/builtinSubagents.ts +71 -0
- package/src/utils/cacheControlUtils.ts +475 -0
- package/src/utils/commandPathResolver.ts +189 -0
- package/src/utils/configPaths.ts +163 -0
- package/src/utils/constants.ts +2 -17
- package/src/utils/convertMessagesForAPI.ts +44 -18
- package/src/utils/customCommands.ts +90 -22
- package/src/utils/fileSearch.ts +107 -0
- package/src/utils/fileUtils.ts +160 -0
- package/src/utils/globalLogger.ts +128 -0
- package/src/utils/largeOutputHandler.ts +55 -0
- package/src/utils/markdownParser.ts +1 -19
- package/src/utils/mcpUtils.ts +34 -3
- package/src/utils/messageOperations.ts +47 -53
- package/src/utils/pathEncoder.ts +394 -0
- package/src/utils/subagentParser.ts +13 -9
- package/src/utils/tokenCalculation.ts +43 -0
- package/src/utils/tokenEstimator.ts +68 -0
- package/dist/utils/configResolver.d.ts +0 -38
- package/dist/utils/configResolver.d.ts.map +0 -1
- package/dist/utils/configResolver.js +0 -106
- 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
|
+
}
|
package/src/services/hook.ts
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hook Services
|
|
2
|
+
* Hook Execution Services
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
|
|
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
|
|
145
|
-
if (childProcess.stdin &&
|
|
178
|
+
// Send JSON input to stdin if we have prepared it
|
|
179
|
+
if (childProcess.stdin && jsonInput) {
|
|
146
180
|
try {
|
|
147
|
-
|
|
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
|
-
}
|