wave-agent-sdk 0.0.8 → 0.0.10

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 (236) hide show
  1. package/dist/agent.d.ts +92 -23
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +340 -137
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -0
  7. package/dist/managers/aiManager.d.ts +14 -36
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +74 -77
  10. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  11. package/dist/managers/backgroundBashManager.js +4 -3
  12. package/dist/managers/hookManager.d.ts +3 -8
  13. package/dist/managers/hookManager.d.ts.map +1 -1
  14. package/dist/managers/hookManager.js +39 -29
  15. package/dist/managers/liveConfigManager.d.ts +55 -18
  16. package/dist/managers/liveConfigManager.d.ts.map +1 -1
  17. package/dist/managers/liveConfigManager.js +372 -90
  18. package/dist/managers/lspManager.d.ts +43 -0
  19. package/dist/managers/lspManager.d.ts.map +1 -0
  20. package/dist/managers/lspManager.js +326 -0
  21. package/dist/managers/messageManager.d.ts +8 -16
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +52 -74
  24. package/dist/managers/permissionManager.d.ts +66 -0
  25. package/dist/managers/permissionManager.d.ts.map +1 -0
  26. package/dist/managers/permissionManager.js +208 -0
  27. package/dist/managers/skillManager.d.ts +1 -0
  28. package/dist/managers/skillManager.d.ts.map +1 -1
  29. package/dist/managers/skillManager.js +2 -1
  30. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  31. package/dist/managers/slashCommandManager.js +0 -1
  32. package/dist/managers/subagentManager.d.ts +8 -23
  33. package/dist/managers/subagentManager.d.ts.map +1 -1
  34. package/dist/managers/subagentManager.js +97 -117
  35. package/dist/managers/toolManager.d.ts +38 -1
  36. package/dist/managers/toolManager.d.ts.map +1 -1
  37. package/dist/managers/toolManager.js +66 -2
  38. package/dist/services/aiService.d.ts +3 -1
  39. package/dist/services/aiService.d.ts.map +1 -1
  40. package/dist/services/aiService.js +123 -30
  41. package/dist/services/configurationService.d.ts +116 -0
  42. package/dist/services/configurationService.d.ts.map +1 -0
  43. package/dist/services/configurationService.js +585 -0
  44. package/dist/services/fileWatcher.d.ts.map +1 -1
  45. package/dist/services/fileWatcher.js +5 -6
  46. package/dist/services/hook.d.ts +7 -124
  47. package/dist/services/hook.d.ts.map +1 -1
  48. package/dist/services/hook.js +46 -458
  49. package/dist/services/jsonlHandler.d.ts +24 -15
  50. package/dist/services/jsonlHandler.d.ts.map +1 -1
  51. package/dist/services/jsonlHandler.js +67 -88
  52. package/dist/services/memory.d.ts +0 -9
  53. package/dist/services/memory.d.ts.map +1 -1
  54. package/dist/services/memory.js +2 -49
  55. package/dist/services/session.d.ts +82 -33
  56. package/dist/services/session.d.ts.map +1 -1
  57. package/dist/services/session.js +275 -181
  58. package/dist/tools/bashTool.d.ts.map +1 -1
  59. package/dist/tools/bashTool.js +72 -13
  60. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  61. package/dist/tools/deleteFileTool.js +25 -0
  62. package/dist/tools/editTool.d.ts.map +1 -1
  63. package/dist/tools/editTool.js +30 -6
  64. package/dist/tools/lspTool.d.ts +6 -0
  65. package/dist/tools/lspTool.d.ts.map +1 -0
  66. package/dist/tools/lspTool.js +589 -0
  67. package/dist/tools/multiEditTool.d.ts.map +1 -1
  68. package/dist/tools/multiEditTool.js +26 -7
  69. package/dist/tools/readTool.d.ts.map +1 -1
  70. package/dist/tools/readTool.js +111 -2
  71. package/dist/tools/skillTool.js +2 -2
  72. package/dist/tools/todoWriteTool.d.ts.map +1 -1
  73. package/dist/tools/todoWriteTool.js +23 -0
  74. package/dist/tools/types.d.ts +11 -8
  75. package/dist/tools/types.d.ts.map +1 -1
  76. package/dist/tools/writeTool.d.ts.map +1 -1
  77. package/dist/tools/writeTool.js +25 -9
  78. package/dist/types/commands.d.ts +0 -1
  79. package/dist/types/commands.d.ts.map +1 -1
  80. package/dist/types/config.d.ts +4 -0
  81. package/dist/types/config.d.ts.map +1 -1
  82. package/dist/types/configuration.d.ts +69 -0
  83. package/dist/types/configuration.d.ts.map +1 -0
  84. package/dist/types/configuration.js +8 -0
  85. package/dist/types/core.d.ts +10 -0
  86. package/dist/types/core.d.ts.map +1 -1
  87. package/dist/types/environment.d.ts +41 -0
  88. package/dist/types/environment.d.ts.map +1 -1
  89. package/dist/types/fileSearch.d.ts +5 -0
  90. package/dist/types/fileSearch.d.ts.map +1 -0
  91. package/dist/types/fileSearch.js +1 -0
  92. package/dist/types/hooks.d.ts +11 -2
  93. package/dist/types/hooks.d.ts.map +1 -1
  94. package/dist/types/hooks.js +1 -7
  95. package/dist/types/index.d.ts +5 -0
  96. package/dist/types/index.d.ts.map +1 -1
  97. package/dist/types/index.js +5 -0
  98. package/dist/types/lsp.d.ts +90 -0
  99. package/dist/types/lsp.d.ts.map +1 -0
  100. package/dist/types/lsp.js +4 -0
  101. package/dist/types/messaging.d.ts +6 -11
  102. package/dist/types/messaging.d.ts.map +1 -1
  103. package/dist/types/permissions.d.ts +35 -0
  104. package/dist/types/permissions.d.ts.map +1 -0
  105. package/dist/types/permissions.js +12 -0
  106. package/dist/types/session.d.ts +1 -6
  107. package/dist/types/session.d.ts.map +1 -1
  108. package/dist/types/skills.d.ts +1 -0
  109. package/dist/types/skills.d.ts.map +1 -1
  110. package/dist/types/tools.d.ts +35 -0
  111. package/dist/types/tools.d.ts.map +1 -0
  112. package/dist/types/tools.js +4 -0
  113. package/dist/utils/abortUtils.d.ts +34 -0
  114. package/dist/utils/abortUtils.d.ts.map +1 -0
  115. package/dist/utils/abortUtils.js +92 -0
  116. package/dist/utils/bashHistory.d.ts +4 -0
  117. package/dist/utils/bashHistory.d.ts.map +1 -1
  118. package/dist/utils/bashHistory.js +21 -4
  119. package/dist/utils/builtinSubagents.d.ts +7 -0
  120. package/dist/utils/builtinSubagents.d.ts.map +1 -0
  121. package/dist/utils/builtinSubagents.js +65 -0
  122. package/dist/utils/cacheControlUtils.d.ts +8 -33
  123. package/dist/utils/cacheControlUtils.d.ts.map +1 -1
  124. package/dist/utils/cacheControlUtils.js +83 -126
  125. package/dist/utils/constants.d.ts +0 -12
  126. package/dist/utils/constants.d.ts.map +1 -1
  127. package/dist/utils/constants.js +1 -13
  128. package/dist/utils/convertMessagesForAPI.d.ts +2 -1
  129. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  130. package/dist/utils/convertMessagesForAPI.js +33 -14
  131. package/dist/utils/fileSearch.d.ts +14 -0
  132. package/dist/utils/fileSearch.d.ts.map +1 -0
  133. package/dist/utils/fileSearch.js +88 -0
  134. package/dist/utils/fileUtils.d.ts +14 -2
  135. package/dist/utils/fileUtils.d.ts.map +1 -1
  136. package/dist/utils/fileUtils.js +101 -17
  137. package/dist/utils/globalLogger.d.ts +0 -14
  138. package/dist/utils/globalLogger.d.ts.map +1 -1
  139. package/dist/utils/globalLogger.js +0 -16
  140. package/dist/utils/largeOutputHandler.d.ts +15 -0
  141. package/dist/utils/largeOutputHandler.d.ts.map +1 -0
  142. package/dist/utils/largeOutputHandler.js +40 -0
  143. package/dist/utils/markdownParser.d.ts.map +1 -1
  144. package/dist/utils/markdownParser.js +1 -17
  145. package/dist/utils/messageOperations.d.ts +1 -11
  146. package/dist/utils/messageOperations.d.ts.map +1 -1
  147. package/dist/utils/messageOperations.js +7 -24
  148. package/dist/utils/pathEncoder.d.ts +4 -0
  149. package/dist/utils/pathEncoder.d.ts.map +1 -1
  150. package/dist/utils/pathEncoder.js +16 -9
  151. package/dist/utils/subagentParser.d.ts +2 -2
  152. package/dist/utils/subagentParser.d.ts.map +1 -1
  153. package/dist/utils/subagentParser.js +10 -7
  154. package/dist/utils/tokenEstimator.d.ts +39 -0
  155. package/dist/utils/tokenEstimator.d.ts.map +1 -0
  156. package/dist/utils/tokenEstimator.js +55 -0
  157. package/package.json +5 -8
  158. package/src/agent.ts +460 -216
  159. package/src/index.ts +2 -0
  160. package/src/managers/aiManager.ts +107 -111
  161. package/src/managers/backgroundBashManager.ts +4 -3
  162. package/src/managers/hookManager.ts +44 -39
  163. package/src/managers/liveConfigManager.ts +524 -138
  164. package/src/managers/lspManager.ts +434 -0
  165. package/src/managers/messageManager.ts +73 -103
  166. package/src/managers/permissionManager.ts +276 -0
  167. package/src/managers/skillManager.ts +3 -1
  168. package/src/managers/slashCommandManager.ts +1 -2
  169. package/src/managers/subagentManager.ts +116 -159
  170. package/src/managers/toolManager.ts +95 -3
  171. package/src/services/aiService.ts +207 -26
  172. package/src/services/configurationService.ts +762 -0
  173. package/src/services/fileWatcher.ts +5 -6
  174. package/src/services/hook.ts +50 -631
  175. package/src/services/jsonlHandler.ts +84 -100
  176. package/src/services/memory.ts +2 -59
  177. package/src/services/session.ts +338 -213
  178. package/src/tools/bashTool.ts +89 -16
  179. package/src/tools/deleteFileTool.ts +36 -0
  180. package/src/tools/editTool.ts +41 -7
  181. package/src/tools/lspTool.ts +760 -0
  182. package/src/tools/multiEditTool.ts +37 -8
  183. package/src/tools/readTool.ts +125 -2
  184. package/src/tools/skillTool.ts +2 -2
  185. package/src/tools/todoWriteTool.ts +33 -1
  186. package/src/tools/types.ts +15 -9
  187. package/src/tools/writeTool.ts +36 -10
  188. package/src/types/commands.ts +0 -1
  189. package/src/types/config.ts +5 -0
  190. package/src/types/configuration.ts +73 -0
  191. package/src/types/core.ts +11 -0
  192. package/src/types/environment.ts +44 -0
  193. package/src/types/fileSearch.ts +4 -0
  194. package/src/types/hooks.ts +14 -11
  195. package/src/types/index.ts +5 -0
  196. package/src/types/lsp.ts +96 -0
  197. package/src/types/messaging.ts +8 -13
  198. package/src/types/permissions.ts +48 -0
  199. package/src/types/session.ts +3 -8
  200. package/src/types/skills.ts +1 -0
  201. package/src/types/tools.ts +38 -0
  202. package/src/utils/abortUtils.ts +118 -0
  203. package/src/utils/bashHistory.ts +28 -4
  204. package/src/utils/builtinSubagents.ts +71 -0
  205. package/src/utils/cacheControlUtils.ts +106 -171
  206. package/src/utils/constants.ts +1 -16
  207. package/src/utils/convertMessagesForAPI.ts +38 -14
  208. package/src/utils/fileSearch.ts +107 -0
  209. package/src/utils/fileUtils.ts +114 -19
  210. package/src/utils/globalLogger.ts +0 -17
  211. package/src/utils/largeOutputHandler.ts +55 -0
  212. package/src/utils/markdownParser.ts +1 -19
  213. package/src/utils/messageOperations.ts +7 -35
  214. package/src/utils/pathEncoder.ts +24 -9
  215. package/src/utils/subagentParser.ts +11 -8
  216. package/src/utils/tokenEstimator.ts +68 -0
  217. package/dist/constants/events.d.ts +0 -28
  218. package/dist/constants/events.d.ts.map +0 -1
  219. package/dist/constants/events.js +0 -27
  220. package/dist/services/configurationWatcher.d.ts +0 -120
  221. package/dist/services/configurationWatcher.d.ts.map +0 -1
  222. package/dist/services/configurationWatcher.js +0 -439
  223. package/dist/services/memoryStore.d.ts +0 -81
  224. package/dist/services/memoryStore.d.ts.map +0 -1
  225. package/dist/services/memoryStore.js +0 -200
  226. package/dist/types/memoryStore.d.ts +0 -82
  227. package/dist/types/memoryStore.d.ts.map +0 -1
  228. package/dist/types/memoryStore.js +0 -7
  229. package/dist/utils/configResolver.d.ts +0 -65
  230. package/dist/utils/configResolver.d.ts.map +0 -1
  231. package/dist/utils/configResolver.js +0 -210
  232. package/src/constants/events.ts +0 -38
  233. package/src/services/configurationWatcher.ts +0 -622
  234. package/src/services/memoryStore.ts +0 -279
  235. package/src/types/memoryStore.ts +0 -94
  236. package/src/utils/configResolver.ts +0 -302
