wave-agent-sdk 0.9.2 → 0.9.4

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 (65) hide show
  1. package/dist/core/plugin.d.ts +4 -0
  2. package/dist/core/plugin.d.ts.map +1 -1
  3. package/dist/core/plugin.js +6 -0
  4. package/dist/core/session.d.ts +1 -1
  5. package/dist/core/session.d.ts.map +1 -1
  6. package/dist/core/session.js +1 -1
  7. package/dist/managers/liveConfigManager.d.ts +1 -5
  8. package/dist/managers/liveConfigManager.d.ts.map +1 -1
  9. package/dist/managers/liveConfigManager.js +12 -126
  10. package/dist/managers/permissionManager.d.ts.map +1 -1
  11. package/dist/managers/permissionManager.js +27 -2
  12. package/dist/managers/pluginManager.d.ts.map +1 -1
  13. package/dist/managers/pluginManager.js +4 -0
  14. package/dist/managers/skillManager.d.ts.map +1 -1
  15. package/dist/managers/skillManager.js +3 -1
  16. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  17. package/dist/managers/slashCommandManager.js +0 -8
  18. package/dist/services/GitService.d.ts.map +1 -1
  19. package/dist/services/GitService.js +6 -0
  20. package/dist/services/MarketplaceService.d.ts +11 -1
  21. package/dist/services/MarketplaceService.d.ts.map +1 -1
  22. package/dist/services/MarketplaceService.js +68 -3
  23. package/dist/services/configurationService.d.ts +1 -20
  24. package/dist/services/configurationService.d.ts.map +1 -1
  25. package/dist/services/configurationService.js +14 -93
  26. package/dist/services/pluginLoader.d.ts.map +1 -1
  27. package/dist/services/pluginLoader.js +1 -6
  28. package/dist/tools/exitPlanMode.d.ts.map +1 -1
  29. package/dist/tools/exitPlanMode.js +7 -0
  30. package/dist/types/commands.d.ts +0 -1
  31. package/dist/types/commands.d.ts.map +1 -1
  32. package/dist/types/history.d.ts +1 -0
  33. package/dist/types/history.d.ts.map +1 -1
  34. package/dist/types/marketplace.d.ts +1 -0
  35. package/dist/types/marketplace.d.ts.map +1 -1
  36. package/dist/types/permissions.d.ts +1 -0
  37. package/dist/types/permissions.d.ts.map +1 -1
  38. package/dist/types/permissions.js +1 -0
  39. package/dist/utils/bashParser.d.ts.map +1 -1
  40. package/dist/utils/bashParser.js +7 -0
  41. package/dist/utils/configPaths.d.ts +5 -5
  42. package/dist/utils/configPaths.js +6 -6
  43. package/dist/utils/promptHistory.d.ts +9 -3
  44. package/dist/utils/promptHistory.d.ts.map +1 -1
  45. package/dist/utils/promptHistory.js +16 -7
  46. package/package.json +1 -1
  47. package/src/core/plugin.ts +7 -0
  48. package/src/core/session.ts +1 -0
  49. package/src/managers/liveConfigManager.ts +20 -163
  50. package/src/managers/permissionManager.ts +32 -2
  51. package/src/managers/pluginManager.ts +6 -0
  52. package/src/managers/skillManager.ts +4 -1
  53. package/src/managers/slashCommandManager.ts +0 -16
  54. package/src/services/GitService.ts +10 -0
  55. package/src/services/MarketplaceService.ts +95 -3
  56. package/src/services/configurationService.ts +14 -116
  57. package/src/services/pluginLoader.ts +1 -7
  58. package/src/tools/exitPlanMode.ts +7 -0
  59. package/src/types/commands.ts +0 -3
  60. package/src/types/history.ts +1 -0
  61. package/src/types/marketplace.ts +1 -0
  62. package/src/types/permissions.ts +2 -0
  63. package/src/utils/bashParser.ts +7 -0
  64. package/src/utils/configPaths.ts +6 -6
  65. package/src/utils/promptHistory.ts +27 -6
