wave-agent-sdk 1.0.4 → 1.0.6
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 +23 -1
- package/dist/agent.js +34 -4
- package/dist/managers/aiManager.d.ts +36 -0
- package/dist/managers/aiManager.js +240 -8
- package/dist/managers/messageManager.d.ts +1 -3
- package/dist/managers/messageManager.js +19 -9
- package/dist/managers/slashCommandManager.js +28 -0
- package/dist/managers/subagentManager.d.ts +0 -2
- package/dist/services/session.js +2 -9
- package/dist/telemetry/instrumentation.d.ts +1 -1
- package/dist/telemetry/instrumentation.js +57 -28
- package/dist/tools/bashTool.js +16 -6
- package/dist/tools/grepTool.js +11 -3
- package/dist/types/index.d.ts +3 -0
- package/dist/types/messaging.d.ts +2 -0
- package/dist/utils/containerSetup.js +0 -3
- package/dist/utils/messageOperations.d.ts +10 -1
- package/dist/utils/messageOperations.js +19 -1
- package/dist/utils/notificationXml.js +5 -0
- package/dist/utils/ripgrep.d.ts +4 -4
- package/dist/utils/ripgrep.js +4 -27
- package/package.json +20 -8
- package/bin/rg +0 -79
- package/dist/managers/forkedAgentManager.d.ts +0 -50
- package/dist/managers/forkedAgentManager.js +0 -130
- package/scripts/install_ripgrep.js +0 -111
- package/scripts/postinstall.js +0 -38
- package/src/agent.ts +0 -1271
- package/src/constants/subagents.ts +0 -4
- package/src/constants/toolLimits.ts +0 -15
- package/src/constants/tools.ts +0 -24
- package/src/core/plugin.ts +0 -237
- package/src/core/session.ts +0 -9
- package/src/index.ts +0 -38
- package/src/managers/MemoryRuleManager.ts +0 -198
- package/src/managers/aiManager.ts +0 -2429
- package/src/managers/backgroundTaskManager.ts +0 -480
- package/src/managers/bangManager.ts +0 -119
- package/src/managers/cronManager.ts +0 -401
- package/src/managers/foregroundTaskManager.ts +0 -30
- package/src/managers/forkedAgentManager.ts +0 -216
- package/src/managers/hookManager.ts +0 -1089
- package/src/managers/liveConfigManager.ts +0 -445
- package/src/managers/lspManager.ts +0 -470
- package/src/managers/mcpManager.ts +0 -1010
- package/src/managers/messageManager.ts +0 -1180
- package/src/managers/messageQueue.ts +0 -157
- package/src/managers/permissionManager.ts +0 -1140
- package/src/managers/planManager.ts +0 -112
- package/src/managers/pluginManager.ts +0 -330
- package/src/managers/pluginScopeManager.ts +0 -124
- package/src/managers/reversionManager.ts +0 -178
- package/src/managers/skillManager.ts +0 -578
- package/src/managers/slashCommandManager.ts +0 -564
- package/src/managers/subagentManager.ts +0 -915
- package/src/managers/toolManager.ts +0 -489
- package/src/managers/workflowManager.ts +0 -493
- package/src/prompts/autoMemory.ts +0 -33
- package/src/prompts/autoMemoryExtraction.ts +0 -146
- package/src/prompts/index.ts +0 -506
- package/src/prompts/planModeReminders.ts +0 -126
- package/src/services/GitService.ts +0 -131
- package/src/services/MarketplaceService.ts +0 -1052
- package/src/services/MemoryRuleService.ts +0 -71
- package/src/services/aiService.ts +0 -919
- package/src/services/authService.ts +0 -565
- package/src/services/autoMemoryService.ts +0 -280
- package/src/services/configurationService.ts +0 -1520
- package/src/services/fileWatcher.ts +0 -319
- package/src/services/hook.ts +0 -348
- package/src/services/initializationService.ts +0 -357
- package/src/services/interactionService.ts +0 -229
- package/src/services/jsonlHandler.ts +0 -295
- package/src/services/memory.ts +0 -234
- package/src/services/pluginLoader.ts +0 -252
- package/src/services/remoteSettingsService.ts +0 -366
- package/src/services/reversionService.ts +0 -122
- package/src/services/session.ts +0 -922
- package/src/services/taskManager.ts +0 -321
- package/src/telemetry/events.ts +0 -65
- package/src/telemetry/instrumentation.ts +0 -499
- package/src/telemetry/sessionTracing.ts +0 -348
- package/src/tools/agentTool.ts +0 -322
- package/src/tools/askUserQuestion.ts +0 -160
- package/src/tools/bashTool.ts +0 -621
- package/src/tools/buildTool.ts +0 -61
- package/src/tools/cronCreateTool.ts +0 -161
- package/src/tools/cronDeleteTool.ts +0 -51
- package/src/tools/cronListTool.ts +0 -47
- package/src/tools/editTool.ts +0 -313
- package/src/tools/enterPlanMode.ts +0 -124
- package/src/tools/enterWorktreeTool.ts +0 -190
- package/src/tools/exitPlanMode.ts +0 -139
- package/src/tools/exitWorktreeTool.ts +0 -236
- package/src/tools/globTool.ts +0 -167
- package/src/tools/grepTool.ts +0 -399
- package/src/tools/lspTool.ts +0 -883
- package/src/tools/readTool.ts +0 -426
- package/src/tools/skillTool.ts +0 -248
- package/src/tools/taskManagementTools.ts +0 -574
- package/src/tools/taskStopTool.ts +0 -78
- package/src/tools/types.ts +0 -137
- package/src/tools/webFetchTool.ts +0 -373
- package/src/tools/workflowTool.ts +0 -205
- package/src/tools/writeTool.ts +0 -267
- package/src/types/agent.ts +0 -128
- package/src/types/auth.ts +0 -19
- package/src/types/commands.ts +0 -30
- package/src/types/config.ts +0 -43
- package/src/types/configuration.ts +0 -168
- package/src/types/core.ts +0 -105
- package/src/types/cron.ts +0 -13
- package/src/types/environment.ts +0 -106
- package/src/types/fileSearch.ts +0 -4
- package/src/types/history.ts +0 -7
- package/src/types/hooks.ts +0 -256
- package/src/types/index.ts +0 -41
- package/src/types/lsp.ts +0 -98
- package/src/types/marketplace.ts +0 -71
- package/src/types/mcp.ts +0 -43
- package/src/types/memoryRule.ts +0 -31
- package/src/types/messaging.ts +0 -114
- package/src/types/permissions.ts +0 -80
- package/src/types/plugins.ts +0 -39
- package/src/types/processes.ts +0 -99
- package/src/types/reversion.ts +0 -29
- package/src/types/session.ts +0 -12
- package/src/types/skills.ts +0 -108
- package/src/types/tasks.ts +0 -13
- package/src/types/telemetry.ts +0 -98
- package/src/types/tools.ts +0 -44
- package/src/types/workflow.ts +0 -6
- package/src/utils/abortUtils.ts +0 -118
- package/src/utils/atomicWrite.ts +0 -61
- package/src/utils/bashParser.ts +0 -899
- package/src/utils/cacheControlUtils.ts +0 -426
- package/src/utils/commandArgumentParser.ts +0 -161
- package/src/utils/commandPathResolver.ts +0 -88
- package/src/utils/configPaths.ts +0 -220
- package/src/utils/configValidator.ts +0 -100
- package/src/utils/constants.ts +0 -39
- package/src/utils/container.ts +0 -92
- package/src/utils/containerSetup.ts +0 -371
- package/src/utils/convertMessagesForAPI.ts +0 -326
- package/src/utils/cronTasks.ts +0 -128
- package/src/utils/cronTasksLock.ts +0 -182
- package/src/utils/cronToHuman.ts +0 -99
- package/src/utils/customCommands.ts +0 -95
- package/src/utils/editUtils.ts +0 -20
- package/src/utils/fileFormat.ts +0 -40
- package/src/utils/fileSearch.ts +0 -151
- package/src/utils/fileUtils.ts +0 -223
- package/src/utils/gitUtils.ts +0 -315
- package/src/utils/globalLogger.ts +0 -128
- package/src/utils/groupMessagesByApiRound.ts +0 -121
- package/src/utils/hookMatcher.ts +0 -176
- package/src/utils/markdownParser.ts +0 -317
- package/src/utils/mcpUtils.ts +0 -138
- package/src/utils/messageOperations.ts +0 -641
- package/src/utils/modelCapabilities.ts +0 -30
- package/src/utils/nameGenerator.ts +0 -99
- package/src/utils/notificationXml.ts +0 -57
- package/src/utils/openaiClient.ts +0 -251
- package/src/utils/parseCronExpression.ts +0 -78
- package/src/utils/path.ts +0 -72
- package/src/utils/pathEncoder.ts +0 -400
- package/src/utils/pathSafety.ts +0 -40
- package/src/utils/promptHistory.ts +0 -168
- package/src/utils/ripgrep.ts +0 -35
- package/src/utils/shellResolver.ts +0 -190
- package/src/utils/skillParser.ts +0 -246
- package/src/utils/stringUtils.ts +0 -156
- package/src/utils/subagentParser.ts +0 -279
- package/src/utils/taskReminder.ts +0 -96
- package/src/utils/tokenCalculation.ts +0 -43
- package/src/utils/tokenEstimate.ts +0 -34
- package/src/utils/toolResultStorage.ts +0 -117
- package/src/utils/worktreeSession.ts +0 -26
- package/src/utils/worktreeUtils.ts +0 -786
- package/src/workflow/budgetTracker.ts +0 -34
- package/src/workflow/concurrencyLimiter.ts +0 -47
- package/src/workflow/journal.ts +0 -95
- package/src/workflow/progressReporter.ts +0 -141
- package/src/workflow/runState.ts +0 -65
- package/src/workflow/scriptRuntime.ts +0 -274
- package/src/workflow/structuredOutput.ts +0 -123
- package/src/workflow/types.ts +0 -101
- package/src/workflow/workflowApis.ts +0 -410
- package/vendor/ripgrep/linux-aarch64/rg +0 -0
- package/vendor/ripgrep/linux-x86_64/rg +0 -0
- package/vendor/ripgrep/macos-aarch64/rg +0 -0
- package/vendor/ripgrep/macos-x86_64/rg +0 -0
- package/vendor/ripgrep/windows-aarch64/rg.exe +0 -0
- package/vendor/ripgrep/windows-x86_64/rg.exe +0 -0
|
@@ -1,319 +0,0 @@
|
|
|
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
|
-
depth: number; // default: 5
|
|
26
|
-
ignored: string[]; // default: ['**/node_modules/**', '**/.git/**', '**/*.swp', '**/*~']
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface FileWatcherStatus {
|
|
30
|
-
isActive: boolean;
|
|
31
|
-
path: string;
|
|
32
|
-
method: "native" | "polling" | "failed";
|
|
33
|
-
errorCount: number;
|
|
34
|
-
lastError?: string;
|
|
35
|
-
lastEvent?: FileWatchEvent;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface FileWatcherEntry {
|
|
39
|
-
path: string;
|
|
40
|
-
watcher: chokidar.FSWatcher | null;
|
|
41
|
-
isActive: boolean;
|
|
42
|
-
lastEvent: number;
|
|
43
|
-
errorCount: number;
|
|
44
|
-
lastError?: string;
|
|
45
|
-
callbacks: Set<(event: FileWatchEvent) => void>;
|
|
46
|
-
config: FileWatcherConfig;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export class FileWatcherService extends EventEmitter {
|
|
50
|
-
private watchers: Map<string, FileWatcherEntry> = new Map();
|
|
51
|
-
private globalWatcher: chokidar.FSWatcher | null = null;
|
|
52
|
-
private defaultConfig: FileWatcherConfig;
|
|
53
|
-
private logger?: Logger;
|
|
54
|
-
|
|
55
|
-
constructor(logger?: Logger, config?: Partial<FileWatcherConfig>) {
|
|
56
|
-
super();
|
|
57
|
-
this.logger = logger;
|
|
58
|
-
this.defaultConfig = {
|
|
59
|
-
stabilityThreshold: 300,
|
|
60
|
-
pollInterval: 100,
|
|
61
|
-
maxRetries: 3,
|
|
62
|
-
fallbackPolling: false,
|
|
63
|
-
ignoreTempFiles: true,
|
|
64
|
-
depth: 5,
|
|
65
|
-
ignored: ["**/node_modules/**", "**/.git/**", "**/*.swp", "**/*~"],
|
|
66
|
-
...config,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Start watching a file
|
|
72
|
-
* Maps to FR-010: Handle file deletion, creation, and modification
|
|
73
|
-
*/
|
|
74
|
-
async watchFile(
|
|
75
|
-
path: string,
|
|
76
|
-
callback: (event: FileWatchEvent) => void,
|
|
77
|
-
): Promise<void> {
|
|
78
|
-
try {
|
|
79
|
-
if (this.watchers.has(path)) {
|
|
80
|
-
// Add callback to existing watcher
|
|
81
|
-
const entry = this.watchers.get(path)!;
|
|
82
|
-
entry.callbacks.add(callback);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Create new watcher entry
|
|
87
|
-
const entry: FileWatcherEntry = {
|
|
88
|
-
path,
|
|
89
|
-
watcher: null,
|
|
90
|
-
isActive: false,
|
|
91
|
-
lastEvent: Date.now(),
|
|
92
|
-
errorCount: 0,
|
|
93
|
-
lastError: undefined,
|
|
94
|
-
callbacks: new Set([callback]),
|
|
95
|
-
config: { ...this.defaultConfig },
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
this.watchers.set(path, entry);
|
|
99
|
-
await this.initializeWatcher(entry);
|
|
100
|
-
} catch (error) {
|
|
101
|
-
this.logger?.error(
|
|
102
|
-
`FileWatcher: Failed to watch file ${path}: ${(error as Error).message}`,
|
|
103
|
-
);
|
|
104
|
-
throw error;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Stop watching a file
|
|
110
|
-
* Resource cleanup
|
|
111
|
-
*/
|
|
112
|
-
async unwatchFile(path: string): Promise<void> {
|
|
113
|
-
const entry = this.watchers.get(path);
|
|
114
|
-
if (!entry) return;
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
if (entry.watcher) {
|
|
118
|
-
entry.watcher.unwatch(path);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
this.watchers.delete(path);
|
|
122
|
-
} catch (error) {
|
|
123
|
-
this.logger?.warn(
|
|
124
|
-
`FileWatcher: Error unwatching file ${path}: ${(error as Error).message}`,
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Get watcher status
|
|
131
|
-
* Maps to FR-012: Handle watcher initialization failures
|
|
132
|
-
*/
|
|
133
|
-
getWatcherStatus(path: string): FileWatcherStatus | null {
|
|
134
|
-
const entry = this.watchers.get(path);
|
|
135
|
-
if (!entry) return null;
|
|
136
|
-
|
|
137
|
-
return {
|
|
138
|
-
isActive: entry.isActive,
|
|
139
|
-
path: entry.path,
|
|
140
|
-
method:
|
|
141
|
-
entry.errorCount > 0
|
|
142
|
-
? "failed"
|
|
143
|
-
: entry.config.fallbackPolling
|
|
144
|
-
? "polling"
|
|
145
|
-
: "native",
|
|
146
|
-
errorCount: entry.errorCount,
|
|
147
|
-
lastError: entry.lastError,
|
|
148
|
-
lastEvent:
|
|
149
|
-
entry.lastEvent > 0
|
|
150
|
-
? {
|
|
151
|
-
type: "change",
|
|
152
|
-
path: entry.path,
|
|
153
|
-
timestamp: entry.lastEvent,
|
|
154
|
-
}
|
|
155
|
-
: undefined,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Get all watcher statuses
|
|
161
|
-
* For monitoring and debugging
|
|
162
|
-
*/
|
|
163
|
-
getAllWatcherStatuses(): FileWatcherStatus[] {
|
|
164
|
-
return Array.from(this.watchers.keys())
|
|
165
|
-
.map((path) => this.getWatcherStatus(path))
|
|
166
|
-
.filter((status): status is FileWatcherStatus => status !== null);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Cleanup all watchers
|
|
171
|
-
*/
|
|
172
|
-
async cleanup(): Promise<void> {
|
|
173
|
-
const paths = Array.from(this.watchers.keys());
|
|
174
|
-
for (const path of paths) {
|
|
175
|
-
await this.unwatchFile(path);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (this.globalWatcher) {
|
|
179
|
-
const watcher = this.globalWatcher;
|
|
180
|
-
this.globalWatcher = null;
|
|
181
|
-
await watcher.close();
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
private async initializeWatcher(entry: FileWatcherEntry): Promise<void> {
|
|
186
|
-
try {
|
|
187
|
-
// Initialize global watcher if needed
|
|
188
|
-
if (!this.globalWatcher) {
|
|
189
|
-
this.globalWatcher = chokidar.watch([], {
|
|
190
|
-
persistent: true,
|
|
191
|
-
ignoreInitial: true,
|
|
192
|
-
depth: entry.config.depth,
|
|
193
|
-
ignored: entry.config.ignored,
|
|
194
|
-
awaitWriteFinish: {
|
|
195
|
-
stabilityThreshold: entry.config.stabilityThreshold,
|
|
196
|
-
pollInterval: entry.config.pollInterval,
|
|
197
|
-
},
|
|
198
|
-
usePolling: entry.config.fallbackPolling,
|
|
199
|
-
interval: entry.config.pollInterval,
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// Unref the global watcher to allow natural exit if it's the only thing left
|
|
203
|
-
// (chokidar watchers keep the process alive by default)
|
|
204
|
-
// Note: some platforms might need additional handling
|
|
205
|
-
const watcher = this.globalWatcher as unknown as { unref?: () => void };
|
|
206
|
-
if (watcher.unref) {
|
|
207
|
-
watcher.unref();
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
this.setupGlobalWatcherEvents();
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Add path to global watcher
|
|
214
|
-
this.globalWatcher.add(entry.path);
|
|
215
|
-
entry.watcher = this.globalWatcher;
|
|
216
|
-
entry.isActive = true;
|
|
217
|
-
entry.errorCount = 0;
|
|
218
|
-
} catch (error) {
|
|
219
|
-
entry.errorCount++;
|
|
220
|
-
entry.isActive = false;
|
|
221
|
-
entry.lastError = (error as Error).message;
|
|
222
|
-
|
|
223
|
-
this.logger?.error(
|
|
224
|
-
`FileWatcher: Failed to initialize watcher for ${entry.path}: ${(error as Error).message}`,
|
|
225
|
-
);
|
|
226
|
-
|
|
227
|
-
// Try fallback polling if not already using it
|
|
228
|
-
if (
|
|
229
|
-
!entry.config.fallbackPolling &&
|
|
230
|
-
entry.errorCount < entry.config.maxRetries
|
|
231
|
-
) {
|
|
232
|
-
entry.config.fallbackPolling = true;
|
|
233
|
-
await this.initializeWatcher(entry);
|
|
234
|
-
} else {
|
|
235
|
-
throw error;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
private setupGlobalWatcherEvents(): void {
|
|
241
|
-
if (!this.globalWatcher) return;
|
|
242
|
-
|
|
243
|
-
this.globalWatcher.on(
|
|
244
|
-
"change",
|
|
245
|
-
(filePath: string, stats?: { size?: number }) => {
|
|
246
|
-
this.handleFileEvent("change", filePath, stats);
|
|
247
|
-
},
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
this.globalWatcher.on(
|
|
251
|
-
"add",
|
|
252
|
-
(filePath: string, stats?: { size?: number }) => {
|
|
253
|
-
this.handleFileEvent("create", filePath, stats);
|
|
254
|
-
},
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
this.globalWatcher.on("unlink", (filePath: string) => {
|
|
258
|
-
this.handleFileEvent("delete", filePath);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
this.globalWatcher.on("addDir", (dirPath: string) => {
|
|
262
|
-
this.handleFileEvent("create", dirPath);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
this.globalWatcher.on("unlinkDir", (dirPath: string) => {
|
|
266
|
-
this.handleFileEvent("delete", dirPath);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
this.globalWatcher.on("error", (err: unknown) => {
|
|
270
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
271
|
-
const isEMFILE = (error as NodeJS.ErrnoException).code === "EMFILE";
|
|
272
|
-
if (isEMFILE) {
|
|
273
|
-
this.logger?.warn(
|
|
274
|
-
"FileWatcher: Too many open files (EMFILE). " +
|
|
275
|
-
"Consider increasing the limit: `ulimit -n 10240` (macOS) or `ulimit -n 65536` (Linux).",
|
|
276
|
-
);
|
|
277
|
-
} else {
|
|
278
|
-
this.logger?.error(`FileWatcher: File watcher error: ${error.message}`);
|
|
279
|
-
}
|
|
280
|
-
this.emit("watcherError", error);
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
private handleFileEvent(
|
|
285
|
-
type: FileWatchEvent["type"],
|
|
286
|
-
filePath: string,
|
|
287
|
-
stats?: { size?: number },
|
|
288
|
-
): void {
|
|
289
|
-
const event: FileWatchEvent = {
|
|
290
|
-
type,
|
|
291
|
-
path: filePath,
|
|
292
|
-
timestamp: Date.now(),
|
|
293
|
-
size: stats?.size,
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
// Notify all watchers that match the path or are parents of the path
|
|
297
|
-
for (const [watchedPath, entry] of this.watchers.entries()) {
|
|
298
|
-
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
299
|
-
const normalizedWatchedPath = watchedPath.replace(/\\/g, "/");
|
|
300
|
-
if (
|
|
301
|
-
normalizedFilePath === normalizedWatchedPath ||
|
|
302
|
-
normalizedFilePath.startsWith(normalizedWatchedPath + "/")
|
|
303
|
-
) {
|
|
304
|
-
entry.lastEvent = event.timestamp;
|
|
305
|
-
|
|
306
|
-
// Notify all callbacks for this watcher
|
|
307
|
-
for (const callback of entry.callbacks) {
|
|
308
|
-
try {
|
|
309
|
-
callback(event);
|
|
310
|
-
} catch (error) {
|
|
311
|
-
this.logger?.error(
|
|
312
|
-
`FileWatcher: Error in file watch callback for ${watchedPath} (event on ${filePath}): ${(error as Error).message}`,
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
package/src/services/hook.ts
DELETED
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hook Execution Services
|
|
3
|
-
*
|
|
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.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { spawn, type ChildProcess } from "child_process";
|
|
10
|
-
import {
|
|
11
|
-
type HookExecutionContext,
|
|
12
|
-
type HookExecutionResult,
|
|
13
|
-
type HookExecutionOptions,
|
|
14
|
-
type ExtendedHookExecutionContext,
|
|
15
|
-
type HookJsonInput,
|
|
16
|
-
} from "../types/hooks.js";
|
|
17
|
-
import { generateSessionFilePath } from "./session.js";
|
|
18
|
-
|
|
19
|
-
// =============================================================================
|
|
20
|
-
// Hook Execution Functions
|
|
21
|
-
// =============================================================================
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Build JSON input data for hook stdin
|
|
25
|
-
*/
|
|
26
|
-
async function buildHookJsonInput(
|
|
27
|
-
context: ExtendedHookExecutionContext,
|
|
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
|
-
|
|
43
|
-
const jsonInput: HookJsonInput = {
|
|
44
|
-
session_id: context.sessionId || "unknown",
|
|
45
|
-
transcript_path: transcriptPath || "",
|
|
46
|
-
cwd: workdir,
|
|
47
|
-
hook_event_name: context.event,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// Add optional fields based on event type
|
|
51
|
-
if (
|
|
52
|
-
context.event === "PreToolUse" ||
|
|
53
|
-
context.event === "PostToolUse" ||
|
|
54
|
-
context.event === "PermissionRequest"
|
|
55
|
-
) {
|
|
56
|
-
if (context.toolName) {
|
|
57
|
-
jsonInput.tool_name = context.toolName;
|
|
58
|
-
}
|
|
59
|
-
if (context.toolInput !== undefined) {
|
|
60
|
-
jsonInput.tool_input = context.toolInput;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (context.event === "PostToolUse" && context.toolResponse !== undefined) {
|
|
65
|
-
jsonInput.tool_response = context.toolResponse;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
context.event === "UserPromptSubmit" &&
|
|
70
|
-
context.userPrompt !== undefined
|
|
71
|
-
) {
|
|
72
|
-
jsonInput.user_prompt = context.userPrompt;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Add plan_file_path if present
|
|
76
|
-
if (context.planFilePath !== undefined) {
|
|
77
|
-
jsonInput.plan_file_path = context.planFilePath;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Add subagent_type if present
|
|
81
|
-
if (context.subagentType !== undefined) {
|
|
82
|
-
jsonInput.subagent_type = context.subagentType;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Add name field for WorktreeCreate events
|
|
86
|
-
if (context.event === "WorktreeCreate") {
|
|
87
|
-
if (context.worktreeName !== undefined) {
|
|
88
|
-
jsonInput.name = context.worktreeName;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Add worktree_path field for WorktreeRemove events
|
|
93
|
-
if (context.event === "WorktreeRemove") {
|
|
94
|
-
if (context.worktreePath !== undefined) {
|
|
95
|
-
jsonInput.worktree_path = context.worktreePath;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Add SessionStart-specific fields
|
|
100
|
-
if (context.event === "SessionStart") {
|
|
101
|
-
if (context.source !== undefined) {
|
|
102
|
-
jsonInput.source = context.source;
|
|
103
|
-
}
|
|
104
|
-
if (context.agentType !== undefined) {
|
|
105
|
-
jsonInput.agent_type = context.agentType;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Add SessionEnd-specific fields
|
|
110
|
-
if (context.event === "SessionEnd") {
|
|
111
|
-
if (context.endSource !== undefined) {
|
|
112
|
-
jsonInput.end_source = context.endSource;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Add background_tasks and session_crons for Stop events only (not SubagentStop).
|
|
117
|
-
// Fields are always present for Stop (empty arrays when nothing in flight).
|
|
118
|
-
if (context.event === "Stop") {
|
|
119
|
-
jsonInput.background_tasks = context.backgroundTasks ?? [];
|
|
120
|
-
jsonInput.session_crons = context.sessionCrons ?? [];
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Add last_assistant_message for Stop and SubagentStop events.
|
|
124
|
-
// Omitted when undefined (no text content in last assistant message).
|
|
125
|
-
if (
|
|
126
|
-
(context.event === "Stop" || context.event === "SubagentStop") &&
|
|
127
|
-
context.lastAssistantMessage !== undefined
|
|
128
|
-
) {
|
|
129
|
-
jsonInput.last_assistant_message = context.lastAssistantMessage;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return jsonInput;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Execute a single hook command
|
|
137
|
-
*/
|
|
138
|
-
export async function executeCommand(
|
|
139
|
-
command: string,
|
|
140
|
-
context: HookExecutionContext | ExtendedHookExecutionContext,
|
|
141
|
-
options?: HookExecutionOptions,
|
|
142
|
-
): Promise<HookExecutionResult> {
|
|
143
|
-
const defaultTimeout = 600000; // 10 minutes
|
|
144
|
-
const maxTimeout = 600000; // 10 minutes
|
|
145
|
-
const skipExecution =
|
|
146
|
-
process.env.NODE_ENV === "test" &&
|
|
147
|
-
process.env.TEST_HOOK_EXECUTION !== "true";
|
|
148
|
-
|
|
149
|
-
const startTime = Date.now();
|
|
150
|
-
const timeout = Math.min(options?.timeout ?? defaultTimeout, maxTimeout);
|
|
151
|
-
|
|
152
|
-
// Return mock result if execution is skipped
|
|
153
|
-
if (skipExecution) {
|
|
154
|
-
return {
|
|
155
|
-
success: true,
|
|
156
|
-
exitCode: 0,
|
|
157
|
-
stdout: "",
|
|
158
|
-
stderr: "",
|
|
159
|
-
duration: 0,
|
|
160
|
-
timedOut: false,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Prepare JSON input for hooks that need it
|
|
165
|
-
let jsonInput: string | null = null;
|
|
166
|
-
if ("sessionId" in context) {
|
|
167
|
-
try {
|
|
168
|
-
const hookJsonInput = await buildHookJsonInput(context);
|
|
169
|
-
jsonInput = JSON.stringify(hookJsonInput, null, 2);
|
|
170
|
-
} catch {
|
|
171
|
-
// Continue execution even if JSON input preparation fails
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return new Promise((resolve) => {
|
|
176
|
-
let stdout = "";
|
|
177
|
-
let stderr = "";
|
|
178
|
-
let timedOut = false;
|
|
179
|
-
|
|
180
|
-
// Parse command for shell execution
|
|
181
|
-
const isWindows = process.platform === "win32";
|
|
182
|
-
const shell = isWindows ? "cmd.exe" : "/bin/sh";
|
|
183
|
-
const shellFlag = isWindows ? "/c" : "-c";
|
|
184
|
-
|
|
185
|
-
const childProcess: ChildProcess = spawn(shell, [shellFlag, command], {
|
|
186
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
187
|
-
cwd: context.projectDir,
|
|
188
|
-
env: {
|
|
189
|
-
...process.env, // Environment variables from process.env
|
|
190
|
-
...("env" in context ? context.env || {} : {}), // Additional environment variables from configuration (if ExtendedHookExecutionContext)
|
|
191
|
-
HOOK_EVENT: context.event,
|
|
192
|
-
HOOK_TOOL_NAME: context.toolName || "",
|
|
193
|
-
WAVE_PROJECT_DIR: context.projectDir,
|
|
194
|
-
},
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
// Handle stdin errors (e.g. EPIPE if the process closes stdin early)
|
|
198
|
-
if (childProcess.stdin) {
|
|
199
|
-
childProcess.stdin.on("error", () => {
|
|
200
|
-
// Ignore stdin errors as they just mean the process closed stdin early
|
|
201
|
-
// or doesn't want to read from it.
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Set up timeout
|
|
206
|
-
let forceKillTimeoutHandle: NodeJS.Timeout | undefined;
|
|
207
|
-
const timeoutHandle = setTimeout(() => {
|
|
208
|
-
timedOut = true;
|
|
209
|
-
childProcess.kill("SIGTERM");
|
|
210
|
-
|
|
211
|
-
// Force kill after additional delay
|
|
212
|
-
forceKillTimeoutHandle = setTimeout(() => {
|
|
213
|
-
if (!childProcess.killed) {
|
|
214
|
-
childProcess.kill("SIGKILL");
|
|
215
|
-
}
|
|
216
|
-
}, 2000);
|
|
217
|
-
}, timeout);
|
|
218
|
-
|
|
219
|
-
// Handle stdout
|
|
220
|
-
if (childProcess.stdout) {
|
|
221
|
-
childProcess.stdout.on("data", (data: Buffer) => {
|
|
222
|
-
stdout += data.toString();
|
|
223
|
-
});
|
|
224
|
-
childProcess.stdout.on("error", () => {
|
|
225
|
-
// Ignore stdout errors
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Handle stderr
|
|
230
|
-
if (childProcess.stderr) {
|
|
231
|
-
childProcess.stderr.on("data", (data: Buffer) => {
|
|
232
|
-
stderr += data.toString();
|
|
233
|
-
});
|
|
234
|
-
childProcess.stderr.on("error", () => {
|
|
235
|
-
// Ignore stderr errors
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Send JSON input to stdin if we have prepared it
|
|
240
|
-
if (childProcess.stdin && childProcess.stdin.writable && jsonInput) {
|
|
241
|
-
try {
|
|
242
|
-
childProcess.stdin.write(jsonInput);
|
|
243
|
-
childProcess.stdin.end();
|
|
244
|
-
} catch {
|
|
245
|
-
// Continue execution even if JSON input fails
|
|
246
|
-
}
|
|
247
|
-
} else if (childProcess.stdin && childProcess.stdin.writable) {
|
|
248
|
-
childProcess.stdin.end();
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Handle process completion
|
|
252
|
-
childProcess.on("close", (code: number | null) => {
|
|
253
|
-
clearTimeout(timeoutHandle);
|
|
254
|
-
if (forceKillTimeoutHandle) clearTimeout(forceKillTimeoutHandle);
|
|
255
|
-
const duration = Date.now() - startTime;
|
|
256
|
-
|
|
257
|
-
resolve({
|
|
258
|
-
success: !timedOut && (code === 0 || code === null),
|
|
259
|
-
exitCode: code || 0,
|
|
260
|
-
stdout: stdout.trim(),
|
|
261
|
-
stderr: stderr.trim(),
|
|
262
|
-
duration,
|
|
263
|
-
timedOut,
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
// Handle process errors
|
|
268
|
-
childProcess.on("error", (error: Error) => {
|
|
269
|
-
clearTimeout(timeoutHandle);
|
|
270
|
-
if (forceKillTimeoutHandle) clearTimeout(forceKillTimeoutHandle);
|
|
271
|
-
const duration = Date.now() - startTime;
|
|
272
|
-
|
|
273
|
-
resolve({
|
|
274
|
-
success: false,
|
|
275
|
-
exitCode: 1,
|
|
276
|
-
stdout: stdout.trim(),
|
|
277
|
-
stderr: error.message,
|
|
278
|
-
duration,
|
|
279
|
-
timedOut,
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Execute multiple commands in sequence
|
|
287
|
-
*/
|
|
288
|
-
export async function executeCommands(
|
|
289
|
-
commands: string[],
|
|
290
|
-
context: HookExecutionContext | ExtendedHookExecutionContext,
|
|
291
|
-
options?: HookExecutionOptions,
|
|
292
|
-
): Promise<HookExecutionResult[]> {
|
|
293
|
-
const results: HookExecutionResult[] = [];
|
|
294
|
-
|
|
295
|
-
for (const command of commands) {
|
|
296
|
-
const result = await executeCommand(command, context, options);
|
|
297
|
-
results.push(result);
|
|
298
|
-
|
|
299
|
-
// Stop on first failure unless continueOnFailure is set
|
|
300
|
-
if (!result.success && !options?.continueOnFailure) {
|
|
301
|
-
break;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return results;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Execute a CwdChanged hook
|
|
310
|
-
*/
|
|
311
|
-
export async function executeCwdChangedHooks(
|
|
312
|
-
oldCwd: string,
|
|
313
|
-
newCwd: string,
|
|
314
|
-
context: ExtendedHookExecutionContext,
|
|
315
|
-
): Promise<HookExecutionResult[]> {
|
|
316
|
-
// CwdChanged hooks are executed through HookManager.executeCwdChangedHooks()
|
|
317
|
-
void context;
|
|
318
|
-
void oldCwd;
|
|
319
|
-
void newCwd;
|
|
320
|
-
return [];
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Validate command safety (basic checks)
|
|
325
|
-
*/
|
|
326
|
-
export function isCommandSafe(command: string): boolean {
|
|
327
|
-
const trimmed = command.trim();
|
|
328
|
-
|
|
329
|
-
// Empty commands are safe (no-op)
|
|
330
|
-
if (!trimmed) {
|
|
331
|
-
return true;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Check for obviously dangerous patterns
|
|
335
|
-
const dangerousPatterns = [
|
|
336
|
-
/rm\s+-rf\s+\//, // rm -rf /
|
|
337
|
-
/sudo\s+rm/, // sudo rm
|
|
338
|
-
/>\s*\/dev\/sd[a-z]/, // writing to disk devices
|
|
339
|
-
/dd\s+if=.*of=\/dev/, // dd to devices
|
|
340
|
-
/mkfs/, // filesystem creation
|
|
341
|
-
/fdisk/, // disk partitioning
|
|
342
|
-
/format\s+[a-z]:/, // Windows format command
|
|
343
|
-
];
|
|
344
|
-
|
|
345
|
-
return !dangerousPatterns.some((pattern) =>
|
|
346
|
-
pattern.test(trimmed.toLowerCase()),
|
|
347
|
-
);
|
|
348
|
-
}
|