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.
Files changed (55) hide show
  1. package/dist/managers/hookManager.d.ts.map +1 -1
  2. package/dist/managers/hookManager.js +0 -21
  3. package/dist/managers/liveConfigManager.d.ts.map +1 -1
  4. package/dist/managers/liveConfigManager.js +0 -36
  5. package/dist/managers/messageManager.d.ts.map +1 -1
  6. package/dist/managers/messageManager.js +2 -1
  7. package/dist/managers/permissionManager.d.ts.map +1 -1
  8. package/dist/managers/permissionManager.js +47 -29
  9. package/dist/managers/pluginManager.d.ts.map +1 -1
  10. package/dist/managers/pluginManager.js +28 -1
  11. package/dist/managers/skillManager.d.ts.map +1 -1
  12. package/dist/managers/skillManager.js +8 -2
  13. package/dist/services/aiService.d.ts.map +1 -1
  14. package/dist/services/aiService.js +2 -0
  15. package/dist/services/fileWatcher.d.ts.map +1 -1
  16. package/dist/services/fileWatcher.js +0 -4
  17. package/dist/services/initializationService.d.ts.map +1 -1
  18. package/dist/services/initializationService.js +2 -10
  19. package/dist/services/pluginLoader.d.ts.map +1 -1
  20. package/dist/services/pluginLoader.js +1 -3
  21. package/dist/services/taskManager.d.ts +2 -0
  22. package/dist/services/taskManager.d.ts.map +1 -1
  23. package/dist/services/taskManager.js +48 -0
  24. package/dist/tools/taskManagementTools.d.ts.map +1 -1
  25. package/dist/tools/taskManagementTools.js +58 -0
  26. package/dist/tools/taskTool.d.ts.map +1 -1
  27. package/dist/tools/taskTool.js +60 -50
  28. package/dist/utils/bashParser.d.ts +4 -0
  29. package/dist/utils/bashParser.d.ts.map +1 -1
  30. package/dist/utils/bashParser.js +39 -2
  31. package/dist/utils/containerSetup.d.ts.map +1 -1
  32. package/dist/utils/containerSetup.js +3 -0
  33. package/dist/utils/messageOperations.d.ts +1 -0
  34. package/dist/utils/messageOperations.d.ts.map +1 -1
  35. package/dist/utils/messageOperations.js +6 -2
  36. package/dist/utils/openaiClient.d.ts.map +1 -1
  37. package/dist/utils/openaiClient.js +3 -1
  38. package/package.json +2 -5
  39. package/src/managers/hookManager.ts +0 -52
  40. package/src/managers/liveConfigManager.ts +0 -75
  41. package/src/managers/messageManager.ts +2 -0
  42. package/src/managers/permissionManager.ts +60 -37
  43. package/src/managers/pluginManager.ts +39 -1
  44. package/src/managers/skillManager.ts +8 -2
  45. package/src/services/aiService.ts +2 -0
  46. package/src/services/fileWatcher.ts +0 -8
  47. package/src/services/initializationService.ts +2 -19
  48. package/src/services/pluginLoader.ts +1 -3
  49. package/src/services/taskManager.ts +51 -0
  50. package/src/tools/taskManagementTools.ts +77 -0
  51. package/src/tools/taskTool.ts +70 -61
  52. package/src/utils/bashParser.ts +50 -2
  53. package/src/utils/containerSetup.ts +3 -0
  54. package/src/utils/messageOperations.ts +7 -2
  55. 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 processedPart = stripRedirections(stripEnvVars(command));
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
- const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
577
- if (commandMatch) {
578
- const cmd = commandMatch[1];
579
- const args = commandMatch[2]?.trim() || "";
580
-
581
- if (SAFE_COMMANDS.includes(cmd)) {
582
- if (cmd === "pwd" || cmd === "true" || cmd === "false") {
583
- return true;
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
- const allPathsSafe = pathArgs.every((pathArg) => {
599
- // Remove quotes if present
600
- const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
601
- const { isInside } = this.isInsideSafeZone(
602
- cleanPath,
603
- workdir,
604
- );
605
- return isInside;
606
- });
607
-
608
- if (allPathsSafe) {
609
- return true;
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: processedPart },
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
- const installedRegistry = await marketplaceService.getInstalledPlugins();
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
- this.skillMetadata.set(skill.name, {
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
  }