wave-agent-sdk 0.8.4 → 0.9.1

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 (68) hide show
  1. package/dist/agent.d.ts.map +1 -1
  2. package/dist/agent.js +5 -8
  3. package/dist/managers/aiManager.d.ts +6 -10
  4. package/dist/managers/aiManager.d.ts.map +1 -1
  5. package/dist/managers/aiManager.js +38 -13
  6. package/dist/managers/hookManager.d.ts.map +1 -1
  7. package/dist/managers/hookManager.js +15 -1
  8. package/dist/managers/messageManager.d.ts +1 -0
  9. package/dist/managers/messageManager.d.ts.map +1 -1
  10. package/dist/managers/messageManager.js +8 -2
  11. package/dist/managers/subagentManager.d.ts +2 -11
  12. package/dist/managers/subagentManager.d.ts.map +1 -1
  13. package/dist/managers/subagentManager.js +4 -53
  14. package/dist/prompts/autoMemory.d.ts +2 -0
  15. package/dist/prompts/autoMemory.d.ts.map +1 -0
  16. package/dist/prompts/autoMemory.js +33 -0
  17. package/dist/prompts/index.d.ts +4 -0
  18. package/dist/prompts/index.d.ts.map +1 -1
  19. package/dist/prompts/index.js +7 -0
  20. package/dist/services/aiService.js +6 -7
  21. package/dist/services/configurationService.d.ts +28 -16
  22. package/dist/services/configurationService.d.ts.map +1 -1
  23. package/dist/services/configurationService.js +87 -28
  24. package/dist/services/hook.js +2 -2
  25. package/dist/services/initializationService.d.ts.map +1 -1
  26. package/dist/services/initializationService.js +20 -9
  27. package/dist/services/memory.d.ts +22 -4
  28. package/dist/services/memory.d.ts.map +1 -1
  29. package/dist/services/memory.js +132 -73
  30. package/dist/types/configuration.d.ts +2 -0
  31. package/dist/types/configuration.d.ts.map +1 -1
  32. package/dist/types/history.d.ts +2 -0
  33. package/dist/types/history.d.ts.map +1 -1
  34. package/dist/types/hooks.d.ts +2 -0
  35. package/dist/types/hooks.d.ts.map +1 -1
  36. package/dist/types/hooks.js +17 -7
  37. package/dist/utils/containerSetup.d.ts +0 -5
  38. package/dist/utils/containerSetup.d.ts.map +1 -1
  39. package/dist/utils/containerSetup.js +11 -24
  40. package/dist/utils/fileSearch.d.ts +1 -6
  41. package/dist/utils/fileSearch.d.ts.map +1 -1
  42. package/dist/utils/fileSearch.js +104 -75
  43. package/dist/utils/gitUtils.d.ts +6 -0
  44. package/dist/utils/gitUtils.d.ts.map +1 -1
  45. package/dist/utils/gitUtils.js +18 -0
  46. package/dist/utils/promptHistory.d.ts +3 -3
  47. package/dist/utils/promptHistory.d.ts.map +1 -1
  48. package/dist/utils/promptHistory.js +12 -6
  49. package/package.json +2 -1
  50. package/src/agent.ts +7 -18
  51. package/src/managers/aiManager.ts +73 -28
  52. package/src/managers/hookManager.ts +22 -1
  53. package/src/managers/messageManager.ts +12 -2
  54. package/src/managers/subagentManager.ts +7 -69
  55. package/src/prompts/autoMemory.ts +33 -0
  56. package/src/prompts/index.ts +12 -0
  57. package/src/services/aiService.ts +8 -8
  58. package/src/services/configurationService.ts +100 -28
  59. package/src/services/hook.ts +2 -2
  60. package/src/services/initializationService.ts +24 -12
  61. package/src/services/memory.ts +144 -82
  62. package/src/types/configuration.ts +2 -0
  63. package/src/types/history.ts +2 -0
  64. package/src/types/hooks.ts +25 -9
  65. package/src/utils/containerSetup.ts +11 -33
  66. package/src/utils/fileSearch.ts +112 -80
  67. package/src/utils/gitUtils.ts +18 -0
  68. package/src/utils/promptHistory.ts +20 -6
@@ -26,6 +26,8 @@ export type HookEvent =
26
26
  export interface HookCommand {
27
27
  type: "command";
28
28
  command: string;
29
+ async?: boolean;
30
+ timeout?: number; // seconds
29
31
  }
30
32
 
31
33
  // Hook event configuration with optional pattern matching
@@ -104,15 +106,29 @@ export function isValidHookEvent(event: string): event is HookEvent {
104
106
  }
