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