@@ -1,4 +1,8 @@
1
- import fs from "node:fs";
1
+ import fs from "node:fs/promises";
2
+ import { createReadStream } from "node:fs";
3
+ import path from "node:path";
4
+ import { execSync } from "node:child_process";
5
+ import { homedir } from "node:os";
2
6
 
3
7
  /**
4
8
  * Reads the first line of a file efficiently using Node.js readline.
@@ -7,7 +11,6 @@ import fs from "node:fs";
7
11
  * @return {Promise<string>} - The first non-empty line of the file, or an empty string otherwise.
8
12
  */
9
13
  export async function readFirstLine(filePath: string): Promise<string> {
10
- const { createReadStream } = fs;
11
14
  const { createInterface } = await import("node:readline");
12
15
 
13
16
  const fileStream = createReadStream(filePath);
@@ -34,32 +37,124 @@ export async function readFirstLine(filePath: string): Promise<string> {
34
37
  }
35
38
 
36
39
  /**
37
- * Reads a file from the end and returns the first non-empty line.
40
+ * Reads a file from the end and returns the last non-empty line.
41
+ *
42
+ * This version supports files that end with:
43
+ * - "\n" (Unix-style, including modern macOS)
44
+ * - "\r\n" (Windows-style)
45
+ * - "\r" (older Mac-style, HL7, etc.)
38
46
  *
39
47
  * @param {string} filePath - The path to the file.
48
+ * @param {number} [minLength=1] - Minimum length for the returned line.
40
49
  * @return {Promise<string>} - The last non-empty line of the file, or an empty string if no non-empty lines found.
41
50
  */
42
- export async function getLastLine(filePath: string): Promise<string> {
43
- const { exec } = await import("child_process");
44
- const { promisify } = await import("util");
45
- const execAsync = promisify(exec);
46
-
51
+ export async function getLastLine(
52
+ filePath: string,
53
+ minLength = 1,
54
+ ): Promise<string> {
55
+ let fileHandle;
47
56
  try {
48
- // Use tail with multiple lines to handle cases where the last line might be empty
49
- const { stdout } = await execAsync(`tail -n 3 "${filePath}"`);
50
- const lines = stdout.split(/\r?\n/);
51
-
52
- // Find the first non-empty line working backwards
53
- for (let i = lines.length - 1; i >= 0; i--) {
54
- const line = lines[i].trim();
55
- if (line.length > 0) {
56
- return line;
57
+ const stats = await fs.stat(filePath);
58
+ const fileSize = stats.size;
59
+
60
+ if (fileSize === 0) return "";
61
+
62
+ fileHandle = await fs.open(filePath, "r");
63
+ const bufferSize = 8 * 1024; // 8KB buffer is usually enough for the last line
64
+ const buffer = Buffer.alloc(bufferSize);
65
+
66
+ let lineEnd: number | null = null;
67
+ let lineStart: number | null = null;
68
+ let currentPosition = fileSize;
69
+
70
+ while (currentPosition > 0 && lineStart === null) {
71
+ const readSize = Math.min(bufferSize, currentPosition);
72
+ currentPosition -= readSize;
73
+
74
+ const { bytesRead } = await fileHandle.read(
75
+ buffer,
76
+ 0,
77
+ readSize,
78
+ currentPosition,
79
+ );
80
+
81
+ for (let i = bytesRead - 1; i >= 0; i--) {
82
+ const charCode = buffer[i];
83
+ if (lineEnd === null) {
84
+ // Still looking for the end of the last non-empty line (skip trailing newlines and whitespace)
85
+ if (charCode > 32) {
86
+ lineEnd = currentPosition + i + 1;
87
+ }
88
+ } else {
89
+ // Looking for the start of the line (the newline before it)
90
+ if (charCode === 10 || charCode === 13) {
91
+ lineStart = currentPosition + i + 1;
92
+ break;
93
+ }
94
+ }
57
95
  }
58
96
  }
59
97
 
60
- return "";
98
+ if (lineEnd === null) return "";
99
+ if (lineStart === null) lineStart = 0;
100
+
101
+ const length = lineEnd - lineStart;
102
+ if (length < minLength) return "";
103
+
104
+ const resultBuffer = Buffer.alloc(length);
105
+ await fileHandle.read(resultBuffer, 0, length, lineStart);
106
+ const result = resultBuffer.toString("utf8").trim();
107
+ return result.length >= minLength ? result : "";
61
108
  } catch {
62
- // If tail fails (e.g., file doesn't exist), return empty string
109
+ // If reading fails (e.g., file doesn't exist), return empty string
63
110
  return "";
111
+ } finally {
112
+ if (fileHandle) {
113
+ await fileHandle.close();
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Ensures that a pattern is present in the global git ignore file.
120
+ *
121
+ * @param {string} pattern - The pattern to add to global git ignore.
122
+ */
123
+ export async function ensureGlobalGitIgnore(pattern: string): Promise<void> {
124
+ try {
125
+ let globalIgnorePath: string;
126
+ try {
127
+ globalIgnorePath = execSync("git config --get core.excludesfile", {
128
+ encoding: "utf8",
129
+ }).trim();
130
+ } catch {
131
+ // If not set, use default paths
132
+ const xdgConfigHome =
133
+ process.env.XDG_CONFIG_HOME || path.join(homedir(), ".config");
134
+ globalIgnorePath = path.join(xdgConfigHome, "git", "ignore");
135
+ }
136
+
137
+ if (!globalIgnorePath) return;
138
+
139
+ // Ensure directory exists
140
+ await fs.mkdir(path.dirname(globalIgnorePath), { recursive: true });
141
+
142
+ let content = "";
143
+ try {
144
+ content = await fs.readFile(globalIgnorePath, "utf8");
145
+ } catch {
146
+ // File doesn't exist
147
+ }
148
+
149
+ const lines = content.split("\n").map((line) => line.trim());
150
+ if (!lines.includes(pattern)) {
151
+ const newContent =
152
+ content.endsWith("\n") || content === ""
153
+ ? `${content}${pattern}\n`
154
+ : `${content}\n${pattern}\n`;
155
+ await fs.writeFile(globalIgnorePath, newContent, "utf8");
156
+ }
157
+ } catch {
158
+ // Ignore errors
64
159
  }
65
160
  }
@@ -46,23 +46,6 @@ export function setGlobalLogger(logger: Logger | null): void {
46
46
  globalLogger = logger;
47
47
  }
48
48
 
49
- /**
50
- * Retrieve the current global logger instance
51
- *
52
- * @returns Current logger instance or null if unconfigured
53
- *
54
- * @example
55
- * ```typescript
56
- * const currentLogger = getGlobalLogger();
57
- * if (currentLogger) {
58
- * currentLogger.info('Direct logger access');
59
- * }
60
- * ```
61
- */
62
- export function getGlobalLogger(): Logger | null {
63
- return globalLogger;
64
- }
65
-
66
49
  /**
67
50
  * Reset global logger to unconfigured state
68
51
  * Equivalent to setGlobalLogger(null)
@@ -0,0 +1,55 @@
1
+ import { writeFile } from "fs/promises";
2
+ import { join } from "path";
3
+ import { tmpdir } from "os";
4
+ import { logger } from "./globalLogger.js";
5
+ import {
6
+ estimateTokenCount,
7
+ getTokenUsageDescription,
8
+ } from "./tokenEstimator.js";
9
+
10
+ // Token threshold for writing output to temp file (20k tokens)
11
+ export const LARGE_OUTPUT_TOKEN_THRESHOLD = 20000;
12
+
13
+ /**
14
+ * Handle large command output by writing to temporary file when token threshold is exceeded
15
+ *
16
+ * Uses token-based threshold (20k tokens) to determine when output should be written to temp file.
17
+ * This provides accurate estimation of actual token cost for LLM processing.
18
+ *
19
+ * @param output - The command output string
20
+ * @returns Object containing processed content and optional file path
21
+ */
22
+ export async function handleLargeOutput(output: string): Promise<{
23
+ content: string;
24
+ filePath?: string;
25
+ }> {
26
+ const estimatedTokens = estimateTokenCount(output);
27
+
28
+ // Check token threshold
29
+ if (estimatedTokens <= LARGE_OUTPUT_TOKEN_THRESHOLD) {
30
+ return { content: output };
31
+ }
32
+
33
+ try {
34
+ // Create temp file for large output
35
+ const tempFileName = `bash-output-${Date.now()}-${Math.random().toString(36).substr(2, 9)}.txt`;
36
+ const tempFilePath = join(tmpdir(), tempFileName);
37
+
38
+ await writeFile(tempFilePath, output, "utf8");
39
+
40
+ const sizeKB = Math.round(output.length / 1024);
41
+ const tokenDescription = getTokenUsageDescription(
42
+ output,
43
+ LARGE_OUTPUT_TOKEN_THRESHOLD,
44
+ );
45
+
46
+ return {
47
+ content: `Large output (${sizeKB} KB, ${tokenDescription}) written to temporary file. Use the Read tool to access the full content.`,
48
+ filePath: tempFilePath,
49
+ };
50
+ } catch (error) {
51
+ logger.warn(`Failed to write large output to temp file: ${error}`);
52
+ // Fallback to direct output if temp file creation fails
53
+ return { content: output };
54
+ }
55
+ }
@@ -37,18 +37,7 @@ function parseFrontmatter(content: string): {
37
37
  const key = trimmedLine.slice(0, colonIndex).trim();
38
38
  const value = trimmedLine.slice(colonIndex + 1).trim();
39
39
 
40
- // Handle array values for allowed-tools
41
- if (key === "allowed-tools" && value) {
42
- // Simple array parsing: "tool1, tool2, tool3" or "[tool1, tool2]"
43
- let arrayValue = value;
44
- if (arrayValue.startsWith("[") && arrayValue.endsWith("]")) {
45
- arrayValue = arrayValue.slice(1, -1);
46
- }
47
- frontmatter[key] = arrayValue
48
- .split(",")
49
- .map((s) => s.trim())
50
- .filter(Boolean);
51
- } else if (value) {
40
+ if (value) {
52
41
  frontmatter[key] = value;
53
42
  }
54
43
  }
@@ -73,13 +62,6 @@ export function parseMarkdownFile(filePath: string): ParsedMarkdownFile {
73
62
  if (frontmatter) {
74
63
  config = {};
75
64
 
76
- if (
77
- frontmatter["allowed-tools"] &&
78
- Array.isArray(frontmatter["allowed-tools"])
79
- ) {
80
- config.allowedTools = frontmatter["allowed-tools"] as string[];
81
- }
82
-
83
65
  if (frontmatter.model && typeof frontmatter.model === "string") {
84
66
  config.model = frontmatter.model;
85
67
  }
@@ -47,12 +47,6 @@ export type AgentToolBlockUpdateParams = Omit<
47
47
  "messages"
48
48
  >;
49
49
 
50
- export interface AddDiffBlockParams {
51
- messages: Message[];
52
- path: string;
53
- diffResult: Array<{ value: string; added?: boolean; removed?: boolean }>;
54
- }
55
-
56
50
  export interface AddErrorBlockParams {
57
51
  messages: Message[];
58
52
  error: string;
@@ -186,7 +180,7 @@ export const addAssistantMessageToMessages = (
186
180
  content?: string,
187
181
  toolCalls?: ChatCompletionMessageFunctionToolCall[],
188
182
  usage?: Usage,
189
- metadata?: Record<string, unknown>,
183
+ additionalFields?: Record<string, unknown>,
190
184
  ): Message[] => {
191
185
  const blocks: Message["blocks"] = [];
192
186
 
@@ -213,34 +207,12 @@ export const addAssistantMessageToMessages = (
213
207
  role: "assistant",
214
208
  blocks,
215
209
  usage, // Include usage data if provided
216
- ...(metadata ? { metadata: { ...metadata } } : {}),
210
+ ...(additionalFields ? { additionalFields: { ...additionalFields } } : {}),
217
211
  };
218
212
 
219
213
  return [...messages, initialAssistantMessage];
220
214
  };
221
215
 
222
- // Update File Operation Block of the last assistant message
223
- export const addDiffBlockToMessage = ({
224
- messages,
225
- path,
226
- diffResult,
227
- }: AddDiffBlockParams): Message[] => {
228
- const newMessages = [...messages];
229
- // Find the last assistant message
230
- for (let i = newMessages.length - 1; i >= 0; i--) {
231
- if (newMessages[i].role === "assistant") {
232
- // Directly add diff block instead of replacing existing blocks
233
- newMessages[i].blocks.push({
234
- type: "diff",
235
- path: path,
236
- diffResult: diffResult,
237
- });
238
- break;
239
- }
240
- }
241
- return newMessages;
242
- };
243
-
244
216
  // Update Tool Block of the last assistant message
245
217
  export const updateToolBlockInMessage = ({
246
218
  messages,
@@ -453,7 +425,7 @@ export const addCommandOutputMessage = ({
453
425
  command,
454
426
  }: AddCommandOutputParams): Message[] => {
455
427
  const outputMessage: Message = {
456
- role: "assistant",
428
+ role: "user",
457
429
  blocks: [
458
430
  {
459
431
  type: "command_output",
@@ -475,10 +447,10 @@ export const updateCommandOutputInMessage = ({
475
447
  output,
476
448
  }: UpdateCommandOutputParams): Message[] => {
477
449
  const newMessages = [...messages];
478
- // Find the last assistant message with a command_output block for this command
450
+ // Find the last user message with a command_output block for this command
479
451
  for (let i = newMessages.length - 1; i >= 0; i--) {
480
452
  const msg = newMessages[i];
481
- if (msg.role === "assistant") {
453
+ if (msg.role === "user") {
482
454
  const commandBlock = msg.blocks.find(
483
455
  (block) =>
484
456
  block.type === "command_output" &&
@@ -501,10 +473,10 @@ export const completeCommandInMessage = ({
501
473
  exitCode,
502
474
  }: CompleteCommandParams): Message[] => {
503
475
  const newMessages = [...messages];
504
- // Find the last assistant message with a command_output block for this command
476
+ // Find the last user message with a command_output block for this command
505
477
  for (let i = newMessages.length - 1; i >= 0; i--) {
506
478
  const msg = newMessages[i];
507
- if (msg.role === "assistant") {
479
+ if (msg.role === "user") {
508
480
  const commandBlock = msg.blocks.find(
509
481
  (block) =>
510
482
  block.type === "command_output" &&
@@ -190,9 +190,9 @@ export class PathEncoder {
190
190
  }
191
191
 
192
192
  /**
193
- * Create project directory entity from original path
193
+ * Get project directory info without creating the directory
194
194
  */
195
- async createProjectDirectory(
195
+ async getProjectDirectory(
196
196
  originalPath: string,
197
197
  baseSessionDir: string,
198
198
  ): Promise<ProjectDirectory> {
@@ -221,13 +221,6 @@ export class PathEncoder {
221
221
  pathHash = this.generateHash(resolvedPath, this.options.hashLength);
222
222
  }
223
223
 
224
- // Ensure the encoded directory exists
225
- try {
226
- await mkdir(encodedPath, { recursive: true });
227
- } catch {
228
- // Ignore errors if directory already exists
229
- }
230
-
231
224
  return {
232
225
  originalPath: resolvedPath,
233
226
  encodedName,
@@ -237,6 +230,28 @@ export class PathEncoder {
237
230
  };
238
231
  }
239
232
 
233
+ /**
234
+ * Create project directory entity from original path
235
+ */
236
+ async createProjectDirectory(
237
+ originalPath: string,
238
+ baseSessionDir: string,
239
+ ): Promise<ProjectDirectory> {
240
+ const projectDirectory = await this.getProjectDirectory(
241
+ originalPath,
242
+ baseSessionDir,
243
+ );
244
+
245
+ // Ensure the encoded directory exists
246
+ try {
247
+ await mkdir(projectDirectory.encodedPath, { recursive: true });
248
+ } catch {
249
+ // Ignore errors if directory already exists
250
+ }
251
+
252
+ return projectDirectory;
253
+ }
254
+
240
255
  /**
241
256
  * Validate that an encoded name is filesystem-safe
242
257
  */
@@ -9,7 +9,7 @@ export interface SubagentConfiguration {
9
9
  model?: string;
10
10
  systemPrompt: string;
11
11
  filePath: string;
12
- scope: "project" | "user";
12
+ scope: "project" | "user" | "builtin";
13
13
  priority: number;
14
14
  }
15
15
 
@@ -101,11 +101,11 @@ function validateConfiguration(
101
101
  throw new Error(`Missing required field 'description' in ${filePath}`);
102
102
  }
103
103
 
104
- // Validate name pattern
105
- const namePattern = /^[a-z][a-z0-9-]*$/;
104
+ // Validate name pattern - allow letters (upper/lowercase), numbers, and hyphens
105
+ const namePattern = /^[a-zA-Z][a-zA-Z0-9-]*$/;
106
106
  if (!namePattern.test(config.name)) {
107
107
  throw new Error(
108
- `Invalid subagent name '${config.name}' in ${filePath}. Must start with a letter and contain only lowercase letters, numbers, and hyphens.`,
108
+ `Invalid subagent name '${config.name}' in ${filePath}. Must start with a letter and contain only letters, numbers, and hyphens.`,
109
109
  );
110
110
  }
111
111
 
@@ -187,7 +187,7 @@ function scanSubagentDirectory(
187
187
  }
188
188
 
189
189
  /**
190
- * Load all subagent configurations from project and user directories
190
+ * Load all subagent configurations from project and user directories, plus built-in subagents
191
191
  */
192
192
  export async function loadSubagentConfigurations(
193
193
  workdir: string,
@@ -195,14 +195,17 @@ export async function loadSubagentConfigurations(
195
195
  const projectDir = join(workdir, ".wave", "agents");
196
196
  const userDir = join(process.env.HOME || "~", ".wave", "agents");
197
197
 
198
+ // Load configurations from all sources
199
+ const { getBuiltinSubagents } = await import("./builtinSubagents.js");
200
+ const builtinConfigs = getBuiltinSubagents();
198
201
  const projectConfigs = scanSubagentDirectory(projectDir, "project");
199
202
  const userConfigs = scanSubagentDirectory(userDir, "user");
200
203
 
201
- // Merge configurations, with project configs taking precedence
204
+ // Merge configurations, with project configs taking highest precedence
202
205
  const configMap = new Map<string, SubagentConfiguration>();
203
206
 
204
- // Process in reverse priority order (user first, then project)
205
- for (const config of [...userConfigs, ...projectConfigs]) {
207
+ // Process in reverse priority order (built-in first, then user, then project)
208
+ for (const config of [...builtinConfigs, ...userConfigs, ...projectConfigs]) {
206
209
  configMap.set(config.name, config);
207
210
  }
208
211
 
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Simple token estimation utility for text content
3
+ *
4
+ * This provides a fast approximation of token count without requiring
5
+ * actual tokenization, which would be expensive for large content.
6
+ */
7
+
8
+ /**
9
+ * Estimate the number of tokens in a text string
10
+ *
11
+ * Uses a simple heuristic based on character count and common patterns:
12
+ * - Average token length varies by language and content type
13
+ * - English text: ~4-5 characters per token
14
+ * - Code/structured text: ~3-4 characters per token
15
+ * - Numbers/symbols: ~2-3 characters per token
16
+ *
17
+ * This function uses a conservative estimate of 4 characters per token
18
+ * which works well for mixed content (text + code + symbols).
19
+ *
20
+ * @param text - The text to estimate tokens for
21
+ * @returns Estimated number of tokens
22
+ */
23
+ export function estimateTokenCount(text: string): number {
24
+ if (!text || text.length === 0) {
25
+ return 0;
26
+ }
27
+
28
+ // Base estimation: 4 characters per token (conservative)
29
+ const baseEstimate = Math.ceil(text.length / 4);
30
+
31
+ // Adjust for whitespace (spaces don't contribute much to token count)
32
+ const whitespaceCount = (text.match(/\s/g) || []).length;
33
+ const adjustedEstimate = Math.ceil((text.length - whitespaceCount * 0.5) / 4);
34
+
35
+ // Use the more conservative (higher) estimate
36
+ return Math.max(baseEstimate, adjustedEstimate);
37
+ }
38
+
39
+ /**
40
+ * Check if estimated token count exceeds a threshold
41
+ *
42
+ * @param text - The text to check
43
+ * @param threshold - Token threshold (default: 20,000)
44
+ * @returns True if estimated tokens exceed threshold
45
+ */
46
+ export function exceedsTokenThreshold(
47
+ text: string,
48
+ threshold: number = 20000,
49
+ ): boolean {
50
+ return estimateTokenCount(text) > threshold;
51
+ }
52
+
53
+ /**
54
+ * Get a human-readable description of estimated token usage
55
+ *
56
+ * @param text - The text to analyze
57
+ * @param threshold - Token threshold for comparison
58
+ * @returns Description string with token count and threshold info
59
+ */
60
+ export function getTokenUsageDescription(
61
+ text: string,
62
+ threshold: number = 20000,
63
+ ): string {
64
+ const estimatedTokens = estimateTokenCount(text);
65
+ const exceedsThreshold = estimatedTokens > threshold;
66
+
67
+ return `${estimatedTokens.toLocaleString()} tokens (${exceedsThreshold ? "exceeds" : "within"} ${threshold.toLocaleString()} limit)`;
68
+ }
@@ -1,28 +0,0 @@
1
- /**
2
- * Event name constants to prevent typos and ensure consistency
3
- *
4
- * Using constants instead of magic strings prevents bugs like:
5
- * - ConfigurationWatcher emitting 'configurationChange'
6
- * - LiveConfigManager listening for 'configurationChanged'
7
- *
8
- * This pattern ensures compile-time validation of event names.
9
- */
10
- export declare const CONFIGURATION_EVENTS: {
11
- readonly CONFIGURATION_CHANGE: "configurationChange";
12
- readonly WATCHER_ERROR: "watcherError";
13
- };
14
- export declare const FILE_WATCHER_EVENTS: {
15
- readonly CHANGE: "change";
16
- readonly CREATE: "create";
17
- readonly DELETE: "delete";
18
- readonly RENAME: "rename";
19
- };
20
- export declare const MEMORY_STORE_EVENTS: {
21
- readonly FILE_ADDED: "fileAdded";
22
- readonly FILE_CHANGED: "fileChanged";
23
- readonly FILE_REMOVED: "fileRemoved";
24
- };
25
- export type ConfigurationEventName = (typeof CONFIGURATION_EVENTS)[keyof typeof CONFIGURATION_EVENTS];
26
- export type FileWatcherEventName = (typeof FILE_WATCHER_EVENTS)[keyof typeof FILE_WATCHER_EVENTS];
27
- export type MemoryStoreEventName = (typeof MEMORY_STORE_EVENTS)[keyof typeof MEMORY_STORE_EVENTS];
28
- //# sourceMappingURL=events.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/constants/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,eAAO,MAAM,oBAAoB;;;CAGvB,CAAC;AAGX,eAAO,MAAM,mBAAmB;;;;;CAKtB,CAAC;AAGX,eAAO,MAAM,mBAAmB;;;;CAItB,CAAC;AAGX,MAAM,MAAM,sBAAsB,GAChC,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,OAAO,oBAAoB,CAAC,CAAC;AACnE,MAAM,MAAM,oBAAoB,GAC9B,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAC;AACjE,MAAM,MAAM,oBAAoB,GAC9B,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAC"}
@@ -1,27 +0,0 @@
1
- /**
2
- * Event name constants to prevent typos and ensure consistency
3
- *
4
- * Using constants instead of magic strings prevents bugs like:
5
- * - ConfigurationWatcher emitting 'configurationChange'
6
- * - LiveConfigManager listening for 'configurationChanged'
7
- *
8
- * This pattern ensures compile-time validation of event names.
9
- */
10
- // Configuration Watcher Events
11
- export const CONFIGURATION_EVENTS = {
12
- CONFIGURATION_CHANGE: "configurationChange",
13
- WATCHER_ERROR: "watcherError",
14
- };
15
- // File Watcher Events
16
- export const FILE_WATCHER_EVENTS = {
17
- CHANGE: "change",
18
- CREATE: "create",
19
- DELETE: "delete",
20
- RENAME: "rename",
21
- };
22
- // Memory Store Events
23
- export const MEMORY_STORE_EVENTS = {
24
- FILE_ADDED: "fileAdded",
25
- FILE_CHANGED: "fileChanged",
26
- FILE_REMOVED: "fileRemoved",
27
- };