105
107
 
106
108
  export function isValidHookCommand(cmd: unknown): cmd is HookCommand {
107
- return (
108
- typeof cmd === "object" &&
109
- cmd !== null &&
110
- "type" in cmd &&
111
- cmd.type === "command" &&
112
- "command" in cmd &&
113
- typeof cmd.command === "string" &&
114
- cmd.command.length > 0
115
- );
109
+ if (
110
+ typeof cmd !== "object" ||
111
+ cmd === null ||
112
+ !("type" in cmd) ||
113
+ cmd.type !== "command" ||
114
+ !("command" in cmd) ||
115
+ typeof cmd.command !== "string" ||
116
+ cmd.command.length === 0
117
+ ) {
118
+ return false;
119
+ }
120
+
121
+ const hookCmd = cmd as Record<string, unknown>;
122
+
123
+ if ("async" in hookCmd && typeof hookCmd.async !== "boolean") {
124
+ return false;
125
+ }
126
+
127
+ if ("timeout" in hookCmd && typeof hookCmd.timeout !== "number") {
128
+ return false;
129
+ }
130
+
131
+ return true;
116
132
  }
117
133
 
118
134
  export function isValidHookEventConfig(
@@ -1,4 +1,3 @@
1
- import * as fs from "fs/promises";
2
1
  import { Container } from "./container.js";
3
2
  import { ForegroundTaskManager } from "../managers/foregroundTaskManager.js";
4
3
  import { BackgroundTaskManager } from "../managers/backgroundTaskManager.js";
@@ -21,6 +20,7 @@ import { SubagentManager } from "../managers/subagentManager.js";
21
20
  import { LiveConfigManager } from "../managers/liveConfigManager.js";
22
21
  import { ConfigurationService } from "../services/configurationService.js";
23
22
  import { ReversionService } from "../services/reversionService.js";
23
+ import { MemoryService } from "../services/memory.js";
24
24
  import type { AgentOptions } from "../types/index.js";
25
25
  import type {
26
26
  PermissionMode,
@@ -29,7 +29,6 @@ import type {
29
29
  BackgroundTask,
30
30
  ToolPermissionContext,
31
31
  } from "../types/index.js";
32
- import type { GatewayConfig, ModelConfig } from "../types/config.js";
33
32
 
34
33
  import { logger } from "./globalLogger.js";
35
34
 
@@ -48,12 +47,6 @@ export interface AgentContainerSetupOptions {
48
47
  setPermissionMode: (mode: PermissionMode) => void;
49
48
  addPermissionRule: (rule: string) => Promise<void>;
50
49
  addUsage: (usage: Usage) => void;
51
-
52
- // Getters
53
- getGatewayConfig: () => GatewayConfig;
54
- getModelConfig: () => ModelConfig;
55
- getMaxInputTokens: () => number;
56
- getLanguage: () => string | undefined;
57
50
  }
58
51
 
59
52
  export function setupAgentContainer(
@@ -72,10 +65,6 @@ export function setupAgentContainer(
72
65
  setPermissionMode,
73
66
  addPermissionRule,
74
67
  addUsage,
75
- getGatewayConfig,
76
- getModelConfig,
77
- getMaxInputTokens,
78
- getLanguage,
79
68
  } = setupOptions;
80
69
 
81
70
  const callbacks = options.callbacks || {};
@@ -85,6 +74,9 @@ export function setupAgentContainer(
85
74
  container.register("ForegroundTaskManager", foregroundTaskManager);
86
75
  container.register("ConfigurationService", configurationService);
87
76
 
77
+ const memoryService = new MemoryService(container);
78
+ container.register("MemoryService", memoryService);
79
+
88
80
  const memoryRuleManager = new MemoryRuleManager(container, { workdir });
89
81
  container.register("MemoryRuleManager", memoryRuleManager);
90
82
 
@@ -139,6 +131,10 @@ export function setupAgentContainer(
139
131
  container.register("LspManager", lspManager);
140
132
 
141
133
  const permissionManager = new PermissionManager(container, { workdir });
134
+ if (configurationService.resolveAutoMemoryEnabled()) {
135
+ const autoMemoryDir = memoryService.getAutoMemoryDirectory(workdir);
136
+ permissionManager.updateAdditionalDirectories([autoMemoryDir]);
137
+ }
142
138
  container.register("PermissionManager", permissionManager);
143
139
  permissionManager.setOnConfiguredDefaultModeChange((mode) => {
144
140
  handlePlanModeTransition(mode);
@@ -198,17 +194,9 @@ export function setupAgentContainer(
198
194
  if (decision.clearContext) {
199
195
  messageManager.clearMessages();
200
196
  if (planFilePath) {
201
- try {
202
- const planContent = await fs.readFile(planFilePath, "utf-8");
203
- messageManager.addUserMessage({
204
- content: `Implement the following plan:\n\n${planContent}`,
205
- });
206
- } catch (error) {
207
- logger.warn("Failed to read plan file for context clearing", {
208
- planFilePath,
209
- error: error instanceof Error ? error.message : String(error),
210
- });
211
- }
197
+ messageManager.addUserMessage({
198
+ content: `Implement the plan at ${planFilePath}`,
199
+ });
212
200
  }
213
201
  }
214
202
 
@@ -246,11 +234,6 @@ export function setupAgentContainer(
246
234
  onSubagentLatestTotalTokensChange:
247
235
  callbacks.onSubagentLatestTotalTokensChange,
248
236
  },
249
- getGatewayConfig,
250
- getModelConfig,
251
- getMaxInputTokens,
252
- getLanguage,
253
- getEnvironmentVars: () => configurationService.getEnvironmentVars(),
254
237
  onUsageAdded: (usage: Usage) => addUsage(usage),
255
238
  });
256
239
  container.register("SubagentManager", subagentManager);
@@ -263,11 +246,6 @@ export function setupAgentContainer(
263
246
  workdir,
264
247
  systemPrompt,
265
248
  stream,
266
- getGatewayConfig,
267
- getModelConfig,
268
- getMaxInputTokens,
269
- getLanguage,
270
- getEnvironmentVars: () => configurationService.getEnvironmentVars(),
271
249
  });
272
250
  container.register("AIManager", aiManager);
273
251
 
@@ -1,26 +1,78 @@
1
- import { globIterate, type Path } from "glob";
1
+ import { spawn } from "child_process";
2
+ import { rgPath } from "@vscode/ripgrep";
3
+ import fuzzysort from "fuzzysort";
2
4
  import { getGlobIgnorePatterns } from "./fileFilter.js";
3
5
  import type { FileItem } from "../types/fileSearch.js";
6
+ import { logger } from "./globalLogger.js";
4
7
 
5
8
  /**
6
- * Convert Path objects to FileItem objects
9
+ * Execute ripgrep to get all file paths
7
10
  */
8
- export const convertPathsToFileItems = (paths: Path[]): FileItem[] => {
9
- return paths.map((pathObj) => {
10
- const isDirectory = pathObj.isDirectory();
11
- let path = pathObj.relative();
12
- if (isDirectory && !path.endsWith("/")) {
13
- path += "/";
14
- }
15
- return {
16
- path,
17
- type: isDirectory ? "directory" : "file",
18
- };
11
+ async function getAllFiles(workingDirectory: string): Promise<string[]> {
12
+ if (!rgPath) {
13
+ throw new Error("ripgrep is not available");
14
+ }
15
+
16
+ const ignorePatterns = getGlobIgnorePatterns(workingDirectory);
17
+ const rgArgs = ["--files", "--color=never", "--hidden"];
18
+ for (const pattern of ignorePatterns) {
19
+ rgArgs.push("--glob", `!${pattern}`);
20
+ }
21
+
22
+ return new Promise((resolve, reject) => {
23
+ const child = spawn(rgPath, rgArgs, {
24
+ cwd: workingDirectory,
25
+ stdio: ["ignore", "pipe", "pipe"],
26
+ });
27
+
28
+ let stdout = "";
29
+ let stderr = "";
30
+
31
+ child.stdout?.on("data", (data) => {
32
+ stdout += data.toString();
33
+ });
34
+
35
+ child.stderr?.on("data", (data) => {
36
+ stderr += data.toString();
37
+ });
38
+
39
+ child.on("close", (code) => {
40
+ if (code !== 0 && code !== 1) {
41
+ reject(new Error(`ripgrep failed: ${stderr}`));
42
+ return;
43
+ }
44
+ const files = stdout
45
+ .trim()
46
+ .split("\n")
47
+ .filter((f) => f.length > 0)
48
+ .map((f) => f.replace(/\\/g, "/")); // Normalize to forward slashes
49
+ resolve(files);
50
+ });
51
+
52
+ child.on("error", (err) => {
53
+ reject(err);
54
+ });
19
55
  });
20
- };
56
+ }
57
+
58
+ /**
59
+ * Derive directory paths from file paths
60
+ */
61
+ function deriveDirectories(files: string[]): string[] {
62
+ const dirs = new Set<string>();
63
+ for (const file of files) {
64
+ const parts = file.split("/");
65
+ // Add all parent directories
66
+ for (let i = 1; i < parts.length; i++) {
67
+ const dir = parts.slice(0, i).join("/") + "/";
68
+ dirs.add(dir);
69
+ }
70
+ }
71
+ return Array.from(dirs);
72
+ }
21
73
 
22
74
  /**
23
- * Search files and directories using glob patterns
75
+ * Search files and directories using fuzzy matching
24
76
  */
25
77
  export const searchFiles = async (
26
78
  query: string,
@@ -32,83 +84,63 @@ export const searchFiles = async (
32
84
  const { maxResults = 10, workingDirectory = process.cwd() } = options || {};
33
85
 
34
86
  try {
35
- const globOptions: import("glob").GlobOptionsWithFileTypesTrue = {
36
- ignore: getGlobIgnorePatterns(workingDirectory),
37
- maxDepth: 10,
38
- nocase: true, // Case insensitive
39
- dot: true, // Include hidden files and directories
40
- cwd: workingDirectory, // Specify search root directory
41
- withFileTypes: true, // Get Path objects instead of strings
42
- };
43
-
44
- // Build glob patterns based on query
45
- let patterns: string[] = [];
87
+ const files = await getAllFiles(workingDirectory);
46
88
 
47
89
  if (!query.trim()) {
48
- // When query is empty, show some common file types and directories
49
- patterns = [
50
- "**/*.{ts,tsx,js,jsx,json,py,java}", // Combine common file extensions
51
- "*/", // First level directories
52
- ];
53
- } else {
54
- // Build multiple glob patterns to support more flexible search
55
- patterns = [
56
- // Match files with filenames containing query
57
- `**/*${query}*`,
58
- // Match files with query in path (match directory names)
59
- `**/${query}*/**/*`,
60
- // Match directory names containing query
61
- `**/*${query}*/`,
62
- // Match directories containing query in path
63
- `**/${query}*/`,
64
- ];
65
- }
90
+ // When query is empty, show some common file types and top-level directories
91
+ const commonExtensions = new Set([
92
+ "ts",
93
+ "tsx",
94
+ "js",
95
+ "jsx",
96
+ "json",
97
+ "py",
98
+ "java",
99
+ ]);
100
+ const results: FileItem[] = [];
101
+ const seenDirs = new Set<string>();
66
102
 
67
- // Collect results until we reach maxResults
68
- const collectedPaths: Path[] = [];
69
- const seenPaths = new Set<string>();
70
-
71
- // Process each pattern sequentially
72
- for (const pattern of patterns) {
73
- if (collectedPaths.length >= maxResults) {
74
- break;
75
- }
76
-
77
- // Use globIterate to get results one by one
78
- const iterator = globIterate(pattern, globOptions) as AsyncGenerator<
79
- Path,
80
- void,
81
- void
82
- >;
83
-
84
- for await (const pathObj of iterator) {
85
- if (collectedPaths.length >= maxResults) {
86
- // Stop the iterator when we have enough results
87
- break;
103
+ for (const file of files) {
104
+ const parts = file.split("/");
105
+ if (parts.length > 1) {
106
+ const topDir = parts[0] + "/";
107
+ if (!seenDirs.has(topDir)) {
108
+ seenDirs.add(topDir);
109
+ results.push({ path: topDir, type: "directory" });
110
+ }
88
111
  }
89
112
 
90
- const relativePath = pathObj.relative();
91
- if (!seenPaths.has(relativePath)) {
92
- seenPaths.add(relativePath);
93
- collectedPaths.push(pathObj);
113
+ const ext = file.split(".").pop();
114
+ if (ext && commonExtensions.has(ext)) {
115
+ results.push({ path: file, type: "file" });
94
116
  }
95
117
  }
118
+
119
+ // Sort: directories first, then files, then alphabetically
120
+ return results
121
+ .sort((a, b) => {
122
+ if (a.type === "directory" && b.type === "file") return -1;
123
+ if (a.type === "file" && b.type === "directory") return 1;
124
+ return a.path.localeCompare(b.path);
125
+ })
126
+ .slice(0, maxResults);
96
127
  }
97
128
 
98
- // Sort collected paths: directories first, then files
99
- collectedPaths.sort((a, b) => {
100
- const aIsDir = a.isDirectory();
101
- const bIsDir = b.isDirectory();
102
- if (aIsDir && !bIsDir) return -1;
103
- if (!aIsDir && bIsDir) return 1;
104
- return a.relative().localeCompare(b.relative());
129
+ const directories = deriveDirectories(files);
130
+ const allItems: FileItem[] = [
131
+ ...files.map((f) => ({ path: f, type: "file" as const })),
132
+ ...directories.map((d) => ({ path: d, type: "directory" as const })),
133
+ ];
134
+
135
+ const fuzzyResults = fuzzysort.go(query, allItems, {
136
+ key: "path",
137
+ limit: maxResults,
138
+ threshold: 0,
105
139
  });
106
140
 
107
- // Convert to FileItems
108
- const fileItems = convertPathsToFileItems(collectedPaths);
109
- return fileItems;
141
+ return fuzzyResults.map((res) => res.obj);
110
142
  } catch (error) {
111
- console.error("Glob search error:", error);
143
+ logger.error("Fuzzy search error:", error);
112
144
  return [];
113
145
  }
114
146
  };
@@ -41,6 +41,24 @@ export function getGitRepoRoot(cwd: string): string {
41
41
  }
42
42
  }
43
43
 
44
+ /**
45
+ * Get the common directory of the git repository (handles worktrees)
46
+ * @param cwd Working directory
47
+ * @returns Repository common directory path
48
+ */
49
+ export function getGitCommonDir(cwd: string): string {
50
+ try {
51
+ const commonDir = execSync("git rev-parse --git-common-dir", {
52
+ cwd,
53
+ encoding: "utf8",
54
+ stdio: ["ignore", "pipe", "ignore"],
55
+ }).trim();
56
+ return path.resolve(cwd, commonDir);
57
+ } catch {
58
+ return getGitRepoRoot(cwd);
59
+ }
60
+ }
61
+
44
62
  /**
45
63
  * Get the default remote branch (e.g., origin/main)
46
64
  * @param cwd Working directory
@@ -22,7 +22,11 @@ export class PromptHistoryManager {
22
22
  /**
23
23
  * Add a new prompt to history
24
24
  */
25
- static async addEntry(prompt: string): Promise<void> {
25
+ static async addEntry(
26
+ prompt: string,
27
+ sessionId?: string,
28
+ longTextMap?: Record<string, string>,
29
+ ): Promise<void> {
26
30
  try {
27
31
  if (!prompt.trim()) return;
28
32
 
@@ -30,6 +34,8 @@ export class PromptHistoryManager {
30
34
  const entry: PromptEntry = {
31
35
  prompt,
32
36
  timestamp: Date.now(),
37
+ sessionId,
38
+ longTextMap,
33
39
  };
34
40
 
35
41
  const line = JSON.stringify(entry) + "\n";
@@ -71,7 +77,7 @@ export class PromptHistoryManager {
71
77
  /**
72
78
  * Get all history entries
73
79
  */
74
- static async getHistory(): Promise<PromptEntry[]> {
80
+ static async getHistory(sessionId?: string): Promise<PromptEntry[]> {
75
81
  try {
76
82
  if (!fs.existsSync(PROMPT_HISTORY_FILE)) {
77
83
  return [];
@@ -91,13 +97,18 @@ export class PromptHistoryManager {
91
97
  })
92
98
  .filter((entry): entry is PromptEntry => entry !== null);
93
99
 
100
+ // Filter by sessionId if provided
101
+ const filteredEntries = sessionId
102
+ ? entries.filter((entry) => entry.sessionId === sessionId)
103
+ : entries;
104
+
94
105
  // Deduplicate by prompt, keeping the most recent one
95
106
  const uniqueEntries: PromptEntry[] = [];
96
107
  const seenPrompts = new Set<string>();
97
108
 
98
109
  // Process from newest to oldest
99
- for (let i = entries.length - 1; i >= 0; i--) {
100
- const entry = entries[i];
110
+ for (let i = filteredEntries.length - 1; i >= 0; i--) {
111
+ const entry = filteredEntries[i];
101
112
  if (!seenPrompts.has(entry.prompt)) {
102
113
  uniqueEntries.push(entry);
103
114
  seenPrompts.add(entry.prompt);
@@ -114,9 +125,12 @@ export class PromptHistoryManager {
114
125
  /**
115
126
  * Search history by query
116
127
  */
117
- static async searchHistory(query: string): Promise<PromptEntry[]> {
128
+ static async searchHistory(
129
+ query: string,
130
+ sessionId?: string,
131
+ ): Promise<PromptEntry[]> {
118
132
  try {
119
- const history = await this.getHistory();
133
+ const history = await this.getHistory(sessionId);
120
134
  if (!query.trim()) {
121
135
  return history;
122
136
  }