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
@@ -0,0 +1,160 @@
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";
6
+
7
+ /**
8
+ * Reads the first line of a file efficiently using Node.js readline.
9
+ *
10
+ * @param {string} filePath - The path to the file.
11
+ * @return {Promise<string>} - The first non-empty line of the file, or an empty string otherwise.
12
+ */
13
+ export async function readFirstLine(filePath: string): Promise<string> {
14
+ const { createInterface } = await import("node:readline");
15
+
16
+ const fileStream = createReadStream(filePath);
17
+ const rl = createInterface({
18
+ input: fileStream,
19
+ crlfDelay: Infinity, // Handle \r\n properly
20
+ });
21
+
22
+ try {
23
+ for await (const line of rl) {
24
+ const trimmedLine = line.trim();
25
+ if (trimmedLine.length > 0) {
26
+ return trimmedLine;
27
+ }
28
+ }
29
+ return "";
30
+ } catch {
31
+ // If reading fails (e.g., file doesn't exist), return empty string
32
+ return "";
33
+ } finally {
34
+ rl.close();
35
+ fileStream.destroy();
36
+ }
37
+ }
38
+
39
+ /**
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.)
46
+ *
47
+ * @param {string} filePath - The path to the file.
48
+ * @param {number} [minLength=1] - Minimum length for the returned line.
49
+ * @return {Promise<string>} - The last non-empty line of the file, or an empty string if no non-empty lines found.
50
+ */
51
+ export async function getLastLine(
52
+ filePath: string,
53
+ minLength = 1,
54
+ ): Promise<string> {
55
+ let fileHandle;
56
+ try {
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
+ }
95
+ }
96
+ }
97
+
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 : "";
108
+ } catch {
109
+ // If reading fails (e.g., file doesn't exist), return empty string
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
159
+ }
160
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Global Logger Registry for Agent SDK
3
+ *
4
+ * Provides zero-overhead logging access for utility functions and services
5
+ * without requiring parameter passing. Maintains singleton logger instance
6
+ * accessible across all SDK modules.
7
+ *
8
+ * Features:
9
+ * - Zero overhead when no logger configured (single null check)
10
+ * - Thread-safe in Node.js single-threaded environment
11
+ * - Maintains backward compatibility with existing Logger interface
12
+ * - Direct function delegation when configured
13
+ */
14
+
15
+ import type { Logger } from "../types/core.js";
16
+
17
+ /**
18
+ * Module-level storage for the global logger instance
19
+ * null = unconfigured (logging calls are no-ops)
20
+ * Logger = configured (calls are forwarded)
21
+ */
22
+ let globalLogger: Logger | null = null;
23
+
24
+ // =============================================================================
25
+ // Registry Management API
26
+ // =============================================================================
27
+
28
+ /**
29
+ * Configure the global logger instance used by utility functions and services
30
+ *
31
+ * @param logger - Logger instance implementing the Logger interface, or null to disable logging
32
+ * @returns void
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * import { setGlobalLogger } from './utils/globalLogger.js';
37
+ *
38
+ * // Configure logger
39
+ * setGlobalLogger(myLogger);
40
+ *
41
+ * // Disable logging
42
+ * setGlobalLogger(null);
43
+ * ```
44
+ */
45
+ export function setGlobalLogger(logger: Logger | null): void {
46
+ globalLogger = logger;
47
+ }
48
+
49
+ /**
50
+ * Reset global logger to unconfigured state
51
+ * Equivalent to setGlobalLogger(null)
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * clearGlobalLogger(); // All subsequent logging calls become no-ops
56
+ * ```
57
+ */
58
+ export function clearGlobalLogger(): void {
59
+ globalLogger = null;
60
+ }
61
+
62
+ /**
63
+ * Check if global logger is currently configured
64
+ *
65
+ * @returns true if logger configured, false otherwise
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * if (isLoggerConfigured()) {
70
+ * // Perform expensive logging operation
71
+ * const debugInfo = generateDebugInfo();
72
+ * logger.debug(debugInfo);
73
+ * }
74
+ * ```
75
+ */
76
+ export function isLoggerConfigured(): boolean {
77
+ return globalLogger !== null;
78
+ }
79
+
80
+ // =============================================================================
81
+ // Zero-Overhead Logging API
82
+ // =============================================================================
83
+
84
+ /**
85
+ * Zero-overhead logging interface
86
+ *
87
+ * Performance characteristics:
88
+ * - Unconfigured: Single null check + early return (near-zero cost)
89
+ * - Configured: Null check + function delegation
90
+ * - No object creation or intermediate allocations
91
+ */
92
+ export const logger = {
93
+ /**
94
+ * Log debug-level message through global logger
95
+ * No-op when global logger is null (zero overhead)
96
+ */
97
+ debug: (...args: unknown[]): void => {
98
+ if (globalLogger === null) return;
99
+ globalLogger.debug(...args);
100
+ },
101
+
102
+ /**
103
+ * Log info-level message through global logger
104
+ * No-op when global logger is null (zero overhead)
105
+ */
106
+ info: (...args: unknown[]): void => {
107
+ if (globalLogger === null) return;
108
+ globalLogger.info(...args);
109
+ },
110
+
111
+ /**
112
+ * Log warning-level message through global logger
113
+ * No-op when global logger is null (zero overhead)
114
+ */
115
+ warn: (...args: unknown[]): void => {
116
+ if (globalLogger === null) return;
117
+ globalLogger.warn(...args);
118
+ },
119
+
120
+ /**
121
+ * Log error-level message through global logger
122
+ * No-op when global logger is null (zero overhead)
123
+ */
124
+ error: (...args: unknown[]): void => {
125
+ if (globalLogger === null) return;
126
+ globalLogger.error(...args);
127
+ },
128
+ } as const;
@@ -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
  }
