wave-agent-sdk 0.8.1 → 0.8.3
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/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +0 -21
- package/dist/managers/liveConfigManager.d.ts.map +1 -1
- package/dist/managers/liveConfigManager.js +0 -36
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +2 -1
- package/dist/managers/permissionManager.d.ts.map +1 -1
- package/dist/managers/permissionManager.js +47 -29
- package/dist/managers/pluginManager.d.ts.map +1 -1
- package/dist/managers/pluginManager.js +28 -1
- package/dist/managers/skillManager.d.ts.map +1 -1
- package/dist/managers/skillManager.js +8 -2
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +2 -0
- package/dist/services/fileWatcher.d.ts.map +1 -1
- package/dist/services/fileWatcher.js +0 -4
- package/dist/services/initializationService.d.ts.map +1 -1
- package/dist/services/initializationService.js +2 -10
- package/dist/services/pluginLoader.d.ts.map +1 -1
- package/dist/services/pluginLoader.js +1 -3
- package/dist/services/taskManager.d.ts +2 -0
- package/dist/services/taskManager.d.ts.map +1 -1
- package/dist/services/taskManager.js +48 -0
- package/dist/tools/taskManagementTools.d.ts.map +1 -1
- package/dist/tools/taskManagementTools.js +58 -0
- package/dist/tools/taskTool.d.ts.map +1 -1
- package/dist/tools/taskTool.js +60 -50
- package/dist/utils/bashParser.d.ts +4 -0
- package/dist/utils/bashParser.d.ts.map +1 -1
- package/dist/utils/bashParser.js +39 -2
- package/dist/utils/containerSetup.d.ts.map +1 -1
- package/dist/utils/containerSetup.js +3 -0
- package/dist/utils/messageOperations.d.ts +1 -0
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +6 -2
- package/dist/utils/openaiClient.d.ts.map +1 -1
- package/dist/utils/openaiClient.js +3 -1
- package/package.json +2 -5
- package/src/managers/hookManager.ts +0 -52
- package/src/managers/liveConfigManager.ts +0 -75
- package/src/managers/messageManager.ts +2 -0
- package/src/managers/permissionManager.ts +60 -37
- package/src/managers/pluginManager.ts +39 -1
- package/src/managers/skillManager.ts +8 -2
- package/src/services/aiService.ts +2 -0
- package/src/services/fileWatcher.ts +0 -8
- package/src/services/initializationService.ts +2 -19
- package/src/services/pluginLoader.ts +1 -3
- package/src/services/taskManager.ts +51 -0
- package/src/tools/taskManagementTools.ts +77 -0
- package/src/tools/taskTool.ts +70 -61
- package/src/utils/bashParser.ts +50 -2
- package/src/utils/containerSetup.ts +3 -0
- package/src/utils/messageOperations.ts +7 -2
- package/src/utils/openaiClient.ts +3 -1
|
@@ -80,10 +80,6 @@ export class HookManager {
|
|
|
80
80
|
*/
|
|
81
81
|
loadConfigurationFromWaveConfig(waveConfig: WaveConfiguration | null): void {
|
|
82
82
|
try {
|
|
83
|
-
logger?.debug(
|
|
84
|
-
`[HookManager] Loading hooks configuration from pre-loaded config...`,
|
|
85
|
-
);
|
|
86
|
-
|
|
87
83
|
this.configuration = waveConfig?.hooks || undefined;
|
|
88
84
|
|
|
89
85
|
// Validate the loaded configuration if it exists
|
|
@@ -96,10 +92,6 @@ export class HookManager {
|
|
|
96
92
|
);
|
|
97
93
|
}
|
|
98
94
|
}
|
|
99
|
-
|
|
100
|
-
logger?.debug(
|
|
101
|
-
`[HookManager] Hooks configuration loaded successfully with ${Object.keys(waveConfig?.hooks || {}).length} event types`,
|
|
102
|
-
);
|
|
103
95
|
} catch (error) {
|
|
104
96
|
// If loading fails, start with undefined configuration (no hooks)
|
|
105
97
|
this.configuration = undefined;
|
|
@@ -139,24 +131,15 @@ export class HookManager {
|
|
|
139
131
|
}
|
|
140
132
|
|
|
141
133
|
if (!this.configuration) {
|
|
142
|
-
logger?.debug(
|
|
143
|
-
`[HookManager] No configuration loaded, skipping ${event} hooks`,
|
|
144
|
-
);
|
|
145
134
|
return [];
|
|
146
135
|
}
|
|
147
136
|
|
|
148
137
|
const eventConfigs = this.configuration[event];
|
|
149
138
|
if (!eventConfigs || eventConfigs.length === 0) {
|
|
150
|
-
logger?.debug(`[HookManager] No hooks configured for ${event} event`);
|
|
151
139
|
return [];
|
|
152
140
|
}
|
|
153
141
|
|
|
154
|
-
logger?.debug(
|
|
155
|
-
`[HookManager] Starting ${event} hook execution with ${eventConfigs.length} configurations`,
|
|
156
|
-
);
|
|
157
|
-
|
|
158
142
|
const results: HookExecutionResult[] = [];
|
|
159
|
-
const startTime = Date.now();
|
|
160
143
|
|
|
161
144
|
for (
|
|
162
145
|
let configIndex = 0;
|
|
@@ -167,16 +150,9 @@ export class HookManager {
|
|
|
167
150
|
|
|
168
151
|
// Check if this config applies to the current context
|
|
169
152
|
if (!this.configApplies(config, event, context.toolName)) {
|
|
170
|
-
logger?.debug(
|
|
171
|
-
`[HookManager] Skipping configuration ${configIndex + 1}: matcher '${config.matcher}' does not match tool '${context.toolName}'`,
|
|
172
|
-
);
|
|
173
153
|
continue;
|
|
174
154
|
}
|
|
175
155
|
|
|
176
|
-
logger?.debug(
|
|
177
|
-
`[HookManager] Executing configuration ${configIndex + 1} with ${config.hooks.length} commands (matcher: ${config.matcher || "any"})`,
|
|
178
|
-
);
|
|
179
|
-
|
|
180
156
|
// Execute all commands for this configuration
|
|
181
157
|
for (
|
|
182
158
|
let commandIndex = 0;
|
|
@@ -186,10 +162,6 @@ export class HookManager {
|
|
|
186
162
|
const hookCommand = config.hooks[commandIndex];
|
|
187
163
|
|
|
188
164
|
try {
|
|
189
|
-
logger?.debug(
|
|
190
|
-
`[HookManager] Executing command ${commandIndex + 1}/${config.hooks.length} in configuration ${configIndex + 1}`,
|
|
191
|
-
);
|
|
192
|
-
|
|
193
165
|
const result = await executeCommand(
|
|
194
166
|
hookCommand.command,
|
|
195
167
|
context,
|
|
@@ -197,17 +169,6 @@ export class HookManager {
|
|
|
197
169
|
);
|
|
198
170
|
results.push(result);
|
|
199
171
|
|
|
200
|
-
// Report individual command result
|
|
201
|
-
if (result.success) {
|
|
202
|
-
logger?.debug(
|
|
203
|
-
`[HookManager] Command ${commandIndex + 1} completed successfully in ${result.duration}ms`,
|
|
204
|
-
);
|
|
205
|
-
} else {
|
|
206
|
-
logger?.debug(
|
|
207
|
-
`[HookManager] Command ${commandIndex + 1} failed in ${result.duration}ms (exit code: ${result.exitCode}, timed out: ${result.timedOut})`,
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
172
|
// Continue with next command even if this one fails
|
|
212
173
|
// This allows for non-critical hooks to fail without stopping the workflow
|
|
213
174
|
} catch (error) {
|
|
@@ -228,15 +189,6 @@ export class HookManager {
|
|
|
228
189
|
}
|
|
229
190
|
}
|
|
230
191
|
|
|
231
|
-
// Generate execution summary
|
|
232
|
-
const totalDuration = Date.now() - startTime;
|
|
233
|
-
const summary = this.generateExecutionSummary(
|
|
234
|
-
event,
|
|
235
|
-
results,
|
|
236
|
-
totalDuration,
|
|
237
|
-
);
|
|
238
|
-
logger?.debug(`[HookManager] ${event} execution summary: ${summary}`);
|
|
239
|
-
|
|
240
192
|
return results;
|
|
241
193
|
}
|
|
242
194
|
|
|
@@ -797,9 +749,5 @@ export class HookManager {
|
|
|
797
749
|
}
|
|
798
750
|
|
|
799
751
|
this.mergeHooksConfiguration(this.configuration, hooks);
|
|
800
|
-
|
|
801
|
-
logger?.debug(
|
|
802
|
-
`Registered plugin hooks. Total event types: ${Object.keys(this.configuration).length}`,
|
|
803
|
-
);
|
|
804
752
|
}
|
|
805
753
|
}
|
|
@@ -83,8 +83,6 @@ export class LiveConfigManager {
|
|
|
83
83
|
projectPaths?: string[],
|
|
84
84
|
): Promise<void> {
|
|
85
85
|
try {
|
|
86
|
-
logger?.debug("Live Config: Initializing configuration watching...");
|
|
87
|
-
|
|
88
86
|
this.userConfigPaths = userPaths;
|
|
89
87
|
this.projectConfigPaths = projectPaths;
|
|
90
88
|
|
|
@@ -94,16 +92,9 @@ export class LiveConfigManager {
|
|
|
94
92
|
// Start watching user configs that exist
|
|
95
93
|
for (const userPath of userPaths) {
|
|
96
94
|
if (existsSync(userPath)) {
|
|
97
|
-
logger?.debug(
|
|
98
|
-
`Live Config: Starting to watch user config: ${userPath}`,
|
|
99
|
-
);
|
|
100
95
|
await this.fileWatcher.watchFile(userPath, (event) =>
|
|
101
96
|
this.handleFileChange(event, "user"),
|
|
102
97
|
);
|
|
103
|
-
} else {
|
|
104
|
-
logger?.debug(
|
|
105
|
-
`Live Config: User config file does not exist: ${userPath}`,
|
|
106
|
-
);
|
|
107
98
|
}
|
|
108
99
|
}
|
|
109
100
|
|
|
@@ -114,24 +105,14 @@ export class LiveConfigManager {
|
|
|
114
105
|
if (projectPath.endsWith("settings.local.json")) {
|
|
115
106
|
await ensureGlobalGitIgnore("**/.wave/settings.local.json");
|
|
116
107
|
}
|
|
117
|
-
logger?.debug(
|
|
118
|
-
`Live Config: Starting to watch project config: ${projectPath}`,
|
|
119
|
-
);
|
|
120
108
|
await this.fileWatcher.watchFile(projectPath, (event) =>
|
|
121
109
|
this.handleFileChange(event, "project"),
|
|
122
110
|
);
|
|
123
|
-
} else {
|
|
124
|
-
logger?.debug(
|
|
125
|
-
`Live Config: Project config file does not exist: ${projectPath}`,
|
|
126
|
-
);
|
|
127
111
|
}
|
|
128
112
|
}
|
|
129
113
|
}
|
|
130
114
|
|
|
131
115
|
this.isWatching = true;
|
|
132
|
-
logger?.debug(
|
|
133
|
-
"Live Config: Configuration watching initialized successfully",
|
|
134
|
-
);
|
|
135
116
|
} catch (error) {
|
|
136
117
|
const errorMessage = `Failed to initialize configuration watching: ${(error as Error).message}`;
|
|
137
118
|
logger?.error(`Live Config: ${errorMessage}`);
|
|
@@ -151,7 +132,6 @@ export class LiveConfigManager {
|
|
|
151
132
|
*/
|
|
152
133
|
async initialize(): Promise<void> {
|
|
153
134
|
if (this.isInitialized) {
|
|
154
|
-
logger?.debug("Already initialized");
|
|
155
135
|
return;
|
|
156
136
|
}
|
|
157
137
|
|
|
@@ -163,9 +143,6 @@ export class LiveConfigManager {
|
|
|
163
143
|
await this.initializeWatching(userPaths, projectPaths);
|
|
164
144
|
|
|
165
145
|
this.isInitialized = true;
|
|
166
|
-
logger?.debug(
|
|
167
|
-
"Live configuration management initialized with file watching",
|
|
168
|
-
);
|
|
169
146
|
} catch (error) {
|
|
170
147
|
logger?.error(`Failed to initialize: ${(error as Error).message}`);
|
|
171
148
|
throw error;
|
|
@@ -181,8 +158,6 @@ export class LiveConfigManager {
|
|
|
181
158
|
}
|
|
182
159
|
|
|
183
160
|
try {
|
|
184
|
-
logger?.debug("Live Config: Shutting down configuration manager...");
|
|
185
|
-
|
|
186
161
|
this.isWatching = false;
|
|
187
162
|
|
|
188
163
|
// Cleanup file watcher
|
|
@@ -193,7 +168,6 @@ export class LiveConfigManager {
|
|
|
193
168
|
this.lastValidConfiguration = null;
|
|
194
169
|
|
|
195
170
|
this.isInitialized = false;
|
|
196
|
-
logger?.debug("Live configuration management shutdown completed");
|
|
197
171
|
} catch (error) {
|
|
198
172
|
logger?.error(`Error during shutdown: ${(error as Error).message}`);
|
|
199
173
|
throw error;
|
|
@@ -206,15 +180,12 @@ export class LiveConfigManager {
|
|
|
206
180
|
*/
|
|
207
181
|
private async reloadConfiguration(): Promise<WaveConfiguration> {
|
|
208
182
|
if (this.reloadInProgress) {
|
|
209
|
-
logger?.debug("Live Config: Reload already in progress, skipping");
|
|
210
183
|
return this.currentConfiguration || {};
|
|
211
184
|
}
|
|
212
185
|
|
|
213
186
|
this.reloadInProgress = true;
|
|
214
187
|
|
|
215
188
|
try {
|
|
216
|
-
logger?.debug("Live Config: Reloading configuration from files...");
|
|
217
|
-
|
|
218
189
|
// Load merged configuration using ConfigurationService
|
|
219
190
|
const loadResult: ConfigurationLoadResult =
|
|
220
191
|
await this.configurationService.loadMergedConfiguration(this.workdir);
|
|
@@ -237,9 +208,6 @@ export class LiveConfigManager {
|
|
|
237
208
|
|
|
238
209
|
// Use fallback configuration if available
|
|
239
210
|
if (this.lastValidConfiguration) {
|
|
240
|
-
logger?.debug(
|
|
241
|
-
"Live Config: Using previous valid configuration due to loading errors",
|
|
242
|
-
);
|
|
243
211
|
this.currentConfiguration = this.lastValidConfiguration;
|
|
244
212
|
|
|
245
213
|
// Apply environment variables to configuration service if configured
|
|
@@ -266,24 +234,6 @@ export class LiveConfigManager {
|
|
|
266
234
|
}
|
|
267
235
|
}
|
|
268
236
|
|
|
269
|
-
// Log success with detailed information
|
|
270
|
-
if (newConfig) {
|
|
271
|
-
logger?.debug(
|
|
272
|
-
`Live Config: Configuration loaded successfully from ${loadResult.sourcePath || "merged sources"}`,
|
|
273
|
-
);
|
|
274
|
-
|
|
275
|
-
// Log detailed configuration info
|
|
276
|
-
const hookCount = Object.keys(newConfig.hooks || {}).length;
|
|
277
|
-
const envCount = Object.keys(newConfig.env || {}).length;
|
|
278
|
-
logger?.debug(
|
|
279
|
-
`Live Config: Loaded ${hookCount} hook events and ${envCount} environment variables`,
|
|
280
|
-
);
|
|
281
|
-
} else {
|
|
282
|
-
logger?.debug(
|
|
283
|
-
"Live Config: No configuration found (using empty configuration)",
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
237
|
// Log warnings from successful loading
|
|
288
238
|
if (loadResult.warnings && loadResult.warnings.length > 0) {
|
|
289
239
|
logger?.warn(
|
|
@@ -300,9 +250,6 @@ export class LiveConfigManager {
|
|
|
300
250
|
|
|
301
251
|
// Use previous valid configuration for error recovery
|
|
302
252
|
if (this.lastValidConfiguration) {
|
|
303
|
-
logger?.debug(
|
|
304
|
-
"Live Config: Using previous valid configuration due to validation errors",
|
|
305
|
-
);
|
|
306
253
|
this.currentConfiguration = this.lastValidConfiguration;
|
|
307
254
|
|
|
308
255
|
// Apply environment variables to configuration service if configured
|
|
@@ -339,9 +286,6 @@ export class LiveConfigManager {
|
|
|
339
286
|
// Save as last valid configuration if it's valid and not empty
|
|
340
287
|
if (newConfig && (newConfig.hooks || newConfig.env)) {
|
|
341
288
|
this.lastValidConfiguration = { ...newConfig };
|
|
342
|
-
logger?.debug(
|
|
343
|
-
"Live Config: Saved current configuration as last valid backup",
|
|
344
|
-
);
|
|
345
289
|
}
|
|
346
290
|
|
|
347
291
|
// Note: Environment variables are already applied by loadMergedConfiguration()
|
|
@@ -378,10 +322,6 @@ export class LiveConfigManager {
|
|
|
378
322
|
}
|
|
379
323
|
}
|
|
380
324
|
|
|
381
|
-
logger?.debug(
|
|
382
|
-
`Live Config: Configuration reload completed successfully with ${Object.keys(newConfig?.hooks || {}).length} event types and ${Object.keys(newConfig?.env || {}).length} environment variables`,
|
|
383
|
-
);
|
|
384
|
-
|
|
385
325
|
return this.currentConfiguration;
|
|
386
326
|
} catch (error) {
|
|
387
327
|
const errorMessage = `Configuration reload failed with exception: ${(error as Error).message}`;
|
|
@@ -389,9 +329,6 @@ export class LiveConfigManager {
|
|
|
389
329
|
|
|
390
330
|
// Use previous valid configuration for error recovery
|
|
391
331
|
if (this.lastValidConfiguration) {
|
|
392
|
-
logger?.debug(
|
|
393
|
-
"Live Config: Using previous valid configuration due to reload exception",
|
|
394
|
-
);
|
|
395
332
|
this.currentConfiguration = this.lastValidConfiguration;
|
|
396
333
|
|
|
397
334
|
// Apply environment variables to configuration service if configured
|
|
@@ -424,7 +361,6 @@ export class LiveConfigManager {
|
|
|
424
361
|
* Reload configuration from files (public method)
|
|
425
362
|
*/
|
|
426
363
|
async reload(): Promise<WaveConfiguration> {
|
|
427
|
-
logger?.debug("Manually reloading configuration...");
|
|
428
364
|
return await this.reloadConfiguration();
|
|
429
365
|
}
|
|
430
366
|
|
|
@@ -464,16 +400,9 @@ export class LiveConfigManager {
|
|
|
464
400
|
event: FileWatchEvent,
|
|
465
401
|
source: Scope,
|
|
466
402
|
): Promise<void> {
|
|
467
|
-
logger?.debug(
|
|
468
|
-
`Live Config: File ${event.type} detected for ${source} config: ${event.path}`,
|
|
469
|
-
);
|
|
470
|
-
|
|
471
403
|
try {
|
|
472
404
|
// Handle file deletion
|
|
473
405
|
if (event.type === "delete") {
|
|
474
|
-
logger?.debug(
|
|
475
|
-
`Live Config: ${source} config file deleted: ${event.path}`,
|
|
476
|
-
);
|
|
477
406
|
// Reload configuration without the deleted file
|
|
478
407
|
await this.reloadConfiguration();
|
|
479
408
|
return;
|
|
@@ -481,10 +410,6 @@ export class LiveConfigManager {
|
|
|
481
410
|
|
|
482
411
|
// Handle file creation or modification
|
|
483
412
|
if (event.type === "change" || event.type === "create") {
|
|
484
|
-
logger?.debug(
|
|
485
|
-
`Live Config: ${source} config file ${event.type}: ${event.path}`,
|
|
486
|
-
);
|
|
487
|
-
|
|
488
413
|
if (
|
|
489
414
|
source === "project" &&
|
|
490
415
|
event.path.endsWith("settings.local.json") &&
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
removeLastUserMessage,
|
|
10
10
|
UserMessageParams,
|
|
11
11
|
type AgentToolBlockUpdateParams,
|
|
12
|
+
generateMessageId,
|
|
12
13
|
} from "../utils/messageOperations.js";
|
|
13
14
|
import type { Message, Usage, SlashCommand } from "../types/index.js";
|
|
14
15
|
import { join } from "path";
|
|
@@ -422,6 +423,7 @@ export class MessageManager {
|
|
|
422
423
|
|
|
423
424
|
// Create compressed message
|
|
424
425
|
const compressMessage: Message = {
|
|
426
|
+
id: generateMessageId(),
|
|
425
427
|
role: "assistant",
|
|
426
428
|
blocks: [
|
|
427
429
|
{
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
splitBashCommand,
|
|
21
21
|
stripEnvVars,
|
|
22
22
|
stripRedirections,
|
|
23
|
+
hasWriteRedirections,
|
|
23
24
|
getSmartPrefix,
|
|
24
25
|
DANGEROUS_COMMANDS,
|
|
25
26
|
} from "../utils/bashParser.js";
|
|
@@ -464,6 +465,9 @@ export class PermissionManager {
|
|
|
464
465
|
const parts = splitBashCommand(command);
|
|
465
466
|
|
|
466
467
|
const isDangerous = parts.some((part) => {
|
|
468
|
+
if (hasWriteRedirections(part)) {
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
467
471
|
const processedPart = stripRedirections(stripEnvVars(part));
|
|
468
472
|
const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
|
|
469
473
|
if (commandMatch) {
|
|
@@ -523,7 +527,17 @@ export class PermissionManager {
|
|
|
523
527
|
// Handle Bash command rules
|
|
524
528
|
if (toolName === BASH_TOOL_NAME) {
|
|
525
529
|
const command = String(context.toolInput?.command || "");
|
|
526
|
-
const
|
|
530
|
+
const hasWriteInPattern = hasWriteRedirections(pattern);
|
|
531
|
+
const hasWriteInCommand = hasWriteRedirections(command);
|
|
532
|
+
|
|
533
|
+
// If the command has write redirections, it must match a pattern that also has write redirections
|
|
534
|
+
if (hasWriteInCommand && !hasWriteInPattern) {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const processedPart = hasWriteInPattern
|
|
539
|
+
? stripEnvVars(command)
|
|
540
|
+
: stripRedirections(stripEnvVars(command));
|
|
527
541
|
// For Bash commands, we want '*' to match everything including slashes and spaces
|
|
528
542
|
// minimatch's default behavior for '*' is to not match across directory separators
|
|
529
543
|
// We use a regex to replace '*' with '.*' (match anything)
|
|
@@ -561,6 +575,7 @@ export class PermissionManager {
|
|
|
561
575
|
const isAllowedByRuleList = (
|
|
562
576
|
ctx: ToolPermissionContext,
|
|
563
577
|
rules: string[],
|
|
578
|
+
isDefaultRules: boolean = false,
|
|
564
579
|
) => {
|
|
565
580
|
if (ctx.toolName === BASH_TOOL_NAME && ctx.toolInput?.command) {
|
|
566
581
|
const command = String(ctx.toolInput.command);
|
|
@@ -570,53 +585,60 @@ export class PermissionManager {
|
|
|
570
585
|
const workdir = ctx.toolInput?.workdir as string | undefined;
|
|
571
586
|
|
|
572
587
|
return parts.every((part) => {
|
|
588
|
+
const hasWrite = hasWriteRedirections(part);
|
|
573
589
|
const processedPart = stripRedirections(stripEnvVars(part));
|
|
574
590
|
|
|
575
591
|
// Check for safe commands
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
if (cmd
|
|
583
|
-
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
if (workdir) {
|
|
587
|
-
// For cd and ls, check paths
|
|
588
|
-
const pathArgs =
|
|
589
|
-
(args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
|
|
590
|
-
(arg) => !arg.startsWith("-"),
|
|
591
|
-
) || [];
|
|
592
|
-
|
|
593
|
-
if (pathArgs.length === 0) {
|
|
594
|
-
// cd or ls without arguments operates on current dir (workdir)
|
|
592
|
+
if (!hasWrite) {
|
|
593
|
+
const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
|
|
594
|
+
if (commandMatch) {
|
|
595
|
+
const cmd = commandMatch[1];
|
|
596
|
+
const args = commandMatch[2]?.trim() || "";
|
|
597
|
+
|
|
598
|
+
if (SAFE_COMMANDS.includes(cmd)) {
|
|
599
|
+
if (cmd === "pwd" || cmd === "true" || cmd === "false") {
|
|
595
600
|
return true;
|
|
596
601
|
}
|
|
597
602
|
|
|
598
|
-
|
|
599
|
-
//
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
603
|
+
if (workdir) {
|
|
604
|
+
// For cd and ls, check paths
|
|
605
|
+
const pathArgs =
|
|
606
|
+
(args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
|
|
607
|
+
(arg) => !arg.startsWith("-"),
|
|
608
|
+
) || [];
|
|
609
|
+
|
|
610
|
+
if (pathArgs.length === 0) {
|
|
611
|
+
// cd or ls without arguments operates on current dir (workdir)
|
|
612
|
+
return true;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const allPathsSafe = pathArgs.every((pathArg) => {
|
|
616
|
+
// Remove quotes if present
|
|
617
|
+
const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
|
|
618
|
+
const { isInside } = this.isInsideSafeZone(
|
|
619
|
+
cleanPath,
|
|
620
|
+
workdir,
|
|
621
|
+
);
|
|
622
|
+
return isInside;
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
if (allPathsSafe) {
|
|
626
|
+
return true;
|
|
627
|
+
}
|
|
610
628
|
}
|
|
611
629
|
}
|
|
612
630
|
}
|
|
613
631
|
}
|
|
614
632
|
|
|
615
633
|
// Check if this specific part is allowed by any rule
|
|
634
|
+
if (hasWrite && isDefaultRules) {
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
|
|
616
638
|
// We create a temporary context with just this part of the command
|
|
617
639
|
const partContext = {
|
|
618
640
|
...ctx,
|
|
619
|
-
toolInput: { ...ctx.toolInput, command:
|
|
641
|
+
toolInput: { ...ctx.toolInput, command: part },
|
|
620
642
|
};
|
|
621
643
|
const allowedByRule = rules.some((rule) => {
|
|
622
644
|
return this.matchesRule(partContext, rule);
|
|
@@ -643,7 +665,7 @@ export class PermissionManager {
|
|
|
643
665
|
}
|
|
644
666
|
|
|
645
667
|
// Check default allowed rules
|
|
646
|
-
return isAllowedByRuleList(context, DEFAULT_ALLOWED_RULES);
|
|
668
|
+
return isAllowedByRuleList(context, DEFAULT_ALLOWED_RULES, true);
|
|
647
669
|
}
|
|
648
670
|
|
|
649
671
|
/**
|
|
@@ -659,13 +681,14 @@ export class PermissionManager {
|
|
|
659
681
|
const rules: string[] = [];
|
|
660
682
|
|
|
661
683
|
for (const part of parts) {
|
|
684
|
+
const hasWrite = hasWriteRedirections(part);
|
|
662
685
|
const processedPart = stripRedirections(stripEnvVars(part));
|
|
663
686
|
|
|
664
687
|
// Check for safe commands
|
|
665
688
|
const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
|
|
666
689
|
let isSafe = false;
|
|
667
690
|
|
|
668
|
-
if (commandMatch) {
|
|
691
|
+
if (commandMatch && !hasWrite) {
|
|
669
692
|
const cmd = commandMatch[1];
|
|
670
693
|
const args = commandMatch[2]?.trim() || "";
|
|
671
694
|
|
|
@@ -724,11 +747,11 @@ export class PermissionManager {
|
|
|
724
747
|
}
|
|
725
748
|
}
|
|
726
749
|
|
|
727
|
-
const smartPrefix = getSmartPrefix(processedPart);
|
|
750
|
+
const smartPrefix = hasWrite ? null : getSmartPrefix(processedPart);
|
|
728
751
|
if (smartPrefix) {
|
|
729
752
|
rules.push(`Bash(${smartPrefix}*)`);
|
|
730
753
|
} else {
|
|
731
|
-
rules.push(`Bash(${processedPart})`);
|
|
754
|
+
rules.push(`Bash(${hasWrite ? stripEnvVars(part) : processedPart})`);
|
|
732
755
|
}
|
|
733
756
|
}
|
|
734
757
|
}
|
|
@@ -73,7 +73,45 @@ export class PluginManager {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
const marketplaceService = new MarketplaceService();
|
|
76
|
-
|
|
76
|
+
let installedRegistry = await marketplaceService.getInstalledPlugins();
|
|
77
|
+
const knownMarketplaces = await marketplaceService.listMarketplaces();
|
|
78
|
+
|
|
79
|
+
// Identify missing enabled plugins and auto-install them if marketplace is known
|
|
80
|
+
for (const pluginId of Object.keys(this.enabledPlugins)) {
|
|
81
|
+
if (this.enabledPlugins[pluginId] !== true) continue;
|
|
82
|
+
|
|
83
|
+
const [name, marketplaceName] = pluginId.split("@");
|
|
84
|
+
if (!name || !marketplaceName) continue;
|
|
85
|
+
|
|
86
|
+
const isInstalled = installedRegistry.plugins.some(
|
|
87
|
+
(p) => p.name === name && p.marketplace === marketplaceName,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (!isInstalled) {
|
|
91
|
+
const isMarketplaceKnown = knownMarketplaces.some(
|
|
92
|
+
(m) => m.name === marketplaceName,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
if (isMarketplaceKnown) {
|
|
96
|
+
logger?.info(`Auto-installing missing plugin: ${pluginId}`);
|
|
97
|
+
try {
|
|
98
|
+
await marketplaceService.installPlugin(pluginId);
|
|
99
|
+
} catch (installError) {
|
|
100
|
+
logger?.error(
|
|
101
|
+
`Failed to auto-install plugin ${pluginId}:`,
|
|
102
|
+
installError,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
logger?.warn(
|
|
107
|
+
`Plugin ${pluginId} is enabled but marketplace ${marketplaceName} is unknown. Skipping auto-install.`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Refresh registry after potential auto-installs
|
|
114
|
+
installedRegistry = await marketplaceService.getInstalledPlugins();
|
|
77
115
|
|
|
78
116
|
for (const p of installedRegistry.plugins) {
|
|
79
117
|
const pluginId = `${p.name}@${p.marketplace}`;
|
|
@@ -355,13 +355,19 @@ export class SkillManager {
|
|
|
355
355
|
*/
|
|
356
356
|
registerPluginSkills(skills: Skill[]): void {
|
|
357
357
|
for (const skill of skills) {
|
|
358
|
-
|
|
358
|
+
const metadata: SkillMetadata = {
|
|
359
359
|
name: skill.name,
|
|
360
360
|
description: skill.description,
|
|
361
361
|
type: skill.type,
|
|
362
362
|
skillPath: skill.skillPath,
|
|
363
363
|
allowedTools: skill.allowedTools,
|
|
364
|
-
|
|
364
|
+
context: skill.context,
|
|
365
|
+
agent: skill.agent,
|
|
366
|
+
model: skill.model,
|
|
367
|
+
disableModelInvocation: skill.disableModelInvocation,
|
|
368
|
+
userInvocable: skill.userInvocable,
|
|
369
|
+
};
|
|
370
|
+
this.skillMetadata.set(skill.name, metadata);
|
|
365
371
|
this.skillContent.set(skill.name, skill);
|
|
366
372
|
}
|
|
367
373
|
logger?.debug(
|
|
@@ -399,6 +399,7 @@ export async function callAgent(
|
|
|
399
399
|
}
|
|
400
400
|
} catch (error) {
|
|
401
401
|
if ((error as Error).name === "AbortError") {
|
|
402
|
+
logger.info("OpenAI request aborted");
|
|
402
403
|
throw new Error("Request was aborted");
|
|
403
404
|
}
|
|
404
405
|
|
|
@@ -818,6 +819,7 @@ export async function compressMessages(
|
|
|
818
819
|
};
|
|
819
820
|
} catch (error) {
|
|
820
821
|
if ((error as Error).name === "AbortError") {
|
|
822
|
+
logger.info("Compression request was aborted");
|
|
821
823
|
throw new Error("Compression request was aborted");
|
|
822
824
|
}
|
|
823
825
|
logger.error("Failed to compress messages:", error);
|
|
@@ -115,7 +115,6 @@ export class FileWatcherService extends EventEmitter {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
this.watchers.delete(path);
|
|
118
|
-
this.logger?.debug(`Live Config: Stopped watching file: ${path}`);
|
|
119
118
|
} catch (error) {
|
|
120
119
|
this.logger?.warn(
|
|
121
120
|
`Live Config: Error unwatching file ${path}: ${(error as Error).message}`,
|
|
@@ -199,8 +198,6 @@ export class FileWatcherService extends EventEmitter {
|
|
|
199
198
|
entry.watcher = this.globalWatcher;
|
|
200
199
|
entry.isActive = true;
|
|
201
200
|
entry.errorCount = 0;
|
|
202
|
-
|
|
203
|
-
this.logger?.debug(`Live Config: Started watching file: ${entry.path}`);
|
|
204
201
|
} catch (error) {
|
|
205
202
|
entry.errorCount++;
|
|
206
203
|
entry.isActive = false;
|
|
@@ -215,9 +212,6 @@ export class FileWatcherService extends EventEmitter {
|
|
|
215
212
|
!entry.config.fallbackPolling &&
|
|
216
213
|
entry.errorCount < entry.config.maxRetries
|
|
217
214
|
) {
|
|
218
|
-
this.logger?.debug(
|
|
219
|
-
`Live Config: Attempting polling fallback for ${entry.path}`,
|
|
220
|
-
);
|
|
221
215
|
entry.config.fallbackPolling = true;
|
|
222
216
|
await this.initializeWatcher(entry);
|
|
223
217
|
} else {
|
|
@@ -281,7 +275,5 @@ export class FileWatcherService extends EventEmitter {
|
|
|
281
275
|
);
|
|
282
276
|
}
|
|
283
277
|
}
|
|
284
|
-
|
|
285
|
-
this.logger?.debug(`Live Config: File ${type} event for ${filePath}`);
|
|
286
278
|
}
|
|
287
279
|
}
|