wave-agent-sdk 1.0.5 → 1.0.7

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 (202) hide show
  1. package/dist/agent.d.ts +34 -1
  2. package/dist/agent.js +49 -4
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.js +1 -1
  5. package/dist/managers/aiManager.d.ts +36 -0
  6. package/dist/managers/aiManager.js +240 -11
  7. package/dist/managers/messageManager.d.ts +2 -4
  8. package/dist/managers/messageManager.js +14 -7
  9. package/dist/managers/slashCommandManager.js +28 -0
  10. package/dist/managers/subagentManager.d.ts +0 -2
  11. package/dist/services/aiService.d.ts +2 -3
  12. package/dist/services/aiService.js +4 -18
  13. package/dist/services/configurationService.d.ts +7 -0
  14. package/dist/services/configurationService.js +10 -0
  15. package/dist/telemetry/instrumentation.d.ts +1 -1
  16. package/dist/telemetry/instrumentation.js +57 -28
  17. package/dist/tools/bashTool.js +16 -6
  18. package/dist/tools/grepTool.js +11 -3
  19. package/dist/types/core.d.ts +1 -34
  20. package/dist/types/index.d.ts +4 -0
  21. package/dist/types/messaging.d.ts +2 -0
  22. package/dist/utils/cacheControlUtils.d.ts +5 -27
  23. package/dist/utils/cacheControlUtils.js +12 -73
  24. package/dist/utils/containerSetup.js +0 -3
  25. package/dist/utils/messageOperations.d.ts +2 -1
  26. package/dist/utils/messageOperations.js +2 -1
  27. package/dist/utils/notificationXml.js +5 -0
  28. package/dist/utils/ripgrep.d.ts +4 -4
  29. package/dist/utils/ripgrep.js +4 -27
  30. package/package.json +20 -8
  31. package/bin/rg +0 -79
  32. package/dist/managers/forkedAgentManager.d.ts +0 -50
  33. package/dist/managers/forkedAgentManager.js +0 -130
  34. package/scripts/install_ripgrep.js +0 -111
  35. package/scripts/postinstall.js +0 -38
  36. package/src/agent.ts +0 -1271
  37. package/src/constants/subagents.ts +0 -4
  38. package/src/constants/toolLimits.ts +0 -15
  39. package/src/constants/tools.ts +0 -24
  40. package/src/core/plugin.ts +0 -237
  41. package/src/core/session.ts +0 -9
  42. package/src/index.ts +0 -38
  43. package/src/managers/MemoryRuleManager.ts +0 -198
  44. package/src/managers/aiManager.ts +0 -2429
  45. package/src/managers/backgroundTaskManager.ts +0 -480
  46. package/src/managers/bangManager.ts +0 -119
  47. package/src/managers/cronManager.ts +0 -401
  48. package/src/managers/foregroundTaskManager.ts +0 -30
  49. package/src/managers/forkedAgentManager.ts +0 -216
  50. package/src/managers/hookManager.ts +0 -1089
  51. package/src/managers/liveConfigManager.ts +0 -445
  52. package/src/managers/lspManager.ts +0 -470
  53. package/src/managers/mcpManager.ts +0 -1010
  54. package/src/managers/messageManager.ts +0 -1184
  55. package/src/managers/messageQueue.ts +0 -157
  56. package/src/managers/permissionManager.ts +0 -1140
  57. package/src/managers/planManager.ts +0 -112
  58. package/src/managers/pluginManager.ts +0 -330
  59. package/src/managers/pluginScopeManager.ts +0 -124
  60. package/src/managers/reversionManager.ts +0 -178
  61. package/src/managers/skillManager.ts +0 -578
  62. package/src/managers/slashCommandManager.ts +0 -564
  63. package/src/managers/subagentManager.ts +0 -915
  64. package/src/managers/toolManager.ts +0 -489
  65. package/src/managers/workflowManager.ts +0 -493
  66. package/src/prompts/autoMemory.ts +0 -33
  67. package/src/prompts/autoMemoryExtraction.ts +0 -146
  68. package/src/prompts/index.ts +0 -506
  69. package/src/prompts/planModeReminders.ts +0 -126
  70. package/src/services/GitService.ts +0 -131
  71. package/src/services/MarketplaceService.ts +0 -1052
  72. package/src/services/MemoryRuleService.ts +0 -71
  73. package/src/services/aiService.ts +0 -919
  74. package/src/services/authService.ts +0 -565
  75. package/src/services/autoMemoryService.ts +0 -280
  76. package/src/services/configurationService.ts +0 -1520
  77. package/src/services/fileWatcher.ts +0 -319
  78. package/src/services/hook.ts +0 -348
  79. package/src/services/initializationService.ts +0 -357
  80. package/src/services/interactionService.ts +0 -229
  81. package/src/services/jsonlHandler.ts +0 -295
  82. package/src/services/memory.ts +0 -234
  83. package/src/services/pluginLoader.ts +0 -252
  84. package/src/services/remoteSettingsService.ts +0 -366
  85. package/src/services/reversionService.ts +0 -122
  86. package/src/services/session.ts +0 -917
  87. package/src/services/taskManager.ts +0 -321
  88. package/src/telemetry/events.ts +0 -65
  89. package/src/telemetry/instrumentation.ts +0 -499
  90. package/src/telemetry/sessionTracing.ts +0 -348
  91. package/src/tools/agentTool.ts +0 -322
  92. package/src/tools/askUserQuestion.ts +0 -160
  93. package/src/tools/bashTool.ts +0 -621
  94. package/src/tools/buildTool.ts +0 -61
  95. package/src/tools/cronCreateTool.ts +0 -161
  96. package/src/tools/cronDeleteTool.ts +0 -51
  97. package/src/tools/cronListTool.ts +0 -47
  98. package/src/tools/editTool.ts +0 -313
  99. package/src/tools/enterPlanMode.ts +0 -124
  100. package/src/tools/enterWorktreeTool.ts +0 -190
  101. package/src/tools/exitPlanMode.ts +0 -139
  102. package/src/tools/exitWorktreeTool.ts +0 -236
  103. package/src/tools/globTool.ts +0 -167
  104. package/src/tools/grepTool.ts +0 -399
  105. package/src/tools/lspTool.ts +0 -883
  106. package/src/tools/readTool.ts +0 -426
  107. package/src/tools/skillTool.ts +0 -248
  108. package/src/tools/taskManagementTools.ts +0 -574
  109. package/src/tools/taskStopTool.ts +0 -78
  110. package/src/tools/types.ts +0 -137
  111. package/src/tools/webFetchTool.ts +0 -373
  112. package/src/tools/workflowTool.ts +0 -205
  113. package/src/tools/writeTool.ts +0 -267
  114. package/src/types/agent.ts +0 -128
  115. package/src/types/auth.ts +0 -19
  116. package/src/types/commands.ts +0 -30
  117. package/src/types/config.ts +0 -43
  118. package/src/types/configuration.ts +0 -168
  119. package/src/types/core.ts +0 -105
  120. package/src/types/cron.ts +0 -13
  121. package/src/types/environment.ts +0 -106
  122. package/src/types/fileSearch.ts +0 -4
  123. package/src/types/history.ts +0 -7
  124. package/src/types/hooks.ts +0 -256
  125. package/src/types/index.ts +0 -41
  126. package/src/types/lsp.ts +0 -98
  127. package/src/types/marketplace.ts +0 -71
  128. package/src/types/mcp.ts +0 -43
  129. package/src/types/memoryRule.ts +0 -31
  130. package/src/types/messaging.ts +0 -114
  131. package/src/types/permissions.ts +0 -80
  132. package/src/types/plugins.ts +0 -39
  133. package/src/types/processes.ts +0 -99
  134. package/src/types/reversion.ts +0 -29
  135. package/src/types/session.ts +0 -12
  136. package/src/types/skills.ts +0 -108
  137. package/src/types/tasks.ts +0 -13
  138. package/src/types/telemetry.ts +0 -98
  139. package/src/types/tools.ts +0 -44
  140. package/src/types/workflow.ts +0 -6
  141. package/src/utils/abortUtils.ts +0 -118
  142. package/src/utils/atomicWrite.ts +0 -61
  143. package/src/utils/bashParser.ts +0 -899
  144. package/src/utils/cacheControlUtils.ts +0 -426
  145. package/src/utils/commandArgumentParser.ts +0 -161
  146. package/src/utils/commandPathResolver.ts +0 -88
  147. package/src/utils/configPaths.ts +0 -220
  148. package/src/utils/configValidator.ts +0 -100
  149. package/src/utils/constants.ts +0 -39
  150. package/src/utils/container.ts +0 -92
  151. package/src/utils/containerSetup.ts +0 -371
  152. package/src/utils/convertMessagesForAPI.ts +0 -326
  153. package/src/utils/cronTasks.ts +0 -128
  154. package/src/utils/cronTasksLock.ts +0 -182
  155. package/src/utils/cronToHuman.ts +0 -99
  156. package/src/utils/customCommands.ts +0 -95
  157. package/src/utils/editUtils.ts +0 -20
  158. package/src/utils/fileFormat.ts +0 -40
  159. package/src/utils/fileSearch.ts +0 -151
  160. package/src/utils/fileUtils.ts +0 -223
  161. package/src/utils/gitUtils.ts +0 -315
  162. package/src/utils/globalLogger.ts +0 -128
  163. package/src/utils/groupMessagesByApiRound.ts +0 -121
  164. package/src/utils/hookMatcher.ts +0 -176
  165. package/src/utils/markdownParser.ts +0 -317
  166. package/src/utils/mcpUtils.ts +0 -138
  167. package/src/utils/messageOperations.ts +0 -659
  168. package/src/utils/modelCapabilities.ts +0 -30
  169. package/src/utils/nameGenerator.ts +0 -99
  170. package/src/utils/notificationXml.ts +0 -57
  171. package/src/utils/openaiClient.ts +0 -251
  172. package/src/utils/parseCronExpression.ts +0 -78
  173. package/src/utils/path.ts +0 -72
  174. package/src/utils/pathEncoder.ts +0 -400
  175. package/src/utils/pathSafety.ts +0 -40
  176. package/src/utils/promptHistory.ts +0 -168
  177. package/src/utils/ripgrep.ts +0 -35
  178. package/src/utils/shellResolver.ts +0 -190
  179. package/src/utils/skillParser.ts +0 -246
  180. package/src/utils/stringUtils.ts +0 -156
  181. package/src/utils/subagentParser.ts +0 -279
  182. package/src/utils/taskReminder.ts +0 -96
  183. package/src/utils/tokenCalculation.ts +0 -43
  184. package/src/utils/tokenEstimate.ts +0 -34
  185. package/src/utils/toolResultStorage.ts +0 -117
  186. package/src/utils/worktreeSession.ts +0 -26
  187. package/src/utils/worktreeUtils.ts +0 -786
  188. package/src/workflow/budgetTracker.ts +0 -34
  189. package/src/workflow/concurrencyLimiter.ts +0 -47
  190. package/src/workflow/journal.ts +0 -95
  191. package/src/workflow/progressReporter.ts +0 -141
  192. package/src/workflow/runState.ts +0 -65
  193. package/src/workflow/scriptRuntime.ts +0 -274
  194. package/src/workflow/structuredOutput.ts +0 -123
  195. package/src/workflow/types.ts +0 -101
  196. package/src/workflow/workflowApis.ts +0 -410
  197. package/vendor/ripgrep/linux-aarch64/rg +0 -0
  198. package/vendor/ripgrep/linux-x86_64/rg +0 -0
  199. package/vendor/ripgrep/macos-aarch64/rg +0 -0
  200. package/vendor/ripgrep/macos-x86_64/rg +0 -0
  201. package/vendor/ripgrep/windows-aarch64/rg.exe +0 -0
  202. 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
- }