@@ -2,6 +2,36 @@ import { ChatCompletionFunctionTool } from "openai/resources.js";
2
2
  import type { ToolPlugin, ToolResult, ToolContext } from "../tools/types.js";
3
3
  import type { McpTool, McpServerStatus } from "../types/index.js";
4
4
 
5
+ /**
6
+ * Recursively clean schema to remove unsupported fields
7
+ */
8
+ function cleanSchema(schema: unknown): unknown {
9
+ if (typeof schema !== "object" || schema === null) {
10
+ return schema;
11
+ }
12
+
13
+ if (Array.isArray(schema)) {
14
+ return schema.map(cleanSchema);
15
+ }
16
+
17
+ const newSchema: Record<string, unknown> = {};
18
+ const obj = schema as Record<string, unknown>;
19
+
20
+ for (const key in obj) {
21
+ // Remove $schema as OpenAI API doesn't accept it
22
+ // Remove exclusiveMinimum/exclusiveMaximum as some models (e.g. Gemini) don't support them
23
+ if (
24
+ key === "$schema" ||
25
+ key === "exclusiveMinimum" ||
26
+ key === "exclusiveMaximum"
27
+ ) {
28
+ continue;
29
+ }
30
+ newSchema[key] = cleanSchema(obj[key]);
31
+ }
32
+ return newSchema;
33
+ }
34
+
5
35
  /**
6
36
  * Convert MCP tool to OpenAI function tool format
7
37
  */
