wave-agent-sdk 0.0.7 → 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 (240) hide show
  1. package/dist/agent.d.ts +105 -24
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +438 -53
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +4 -0
  7. package/dist/managers/aiManager.d.ts +18 -7
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +254 -142
  10. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  11. package/dist/managers/backgroundBashManager.js +11 -9
  12. package/dist/managers/hookManager.d.ts +6 -6
  13. package/dist/managers/hookManager.d.ts.map +1 -1
  14. package/dist/managers/hookManager.js +81 -39
  15. package/dist/managers/liveConfigManager.d.ts +95 -0
  16. package/dist/managers/liveConfigManager.d.ts.map +1 -0
  17. package/dist/managers/liveConfigManager.js +442 -0
  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 +41 -24
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +184 -73
  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 +4 -2
  32. package/dist/managers/subagentManager.d.ts +42 -6
  33. package/dist/managers/subagentManager.d.ts.map +1 -1
  34. package/dist/managers/subagentManager.js +213 -62
  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 +15 -5
  39. package/dist/services/aiService.d.ts.map +1 -1
  40. package/dist/services/aiService.js +446 -77
  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 +69 -0
  45. package/dist/services/fileWatcher.d.ts.map +1 -0
  46. package/dist/services/fileWatcher.js +212 -0
  47. package/dist/services/hook.d.ts +5 -40
  48. package/dist/services/hook.d.ts.map +1 -1
  49. package/dist/services/hook.js +47 -109
  50. package/dist/services/jsonlHandler.d.ts +71 -0
  51. package/dist/services/jsonlHandler.d.ts.map +1 -0
  52. package/dist/services/jsonlHandler.js +236 -0
  53. package/dist/services/memory.d.ts.map +1 -1
  54. package/dist/services/memory.js +33 -11
  55. package/dist/services/session.d.ts +116 -52
  56. package/dist/services/session.d.ts.map +1 -1
  57. package/dist/services/session.js +415 -143
  58. package/dist/tools/bashTool.d.ts.map +1 -1
  59. package/dist/tools/bashTool.js +77 -17
  60. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  61. package/dist/tools/deleteFileTool.js +27 -1
  62. package/dist/tools/editTool.d.ts.map +1 -1
  63. package/dist/tools/editTool.js +33 -8
  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 +30 -10
  69. package/dist/tools/readTool.d.ts.map +1 -1
  70. package/dist/tools/readTool.js +113 -3
  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 +30 -15
  78. package/dist/types/commands.d.ts +4 -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 +45 -0
  86. package/dist/types/core.d.ts.map +1 -1
  87. package/dist/types/environment.d.ts +83 -0
  88. package/dist/types/environment.d.ts.map +1 -0
  89. package/dist/types/environment.js +21 -0
  90. package/dist/types/fileSearch.d.ts +5 -0
  91. package/dist/types/fileSearch.d.ts.map +1 -0
  92. package/dist/types/fileSearch.js +1 -0
  93. package/dist/types/hooks.d.ts +18 -3
  94. package/dist/types/hooks.d.ts.map +1 -1
  95. package/dist/types/hooks.js +8 -8
  96. package/dist/types/index.d.ts +7 -0
  97. package/dist/types/index.d.ts.map +1 -1
  98. package/dist/types/index.js +7 -0
  99. package/dist/types/lsp.d.ts +90 -0
  100. package/dist/types/lsp.d.ts.map +1 -0
  101. package/dist/types/lsp.js +4 -0
  102. package/dist/types/messaging.d.ts +19 -12
  103. package/dist/types/messaging.d.ts.map +1 -1
  104. package/dist/types/permissions.d.ts +35 -0
  105. package/dist/types/permissions.d.ts.map +1 -0
  106. package/dist/types/permissions.js +12 -0
  107. package/dist/types/session.d.ts +15 -0
  108. package/dist/types/session.d.ts.map +1 -0
  109. package/dist/types/session.js +7 -0
  110. package/dist/types/skills.d.ts +1 -0
  111. package/dist/types/skills.d.ts.map +1 -1
  112. package/dist/types/tools.d.ts +35 -0
  113. package/dist/types/tools.d.ts.map +1 -0
  114. package/dist/types/tools.js +4 -0
  115. package/dist/utils/abortUtils.d.ts +34 -0
  116. package/dist/utils/abortUtils.d.ts.map +1 -0
  117. package/dist/utils/abortUtils.js +92 -0
  118. package/dist/utils/bashHistory.d.ts +4 -0
  119. package/dist/utils/bashHistory.d.ts.map +1 -1
  120. package/dist/utils/bashHistory.js +48 -30
  121. package/dist/utils/builtinSubagents.d.ts +7 -0
  122. package/dist/utils/builtinSubagents.d.ts.map +1 -0
  123. package/dist/utils/builtinSubagents.js +65 -0
  124. package/dist/utils/cacheControlUtils.d.ts +96 -0
  125. package/dist/utils/cacheControlUtils.d.ts.map +1 -0
  126. package/dist/utils/cacheControlUtils.js +324 -0
  127. package/dist/utils/commandPathResolver.d.ts +52 -0
  128. package/dist/utils/commandPathResolver.d.ts.map +1 -0
  129. package/dist/utils/commandPathResolver.js +145 -0
  130. package/dist/utils/configPaths.d.ts +85 -0
  131. package/dist/utils/configPaths.d.ts.map +1 -0
  132. package/dist/utils/configPaths.js +121 -0
  133. package/dist/utils/constants.d.ts +1 -13
  134. package/dist/utils/constants.d.ts.map +1 -1
  135. package/dist/utils/constants.js +2 -14
  136. package/dist/utils/convertMessagesForAPI.d.ts +2 -1
  137. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  138. package/dist/utils/convertMessagesForAPI.js +39 -18
  139. package/dist/utils/customCommands.d.ts.map +1 -1
  140. package/dist/utils/customCommands.js +66 -21
  141. package/dist/utils/fileSearch.d.ts +14 -0
  142. package/dist/utils/fileSearch.d.ts.map +1 -0
  143. package/dist/utils/fileSearch.js +88 -0
  144. package/dist/utils/fileUtils.d.ts +27 -0
  145. package/dist/utils/fileUtils.d.ts.map +1 -0
  146. package/dist/utils/fileUtils.js +145 -0
  147. package/dist/utils/globalLogger.d.ts +88 -0
  148. package/dist/utils/globalLogger.d.ts.map +1 -0
  149. package/dist/utils/globalLogger.js +120 -0
  150. package/dist/utils/largeOutputHandler.d.ts +15 -0
  151. package/dist/utils/largeOutputHandler.d.ts.map +1 -0
  152. package/dist/utils/largeOutputHandler.js +40 -0
  153. package/dist/utils/markdownParser.d.ts.map +1 -1
  154. package/dist/utils/markdownParser.js +1 -17
  155. package/dist/utils/mcpUtils.d.ts.map +1 -1
  156. package/dist/utils/mcpUtils.js +25 -3
  157. package/dist/utils/messageOperations.d.ts +20 -18
  158. package/dist/utils/messageOperations.d.ts.map +1 -1
  159. package/dist/utils/messageOperations.js +30 -38
  160. package/dist/utils/pathEncoder.d.ts +108 -0
  161. package/dist/utils/pathEncoder.d.ts.map +1 -0
  162. package/dist/utils/pathEncoder.js +279 -0
  163. package/dist/utils/subagentParser.d.ts +2 -2
  164. package/dist/utils/subagentParser.d.ts.map +1 -1
  165. package/dist/utils/subagentParser.js +12 -8
  166. package/dist/utils/tokenCalculation.d.ts +26 -0
  167. package/dist/utils/tokenCalculation.d.ts.map +1 -0
  168. package/dist/utils/tokenCalculation.js +36 -0
  169. package/dist/utils/tokenEstimator.d.ts +39 -0
  170. package/dist/utils/tokenEstimator.d.ts.map +1 -0
  171. package/dist/utils/tokenEstimator.js +55 -0
  172. package/package.json +6 -6
  173. package/src/agent.ts +586 -78
  174. package/src/index.ts +4 -0
  175. package/src/managers/aiManager.ts +341 -192
  176. package/src/managers/backgroundBashManager.ts +11 -9
  177. package/src/managers/hookManager.ts +102 -54
  178. package/src/managers/liveConfigManager.ts +634 -0
  179. package/src/managers/lspManager.ts +434 -0
  180. package/src/managers/messageManager.ts +258 -121
  181. package/src/managers/permissionManager.ts +276 -0
  182. package/src/managers/skillManager.ts +3 -1
  183. package/src/managers/slashCommandManager.ts +5 -3
  184. package/src/managers/subagentManager.ts +295 -76
  185. package/src/managers/toolManager.ts +95 -3
  186. package/src/services/aiService.ts +656 -84
  187. package/src/services/configurationService.ts +762 -0
  188. package/src/services/fileWatcher.ts +300 -0
  189. package/src/services/hook.ts +54 -144
  190. package/src/services/jsonlHandler.ts +303 -0
  191. package/src/services/memory.ts +34 -11
  192. package/src/services/session.ts +522 -173
  193. package/src/tools/bashTool.ts +94 -20
  194. package/src/tools/deleteFileTool.ts +38 -1
  195. package/src/tools/editTool.ts +44 -9
  196. package/src/tools/lspTool.ts +760 -0
  197. package/src/tools/multiEditTool.ts +41 -11
  198. package/src/tools/readTool.ts +127 -3
  199. package/src/tools/skillTool.ts +2 -2
  200. package/src/tools/todoWriteTool.ts +33 -1
  201. package/src/tools/types.ts +15 -9
  202. package/src/tools/writeTool.ts +43 -16
  203. package/src/types/commands.ts +6 -1
  204. package/src/types/config.ts +5 -0
  205. package/src/types/configuration.ts +73 -0
  206. package/src/types/core.ts +55 -0
  207. package/src/types/environment.ts +104 -0
  208. package/src/types/fileSearch.ts +4 -0
  209. package/src/types/hooks.ts +32 -16
  210. package/src/types/index.ts +7 -0
  211. package/src/types/lsp.ts +96 -0
  212. package/src/types/messaging.ts +21 -14
  213. package/src/types/permissions.ts +48 -0
  214. package/src/types/session.ts +20 -0
  215. package/src/types/skills.ts +1 -0
  216. package/src/types/tools.ts +38 -0
  217. package/src/utils/abortUtils.ts +118 -0
  218. package/src/utils/bashHistory.ts +55 -31
  219. package/src/utils/builtinSubagents.ts +71 -0
  220. package/src/utils/cacheControlUtils.ts +475 -0
  221. package/src/utils/commandPathResolver.ts +189 -0
  222. package/src/utils/configPaths.ts +163 -0
  223. package/src/utils/constants.ts +2 -17
  224. package/src/utils/convertMessagesForAPI.ts +44 -18
  225. package/src/utils/customCommands.ts +90 -22
  226. package/src/utils/fileSearch.ts +107 -0
  227. package/src/utils/fileUtils.ts +160 -0
  228. package/src/utils/globalLogger.ts +128 -0
  229. package/src/utils/largeOutputHandler.ts +55 -0
  230. package/src/utils/markdownParser.ts +1 -19
  231. package/src/utils/mcpUtils.ts +34 -3
  232. package/src/utils/messageOperations.ts +47 -53
  233. package/src/utils/pathEncoder.ts +394 -0
  234. package/src/utils/subagentParser.ts +13 -9
  235. package/src/utils/tokenCalculation.ts +43 -0
  236. package/src/utils/tokenEstimator.ts +68 -0
  237. package/dist/utils/configResolver.d.ts +0 -38
  238. package/dist/utils/configResolver.d.ts.map +0 -1
  239. package/dist/utils/configResolver.js +0 -106
  240. package/src/utils/configResolver.ts +0 -142