@@ -69,30 +69,10 @@ export class ConfigurationService {
69
69
  workdir: string,
70
70
  ): Promise<ConfigurationLoadResult> {
71
71
  try {
72
- const userConfigPaths = getUserConfigPaths();
73
- const projectConfigPaths = getProjectConfigPaths(workdir);
74
-
75
72
  // Use the merged configuration function (this loads user and project configs internally)
76
73
  const mergedConfig = loadMergedWaveConfig(workdir);
77
74
 
78
- // Track loading context for better error messages by checking which files exist
79
- const loadingContext: string[] = [];
80
- const userPath = userConfigPaths.find((path) => existsSync(path));
81
- if (userPath) {
82
- loadingContext.push(`user config from ${userPath}`);
83
- }
84
-
85
- const projectPath = projectConfigPaths.find((path) => existsSync(path));
86
- if (projectPath) {
87
- loadingContext.push(`project config from ${projectPath}`);
88
- }
89
-
90
75
  if (!mergedConfig) {
91
- const message =
92
- loadingContext.length > 0
93
- ? `No valid configuration found despite attempting to load: ${loadingContext.join(", ")}`
94
- : "No configuration files found in user or project directories";
95
-
96
76
  // Still set system environment variables even if no config is found
97
77
  const env = { WAVE_PROJECT_DIR: workdir };
98
78
  this.setEnvironmentVars(env);
@@ -100,7 +80,9 @@ export class ConfigurationService {
100
80
  return {
101
81
  configuration: { env },
102
82
  success: true, // No config is valid
103
- warnings: [message],
83
+ warnings: [
84
+ "No configuration files found in user or project directories",
85
+ ],
104
86
  };
105
87
  }
106
88
 
@@ -108,11 +90,10 @@ export class ConfigurationService {
108
90
  const validation = this.validateConfiguration(mergedConfig);
109
91
 
110
92
  if (!validation.isValid) {
111
- const sourcePaths = loadingContext.join(" and ");
112
93
  return {
113
94
  configuration: null,
114
95
  success: false,
115
- error: `Merged configuration validation failed (sources: ${sourcePaths}): ${validation.errors.join(", ")}`,
96
+ error: `Merged configuration validation failed: ${validation.errors.join(", ")}`,
116
97
  warnings: validation.warnings,
117
98
  };
118
99
  }
@@ -128,12 +109,10 @@ export class ConfigurationService {
128
109
  this.setEnvironmentVars(env);
129
110
  mergedConfig.env = env;
130
111
 
131
- const sourcePaths = loadingContext.join(" and ");
132
-
133
112
  return {
134
113
  configuration: mergedConfig,
135
114
  success: true,
136
- sourcePath: sourcePaths || "merged configuration",
115
+ sourcePath: "merged configuration",
137
116
  warnings: validation.warnings,
138
117
  };
139
118
  } catch (error) {
@@ -342,13 +321,6 @@ export class ConfigurationService {
342
321
 
343
322
  // Utility operations
344
323
 
345
- /**
346
- * Get currently loaded configuration
347
- */
348
- getCurrentConfiguration(): WaveConfiguration | null {
349
- return this.currentConfiguration;
350
- }
351
-
352
324
  /**
353
325
  * Set environment variables from configuration
354
326
  * This replaces direct process.env modification
@@ -408,16 +380,6 @@ export class ConfigurationService {
408
380
  resolvedBaseURL = this.env.WAVE_BASE_URL || "";
409
381
  }
410
382
 
411
- // If we have a parent configuration, use it as a fallback for API key and base URL
412
- if (this.currentConfiguration?.env) {
413
- if (resolvedApiKey === undefined) {
414
- resolvedApiKey = this.currentConfiguration.env.WAVE_API_KEY;
415
- }
416
- if (!resolvedBaseURL) {
417
- resolvedBaseURL = this.currentConfiguration.env.WAVE_BASE_URL || "";
418
- }
419
- }
420
-
421
383
  // Fallback to process.env if still not resolved (for dynamic updates in tests)
422
384
  if (resolvedApiKey === undefined) {
423
385
  resolvedApiKey = process.env.WAVE_API_KEY;
@@ -486,9 +448,6 @@ export class ConfigurationService {
486
448
  // Resolve agent model: override > options > env (settings.json) > process.env > default
487
449
  let resolvedAgentModel = model || this.options.model || this.env.WAVE_MODEL;
488
450
 
489
- if (!resolvedAgentModel && this.currentConfiguration?.env?.WAVE_MODEL) {
490
- resolvedAgentModel = this.currentConfiguration.env.WAVE_MODEL;
491
- }
492
451
  resolvedAgentModel =
493
452
  resolvedAgentModel || process.env.WAVE_MODEL || DEFAULT_AGENT_MODEL;
494
453
 
@@ -496,9 +455,6 @@ export class ConfigurationService {
496
455
  let resolvedFastModel =
497
456
  fastModel || this.options.fastModel || this.env.WAVE_FAST_MODEL;
498
457
 
499
- if (!resolvedFastModel && this.currentConfiguration?.env?.WAVE_FAST_MODEL) {
500
- resolvedFastModel = this.currentConfiguration.env.WAVE_FAST_MODEL;
501
- }
502
458
  resolvedFastModel =
503
459
  resolvedFastModel || process.env.WAVE_FAST_MODEL || DEFAULT_FAST_MODEL;
504
460
 
@@ -638,7 +594,7 @@ export class ConfigurationService {
638
594
  }
639
595
 
640
596
  /**
641
- * Add a permission rule to the project's settings.local.json
597
+ * Add a permission rule to the local settings.local.json
642
598
  */
643
599
  async addAllowedRule(workdir: string, rule: string): Promise<void> {
644
600
  const localConfigPath = path.join(workdir, ".wave", "settings.local.json");
@@ -691,11 +647,11 @@ export class ConfigurationService {
691
647
 
692
648
  let configPath: string;
693
649
  if (scope === "user") {
694
- configPath = getUserConfigPaths()[1]; // settings.json
650
+ configPath = getUserConfigPaths()[0]; // settings.json
695
651
  } else if (scope === "project") {
696
652
  configPath = getProjectConfigPaths(workdir)[1]; // settings.json
697
653
  } else {
698
- configPath = getProjectConfigPaths(workdir)[0]; // settings.local.json
654
+ configPath = getProjectConfigPaths(workdir)[0]; // local settings.local.json
699
655
  }
700
656
 
701
657
  // Ensure directory exists
@@ -737,11 +693,11 @@ export class ConfigurationService {
737
693
 
738
694
  let configPath: string;
739
695
  if (scope === "user") {
740
- configPath = getUserConfigPaths()[1]; // settings.json
696
+ configPath = getUserConfigPaths()[0]; // settings.json
741
697
  } else if (scope === "project") {
742
698
  configPath = getProjectConfigPaths(workdir)[1]; // settings.json
743
699
  } else {
744
- configPath = getProjectConfigPaths(workdir)[0]; // settings.local.json
700
+ configPath = getProjectConfigPaths(workdir)[0]; // local settings.local.json
745
701
  }
746
702
 
747
703
  if (!existsSync(configPath)) {
@@ -907,29 +863,6 @@ export function loadWaveConfigFromFile(
907
863
  const content = readFileSync(filePath, "utf-8");
908
864
  const config = JSON.parse(content) as WaveConfiguration;
909
865
 
910
- // Validate basic structure
911
- if (!config || typeof config !== "object") {
912
- throw new Error(`Invalid configuration structure in ${filePath}`);
913
- }
914
-
915
- // Validate environment variables if present
916
- if (config.env !== undefined) {
917
- const envValidation = validateEnvironmentConfig(config.env, filePath);
918
-
919
- if (!envValidation.isValid) {
920
- throw new Error(
921
- `Environment variable validation failed in ${filePath}: ${envValidation.errors.join(", ")}`,
922
- );
923
- }
924
-
925
- // Log warnings if any
926
- if (envValidation.warnings.length > 0) {
927
- console.warn(
928
- `Environment variable warnings in ${filePath}:\n- ${envValidation.warnings.join("\n- ")}`,
929
- );
930
- }
931
- }
932
-
933
866
  return {
934
867
  hooks: config.hooks || undefined,
935
868
  env: config.env || undefined,
@@ -951,40 +884,6 @@ export function loadWaveConfigFromFile(
951
884
  }
952
885
  }
953
886
 
954
- /**
955
- * Load Wave configuration from multiple file paths in priority order
956
- * Returns the first valid configuration found, or null if none exist
957
- */
958
- export function loadWaveConfigFromFiles(
959
- filePaths: string[],
960
- ): WaveConfiguration | null {
961
- for (const filePath of filePaths) {
962
- const config = loadWaveConfigFromFile(filePath);
963
- if (config !== null) {
964
- return config;
965
- }
966
- }
967
- return null;
968
- }
969
-
970
- /**
971
- * Load user-specific Wave configuration
972
- * Checks .local.json first, then falls back to .json
973
- */
974
- export function loadUserWaveConfig(): WaveConfiguration | null {
975
- return loadWaveConfigFromFiles(getUserConfigPaths());
976
- }
977
-
978
- /**
979
- * Load project-specific Wave configuration
980
- * Checks .local.json first, then falls back to .json
981
- */
982
- export function loadProjectWaveConfig(
983
- workdir: string,
984
- ): WaveConfiguration | null {
985
- return loadWaveConfigFromFiles(getProjectConfigPaths(workdir));
986
- }
987
-
988
887
  /**
989
888
  * Load and merge Wave configuration from both user and project sources
990
889
  * Project configuration takes precedence over user configuration
@@ -993,16 +892,15 @@ export function loadProjectWaveConfig(
993
892
  export function loadMergedWaveConfig(
994
893
  workdir: string,
995
894
  ): WaveConfiguration | null {
996
- const userPaths = getUserConfigPaths(); // [local, json]
895
+ const userPaths = getUserConfigPaths(); // [json]
997
896
  const projectPaths = getProjectConfigPaths(workdir); // [local, json]
998
897
 
999
898
  // Priority order (lowest to highest):
1000
- // user settings.json -> user settings.local.json -> project settings.json -> project settings.local.json
899
+ // user settings.json -> project settings.json -> local settings.local.json
1001
900
  const pathsToLoad = [
1002
- userPaths[1], // user settings.json
1003
- userPaths[0], // user settings.local.json
901
+ userPaths[0], // user settings.json
1004
902
  projectPaths[1], // project settings.json
1005
- projectPaths[0], // project settings.local.json
903
+ projectPaths[0], // local settings.local.json
1006
904
  ];
1007
905
 
1008
906
  const configs: WaveConfiguration[] = [];
@@ -67,13 +67,7 @@ export class PluginLoader {
67
67
  */
68
68
  static loadCommands(pluginPath: string): CustomSlashCommand[] {
69
69
  const commandsPath = path.join(pluginPath, "commands");
70
- const commands = scanCommandsDirectory(commandsPath);
71
-
72
- // Attach plugin path to each command for WAVE_PLUGIN_ROOT support
73
- return commands.map((command) => ({
74
- ...command,
75
- pluginPath,
76
- }));
70
+ return scanCommandsDirectory(commandsPath);
77
71
  }
78
72
 
79
73
  /**
@@ -2,6 +2,7 @@ import { readFile } from "fs/promises";
2
2
  import { logger } from "../utils/globalLogger.js";
3
3
  import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
4
4
  import { EXIT_PLAN_MODE_TOOL_NAME } from "../constants/tools.js";
5
+ import { OPERATION_CANCELLED_BY_USER } from "../types/permissions.js";
5
6
 
6
7
  /**
7
8
  * Exit Plan Mode Tool Plugin
@@ -91,6 +92,12 @@ Ensure your plan is complete and unambiguous:
91
92
  await context.permissionManager.checkPermission(permissionContext);
92
93
 
93
94
  if (permissionResult.behavior === "deny") {
95
+ if (permissionResult.message === OPERATION_CANCELLED_BY_USER) {
96
+ return {
97
+ success: false,
98
+ content: OPERATION_CANCELLED_BY_USER,
99
+ };
100
+ }
94
101
  return {
95
102
  success: false,
96
103
  content: `Please update your proposal based on the following user feedback: ${permissionResult.message || "Plan rejected by user"}`,
@@ -23,7 +23,4 @@ export interface CustomSlashCommand {
23
23
  filePath: string;
24
24
  content: string;
25
25
  config?: CustomSlashCommandConfig;
26
-
27
- // Plugin support
28
- pluginPath?: string; // Absolute path to the plugin root directory (only set for plugin commands)
29
26
  }
@@ -2,5 +2,6 @@ export interface PromptEntry {
2
2
  prompt: string;
3
3
  timestamp: number;
4
4
  sessionId?: string;
5
+ workdir?: string;
5
6
  longTextMap?: Record<string, string>;
6
7
  }
@@ -46,6 +46,7 @@ export interface KnownMarketplace {
46
46
  name: string;
47
47
  source: MarketplaceSource;
48
48
  isBuiltin?: boolean;
49
+ autoUpdate?: boolean;
49
50
  }
50
51
 
51
52
  export interface KnownMarketplacesRegistry {
@@ -70,4 +70,6 @@ export const RESTRICTED_TOOLS = [
70
70
  /** Type for restricted tool names */
71
71
  export type RestrictedTool = (typeof RESTRICTED_TOOLS)[number];
72
72
 
73
+ export const OPERATION_CANCELLED_BY_USER = "Operation cancelled by user";
74
+
73
75
  export type { AskUserQuestion, AskUserQuestionInput, AskUserQuestionOption };
@@ -470,6 +470,13 @@ export function getSmartPrefix(command: string): string | null {
470
470
  ];
471
471
 
472
472
  if (safeGitSubcommands.includes(subCommand)) {
473
+ if (subCommand === "branch") {
474
+ // Check for destructive flags
475
+ const destructiveFlags = ["-d", "-D", "--delete"];
476
+ if (tokens.some((t) => destructiveFlags.includes(t))) {
477
+ return null;
478
+ }
479
+ }
473
480
  prefixParts.push(subCommand);
474
481
  return prefixParts.join(" ");
475
482
  }
@@ -2,11 +2,11 @@
2
2
  * Configuration Path Utilities
3
3
  *
4
4
  * Centralized utilities for resolving Wave configuration file paths.
5
- * Supports both regular settings.json and settings.local.json with proper priority.
5
+ * Supports both regular settings.json and local settings.local.json with proper priority.
6
6
  *
7
7
  * Priority system:
8
- * - User configs: ~/.wave/settings.local.json > ~/.wave/settings.json
9
- * - Project configs: {workdir}/.wave/settings.local.json > {workdir}/.wave/settings.json
8
+ * - User configs: ~/.wave/settings.json
9
+ * - Local configs: {workdir}/.wave/settings.local.json > {workdir}/.wave/settings.json
10
10
  * - Project configs override user configs (existing behavior)
11
11
  */
12
12
 
@@ -32,11 +32,11 @@ export function getProjectConfigPath(workdir: string): string {
32
32
 
33
33
  /**
34
34
  * Get the user-specific configuration file paths in priority order
35
- * Returns array with .local.json first, then .json
35
+ * Returns array with .json only
36
36
  */
37
37
  export function getUserConfigPaths(): string[] {
38
38
  const baseDir = join(homedir(), ".wave");
39
- return [join(baseDir, "settings.local.json"), join(baseDir, "settings.json")];
39
+ return [join(baseDir, "settings.json")];
40
40
  }
41
41
 
42
42
  /**
@@ -47,7 +47,7 @@ export function getPluginsDir(): string {
47
47
  }
48
48
 
49
49
  /**
50
- * Get the project-specific configuration file paths in priority order
50
+ * Get the local configuration file paths in priority order
51
51
  * Returns array with .local.json first, then .json
52
52
  */
53
53
  export function getProjectConfigPaths(workdir: string): string[] {
@@ -26,6 +26,7 @@ export class PromptHistoryManager {
26
26
  prompt: string,
27
27
  sessionId?: string,
28
28
  longTextMap?: Record<string, string>,
29
+ workdir?: string,
29
30
  ): Promise<void> {
30
31
  try {
31
32
  if (!prompt.trim()) return;
@@ -36,6 +37,7 @@ export class PromptHistoryManager {
36
37
  timestamp: Date.now(),
37
38
  sessionId,
38
39
  longTextMap,
40
+ workdir,
39
41
  };
40
42
 
41
43
  const line = JSON.stringify(entry) + "\n";
@@ -77,7 +79,10 @@ export class PromptHistoryManager {
77
79
  /**
78
80
  * Get all history entries
79
81
  */
80
- static async getHistory(sessionId?: string): Promise<PromptEntry[]> {
82
+ static async getHistory(options?: {
83
+ sessionId?: string | string[];
84
+ workdir?: string;
85
+ }): Promise<PromptEntry[]> {
81
86
  try {
82
87
  if (!fs.existsSync(PROMPT_HISTORY_FILE)) {
83
88
  return [];
@@ -98,9 +103,22 @@ export class PromptHistoryManager {
98
103
  .filter((entry): entry is PromptEntry => entry !== null);
99
104
 
100
105
  // Filter by sessionId if provided
101
- const filteredEntries = sessionId
102
- ? entries.filter((entry) => entry.sessionId === sessionId)
103
- : entries;
106
+ let filteredEntries = entries;
107
+ if (options?.sessionId) {
108
+ const sessionIds = Array.isArray(options.sessionId)
109
+ ? options.sessionId
110
+ : [options.sessionId];
111
+ filteredEntries = filteredEntries.filter(
112
+ (entry) => entry.sessionId && sessionIds.includes(entry.sessionId),
113
+ );
114
+ }
115
+
116
+ // Filter by workdir if provided
117
+ if (options?.workdir) {
118
+ filteredEntries = filteredEntries.filter(
119
+ (entry) => entry.workdir === options.workdir,
120
+ );
121
+ }
104
122
 
105
123
  // Deduplicate by prompt, keeping the most recent one
106
124
  const uniqueEntries: PromptEntry[] = [];
@@ -127,10 +145,13 @@ export class PromptHistoryManager {
127
145
  */
128
146
  static async searchHistory(
129
147
  query: string,
130
- sessionId?: string,
148
+ options?: {
149
+ sessionId?: string | string[];
150
+ workdir?: string;
151
+ },
131
152
  ): Promise<PromptEntry[]> {
132
153
  try {
133
- const history = await this.getHistory(sessionId);
154
+ const history = await this.getHistory(options);
134
155
  if (!query.trim()) {
135
156
  return history;
136
157
  }