@@ -9,9 +39,10 @@ export function mcpToolToOpenAITool(
9
39
  mcpTool: McpTool,
10
40
  serverName: string,
11
41
  ): ChatCompletionFunctionTool {
12
- // Remove $schema field if it exists, as OpenAI API doesn't accept it
13
- const cleanInputSchema = { ...mcpTool.inputSchema };
14
- delete cleanInputSchema.$schema;
42
+ const cleanInputSchema = cleanSchema(mcpTool.inputSchema) as Record<
43
+ string,
44
+ unknown
45
+ >;
15
46
 
16
47
  return {
17
48
  type: "function",
@@ -1,7 +1,10 @@
1
- import type { Message, Usage, MessageSource } from "../types/index.js";
1
+ import type { Message, Usage } from "../types/index.js";
2
+ import { MessageSource } from "../types/index.js";
3
+ import type { SubagentConfiguration } from "./subagentParser.js";
2
4
  import { readFileSync } from "fs";
3
5
  import { extname } from "path";
4
6
  import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
7
+ import { logger } from "./globalLogger.js";
5
8
 
6
9
  // Base user message parameters interface
7
10
  export interface UserMessageParams {
@@ -23,11 +26,19 @@ export interface UpdateToolBlockParams {
23
26
  result?: string;
24
27
  success?: boolean;
25
28
  error?: string;
26
- isRunning?: boolean;
29
+ /**
30
+ * Tool execution stage:
31
+ * - 'start': Tool call initiated during AI streaming
32
+ * - 'streaming': Tool parameters being received incrementally
33
+ * - 'running': Tool execution in progress
34
+ * - 'end': Tool execution completed with final result
35
+ */
36
+ stage: "start" | "streaming" | "running" | "end";
27
37
  name?: string;
28
38
  shortResult?: string;
29
39
  images?: Array<{ data: string; mediaType?: string }>;
30
40
  compactParams?: string;
41
+ parametersChunk?: string; // Incremental parameter updates for streaming
31
42
  }
32
43
 
33
44
  // Agent specific interfaces (without messages parameter)
@@ -36,12 +47,6 @@ export type AgentToolBlockUpdateParams = Omit<
36
47
  "messages"
37
48
  >;
38
49
 
39
- export interface AddDiffBlockParams {
40
- messages: Message[];
41
- path: string;
42
- diffResult: Array<{ value: string; added?: boolean; removed?: boolean }>;
43
- }
44
-
45
50
  export interface AddErrorBlockParams {
46
51
  messages: Message[];
47
52
  error: string;
@@ -74,17 +79,18 @@ export interface CompleteCommandParams {
74
79
 
75
80
  /**
76
81
  * Extract text content from user messages in the messages array
82
+ * Excludes messages with source HOOK to prevent hook-generated content from entering user history
77
83
  */
78
84
  export const extractUserInputHistory = (messages: Message[]): string[] => {
79
85
  return messages
80
86
  .filter((message) => message.role === "user")
81
87
  .map((message) => {
82
- // Extract all text block content and merge
88
+ // Extract text block content, excluding HOOK-sourced blocks
83
89
  const textBlocks = message.blocks.filter(
84
- (block) => block.type === "text",
90
+ (block) => block.type === "text" && block.source !== MessageSource.HOOK,
85
91
  );
86
92
  return textBlocks
87
- .map((block) => block.content)
93
+ .map((block) => (block as { content: string }).content)
88
94
  .join(" ")
89
95
  .trim();
90
96
  })
@@ -126,8 +132,8 @@ export const convertImageToBase64 = (imagePath: string): string => {
126
132
 
127
133
  const base64String = imageBuffer.toString("base64");
128
134
  return `data:${mimeType};base64,${base64String}`;
129
- } catch {
130
- // logger.error(`Failed to convert image to base64: ${imagePath}`, error);
135
+ } catch (error) {
136
+ logger.error(`Failed to convert image to base64: ${imagePath}`, error);
131
137
  // Return an error placeholder or throw error
132
138
  return `data:image/png;base64,`; // Empty base64, avoid program crash
133
139
  }
@@ -174,6 +180,7 @@ export const addAssistantMessageToMessages = (
174
180
  content?: string,
175
181
  toolCalls?: ChatCompletionMessageFunctionToolCall[],
176
182
  usage?: Usage,
183
+ additionalFields?: Record<string, unknown>,
177
184
  ): Message[] => {
178
185
  const blocks: Message["blocks"] = [];
179
186
 
@@ -191,7 +198,7 @@ export const addAssistantMessageToMessages = (
191
198
  result: "",
192
199
  id: toolCall.id || "",
193
200
  name: toolCall.function?.name || "",
194
- isRunning: false,
201
+ stage: "start",
195
202
  });
196
203
  });
197
204
  }
@@ -200,33 +207,12 @@ export const addAssistantMessageToMessages = (
200
207
  role: "assistant",
201
208
  blocks,
202
209
  usage, // Include usage data if provided
210
+ ...(additionalFields ? { additionalFields: { ...additionalFields } } : {}),
203
211
  };
204
212
 
205
213
  return [...messages, initialAssistantMessage];
206
214
  };
207
215
 
208
- // Update File Operation Block of the last assistant message
209
- export const addDiffBlockToMessage = ({
210
- messages,
211
- path,
212
- diffResult,
213
- }: AddDiffBlockParams): Message[] => {
214
- const newMessages = [...messages];
215
- // Find the last assistant message
216
- for (let i = newMessages.length - 1; i >= 0; i--) {
217
- if (newMessages[i].role === "assistant") {
218
- // Directly add diff block instead of replacing existing blocks
219
- newMessages[i].blocks.push({
220
- type: "diff",
221
- path: path,
222
- diffResult: diffResult,
223
- });
224
- break;
225
- }
226
- }
227
- return newMessages;
228
- };
229
-
230
216
  // Update Tool Block of the last assistant message
231
217
  export const updateToolBlockInMessage = ({
232
218
  messages,
@@ -235,11 +221,12 @@ export const updateToolBlockInMessage = ({
235
221
  result,
236
222
  success,
237
223
  error,
238
- isRunning,
224
+ stage,
239
225
  name,
240
226
  shortResult,
241
227
  images,
242
228
  compactParams,
229
+ parametersChunk,
243
230
  }: UpdateToolBlockParams): Message[] => {
244
231
  const newMessages = [...messages];
245
232
  // Find the last assistant message
@@ -258,24 +245,28 @@ export const updateToolBlockInMessage = ({
258
245
  toolBlock.images = images; // Add image data update
259
246
  if (success !== undefined) toolBlock.success = success;
260
247
  if (error !== undefined) toolBlock.error = error;
261
- if (isRunning !== undefined) toolBlock.isRunning = isRunning;
248
+ if (stage !== undefined) toolBlock.stage = stage;
262
249
  if (compactParams !== undefined)
263
250
  toolBlock.compactParams = compactParams;
251
+ if (parametersChunk !== undefined)
252
+ toolBlock.parametersChunk = parametersChunk;
264
253
  }
265
- } else if (result !== undefined) {
254
+ } else {
266
255
  // If existing block not found, create new one
256
+ // This handles cases where we're streaming tool parameters before execution
267
257
  newMessages[i].blocks.push({
268
258
  type: "tool",
269
259
  parameters: parameters,
270
- result: result,
260
+ result: result || "",
271
261
  shortResult: shortResult,
272
262
  images: images, // Add image data
273
263
  id: id,
274
264
  name: name || "unknown",
275
265
  success: success,
276
266
  error: error,
277
- isRunning: isRunning ?? false,
267
+ stage: stage ?? "start",
278
268
  compactParams: compactParams,
269
+ parametersChunk: parametersChunk,
279
270
  });
280
271
  }
281
272
  break;
@@ -434,7 +425,7 @@ export const addCommandOutputMessage = ({
434
425
  command,
435
426
  }: AddCommandOutputParams): Message[] => {
436
427
  const outputMessage: Message = {
437
- role: "assistant",
428
+ role: "user",
438
429
  blocks: [
439
430
  {
440
431
  type: "command_output",
@@ -456,10 +447,10 @@ export const updateCommandOutputInMessage = ({
456
447
  output,
457
448
  }: UpdateCommandOutputParams): Message[] => {
458
449
  const newMessages = [...messages];
459
- // 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
460
451
  for (let i = newMessages.length - 1; i >= 0; i--) {
461
452
  const msg = newMessages[i];
462
- if (msg.role === "assistant") {
453
+ if (msg.role === "user") {
463
454
  const commandBlock = msg.blocks.find(
464
455
  (block) =>
465
456
  block.type === "command_output" &&
@@ -482,10 +473,10 @@ export const completeCommandInMessage = ({
482
473
  exitCode,
483
474
  }: CompleteCommandParams): Message[] => {
484
475
  const newMessages = [...messages];
485
- // 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
486
477
  for (let i = newMessages.length - 1; i >= 0; i--) {
487
478
  const msg = newMessages[i];
488
- if (msg.role === "assistant") {
479
+ if (msg.role === "user") {
489
480
  const commandBlock = msg.blocks.find(
490
481
  (block) =>
491
482
  block.type === "command_output" &&
@@ -508,14 +499,15 @@ export interface AddSubagentBlockParams {
508
499
  subagentId: string;
509
500
  subagentName: string;
510
501
  status: "active" | "completed" | "error" | "aborted";
511
- subagentMessages?: Message[];
502
+ sessionId: string;
503
+ configuration: SubagentConfiguration;
512
504
  }
513
505
 
514
506
  export interface UpdateSubagentBlockParams {
515
507
  messages: Message[];
516
508
  subagentId: string;
517
509
  status: "active" | "completed" | "error" | "aborted";
518
- subagentMessages: Message[];
510
+ sessionId?: string;
519
511
  }
520
512
 
521
513
  export const addSubagentBlockToMessage = ({
@@ -523,7 +515,8 @@ export const addSubagentBlockToMessage = ({
523
515
  subagentId,
524
516
  subagentName,
525
517
  status,
526
- subagentMessages = [],
518
+ sessionId,
519
+ configuration,
527
520
  }: AddSubagentBlockParams): Message[] => {
528
521
  const newMessages = [...messages];
529
522
 
@@ -545,7 +538,8 @@ export const addSubagentBlockToMessage = ({
545
538
  subagentId,
546
539
  subagentName,
547
540
  status,
548
- messages: subagentMessages,
541
+ sessionId,
542
+ configuration,
549
543
  });
550
544
 
551
545
  return newMessages;
@@ -556,7 +550,7 @@ export const updateSubagentBlockInMessage = (
556
550
  subagentId: string,
557
551
  updates: Partial<{
558
552
  status: "active" | "completed" | "error" | "aborted";
559
- messages: Message[];
553
+ sessionId: string;
560
554
  }>,
561
555
  ): Message[] => {
562
556
  const newMessages = [...messages];
@@ -570,8 +564,8 @@ export const updateSubagentBlockInMessage = (
570
564
  if (updates.status !== undefined) {
571
565
  block.status = updates.status;
572
566
  }
573
- if (updates.messages !== undefined) {
574
- block.messages = updates.messages;
567
+ if (updates.sessionId !== undefined) {
568
+ block.sessionId = updates.sessionId;
575
569
  }
576
570
  return newMessages;
577
571
  }