wave-agent-sdk 1.0.5 → 1.0.6

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 (193) hide show
  1. package/dist/agent.d.ts +23 -1
  2. package/dist/agent.js +34 -4
  3. package/dist/managers/aiManager.d.ts +36 -0
  4. package/dist/managers/aiManager.js +240 -8
  5. package/dist/managers/messageManager.d.ts +1 -3
  6. package/dist/managers/messageManager.js +13 -6
  7. package/dist/managers/slashCommandManager.js +28 -0
  8. package/dist/managers/subagentManager.d.ts +0 -2
  9. package/dist/telemetry/instrumentation.d.ts +1 -1
  10. package/dist/telemetry/instrumentation.js +57 -28
  11. package/dist/tools/bashTool.js +16 -6
  12. package/dist/tools/grepTool.js +11 -3
  13. package/dist/types/index.d.ts +3 -0
  14. package/dist/types/messaging.d.ts +2 -0
  15. package/dist/utils/containerSetup.js +0 -3
  16. package/dist/utils/messageOperations.d.ts +2 -1
  17. package/dist/utils/messageOperations.js +2 -1
  18. package/dist/utils/notificationXml.js +5 -0
  19. package/dist/utils/ripgrep.d.ts +4 -4
  20. package/dist/utils/ripgrep.js +4 -27
  21. package/package.json +20 -8
  22. package/bin/rg +0 -79
  23. package/dist/managers/forkedAgentManager.d.ts +0 -50
  24. package/dist/managers/forkedAgentManager.js +0 -130
  25. package/scripts/install_ripgrep.js +0 -111
  26. package/scripts/postinstall.js +0 -38
  27. package/src/agent.ts +0 -1271
  28. package/src/constants/subagents.ts +0 -4
  29. package/src/constants/toolLimits.ts +0 -15
  30. package/src/constants/tools.ts +0 -24
  31. package/src/core/plugin.ts +0 -237
  32. package/src/core/session.ts +0 -9
  33. package/src/index.ts +0 -38
  34. package/src/managers/MemoryRuleManager.ts +0 -198
  35. package/src/managers/aiManager.ts +0 -2429
  36. package/src/managers/backgroundTaskManager.ts +0 -480
  37. package/src/managers/bangManager.ts +0 -119
  38. package/src/managers/cronManager.ts +0 -401
  39. package/src/managers/foregroundTaskManager.ts +0 -30
  40. package/src/managers/forkedAgentManager.ts +0 -216
  41. package/src/managers/hookManager.ts +0 -1089
  42. package/src/managers/liveConfigManager.ts +0 -445
  43. package/src/managers/lspManager.ts +0 -470
  44. package/src/managers/mcpManager.ts +0 -1010
  45. package/src/managers/messageManager.ts +0 -1184
  46. package/src/managers/messageQueue.ts +0 -157
  47. package/src/managers/permissionManager.ts +0 -1140
  48. package/src/managers/planManager.ts +0 -112
  49. package/src/managers/pluginManager.ts +0 -330
  50. package/src/managers/pluginScopeManager.ts +0 -124
  51. package/src/managers/reversionManager.ts +0 -178
  52. package/src/managers/skillManager.ts +0 -578
  53. package/src/managers/slashCommandManager.ts +0 -564
  54. package/src/managers/subagentManager.ts +0 -915
  55. package/src/managers/toolManager.ts +0 -489
  56. package/src/managers/workflowManager.ts +0 -493
  57. package/src/prompts/autoMemory.ts +0 -33
  58. package/src/prompts/autoMemoryExtraction.ts +0 -146
  59. package/src/prompts/index.ts +0 -506
  60. package/src/prompts/planModeReminders.ts +0 -126
  61. package/src/services/GitService.ts +0 -131
  62. package/src/services/MarketplaceService.ts +0 -1052
  63. package/src/services/MemoryRuleService.ts +0 -71
  64. package/src/services/aiService.ts +0 -919
  65. package/src/services/authService.ts +0 -565
  66. package/src/services/autoMemoryService.ts +0 -280
  67. package/src/services/configurationService.ts +0 -1520
  68. package/src/services/fileWatcher.ts +0 -319
  69. package/src/services/hook.ts +0 -348
  70. package/src/services/initializationService.ts +0 -357
  71. package/src/services/interactionService.ts +0 -229
  72. package/src/services/jsonlHandler.ts +0 -295
  73. package/src/services/memory.ts +0 -234
  74. package/src/services/pluginLoader.ts +0 -252
  75. package/src/services/remoteSettingsService.ts +0 -366
  76. package/src/services/reversionService.ts +0 -122
  77. package/src/services/session.ts +0 -917
  78. package/src/services/taskManager.ts +0 -321
  79. package/src/telemetry/events.ts +0 -65
  80. package/src/telemetry/instrumentation.ts +0 -499
  81. package/src/telemetry/sessionTracing.ts +0 -348
  82. package/src/tools/agentTool.ts +0 -322
  83. package/src/tools/askUserQuestion.ts +0 -160
  84. package/src/tools/bashTool.ts +0 -621
  85. package/src/tools/buildTool.ts +0 -61
  86. package/src/tools/cronCreateTool.ts +0 -161
  87. package/src/tools/cronDeleteTool.ts +0 -51
  88. package/src/tools/cronListTool.ts +0 -47
  89. package/src/tools/editTool.ts +0 -313
  90. package/src/tools/enterPlanMode.ts +0 -124
  91. package/src/tools/enterWorktreeTool.ts +0 -190
  92. package/src/tools/exitPlanMode.ts +0 -139
  93. package/src/tools/exitWorktreeTool.ts +0 -236
  94. package/src/tools/globTool.ts +0 -167
  95. package/src/tools/grepTool.ts +0 -399
  96. package/src/tools/lspTool.ts +0 -883
  97. package/src/tools/readTool.ts +0 -426
  98. package/src/tools/skillTool.ts +0 -248
  99. package/src/tools/taskManagementTools.ts +0 -574
  100. package/src/tools/taskStopTool.ts +0 -78
  101. package/src/tools/types.ts +0 -137
  102. package/src/tools/webFetchTool.ts +0 -373
  103. package/src/tools/workflowTool.ts +0 -205
  104. package/src/tools/writeTool.ts +0 -267
  105. package/src/types/agent.ts +0 -128
  106. package/src/types/auth.ts +0 -19
  107. package/src/types/commands.ts +0 -30
  108. package/src/types/config.ts +0 -43
  109. package/src/types/configuration.ts +0 -168
  110. package/src/types/core.ts +0 -105
  111. package/src/types/cron.ts +0 -13
  112. package/src/types/environment.ts +0 -106
  113. package/src/types/fileSearch.ts +0 -4
  114. package/src/types/history.ts +0 -7
  115. package/src/types/hooks.ts +0 -256
  116. package/src/types/index.ts +0 -41
  117. package/src/types/lsp.ts +0 -98
  118. package/src/types/marketplace.ts +0 -71
  119. package/src/types/mcp.ts +0 -43
  120. package/src/types/memoryRule.ts +0 -31
  121. package/src/types/messaging.ts +0 -114
  122. package/src/types/permissions.ts +0 -80
  123. package/src/types/plugins.ts +0 -39
  124. package/src/types/processes.ts +0 -99
  125. package/src/types/reversion.ts +0 -29
  126. package/src/types/session.ts +0 -12
  127. package/src/types/skills.ts +0 -108
  128. package/src/types/tasks.ts +0 -13
  129. package/src/types/telemetry.ts +0 -98
  130. package/src/types/tools.ts +0 -44
  131. package/src/types/workflow.ts +0 -6
  132. package/src/utils/abortUtils.ts +0 -118
  133. package/src/utils/atomicWrite.ts +0 -61
  134. package/src/utils/bashParser.ts +0 -899
  135. package/src/utils/cacheControlUtils.ts +0 -426
  136. package/src/utils/commandArgumentParser.ts +0 -161
  137. package/src/utils/commandPathResolver.ts +0 -88
  138. package/src/utils/configPaths.ts +0 -220
  139. package/src/utils/configValidator.ts +0 -100
  140. package/src/utils/constants.ts +0 -39
  141. package/src/utils/container.ts +0 -92
  142. package/src/utils/containerSetup.ts +0 -371
  143. package/src/utils/convertMessagesForAPI.ts +0 -326
  144. package/src/utils/cronTasks.ts +0 -128
  145. package/src/utils/cronTasksLock.ts +0 -182
  146. package/src/utils/cronToHuman.ts +0 -99
  147. package/src/utils/customCommands.ts +0 -95
  148. package/src/utils/editUtils.ts +0 -20
  149. package/src/utils/fileFormat.ts +0 -40
  150. package/src/utils/fileSearch.ts +0 -151
  151. package/src/utils/fileUtils.ts +0 -223
  152. package/src/utils/gitUtils.ts +0 -315
  153. package/src/utils/globalLogger.ts +0 -128
  154. package/src/utils/groupMessagesByApiRound.ts +0 -121
  155. package/src/utils/hookMatcher.ts +0 -176
  156. package/src/utils/markdownParser.ts +0 -317
  157. package/src/utils/mcpUtils.ts +0 -138
  158. package/src/utils/messageOperations.ts +0 -659
  159. package/src/utils/modelCapabilities.ts +0 -30
  160. package/src/utils/nameGenerator.ts +0 -99
  161. package/src/utils/notificationXml.ts +0 -57
  162. package/src/utils/openaiClient.ts +0 -251
  163. package/src/utils/parseCronExpression.ts +0 -78
  164. package/src/utils/path.ts +0 -72
  165. package/src/utils/pathEncoder.ts +0 -400
  166. package/src/utils/pathSafety.ts +0 -40
  167. package/src/utils/promptHistory.ts +0 -168
  168. package/src/utils/ripgrep.ts +0 -35
  169. package/src/utils/shellResolver.ts +0 -190
  170. package/src/utils/skillParser.ts +0 -246
  171. package/src/utils/stringUtils.ts +0 -156
  172. package/src/utils/subagentParser.ts +0 -279
  173. package/src/utils/taskReminder.ts +0 -96
  174. package/src/utils/tokenCalculation.ts +0 -43
  175. package/src/utils/tokenEstimate.ts +0 -34
  176. package/src/utils/toolResultStorage.ts +0 -117
  177. package/src/utils/worktreeSession.ts +0 -26
  178. package/src/utils/worktreeUtils.ts +0 -786
  179. package/src/workflow/budgetTracker.ts +0 -34
  180. package/src/workflow/concurrencyLimiter.ts +0 -47
  181. package/src/workflow/journal.ts +0 -95
  182. package/src/workflow/progressReporter.ts +0 -141
  183. package/src/workflow/runState.ts +0 -65
  184. package/src/workflow/scriptRuntime.ts +0 -274
  185. package/src/workflow/structuredOutput.ts +0 -123
  186. package/src/workflow/types.ts +0 -101
  187. package/src/workflow/workflowApis.ts +0 -410
  188. package/vendor/ripgrep/linux-aarch64/rg +0 -0
  189. package/vendor/ripgrep/linux-x86_64/rg +0 -0
  190. package/vendor/ripgrep/macos-aarch64/rg +0 -0
  191. package/vendor/ripgrep/macos-x86_64/rg +0 -0
  192. package/vendor/ripgrep/windows-aarch64/rg.exe +0 -0
  193. package/vendor/ripgrep/windows-x86_64/rg.exe +0 -0
