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.
- package/dist/agent.d.ts +23 -1
- package/dist/agent.js +34 -4
- package/dist/managers/aiManager.d.ts +36 -0
- package/dist/managers/aiManager.js +240 -8
- package/dist/managers/messageManager.d.ts +1 -3
- package/dist/managers/messageManager.js +19 -9
- package/dist/managers/slashCommandManager.js +28 -0
- package/dist/managers/subagentManager.d.ts +0 -2
- package/dist/services/session.js +2 -9
- package/dist/telemetry/instrumentation.d.ts +1 -1
- package/dist/telemetry/instrumentation.js +57 -28
- package/dist/tools/bashTool.js +16 -6
- package/dist/tools/grepTool.js +11 -3
- package/dist/types/index.d.ts +3 -0
- package/dist/types/messaging.d.ts +2 -0
- package/dist/utils/containerSetup.js +0 -3
- package/dist/utils/messageOperations.d.ts +10 -1
- package/dist/utils/messageOperations.js +19 -1
- package/dist/utils/notificationXml.js +5 -0
- package/dist/utils/ripgrep.d.ts +4 -4
- package/dist/utils/ripgrep.js +4 -27
- package/package.json +20 -8
- package/bin/rg +0 -79
- package/dist/managers/forkedAgentManager.d.ts +0 -50
- package/dist/managers/forkedAgentManager.js +0 -130
- package/scripts/install_ripgrep.js +0 -111
- package/scripts/postinstall.js +0 -38
- package/src/agent.ts +0 -1271
- package/src/constants/subagents.ts +0 -4
- package/src/constants/toolLimits.ts +0 -15
- package/src/constants/tools.ts +0 -24
- package/src/core/plugin.ts +0 -237
- package/src/core/session.ts +0 -9
- package/src/index.ts +0 -38
- package/src/managers/MemoryRuleManager.ts +0 -198
- package/src/managers/aiManager.ts +0 -2429
- package/src/managers/backgroundTaskManager.ts +0 -480
- package/src/managers/bangManager.ts +0 -119
- package/src/managers/cronManager.ts +0 -401
- package/src/managers/foregroundTaskManager.ts +0 -30
- package/src/managers/forkedAgentManager.ts +0 -216
- package/src/managers/hookManager.ts +0 -1089
- package/src/managers/liveConfigManager.ts +0 -445
- package/src/managers/lspManager.ts +0 -470
- package/src/managers/mcpManager.ts +0 -1010
- package/src/managers/messageManager.ts +0 -1180
- package/src/managers/messageQueue.ts +0 -157
- package/src/managers/permissionManager.ts +0 -1140
- package/src/managers/planManager.ts +0 -112
- package/src/managers/pluginManager.ts +0 -330
- package/src/managers/pluginScopeManager.ts +0 -124
- package/src/managers/reversionManager.ts +0 -178
- package/src/managers/skillManager.ts +0 -578
- package/src/managers/slashCommandManager.ts +0 -564
- package/src/managers/subagentManager.ts +0 -915
- package/src/managers/toolManager.ts +0 -489
- package/src/managers/workflowManager.ts +0 -493
- package/src/prompts/autoMemory.ts +0 -33
- package/src/prompts/autoMemoryExtraction.ts +0 -146
- package/src/prompts/index.ts +0 -506
- package/src/prompts/planModeReminders.ts +0 -126
- package/src/services/GitService.ts +0 -131
- package/src/services/MarketplaceService.ts +0 -1052
- package/src/services/MemoryRuleService.ts +0 -71
- package/src/services/aiService.ts +0 -919
- package/src/services/authService.ts +0 -565
- package/src/services/autoMemoryService.ts +0 -280
- package/src/services/configurationService.ts +0 -1520
- package/src/services/fileWatcher.ts +0 -319
- package/src/services/hook.ts +0 -348
- package/src/services/initializationService.ts +0 -357
- package/src/services/interactionService.ts +0 -229
- package/src/services/jsonlHandler.ts +0 -295
- package/src/services/memory.ts +0 -234
- package/src/services/pluginLoader.ts +0 -252
- package/src/services/remoteSettingsService.ts +0 -366
- package/src/services/reversionService.ts +0 -122
- package/src/services/session.ts +0 -922
- package/src/services/taskManager.ts +0 -321
- package/src/telemetry/events.ts +0 -65
- package/src/telemetry/instrumentation.ts +0 -499
- package/src/telemetry/sessionTracing.ts +0 -348
- package/src/tools/agentTool.ts +0 -322
- package/src/tools/askUserQuestion.ts +0 -160
- package/src/tools/bashTool.ts +0 -621
- package/src/tools/buildTool.ts +0 -61
- package/src/tools/cronCreateTool.ts +0 -161
- package/src/tools/cronDeleteTool.ts +0 -51
- package/src/tools/cronListTool.ts +0 -47
- package/src/tools/editTool.ts +0 -313
- package/src/tools/enterPlanMode.ts +0 -124
- package/src/tools/enterWorktreeTool.ts +0 -190
- package/src/tools/exitPlanMode.ts +0 -139
- package/src/tools/exitWorktreeTool.ts +0 -236
- package/src/tools/globTool.ts +0 -167
- package/src/tools/grepTool.ts +0 -399
- package/src/tools/lspTool.ts +0 -883
- package/src/tools/readTool.ts +0 -426
- package/src/tools/skillTool.ts +0 -248
- package/src/tools/taskManagementTools.ts +0 -574
- package/src/tools/taskStopTool.ts +0 -78
- package/src/tools/types.ts +0 -137
- package/src/tools/webFetchTool.ts +0 -373
- package/src/tools/workflowTool.ts +0 -205
- package/src/tools/writeTool.ts +0 -267
- package/src/types/agent.ts +0 -128
- package/src/types/auth.ts +0 -19
- package/src/types/commands.ts +0 -30
- package/src/types/config.ts +0 -43
- package/src/types/configuration.ts +0 -168
- package/src/types/core.ts +0 -105
- package/src/types/cron.ts +0 -13
- package/src/types/environment.ts +0 -106
- package/src/types/fileSearch.ts +0 -4
- package/src/types/history.ts +0 -7
- package/src/types/hooks.ts +0 -256
- package/src/types/index.ts +0 -41
- package/src/types/lsp.ts +0 -98
- package/src/types/marketplace.ts +0 -71
- package/src/types/mcp.ts +0 -43
- package/src/types/memoryRule.ts +0 -31
- package/src/types/messaging.ts +0 -114
- package/src/types/permissions.ts +0 -80
- package/src/types/plugins.ts +0 -39
- package/src/types/processes.ts +0 -99
- package/src/types/reversion.ts +0 -29
- package/src/types/session.ts +0 -12
- package/src/types/skills.ts +0 -108
- package/src/types/tasks.ts +0 -13
- package/src/types/telemetry.ts +0 -98
- package/src/types/tools.ts +0 -44
- package/src/types/workflow.ts +0 -6
- package/src/utils/abortUtils.ts +0 -118
- package/src/utils/atomicWrite.ts +0 -61
- package/src/utils/bashParser.ts +0 -899
- package/src/utils/cacheControlUtils.ts +0 -426
- package/src/utils/commandArgumentParser.ts +0 -161
- package/src/utils/commandPathResolver.ts +0 -88
- package/src/utils/configPaths.ts +0 -220
- package/src/utils/configValidator.ts +0 -100
- package/src/utils/constants.ts +0 -39
- package/src/utils/container.ts +0 -92
- package/src/utils/containerSetup.ts +0 -371
- package/src/utils/convertMessagesForAPI.ts +0 -326
- package/src/utils/cronTasks.ts +0 -128
- package/src/utils/cronTasksLock.ts +0 -182
- package/src/utils/cronToHuman.ts +0 -99
- package/src/utils/customCommands.ts +0 -95
- package/src/utils/editUtils.ts +0 -20
- package/src/utils/fileFormat.ts +0 -40
- package/src/utils/fileSearch.ts +0 -151
- package/src/utils/fileUtils.ts +0 -223
- package/src/utils/gitUtils.ts +0 -315
- package/src/utils/globalLogger.ts +0 -128
- package/src/utils/groupMessagesByApiRound.ts +0 -121
- package/src/utils/hookMatcher.ts +0 -176
- package/src/utils/markdownParser.ts +0 -317
- package/src/utils/mcpUtils.ts +0 -138
- package/src/utils/messageOperations.ts +0 -641
- package/src/utils/modelCapabilities.ts +0 -30
- package/src/utils/nameGenerator.ts +0 -99
- package/src/utils/notificationXml.ts +0 -57
- package/src/utils/openaiClient.ts +0 -251
- package/src/utils/parseCronExpression.ts +0 -78
- package/src/utils/path.ts +0 -72
- package/src/utils/pathEncoder.ts +0 -400
- package/src/utils/pathSafety.ts +0 -40
- package/src/utils/promptHistory.ts +0 -168
- package/src/utils/ripgrep.ts +0 -35
- package/src/utils/shellResolver.ts +0 -190
- package/src/utils/skillParser.ts +0 -246
- package/src/utils/stringUtils.ts +0 -156
- package/src/utils/subagentParser.ts +0 -279
- package/src/utils/taskReminder.ts +0 -96
- package/src/utils/tokenCalculation.ts +0 -43
- package/src/utils/tokenEstimate.ts +0 -34
- package/src/utils/toolResultStorage.ts +0 -117
- package/src/utils/worktreeSession.ts +0 -26
- package/src/utils/worktreeUtils.ts +0 -786
- package/src/workflow/budgetTracker.ts +0 -34
- package/src/workflow/concurrencyLimiter.ts +0 -47
- package/src/workflow/journal.ts +0 -95
- package/src/workflow/progressReporter.ts +0 -141
- package/src/workflow/runState.ts +0 -65
- package/src/workflow/scriptRuntime.ts +0 -274
- package/src/workflow/structuredOutput.ts +0 -123
- package/src/workflow/types.ts +0 -101
- package/src/workflow/workflowApis.ts +0 -410
- package/vendor/ripgrep/linux-aarch64/rg +0 -0
- package/vendor/ripgrep/linux-x86_64/rg +0 -0
- package/vendor/ripgrep/macos-aarch64/rg +0 -0
- package/vendor/ripgrep/macos-x86_64/rg +0 -0
- package/vendor/ripgrep/windows-aarch64/rg.exe +0 -0
- package/vendor/ripgrep/windows-x86_64/rg.exe +0 -0
package/src/tools/bashTool.ts
DELETED
|
@@ -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
|
-
};
|
package/src/tools/buildTool.ts
DELETED
|
@@ -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
|
-
}
|