@@ -1,7 +1,7 @@
1
1
  import { readFile, writeFile } from "fs/promises";
2
+ import { logger } from "../utils/globalLogger.js";
2
3
  import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
3
4
  import { resolvePath, getDisplayPath } from "../utils/path.js";
4
- import { diffLines } from "diff";
5
5
 
6
6
  interface EditOperation {
7
7
  old_string: string;
@@ -149,7 +149,7 @@ export const multiEditTool: ToolPlugin = {
149
149
  if (edits[0] && edits[0].old_string === "") {
150
150
  originalContent = "";
151
151
  isNewFile = true;
152
- // logger.debug(`Creating new file: ${resolvedPath}`);
152
+ logger.debug(`Creating new file: ${resolvedPath}`);
153
153
  } else {
154
154
  return {
155
155
  success: false,
@@ -181,7 +181,7 @@ export const multiEditTool: ToolPlugin = {
181
181
  return {
182
182
  success: false,
183
183
  content: "",
184
- error: `Edit operation ${i + 1}: old_string not found in current content: "${edit.old_string}"`,
184
+ error: `Edit operation ${i + 1}: old_string not found in current content`,
185
185
  };
186
186
  }
187
187
 
@@ -216,6 +216,42 @@ export const multiEditTool: ToolPlugin = {
216
216
  }
217
217
  }
218
218
 
219
+ // Permission check after validation but before real operation
220
+ if (
221
+ context.permissionManager &&
222
+ context.permissionMode &&
223
+ context.permissionMode !== "bypassPermissions"
224
+ ) {
225
+ if (context.permissionManager.isRestrictedTool("MultiEdit")) {
226
+ try {
227
+ const permissionContext = context.permissionManager.createContext(
228
+ "MultiEdit",
229
+ context.permissionMode,
230
+ context.canUseToolCallback,
231
+ { file_path: filePath, edits },
232
+ );
233
+ const permissionResult =
234
+ await context.permissionManager.checkPermission(
235
+ permissionContext,
236
+ );
237
+
238
+ if (permissionResult.behavior === "deny") {
239
+ return {
240
+ success: false,
241
+ content: "",
242
+ error: `MultiEdit operation denied by user, reason: ${permissionResult.message || "No reason provided"}`,
243
+ };
244
+ }
245
+ } catch {
246
+ return {
247
+ success: false,
248
+ content: "",
249
+ error: "Permission check failed",
250
+ };
251
+ }
252
+ }
253
+ }
254
+
219
255
  // Write file
220
256
  try {
221
257
  await writeFile(resolvedPath, currentContent, "utf-8");
@@ -227,30 +263,24 @@ export const multiEditTool: ToolPlugin = {
227
263
  };
228
264
  }
229
265
 
230
- // Generate diff information
231
- const diffResult = diffLines(originalContent, currentContent);
232
-
233
266
  const shortResult = isNewFile
234
267
  ? `Created file with ${edits.length} operations`
235
268
  : `Applied ${edits.length} edits`;
236
269
 
237
270
  const detailedContent = `${shortResult}\n\nOperations performed:\n${appliedEdits.map((edit, i) => `${i + 1}. ${edit}`).join("\n")}`;
238
271
 
239
- // logger.debug(`MultiEdit tool: ${shortResult}`);
272
+ logger.debug(`MultiEdit tool: ${shortResult}`);
240
273
 
241
274
  return {
242
275
  success: true,
243
276
  content: detailedContent,
244
277
  shortResult,
245
278
  filePath: resolvedPath,
246
- originalContent,
247
- newContent: currentContent,
248
- diffResult,
249
279
  };
250
280
  } catch (error) {
251
281
  const errorMessage =
252
282
  error instanceof Error ? error.message : String(error);
253
- // logger.error(`MultiEdit tool error: ${errorMessage}`);
283
+ logger.error(`MultiEdit tool error: ${errorMessage}`);
254
284
  return {
255
285
  success: false,
256
286
  content: "",
@@ -1,10 +1,129 @@
1
- import { readFile } from "fs/promises";
1
+ import { readFile, stat } from "fs/promises";
2
+ import { extname } from "path";
3
+ import { logger } from "../utils/globalLogger.js";
2
4
  import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
3
5
  import { resolvePath, getDisplayPath } from "../utils/path.js";
4
6
  import {
5
7
  isBinaryDocument,
6
8
  getBinaryDocumentError,
7
9
  } from "../utils/fileFormat.js";
10
+ import { convertImageToBase64 } from "../utils/messageOperations.js";
11
+
12
+ /**
13
+ * Supported image file extensions
14
+ */
15
+ const SUPPORTED_IMAGE_EXTENSIONS = [
16
+ "png",
17
+ "jpeg",
18
+ "jpg",
19
+ "gif",
20
+ "webp",
21
+ ] as const;
22
+
23
+ /**
24
+ * Check if a file path represents an image file
25
+ * @param filePath - Path to the file
26
+ * @returns true if the file is a supported image format
27
+ */
28
+ function isImageFile(filePath: string): boolean {
29
+ const ext = extname(filePath).toLowerCase().substring(1);
30
+ return (SUPPORTED_IMAGE_EXTENSIONS as readonly string[]).includes(ext);
31
+ }
32
+
33
+ /**
34
+ * Validate image file size
35
+ * @param filePath - Path to the image file
36
+ * @param maxSizeBytes - Maximum allowed file size in bytes (default: 20MB)
37
+ * @returns Promise<boolean> - true if file size is within limit
38
+ */
39
+ async function validateImageFileSize(
40
+ filePath: string,
41
+ maxSizeBytes: number = 20 * 1024 * 1024,
42
+ ): Promise<boolean> {
43
+ try {
44
+ const stats = await stat(filePath);
45
+ return stats.size <= maxSizeBytes;
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Get MIME type for image file based on extension
53
+ * @param filePath - Path to the image file
54
+ * @returns MIME type string
55
+ */
56
+ function getImageMimeType(filePath: string): string {
57
+ const ext = extname(filePath).toLowerCase().substring(1);
58
+ switch (ext) {
59
+ case "png":
60
+ return "image/png";
61
+ case "jpg":
62
+ case "jpeg":
63
+ return "image/jpeg";
64
+ case "gif":
65
+ return "image/gif";
66
+ case "webp":
67
+ return "image/webp";
68
+ default:
69
+ return "image/png"; // Default fallback
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Process an image file and return ToolResult with image data
75
+ * @param filePath - Path to the image file
76
+ * @param context - Tool execution context
77
+ * @returns Promise<ToolResult> with image data
78
+ */
79
+ async function processImageFile(
80
+ filePath: string,
81
+ context: ToolContext,
82
+ ): Promise<ToolResult> {
83
+ try {
84
+ // Resolve path
85
+ const actualFilePath = filePath.startsWith("/")
86
+ ? filePath
87
+ : resolvePath(filePath, context.workdir);
88
+
89
+ // Validate file size
90
+ const isValidSize = await validateImageFileSize(actualFilePath);
91
+ if (!isValidSize) {
92
+ const stats = await stat(actualFilePath);
93
+ const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
94
+ return {
95
+ success: false,
96
+ content: "",
97
+ error: `Image file exceeds 20MB limit (actual: ${sizeMB}MB)`,
98
+ };
99
+ }
100
+
101
+ // Convert image to base64
102
+ const imageDataUrl = convertImageToBase64(actualFilePath);
103
+ const mimeType = getImageMimeType(actualFilePath);
104
+
105
+ // Extract base64 data from data URL (remove data:image/type;base64, prefix)
106
+ const base64Data = imageDataUrl.split(",")[1] || "";
107
+
108
+ return {
109
+ success: true,
110
+ content: `Image file processed: ${getDisplayPath(filePath, context.workdir)}\nFormat: ${mimeType}\nSize: Available for AI processing`,
111
+ shortResult: `Image processed (${mimeType})`,
112
+ images: [
113
+ {
114
+ data: base64Data,
115
+ mediaType: mimeType,
116
+ },
117
+ ],
118
+ };
119
+ } catch (error) {
120
+ return {
121
+ success: false,
122
+ content: "",
123
+ error: `Failed to process image: ${error instanceof Error ? error.message : String(error)}`,
124
+ };
125
+ }
126
+ }
8
127
 
9
128
  /**
10
129
  * Read Tool Plugin - Read file content
@@ -16,7 +135,7 @@ export const readTool: ToolPlugin = {
16
135
  function: {
17
136
  name: "Read",
18
137
  description:
19
- "Reads a file from the local filesystem. You can access any file directly by using this tool.\nAssume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.\n\nUsage:\n- The file_path parameter must be an absolute path, not a relative path\n- By default, it reads up to 2000 lines starting from the beginning of the file\n- You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters\n- Any lines longer than 2000 characters will be truncated\n- Results are returned using cat -n format, with line numbers starting at 1\n- This tool allows Claude Code to read images (eg PNG, JPG, etc). When reading an image file the contents are presented visually as Claude Code is a multimodal LLM.\n- This tool can read Jupyter notebooks (.ipynb files) and returns all cells with their outputs, combining code, text, and visualizations.\n- You have the capability to call multiple tools in a single response. It is always better to speculatively read multiple files as a batch that are potentially useful.\n- You will regularly be asked to read screenshots. If the user provides a path to a screenshot ALWAYS use this tool to view the file at the path. This tool will work with all temporary file paths like /var/folders/123/abc/T/TemporaryItems/NSIRD_screencaptureui_ZfB1tD/Screenshot.png\n- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.\n- Binary document formats (PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX) are not supported and will return an error.",
138
+ "Reads a file from the local filesystem. You can access any file directly by using this tool.\nAssume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.\n\nUsage:\n- The file_path parameter must be an absolute path, not a relative path\n- By default, it reads up to 2000 lines starting from the beginning of the file\n- You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters\n- Any lines longer than 2000 characters will be truncated\n- Results are returned using cat -n format, with line numbers starting at 1\n- This tool allows Agent to read images (eg PNG, JPG, etc). When reading an image file the contents are presented visually as Agent is a multimodal LLM.\n- This tool can read Jupyter notebooks (.ipynb files) and returns all cells with their outputs, combining code, text, and visualizations.\n- You have the capability to call multiple tools in a single response. It is always better to speculatively read multiple files as a batch that are potentially useful.\n- You will regularly be asked to read screenshots. If the user provides a path to a screenshot ALWAYS use this tool to view the file at the path. This tool will work with all temporary file paths like /var/folders/123/abc/T/TemporaryItems/NSIRD_screencaptureui_ZfB1tD/Screenshot.png\n- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.\n- Binary document formats (PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX) are not supported and will return an error.",
20
139
  parameters: {
21
140
  type: "object",
22
141
  properties: {
@@ -64,6 +183,11 @@ export const readTool: ToolPlugin = {
64
183
  };
65
184
  }
66
185
 
186
+ // Check if this is an image file
187
+ if (isImageFile(filePath)) {
188
+ return processImageFile(filePath, context);
189
+ }
190
+
67
191
  try {
68
192
  // Note: New Read tool requires absolute paths, so we don't use resolvePath
69
193
  // But for compatibility, if it's not an absolute path, we still try to resolve
@@ -75,7 +199,7 @@ export const readTool: ToolPlugin = {
75
199
 
76
200
  // Check if file is empty
77
201
  if (fileContent.length === 0) {
78
- // logger.warn(`File ${filePath} exists but has empty contents`);
202
+ logger.warn(`File ${filePath} exists but has empty contents`);
79
203
  return {
80
204
  success: true,
81
205
  content:
@@ -23,11 +23,11 @@ export function createSkillTool(skillManager: SkillManager): ToolPlugin {
23
23
  };
24
24
 
25
25
  return {
26
- name: "skill",
26
+ name: "Skill",
27
27
  config: {
28
28
  type: "function",
29
29
  function: {
30
- name: "skill",
30
+ name: "Skill",
31
31
  description: getToolDescription(),
32
32
  parameters: {
33
33
  type: "object",
@@ -1,4 +1,4 @@
1
- import type { ToolPlugin, ToolResult } from "./types.js";
1
+ import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
2
2
 
3
3
  interface TodoItem {
4
4
  content: string;
@@ -101,6 +101,38 @@ When in doubt, use this tool. Being proactive with task management demonstrates
101
101
  },
102
102
  },
103
103
 
104
+ formatCompactParams: (
105
+ params: Record<string, unknown>,
106
+ context: ToolContext,
107
+ ) => {
108
+ void context; // Context not needed for this tool
109
+ try {
110
+ const { todos } = params as { todos?: TodoItem[] };
111
+
112
+ // Handle invalid or missing tasks array
113
+ if (!todos || !Array.isArray(todos)) {
114
+ return "invalid todos";
115
+ }
116
+
117
+ // Handle empty task list
118
+ if (todos.length === 0) {
119
+ return "0 tasks";
120
+ }
121
+
122
+ // Count completed tasks
123
+ const completedCount = todos.filter(
124
+ (todo) => todo && todo.status === "completed",
125
+ ).length;
126
+ const totalCount = todos.length;
127
+
128
+ // Format with proper singular/plural
129
+ const taskWord = totalCount === 1 ? "task" : "tasks";
130
+ return `${completedCount}/${totalCount} ${taskWord}`;
131
+ } catch {
132
+ return "invalid todos";
133
+ }
134
+ },
135
+
104
136
  execute: async (args: Record<string, unknown>): Promise<ToolResult> => {
105
137
  try {
106
138
  // Validate arguments
@@ -3,6 +3,10 @@
3
3
  */
4
4
 
5
5
  import { ChatCompletionFunctionTool } from "openai/resources.js";
6
+ import type {
7
+ PermissionMode,
8
+ PermissionCallback,
9
+ } from "../types/permissions.js";
6
10
 
7
11
  export interface ToolPlugin {
8
12
  name: string;
@@ -23,15 +27,7 @@ export interface ToolResult {
23
27
  error?: string;
24
28
  // Short output, used to display summary information in collapsed state
25
29
  shortResult?: string;
26
- // Additional properties for file editing tools
27
- originalContent?: string;
28
- newContent?: string;
29
- diffResult?: Array<{
30
- count?: number;
31
- value: string;
32
- added?: boolean;
33
- removed?: boolean;
34
- }>;
30
+ // File path for operations that affect files
35
31
  filePath?: string;
36
32
  // Image data, for supporting multimedia content
37
33
  images?: Array<{
@@ -44,4 +40,14 @@ export interface ToolContext {
44
40
  abortSignal?: AbortSignal;
45
41
  backgroundBashManager?: import("../managers/backgroundBashManager.js").BackgroundBashManager;
46
42
  workdir: string;
43
+ /** Permission mode for this tool execution */
44
+ permissionMode?: PermissionMode;
45
+ /** Custom permission callback */
46
+ canUseToolCallback?: PermissionCallback;
47
+ /** Permission manager instance for permission checks */
48
+ permissionManager?: import("../managers/permissionManager.js").PermissionManager;
49
+ /** MCP manager instance for calling MCP tools */
50
+ mcpManager?: import("../managers/mcpManager.js").McpManager;
51
+ /** LSP manager instance for code intelligence */
52
+ lspManager?: import("../types/lsp.js").ILspManager;
47
53
  }
@@ -1,8 +1,8 @@
1
1
  import { readFile, writeFile, mkdir } from "fs/promises";
2
2
  import { dirname } from "path";
3
+ import { logger } from "../utils/globalLogger.js";
3
4
  import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
4
5
  import { resolvePath, getDisplayPath } from "../utils/path.js";
5
- import { diffLines } from "diff";
6
6
 
7
7
  /**
8
8
  * File Write Tool Plugin
@@ -14,7 +14,7 @@ export const writeTool: ToolPlugin = {
14
14
  function: {
15
15
  name: "Write",
16
16
  description:
17
- "Writes a file to the local filesystem.\n\nUsage:\n- This tool will overwrite the existing file if there is one at the provided path.\n- If this is an existing file, you MUST use the Read tool first to read the file's contents. This tool will fail if you did not read the file first.\n- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.\n- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.\n- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.",
17
+ "Writes a file to the local filesystem.\n\nUsage:\n- This tool will overwrite the existing file if there is one at the provided path.\n- If this is an existing file, you MUST use the Read tool first to read the file's contents. This tool will fail if you did not read the file first.\n- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.\n- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.\n- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.\n- IMPORTANT: Always provide file_path parameter before content parameter when calling this tool.",
18
18
  parameters: {
19
19
  type: "object",
20
20
  properties: {
@@ -79,9 +79,6 @@ export const writeTool: ToolPlugin = {
79
79
  content: `File ${filePath} already has the same content, no changes needed`,
80
80
  shortResult: "No changes needed",
81
81
  filePath: resolvedPath,
82
- originalContent,
83
- newContent: content,
84
- diffResult: [],
85
82
  };
86
83
  }
87
84
 
@@ -95,9 +92,45 @@ export const writeTool: ToolPlugin = {
95
92
  mkdirError instanceof Error &&
96
93
  !mkdirError.message.includes("EEXIST")
97
94
  ) {
98
- // logger.warn(
99
- // `Failed to create directory ${fileDir}: ${mkdirError.message}`,
100
- // );
95
+ logger.warn(
96
+ `Failed to create directory ${fileDir}: ${mkdirError.message}`,
97
+ );
98
+ }
99
+ }
100
+
101
+ // Permission check after validation but before real operation
102
+ if (
103
+ context.permissionManager &&
104
+ context.permissionMode &&
105
+ context.permissionMode !== "bypassPermissions"
106
+ ) {
107
+ if (context.permissionManager.isRestrictedTool("Write")) {
108
+ try {
109
+ const permissionContext = context.permissionManager.createContext(
110
+ "Write",
111
+ context.permissionMode,
112
+ context.canUseToolCallback,
113
+ { file_path: filePath, content },
114
+ );
115
+ const permissionResult =
116
+ await context.permissionManager.checkPermission(
117
+ permissionContext,
118
+ );
119
+
120
+ if (permissionResult.behavior === "deny") {
121
+ return {
122
+ success: false,
123
+ content: "",
124
+ error: `Write operation denied by user, reason: ${permissionResult.message || "No reason provided"}`,
125
+ };
126
+ }
127
+ } catch {
128
+ return {
129
+ success: false,
130
+ content: "",
131
+ error: "Permission check failed",
132
+ };
133
+ }
101
134
  }
102
135
  }
103
136
 
@@ -112,30 +145,24 @@ export const writeTool: ToolPlugin = {
112
145
  };
113
146
  }
114
147
 
115
- // Generate diff information
116
- const diffResult = diffLines(originalContent, content);
117
-
118
148
  const shortResult = isExistingFile ? "File overwritten" : "File created";
119
149
 
120
150
  const lines = content.split("\n").length;
121
151
  const chars = content.length;
122
152
  const detailedContent = `${shortResult} (${lines} lines, ${chars} characters)`;
123
153
 
124
- // logger.debug(`Write tool: ${shortResult}`);
154
+ logger.debug(`Write tool: ${shortResult}`);
125
155
 
126
156
  return {
127
157
  success: true,
128
158
  content: detailedContent,
129
159
  shortResult,
130
160
  filePath: resolvedPath,
131
- originalContent,
132
- newContent: content,
133
- diffResult,
134
161
  };
135
162
  } catch (error) {
136
163
  const errorMessage =
137
164
  error instanceof Error ? error.message : String(error);
138
- // logger.error(`Write tool error: ${errorMessage}`);
165
+ logger.error(`Write tool error: ${errorMessage}`);
139
166
  return {
140
167
  success: false,
141
168
  content: "",
@@ -11,7 +11,6 @@ export interface SlashCommand {
11
11
  }
12
12
 
13
13
  export interface CustomSlashCommandConfig {
14
- allowedTools?: string[];
15
14
  model?: string;
16
15
  description?: string;
17
16
  }
@@ -23,4 +22,10 @@ export interface CustomSlashCommand {
23
22
  filePath: string;
24
23
  content: string;
25
24
  config?: CustomSlashCommandConfig;
25
+
26
+ // Nested command support
27
+ namespace?: string; // Parent directory for nested commands (e.g., "openspec")
28
+ isNested: boolean; // Whether command is in a subdirectory
29
+ depth: number; // 0 = root, 1 = nested
30
+ segments: string[]; // Path components for ID generation (e.g., ["openspec", "apply"])
26
31
  }
@@ -3,9 +3,14 @@
3
3
  * Dependencies: None
4
4
  */
5
5
 
6
+ import OpenAI from "openai";
7
+
6
8
  export interface GatewayConfig {
7
9
  apiKey: string;
8
10
  baseURL: string;
11
+ defaultHeaders?: Record<string, string>;
12
+ fetchOptions?: OpenAI["fetchOptions"];
13
+ fetch?: OpenAI["fetch"];
9
14
  }
10
15
 
11
16
  export interface ModelConfig {
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Configuration Management Types
3
+ *
4
+ * Types for centralized configuration loading and validation services.
5
+ * These support the refactored configuration architecture that separates
6
+ * configuration management from hook execution.
7
+ */
8
+
9
+ import type { WaveConfiguration } from "./hooks.js";
10
+
11
+ /**
12
+ * Result of configuration loading operations with detailed status information
13
+ */
14
+ export interface ConfigurationLoadResult {
15
+ /** The loaded configuration, or null if loading failed */
16
+ configuration: WaveConfiguration | null;
17
+ /** Whether the loading operation was successful */
18
+ success: boolean;
19
+ /** Error message if loading failed */
20
+ error?: string;
21
+ /** Path of the successfully loaded file */
22
+ sourcePath?: string;
23
+ /** Non-critical warnings during loading */
24
+ warnings: string[];
25
+ }
26
+
27
+ /**
28
+ * Result of configuration validation operations
29
+ */
30
+ export interface ValidationResult {
31
+ /** Whether the configuration is valid */
32
+ isValid: boolean;
33
+ /** Critical errors that prevent configuration use */
34
+ errors: string[];
35
+ /** Non-critical warnings about the configuration */
36
+ warnings: string[];
37
+ }
38
+
39
+ /**
40
+ * Configuration file paths organized by category
41
+ */
42
+ export interface ConfigurationPaths {
43
+ /** User-specific configuration file paths in priority order */
44
+ userPaths: string[];
45
+ /** Project-specific configuration file paths in priority order */
46
+ projectPaths: string[];
47
+ /** All configuration paths combined */
48
+ allPaths: string[];
49
+ /** Only the paths that actually exist on the filesystem */
50
+ existingPaths: string[];
51
+ }
52
+
53
+ /**
54
+ * Options for configuring the ConfigurationService
55
+ */
56
+ export interface ConfigurationServiceOptions {
57
+ /** Working directory for resolving project configurations */
58
+ workdir: string;
59
+ /** Optional logger for configuration operations */
60
+ logger?: Logger;
61
+ /** Whether to enable validation during loading (default: true) */
62
+ enableValidation?: boolean;
63
+ }
64
+
65
+ /**
66
+ * Minimal logger interface for configuration services
67
+ */
68
+ interface Logger {
69
+ error: (...args: unknown[]) => void;
70
+ warn: (...args: unknown[]) => void;
71
+ info: (...args: unknown[]) => void;
72
+ debug: (...args: unknown[]) => void;
73
+ }
package/src/types/core.ts CHANGED
@@ -3,6 +3,8 @@
3
3
  * Dependencies: None (foundation layer)
4
4
  */
5
5
 
6
+ import type { CompletionUsage } from "openai/resources";
7
+
6
8
  /**
7
9
  * Logger interface definition
8
10
  * Compatible with OpenAI package Logger interface
@@ -24,6 +26,59 @@ export interface Usage {
24
26
  total_tokens: number; // Sum of prompt + completion tokens
25
27
  model?: string; // Model used for the operation (e.g., "gpt-4", "gpt-3.5-turbo")
26
28
  operation_type?: "agent" | "compress"; // Type of operation that generated usage
29
+
30
+ // Cache-related tokens (Claude models only)
31
+ cache_read_input_tokens?: number; // Tokens read from cache
32
+ cache_creation_input_tokens?: number; // Tokens used to create cache entries
33
+ cache_creation?: {
34
+ ephemeral_5m_input_tokens: number; // Tokens cached for 5 minutes
35
+ ephemeral_1h_input_tokens: number; // Tokens cached for 1 hour
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Enhanced usage metrics including Claude cache information
41
+ * Backward compatible with standard OpenAI CompletionUsage
42
+ */
43
+ export interface ClaudeUsage extends CompletionUsage {
44
+ // Standard OpenAI usage fields (inherited)
45
+ prompt_tokens: number;
46
+ completion_tokens: number;
47
+ total_tokens: number;
48
+
49
+ // Claude-specific cache extensions
50
+ /**
51
+ * Number of tokens read from existing cache
52
+ * Indicates cost savings from cache hits
53
+ */
54
+ cache_read_input_tokens?: number;
55
+
56
+ /**
57
+ * Number of tokens used to create new cache entries
58
+ * Investment in future cache hits
59
+ */
60
+ cache_creation_input_tokens?: number;
61
+
62
+ /**
63
+ * Detailed breakdown of cache creation by duration
64
+ */
65
+ cache_creation?: {
66
+ /** Tokens cached for 5 minute duration */
67
+ ephemeral_5m_input_tokens: number;
68
+ /** Tokens cached for 1 hour duration */
69
+ ephemeral_1h_input_tokens: number;
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Represents a diff change for tool parameter-based diff display
75
+ * Contains the old content and new content for comparison
76
+ */
77
+ export interface Change {
78
+ /** The original content (empty string for additions) */
79
+ oldContent: string;
80
+ /** The new content (empty string for deletions) */
81
+ newContent: string;
27
82
  }
28
83
 
29
84
  export class ConfigurationError extends Error {