@@ -1,621 +0,0 @@
1
- import { spawn, ChildProcess } from "child_process";
2
- import * as fs from "fs";
3
- import * as path from "path";
4
- import * as os from "os";
5
- import { logger } from "../utils/globalLogger.js";
6
- import { resolveShellPath } from "../utils/shellResolver.js";
7
- import { toPosixPath } from "../utils/path.js";
8
- import { stripAnsiColors } from "../utils/stringUtils.js";
9
- import { processToolResult } from "../utils/toolResultStorage.js";
10
- import { BASH_MAX_OUTPUT_CHARS } from "../constants/toolLimits.js";
11
- import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
12
- import {
13
- BASH_TOOL_NAME,
14
- GLOB_TOOL_NAME,
15
- GREP_TOOL_NAME,
16
- READ_TOOL_NAME,
17
- EDIT_TOOL_NAME,
18
- WRITE_TOOL_NAME,
19
- } from "../constants/tools.js";
20
-
21
- const BASH_DEFAULT_TIMEOUT_MS = 120000;
22
-
23
- /**
24
- * Wrap a user command so we can append CWD tracking (`&& pwd -P`) without the
25
- * appended part being affected by trailing here-docs, unbalanced quotes, or
26
- * multi-line syntax in the user command.
27
- *
28
- * The command is single-quoted (with embedded single quotes escaped via the
29
- * classic `'"'"'` sequence) and run through `eval`. Because the entire user
30
- * command becomes a single literal argument to `eval`, any here-doc inside it
31
- * only opens/closes during eval's own second parse pass and cannot leak out to
32
- * clobber the trailing `&& pwd -P`. Works on sh/dash/bash and Git Bash.
33
- */
34
- function wrapCommandForCwdTracking(
35
- command: string,
36
- cwdFileForBash: string,
37
- ): string {
38
- const escaped = command.replace(/'/g, `'"'"'`);
39
- return `eval '${escaped}' && pwd -P >| ${cwdFileForBash}`;
40
- }
41
-
42
- // Commands that should not be auto-backgrounded on timeout (e.g. sleep should just be killed)
43
- const DISALLOWED_AUTO_BACKGROUND_COMMANDS = ["sleep"];
44
-
45
- function isAutobackgroundingAllowed(command: string): boolean {
46
- const trimmed = command.trim();
47
- // Get the first word of the command
48
- const baseCommand = trimmed.split(/\s+/)[0];
49
- if (!baseCommand) return true;
50
- return !DISALLOWED_AUTO_BACKGROUND_COMMANDS.includes(baseCommand);
51
- }
52
-
53
- /**
54
- * Bash command execution tool - supports both foreground and background execution
55
- */
56
- export const bashTool: ToolPlugin = {
57
- name: BASH_TOOL_NAME,
58
- isConcurrencySafe: false,
59
- config: {
60
- type: "function",
61
- function: {
62
- name: BASH_TOOL_NAME,
63
- description: "Run shell command",
64
- parameters: {
65
- type: "object",
66
- properties: {
67
- command: {
68
- type: "string",
69
- description: "The command to execute",
70
- },
71
- timeout: {
72
- type: "number",
73
- description: "Optional timeout in milliseconds (max 600000)",
74
- },
75
- description: {
76
- type: "string",
77
- description:
78
- "Clear, concise description of what this command does in 5-10 words.",
79
- },
80
- run_in_background: {
81
- type: "boolean",
82
- description: `Set to true to run this command in the background. Use ${READ_TOOL_NAME} to read the output later.`,
83
- },
84
- },
85
- required: ["command"],
86
- },
87
- },
88
- },
89
- prompt: () => `
90
- Executes a given bash command with optional timeout, ensuring proper handling and security measures. Each invocation runs in a fresh shell process starting from the project root.
91
-
92
- IMPORTANT: This tool is for terminal operations like git, npm, docker, etc. DO NOT use it for file operations (reading, writing, editing, searching, finding files) - use the specialized tools for this instead.
93
-
94
- Before executing the command, please follow these steps:
95
-
96
- 1. Directory Verification:
97
- - If the command will create new directories or files, first use \`ls\` to verify the parent directory exists and is the correct location
98
- - For example, before running "mkdir foo/bar", first use \`ls foo\` to check that "foo" exists and is the intended parent directory
99
-
100
- 2. Command Execution:
101
- - Always quote file paths that contain spaces with double quotes (e.g., cd "path with spaces/file.txt")
102
- - Examples of proper quoting:
103
- - cd "/Users/name/My Documents" (correct)
104
- - cd /Users/name/My Documents (incorrect - will fail)
105
- - python "/path/with spaces/script.py" (correct)
106
- - python /path/with spaces/script.py (incorrect - will fail)
107
- - After ensuring proper quoting, execute the command.
108
- - Capture the output of the command.
109
-
110
- Usage notes:
111
- - The command argument is required.
112
- - You can specify an optional timeout in milliseconds (up to ${BASH_DEFAULT_TIMEOUT_MS}ms / ${BASH_DEFAULT_TIMEOUT_MS / 60000} minutes). If not specified, commands will timeout after ${BASH_DEFAULT_TIMEOUT_MS}ms (${BASH_DEFAULT_TIMEOUT_MS / 60000} minutes).
113
- - It is very helpful if you write a clear, concise description of what this command does in 5-10 words.
114
- - If the output exceeds ${BASH_MAX_OUTPUT_CHARS.toLocaleString()} characters, output will be truncated and the full output will be persisted to a file you can read with the Read tool.
115
- - You can use the \`run_in_background\` parameter to run the command in the background, which allows you to continue working while the command runs. You can monitor the output using the ${READ_TOOL_NAME} tool as it becomes available. You do not need to use '&' at the end of the command when using this parameter.
116
- - Avoid using ${BASH_TOOL_NAME} with the \`find\`, \`sed\`, \`awk\`, or \`echo\` commands, unless explicitly instructed or when these commands are truly necessary for the task. Instead, always prefer using the dedicated tools for these commands:
117
- - File search: Use ${GLOB_TOOL_NAME} (NOT find or ls)
118
- - Content search: Use ${GREP_TOOL_NAME} (NOT grep or rg)
119
- - Read files: Use ${READ_TOOL_NAME} (NOT cat/head/tail)
120
- - Edit files: Use ${EDIT_TOOL_NAME} (NOT sed/awk)
121
- - Write files: Use ${WRITE_TOOL_NAME} (NOT echo >/cat <<EOF)
122
- - Communication: Output text directly (NOT echo/printf)
123
- - When issuing multiple commands:
124
- - If the commands are independent and can run in parallel, make multiple ${BASH_TOOL_NAME} tool calls in a single message. For example, if you need to run "git status" and "git diff", send a single message with two ${BASH_TOOL_NAME} tool calls in parallel.
125
- - If the commands depend on each other and must run sequentially, use a single ${BASH_TOOL_NAME} call with '&&' to chain them together (e.g., \`git add . && git commit -m "message" && git push\`). For instance, if one operation must complete before another starts (like mkdir before cp, ${WRITE_TOOL_NAME} before ${BASH_TOOL_NAME} for git operations, or git add before git commit), run these operations sequentially instead.
126
- - Use ';' only when you need to run commands sequentially but don't care if earlier commands fail
127
- - DO NOT use newlines to separate commands (newlines are ok in quoted strings)
128
- - Reserve bash tools exclusively for actual system commands and terminal operations that require shell execution. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.
129
-
130
- # Git operations
131
- Git Safety Protocol:
132
- - NEVER update the git config
133
- - NEVER run destructive git commands (push --force, reset --hard, checkout ., restore ., clean -f, branch -D) unless the user explicitly requests these actions. Taking unauthorized destructive actions is unhelpful and can result in lost work, so it's best to ONLY run these commands when given direct instructions
134
- - NEVER skip hooks (--no-verify, --no-gpg-sign, etc) unless the user explicitly requests it
135
- - NEVER run force push to main/master, warn the user if they request it
136
- - CRITICAL: Always create NEW commits rather than amending, unless the user explicitly requests a git amend. When a pre-commit hook fails, the commit did NOT happen — so --amend would modify the PREVIOUS commit, which may result in destroying work or losing previous changes. Instead, after hook failure, fix the issue, re-stage, and create a NEW commit
137
- - When staging files, prefer adding specific files by name rather than using "git add -A" or "git add .", which can accidentally include sensitive files (.env, credentials) or large binaries
138
- - NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTANT to only commit when explicitly asked, otherwise the user will feel that you are being too proactive
139
- - IMPORTANT: Never use git commands with the -i flag (like git rebase -i or git add -i) since they require interactive input which is not supported.
140
-
141
- Use the gh command via the Bash tool for GitHub-related tasks including working with issues, pull requests, checks, and releases. If given a Github URL use the gh command to get the information needed.
142
-
143
- # Avoid unnecessary sleep commands
144
- - Do not sleep between commands that can run immediately — just run them.
145
- - If your command is long running and you would like to be notified when it finishes — use \`run_in_background\`. No sleep needed.
146
- - Do not retry failing commands in a sleep loop — diagnose the root cause.
147
- - If waiting for a background task you started with \`run_in_background\`, you will be notified when it completes — do not poll.
148
- - If you must poll an external process, use a check command (e.g. \`gh run view\`) rather than sleeping first.
149
- - If you must sleep, keep the duration short (1-5 seconds) to avoid blocking the user.
150
-
151
- # CWD management
152
- The working directory persists between commands. Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of \`cd\`. You may use \`cd\` if the User explicitly requests it.`,
153
- execute: async (
154
- args: Record<string, unknown>,
155
- context: ToolContext,
156
- ): Promise<ToolResult> => {
157
- const command = args.command as string;
158
- const runInBackground = args.run_in_background as boolean | undefined;
159
- const description = args.description as string | undefined;
160
- // Set default timeout: BASH_DEFAULT_TIMEOUT_MS for foreground, no timeout for background
161
- // When run_in_background is explicitly set, cancel any timeout — the intent is to
162
- // let the process run to completion (matching Claude Code behavior where background()
163
- // clears the timeout via cleanupListeners).
164
- const timeout = runInBackground
165
- ? undefined
166
- : ((args.timeout as number | undefined) ?? BASH_DEFAULT_TIMEOUT_MS);
167
-
168
- if (!command || typeof command !== "string") {
169
- return {
170
- success: false,
171
- content: "",
172
- error: "Command parameter is required and must be a string",
173
- };
174
- }
175
-
176
- // Resolve shell path: on Windows, use Git Bash; on macOS/Linux, use bash or zsh
177
- const shellPath = resolveShellPath();
178
- if (!shellPath) {
179
- return {
180
- success: false,
181
- content: "",
182
- error:
183
- process.platform === "win32"
184
- ? "Git Bash not found. Please install Git for Windows or set WAVE_GIT_BASH_PATH environment variable."
185
- : "No suitable shell found. Please ensure bash or zsh is installed, or set WAVE_SHELL environment variable.",
186
- };
187
- }
188
-
189
- // Validate timeout
190
- if (
191
- timeout !== undefined &&
192
- (typeof timeout !== "number" || timeout < 0 || timeout > 600000)
193
- ) {
194
- return {
195
- success: false,
196
- content: "",
197
- error: "Timeout must be a number between 0 and 600000 milliseconds",
198
- };
199
- }
200
-
201
- // Permission check after validation but before real operation
202
- if (context.permissionManager) {
203
- try {
204
- const permissionContext = context.permissionManager.createContext(
205
- BASH_TOOL_NAME,
206
- context.permissionMode || "default",
207
- context.canUseToolCallback,
208
- {
209
- command,
210
- description,
211
- run_in_background: runInBackground,
212
- timeout,
213
- workdir: context.workdir,
214
- },
215
- context.toolCallId,
216
- );
217
- const permissionResult =
218
- await context.permissionManager.checkPermission(permissionContext);
219
-
220
- if (permissionResult.behavior === "deny") {
221
- return {
222
- success: false,
223
- content: "",
224
- error: `${BASH_TOOL_NAME} operation denied by user, reason: ${permissionResult.message || "No reason provided"}`,
225
- };
226
- }
227
- } catch {
228
- return {
229
- success: false,
230
- content: "",
231
- error: "Permission check failed",
232
- };
233
- }
234
- }
235
-
236
- if (runInBackground) {
237
- // Background execution
238
- const backgroundTaskManager = context?.backgroundTaskManager;
239
- if (!backgroundTaskManager) {
240
- return {
241
- success: false,
242
- content: "",
243
- error: "Background task manager not available",
244
- };
245
- }
246
-
247
- const { id: taskId } = backgroundTaskManager.startShell(
248
- command,
249
- undefined,
250
- context.workdir,
251
- );
252
- const task = backgroundTaskManager.getTask(taskId);
253
- const outputPath = task?.outputPath;
254
- const backgroundMsg = [
255
- `Command started in background with ID: ${taskId}.`,
256
- `You will be notified automatically when it completes.`,
257
- outputPath
258
- ? `output_file: ${outputPath}`
259
- : `Use ${READ_TOOL_NAME} tool with task_id="${taskId}" to read the output.`,
260
- ].join("\n");
261
- return {
262
- success: true,
263
- content: backgroundMsg,
264
- shortResult: `Background process ${taskId} started${outputPath ? ` → ${outputPath}` : ""}`,
265
- };
266
- }
267
-
268
- // Foreground execution (original behavior)
269
- return new Promise((resolve) => {
270
- // Create a temporary file to store the CWD
271
- const tempCwdFile = path.join(
272
- os.tmpdir(),
273
- `wave_cwd_${Date.now()}_${Math.random().toString(36).substring(2, 11)}.tmp`,
274
- );
275
- const tempCwdFileForBash = toPosixPath(tempCwdFile);
276
- const wrappedCommand = wrapCommandForCwdTracking(
277
- command,
278
- tempCwdFileForBash,
279
- );
280
-
281
- const child: ChildProcess = spawn(wrappedCommand, {
282
- shell: shellPath,
283
- stdio: "pipe",
284
- detached: true,
285
- cwd: context.workdir,
286
- env: {
287
- ...process.env,
288
- ...context.sessionEnv,
289
- },
290
- });
291
-
292
- let outputBuffer = "";
293
- let errorBuffer = "";
294
- let isAborted = false;
295
- let isBackgrounded = false;
296
- let isFinished = false;
297
-
298
- // Best-effort cleanup of the temp CWD file — used by abort/error/exit paths
299
- const cleanupTempFile = () => {
300
- try {
301
- if (fs.existsSync(tempCwdFile)) {
302
- fs.unlinkSync(tempCwdFile);
303
- }
304
- } catch {
305
- // ignore — best-effort cleanup
306
- }
307
- };
308
-
309
- const updateRealtimeResults = () => {
310
- if (isAborted || isBackgrounded || isFinished) return;
311
-
312
- const combinedOutput =
313
- outputBuffer + (errorBuffer ? "\n" + errorBuffer : "");
314
-
315
- // Update shortResult: last 3 lines
316
- if (context.onShortResultUpdate) {
317
- const tail = combinedOutput.slice(-5000);
318
- const lines = tail.trim().split("\n");
319
- const shortResult =
320
- lines.length <= 3
321
- ? lines.join("\n")
322
- : `... +${lines.length - 3} lines\n` + lines.slice(-3).join("\n");
323
- context.onShortResultUpdate(shortResult);
324
- }
325
-
326
- // Update full result (simple truncation for streaming — persistence happens at final result)
327
- if (context.onResultUpdate) {
328
- const content =
329
- combinedOutput.length <= BASH_MAX_OUTPUT_CHARS
330
- ? combinedOutput
331
- : combinedOutput.substring(0, BASH_MAX_OUTPUT_CHARS) +
332
- "\n\n... (output truncated)";
333
- context.onResultUpdate(content);
334
- }
335
- };
336
-
337
- const foregroundTaskId = `bash_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
338
-
339
- // Register as foreground task
340
- if (context.foregroundTaskManager && command) {
341
- context.foregroundTaskManager.registerForegroundTask({
342
- id: foregroundTaskId,
343
- backgroundHandler: async () => {
344
- isBackgrounded = true;
345
- if (timeoutHandle) {
346
- clearTimeout(timeoutHandle);
347
- }
348
-
349
- const backgroundTaskManager = context.backgroundTaskManager;
350
- if (backgroundTaskManager) {
351
- const taskId = backgroundTaskManager.adoptProcess(
352
- child,
353
- command,
354
- outputBuffer,
355
- errorBuffer,
356
- );
357
- const task = backgroundTaskManager.getTask(taskId);
358
- const outputPath = task?.outputPath;
359
- resolve({
360
- success: true,
361
- content: `Command moved to background with ID: ${taskId}.${outputPath ? ` Real-time output: ${outputPath}` : ""}`,
362
- shortResult: `Process ${taskId} backgrounded`,
363
- isManuallyBackgrounded: true,
364
- });
365
- } else {
366
- handleAbort(
367
- "Failed to background: Background task manager not available",
368
- );
369
- }
370
- },
371
- });
372
- }
373
-
374
- // Set up timeout — auto-background if allowed, otherwise kill
375
- let timeoutHandle: NodeJS.Timeout | undefined;
376
- const shouldAutoBackground =
377
- !!context.backgroundTaskManager && isAutobackgroundingAllowed(command);
378
- if (timeout && timeout > 0) {
379
- timeoutHandle = setTimeout(() => {
380
- if (!isAborted && !isBackgrounded) {
381
- if (shouldAutoBackground) {
382
- // Auto-background: move the process to background task manager instead of killing it
383
- isBackgrounded = true;
384
- if (timeoutHandle) {
385
- clearTimeout(timeoutHandle);
386
- }
387
-
388
- // Unregister foreground task since it's now backgrounded
389
- if (context.foregroundTaskManager) {
390
- context.foregroundTaskManager.unregisterForegroundTask(
391
- foregroundTaskId,
392
- );
393
- }
394
-
395
- const backgroundTaskManager = context.backgroundTaskManager!;
396
- const taskId = backgroundTaskManager.adoptProcess(
397
- child,
398
- command,
399
- outputBuffer,
400
- errorBuffer,
401
- );
402
- const task = backgroundTaskManager.getTask(taskId);
403
- const outputPath = task?.outputPath;
404
- logger.info(
405
- `[Bash] Command timed out after ${timeout}ms, auto-backgrounded as ${taskId}`,
406
- );
407
- resolve({
408
- success: true,
409
- content: `Command timed out after ${timeout / 1000} seconds and was moved to background with ID: ${taskId}.${outputPath ? ` Real-time output: ${outputPath}` : ""}`,
410
- shortResult: `Process ${taskId} auto-backgrounded (timeout)`,
411
- });
412
- } else {
413
- handleAbort("Command timed out");
414
- }
415
- }
416
- }, timeout);
417
- }
418
-
419
- // Handle abort signal
420
- const handleAbort = (reason = "Command execution was aborted") => {
421
- if (!isAborted) {
422
- isAborted = true;
423
-
424
- if (timeoutHandle) {
425
- clearTimeout(timeoutHandle);
426
- }
427
-
428
- // Force terminate child process and its children
429
- if (child.pid) {
430
- try {
431
- // Try graceful termination of process group
432
- process.kill(-child.pid, "SIGTERM");
433
-
434
- // Set timeout for force kill
435
- setTimeout(() => {
436
- if (child.pid && !child.killed) {
437
- try {
438
- process.kill(-child.pid, "SIGKILL");
439
- } catch (killError: unknown) {
440
- // ESRCH means the process already exited — not an error
441
- if (
442
- !(killError instanceof Error) ||
443
- (killError as NodeJS.ErrnoException).code !== "ESRCH"
444
- ) {
445
- logger.error("Failed to force kill process:", killError);
446
- }
447
- }
448
- }
449
- }, 1000);
450
- } catch {
451
- // If process group termination fails, try direct child process termination
452
- try {
453
- child.kill("SIGTERM");
454
- setTimeout(() => {
455
- if (!child.killed) {
456
- child.kill("SIGKILL");
457
- }
458
- }, 1000);
459
- } catch (directKillError) {
460
- logger.error("Failed to kill child process:", directKillError);
461
- }
462
- }
463
- }
464
-
465
- cleanupTempFile();
466
-
467
- const processedOutput = processToolResult(
468
- outputBuffer + (errorBuffer ? "\n" + errorBuffer : ""),
469
- BASH_MAX_OUTPUT_CHARS,
470
- "bash",
471
- );
472
- resolve({
473
- success: false,
474
- content: processedOutput
475
- ? `${processedOutput}\n\n${reason}`
476
- : reason,
477
- error: reason,
478
- });
479
- }
480
- };
481
-
482
- // Handle abort signal from context
483
- if (context?.abortSignal) {
484
- if (context.abortSignal.aborted) {
485
- handleAbort();
486
- return;
487
- }
488
- // Use { once: true } to prevent listener accumulation on signal reuse
489
- context.abortSignal.addEventListener("abort", () => handleAbort(), {
490
- once: true,
491
- });
492
- }
493
-
494
- child.stdout?.on("data", (data) => {
495
- if (!isAborted && !isBackgrounded && !runInBackground) {
496
- const chunk = stripAnsiColors(data.toString());
497
- outputBuffer += chunk;
498
- updateRealtimeResults();
499
- }
500
- });
501
-
502
- child.stderr?.on("data", (data) => {
503
- if (!isAborted && !isBackgrounded && !runInBackground) {
504
- const chunk = stripAnsiColors(data.toString());
505
- errorBuffer += chunk;
506
- updateRealtimeResults();
507
- }
508
- });
509
-
510
- child.on("exit", async (code) => {
511
- isFinished = true;
512
- if (context.foregroundTaskManager) {
513
- context.foregroundTaskManager.unregisterForegroundTask(
514
- foregroundTaskId,
515
- );
516
- }
517
-
518
- if (!isAborted && !isBackgrounded) {
519
- if (timeoutHandle) {
520
- clearTimeout(timeoutHandle);
521
- }
522
-
523
- // Read the new CWD from the temporary file
524
- let newCwd: string | undefined;
525
- try {
526
- if (fs.existsSync(tempCwdFile)) {
527
- newCwd = fs.readFileSync(tempCwdFile, "utf8").trim();
528
- // Validate the path exists before calling the callback
529
- fs.accessSync(newCwd, fs.constants.F_OK);
530
- }
531
- } catch (fileError) {
532
- logger.warn(
533
- `Could not read or validate new CWD from temp file ${tempCwdFile}:`,
534
- fileError,
535
- );
536
- newCwd = undefined;
537
- } finally {
538
- cleanupTempFile();
539
- }
540
-
541
- // If CWD changed, call the onCwdChange callback and add notification
542
- let cwdMessage: string | undefined;
543
- if (newCwd && newCwd !== context.workdir && context.onCwdChange) {
544
- const isInSafeZone =
545
- context.permissionManager?.isPathInSafeZone?.(newCwd) ?? true;
546
-
547
- if (!isInSafeZone && context.originalWorkdir) {
548
- context.onCwdChange(context.originalWorkdir);
549
- cwdMessage = `Shell cwd was reset to ${context.originalWorkdir}`;
550
- } else {
551
- context.onCwdChange(newCwd);
552
- cwdMessage = `Shell working directory changed to ${newCwd}`;
553
- }
554
- }
555
-
556
- const exitCode = code ?? 0;
557
- const combinedOutput =
558
- outputBuffer + (errorBuffer ? "\n" + errorBuffer : "");
559
-
560
- // Prepend CWD change message to output if present
561
- const finalOutput =
562
- (cwdMessage ? cwdMessage + "\n" : "") +
563
- (combinedOutput || `Command executed with exit code: ${exitCode}`);
564
- const content = processToolResult(
565
- finalOutput,
566
- BASH_MAX_OUTPUT_CHARS,
567
- "bash",
568
- );
569
-
570
- const lines = combinedOutput.trim().split("\n");
571
- const shortResult =
572
- lines.length <= 3
573
- ? lines.join("\n")
574
- : `... +${lines.length - 3} lines\n` + lines.slice(-3).join("\n");
575
-
576
- resolve({
577
- success: exitCode === 0,
578
- content,
579
- shortResult: shortResult || undefined,
580
- error:
581
- exitCode !== 0
582
- ? `Command failed with exit code: ${exitCode}`
583
- : undefined,
584
- });
585
- }
586
- });
587
-
588
- child.on("error", (error) => {
589
- isFinished = true;
590
- if (context.foregroundTaskManager) {
591
- context.foregroundTaskManager.unregisterForegroundTask(
592
- foregroundTaskId,
593
- );
594
- }
595
-
596
- if (!isAborted && !isBackgrounded) {
597
- if (timeoutHandle) {
598
- clearTimeout(timeoutHandle);
599
- }
600
- cleanupTempFile();
601
- resolve({
602
- success: false,
603
- content: "",
604
- error: `Failed to execute command: ${error.message}`,
605
- });
606
- }
607
- });
608
- });
609
- },
610
- formatCompactParams: (params: Record<string, unknown>) => {
611
- const description = params.description as string;
612
- const command = params.command as string;
613
- const runInBackground = params.run_in_background as boolean;
614
-
615
- if (description) {
616
- return description;
617
- }
618
-
619
- return `${command}${runInBackground ? " (background)" : ""}`;
620
- },
621
- };
@@ -1,61 +0,0 @@
1
- import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
2
- import { ChatCompletionFunctionTool } from "openai/resources.js";
3
- import type { SubagentConfiguration } from "../utils/subagentParser.js";
4
- import type { SkillMetadata } from "../types/skills.js";
5
-
6
- export interface ToolDef {
7
- name: string;
8
- description: string;
9
- parameters: Record<string, unknown>;
10
- required?: string[];
11
- execute: (
12
- args: Record<string, unknown>,
13
- context: ToolContext,
14
- ) => Promise<ToolResult>;
15
- prompt?:
16
- | string
17
- | ((args?: {
18
- availableSubagents?: SubagentConfiguration[];
19
- availableSkills?: SkillMetadata[];
20
- workdir?: string;
21
- isSubagent?: boolean;
22
- }) => string);
23
- formatCompactParams?: (
24
- params: Record<string, unknown>,
25
- context: ToolContext,
26
- ) => string;
27
- additionalProperties?: boolean;
28
- }
29
-
30
- export function buildTool(def: ToolDef): ToolPlugin {
31
- const config: ChatCompletionFunctionTool = {
32
- type: "function",
33
- function: {
34
- name: def.name,
35
- description: def.description,
36
- parameters: {
37
- type: "object",
38
- properties: def.parameters,
39
- required: def.required || [],
40
- additionalProperties: def.additionalProperties ?? false,
41
- },
42
- },
43
- };
44
-
45
- // Normalize prompt: if string, wrap in function
46
- let promptFn: ToolPlugin["prompt"];
47
- if (typeof def.prompt === "string") {
48
- const staticPrompt = def.prompt;
49
- promptFn = () => staticPrompt;
50
- } else {
51
- promptFn = def.prompt;
52
- }
53
-
54
- return {
55
- name: def.name,
56
- config,
57
- execute: def.execute,
58
- prompt: promptFn,
59
- formatCompactParams: def.formatCompactParams,
60
- };
61
- }