wave-agent-sdk 0.0.8 → 0.0.11
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 +92 -23
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +351 -137
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/managers/aiManager.d.ts +14 -36
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +74 -77
- package/dist/managers/backgroundBashManager.d.ts.map +1 -1
- package/dist/managers/backgroundBashManager.js +4 -3
- package/dist/managers/hookManager.d.ts +3 -8
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +39 -29
- package/dist/managers/liveConfigManager.d.ts +55 -18
- package/dist/managers/liveConfigManager.d.ts.map +1 -1
- package/dist/managers/liveConfigManager.js +372 -90
- package/dist/managers/lspManager.d.ts +43 -0
- package/dist/managers/lspManager.d.ts.map +1 -0
- package/dist/managers/lspManager.js +326 -0
- package/dist/managers/messageManager.d.ts +8 -16
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +52 -74
- package/dist/managers/permissionManager.d.ts +75 -0
- package/dist/managers/permissionManager.d.ts.map +1 -0
- package/dist/managers/permissionManager.js +368 -0
- package/dist/managers/skillManager.d.ts +1 -0
- package/dist/managers/skillManager.d.ts.map +1 -1
- package/dist/managers/skillManager.js +2 -1
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +0 -1
- package/dist/managers/subagentManager.d.ts +8 -23
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +97 -117
- package/dist/managers/toolManager.d.ts +38 -1
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +66 -2
- package/dist/services/aiService.d.ts +3 -1
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +123 -30
- package/dist/services/configurationService.d.ts +116 -0
- package/dist/services/configurationService.d.ts.map +1 -0
- package/dist/services/configurationService.js +585 -0
- package/dist/services/fileWatcher.d.ts.map +1 -1
- package/dist/services/fileWatcher.js +5 -6
- package/dist/services/hook.d.ts +7 -124
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +46 -458
- package/dist/services/jsonlHandler.d.ts +24 -15
- package/dist/services/jsonlHandler.d.ts.map +1 -1
- package/dist/services/jsonlHandler.js +67 -88
- package/dist/services/memory.d.ts +0 -9
- package/dist/services/memory.d.ts.map +1 -1
- package/dist/services/memory.js +2 -49
- package/dist/services/session.d.ts +82 -33
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +275 -181
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +109 -11
- package/dist/tools/deleteFileTool.d.ts.map +1 -1
- package/dist/tools/deleteFileTool.js +25 -0
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +30 -6
- package/dist/tools/lspTool.d.ts +6 -0
- package/dist/tools/lspTool.d.ts.map +1 -0
- package/dist/tools/lspTool.js +589 -0
- package/dist/tools/multiEditTool.d.ts.map +1 -1
- package/dist/tools/multiEditTool.js +26 -7
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +111 -2
- package/dist/tools/skillTool.js +2 -2
- package/dist/tools/todoWriteTool.d.ts.map +1 -1
- package/dist/tools/todoWriteTool.js +23 -0
- package/dist/tools/types.d.ts +11 -8
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/writeTool.d.ts.map +1 -1
- package/dist/tools/writeTool.js +25 -9
- package/dist/types/commands.d.ts +0 -1
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/config.d.ts +4 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/configuration.d.ts +69 -0
- package/dist/types/configuration.d.ts.map +1 -0
- package/dist/types/configuration.js +8 -0
- package/dist/types/core.d.ts +10 -0
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/environment.d.ts +41 -0
- package/dist/types/environment.d.ts.map +1 -1
- package/dist/types/fileSearch.d.ts +5 -0
- package/dist/types/fileSearch.d.ts.map +1 -0
- package/dist/types/fileSearch.js +1 -0
- package/dist/types/hooks.d.ts +11 -2
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +1 -7
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +5 -0
- package/dist/types/lsp.d.ts +90 -0
- package/dist/types/lsp.d.ts.map +1 -0
- package/dist/types/lsp.js +4 -0
- package/dist/types/messaging.d.ts +6 -11
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/permissions.d.ts +39 -0
- package/dist/types/permissions.d.ts.map +1 -0
- package/dist/types/permissions.js +12 -0
- package/dist/types/session.d.ts +1 -6
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/skills.d.ts +1 -0
- package/dist/types/skills.d.ts.map +1 -1
- package/dist/types/tools.d.ts +35 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +4 -0
- package/dist/utils/abortUtils.d.ts +34 -0
- package/dist/utils/abortUtils.d.ts.map +1 -0
- package/dist/utils/abortUtils.js +92 -0
- package/dist/utils/bashHistory.d.ts +4 -0
- package/dist/utils/bashHistory.d.ts.map +1 -1
- package/dist/utils/bashHistory.js +21 -4
- package/dist/utils/bashParser.d.ts +24 -0
- package/dist/utils/bashParser.d.ts.map +1 -0
- package/dist/utils/bashParser.js +413 -0
- package/dist/utils/builtinSubagents.d.ts +7 -0
- package/dist/utils/builtinSubagents.d.ts.map +1 -0
- package/dist/utils/builtinSubagents.js +65 -0
- package/dist/utils/cacheControlUtils.d.ts +8 -33
- package/dist/utils/cacheControlUtils.d.ts.map +1 -1
- package/dist/utils/cacheControlUtils.js +83 -126
- package/dist/utils/constants.d.ts +0 -12
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +1 -13
- package/dist/utils/convertMessagesForAPI.d.ts +2 -1
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +33 -14
- package/dist/utils/fileSearch.d.ts +14 -0
- package/dist/utils/fileSearch.d.ts.map +1 -0
- package/dist/utils/fileSearch.js +88 -0
- package/dist/utils/fileUtils.d.ts +14 -2
- package/dist/utils/fileUtils.d.ts.map +1 -1
- package/dist/utils/fileUtils.js +101 -17
- package/dist/utils/globalLogger.d.ts +0 -14
- package/dist/utils/globalLogger.d.ts.map +1 -1
- package/dist/utils/globalLogger.js +0 -16
- package/dist/utils/markdownParser.d.ts.map +1 -1
- package/dist/utils/markdownParser.js +1 -17
- package/dist/utils/messageOperations.d.ts +1 -11
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +7 -24
- package/dist/utils/pathEncoder.d.ts +4 -0
- package/dist/utils/pathEncoder.d.ts.map +1 -1
- package/dist/utils/pathEncoder.js +16 -9
- package/dist/utils/pathSafety.d.ts +10 -0
- package/dist/utils/pathSafety.d.ts.map +1 -0
- package/dist/utils/pathSafety.js +23 -0
- package/dist/utils/subagentParser.d.ts +2 -2
- package/dist/utils/subagentParser.d.ts.map +1 -1
- package/dist/utils/subagentParser.js +10 -7
- package/package.json +9 -9
- package/src/agent.ts +475 -216
- package/src/index.ts +3 -0
- package/src/managers/aiManager.ts +107 -111
- package/src/managers/backgroundBashManager.ts +4 -3
- package/src/managers/hookManager.ts +44 -39
- package/src/managers/liveConfigManager.ts +524 -138
- package/src/managers/lspManager.ts +434 -0
- package/src/managers/messageManager.ts +73 -103
- package/src/managers/permissionManager.ts +480 -0
- package/src/managers/skillManager.ts +3 -1
- package/src/managers/slashCommandManager.ts +1 -2
- package/src/managers/subagentManager.ts +116 -159
- package/src/managers/toolManager.ts +95 -3
- package/src/services/aiService.ts +207 -26
- package/src/services/configurationService.ts +762 -0
- package/src/services/fileWatcher.ts +5 -6
- package/src/services/hook.ts +50 -631
- package/src/services/jsonlHandler.ts +84 -100
- package/src/services/memory.ts +2 -59
- package/src/services/session.ts +338 -213
- package/src/tools/bashTool.ts +126 -13
- package/src/tools/deleteFileTool.ts +36 -0
- package/src/tools/editTool.ts +41 -7
- package/src/tools/lspTool.ts +760 -0
- package/src/tools/multiEditTool.ts +37 -8
- package/src/tools/readTool.ts +125 -2
- package/src/tools/skillTool.ts +2 -2
- package/src/tools/todoWriteTool.ts +33 -1
- package/src/tools/types.ts +15 -9
- package/src/tools/writeTool.ts +36 -10
- package/src/types/commands.ts +0 -1
- package/src/types/config.ts +5 -0
- package/src/types/configuration.ts +73 -0
- package/src/types/core.ts +11 -0
- package/src/types/environment.ts +44 -0
- package/src/types/fileSearch.ts +4 -0
- package/src/types/hooks.ts +14 -11
- package/src/types/index.ts +5 -0
- package/src/types/lsp.ts +96 -0
- package/src/types/messaging.ts +8 -13
- package/src/types/permissions.ts +52 -0
- package/src/types/session.ts +3 -8
- package/src/types/skills.ts +1 -0
- package/src/types/tools.ts +38 -0
- package/src/utils/abortUtils.ts +118 -0
- package/src/utils/bashHistory.ts +28 -4
- package/src/utils/bashParser.ts +444 -0
- package/src/utils/builtinSubagents.ts +71 -0
- package/src/utils/cacheControlUtils.ts +106 -171
- package/src/utils/constants.ts +1 -16
- package/src/utils/convertMessagesForAPI.ts +38 -14
- package/src/utils/fileSearch.ts +107 -0
- package/src/utils/fileUtils.ts +114 -19
- package/src/utils/globalLogger.ts +0 -17
- package/src/utils/markdownParser.ts +1 -19
- package/src/utils/messageOperations.ts +7 -35
- package/src/utils/pathEncoder.ts +24 -9
- package/src/utils/pathSafety.ts +26 -0
- package/src/utils/subagentParser.ts +11 -8
- package/dist/constants/events.d.ts +0 -28
- package/dist/constants/events.d.ts.map +0 -1
- package/dist/constants/events.js +0 -27
- package/dist/services/configurationWatcher.d.ts +0 -120
- package/dist/services/configurationWatcher.d.ts.map +0 -1
- package/dist/services/configurationWatcher.js +0 -439
- package/dist/services/memoryStore.d.ts +0 -81
- package/dist/services/memoryStore.d.ts.map +0 -1
- package/dist/services/memoryStore.js +0 -200
- package/dist/types/memoryStore.d.ts +0 -82
- package/dist/types/memoryStore.d.ts.map +0 -1
- package/dist/types/memoryStore.js +0 -7
- package/dist/utils/configResolver.d.ts +0 -65
- package/dist/utils/configResolver.d.ts.map +0 -1
- package/dist/utils/configResolver.js +0 -210
- package/src/constants/events.ts +0 -38
- package/src/services/configurationWatcher.ts +0 -622
- package/src/services/memoryStore.ts +0 -279
- package/src/types/memoryStore.ts +0 -94
- package/src/utils/configResolver.ts +0 -302
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission Manager for handling tool permission checks
|
|
3
|
+
*
|
|
4
|
+
* This manager provides utilities for checking permissions before tool execution.
|
|
5
|
+
* It implements the permission logic for different modes (default vs bypass) and
|
|
6
|
+
* handles custom callback integration.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import type {
|
|
11
|
+
PermissionDecision,
|
|
12
|
+
ToolPermissionContext,
|
|
13
|
+
PermissionCallback,
|
|
14
|
+
PermissionMode,
|
|
15
|
+
} from "../types/permissions.js";
|
|
16
|
+
import { RESTRICTED_TOOLS } from "../types/permissions.js";
|
|
17
|
+
import type { Logger } from "../types/index.js";
|
|
18
|
+
import {
|
|
19
|
+
splitBashCommand,
|
|
20
|
+
stripEnvVars,
|
|
21
|
+
stripRedirections,
|
|
22
|
+
getSmartPrefix,
|
|
23
|
+
DANGEROUS_COMMANDS,
|
|
24
|
+
} from "../utils/bashParser.js";
|
|
25
|
+
import { isPathInside } from "../utils/pathSafety.js";
|
|
26
|
+
|
|
27
|
+
const SAFE_COMMANDS = ["cd", "ls", "pwd"];
|
|
28
|
+
|
|
29
|
+
export interface PermissionManagerOptions {
|
|
30
|
+
/** Logger for debugging permission decisions */
|
|
31
|
+
logger?: Logger;
|
|
32
|
+
/** Configured default permission mode from settings */
|
|
33
|
+
configuredDefaultMode?: PermissionMode;
|
|
34
|
+
/** Allowed rules from settings */
|
|
35
|
+
allowedRules?: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class PermissionManager {
|
|
39
|
+
private logger?: Logger;
|
|
40
|
+
private configuredDefaultMode?: PermissionMode;
|
|
41
|
+
private allowedRules: string[] = [];
|
|
42
|
+
private onConfiguredDefaultModeChange?: (mode: PermissionMode) => void;
|
|
43
|
+
|
|
44
|
+
constructor(options: PermissionManagerOptions = {}) {
|
|
45
|
+
this.logger = options.logger;
|
|
46
|
+
this.configuredDefaultMode = options.configuredDefaultMode;
|
|
47
|
+
this.allowedRules = options.allowedRules || [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Set a callback to be notified when the effective permission mode changes due to configuration updates
|
|
52
|
+
*/
|
|
53
|
+
public setOnConfiguredDefaultModeChange(
|
|
54
|
+
callback: (mode: PermissionMode) => void,
|
|
55
|
+
): void {
|
|
56
|
+
this.onConfiguredDefaultModeChange = callback;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Update the configured default mode (e.g., when configuration reloads)
|
|
61
|
+
*/
|
|
62
|
+
updateConfiguredDefaultMode(defaultMode?: PermissionMode): void {
|
|
63
|
+
const oldEffectiveMode = this.getCurrentEffectiveMode();
|
|
64
|
+
|
|
65
|
+
this.logger?.debug("Updating configured default permission mode", {
|
|
66
|
+
previous: this.configuredDefaultMode,
|
|
67
|
+
new: defaultMode,
|
|
68
|
+
});
|
|
69
|
+
this.configuredDefaultMode = defaultMode;
|
|
70
|
+
|
|
71
|
+
const newEffectiveMode = this.getCurrentEffectiveMode();
|
|
72
|
+
if (
|
|
73
|
+
oldEffectiveMode !== newEffectiveMode &&
|
|
74
|
+
this.onConfiguredDefaultModeChange
|
|
75
|
+
) {
|
|
76
|
+
this.logger?.debug(
|
|
77
|
+
"Effective permission mode changed due to configuration update",
|
|
78
|
+
{
|
|
79
|
+
oldMode: oldEffectiveMode,
|
|
80
|
+
newMode: newEffectiveMode,
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
this.onConfiguredDefaultModeChange(newEffectiveMode);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get all currently allowed rules
|
|
89
|
+
*/
|
|
90
|
+
public getAllowedRules(): string[] {
|
|
91
|
+
return [...this.allowedRules];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Update the allowed rules (e.g., when configuration reloads)
|
|
96
|
+
*/
|
|
97
|
+
updateAllowedRules(rules: string[]): void {
|
|
98
|
+
this.logger?.debug("Updating allowed permission rules", {
|
|
99
|
+
count: rules.length,
|
|
100
|
+
});
|
|
101
|
+
this.allowedRules = rules;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get the current effective permission mode for tool execution context
|
|
106
|
+
*/
|
|
107
|
+
getCurrentEffectiveMode(cliPermissionMode?: PermissionMode): PermissionMode {
|
|
108
|
+
return this.resolveEffectivePermissionMode(cliPermissionMode);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Resolve the effective permission mode based on CLI override and configured default
|
|
113
|
+
*/
|
|
114
|
+
resolveEffectivePermissionMode(
|
|
115
|
+
cliPermissionMode?: PermissionMode,
|
|
116
|
+
): PermissionMode {
|
|
117
|
+
// CLI override takes highest precedence
|
|
118
|
+
if (cliPermissionMode !== undefined) {
|
|
119
|
+
this.logger?.debug("Using CLI permission mode override", {
|
|
120
|
+
cliMode: cliPermissionMode,
|
|
121
|
+
configuredDefault: this.configuredDefaultMode,
|
|
122
|
+
});
|
|
123
|
+
return cliPermissionMode;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Use configured default mode if available
|
|
127
|
+
if (this.configuredDefaultMode !== undefined) {
|
|
128
|
+
this.logger?.debug("Using configured default permission mode", {
|
|
129
|
+
configuredDefault: this.configuredDefaultMode,
|
|
130
|
+
});
|
|
131
|
+
return this.configuredDefaultMode;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Fall back to system default
|
|
135
|
+
this.logger?.debug("Using system default permission mode");
|
|
136
|
+
return "default";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Check if a tool execution requires permission and handle the authorization flow
|
|
141
|
+
* Called by individual tools after validation/diff, before real operation
|
|
142
|
+
*/
|
|
143
|
+
async checkPermission(
|
|
144
|
+
context: ToolPermissionContext,
|
|
145
|
+
): Promise<PermissionDecision> {
|
|
146
|
+
this.logger?.debug("Checking permission for tool", {
|
|
147
|
+
toolName: context.toolName,
|
|
148
|
+
permissionMode: context.permissionMode,
|
|
149
|
+
hasCallback: !!context.canUseToolCallback,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// 1. If bypassPermissions mode, always allow
|
|
153
|
+
if (context.permissionMode === "bypassPermissions") {
|
|
154
|
+
this.logger?.debug("Permission bypassed for tool", {
|
|
155
|
+
toolName: context.toolName,
|
|
156
|
+
});
|
|
157
|
+
return { behavior: "allow" };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 1.1 If acceptEdits mode, allow Edit, MultiEdit, Delete, Write
|
|
161
|
+
if (context.permissionMode === "acceptEdits") {
|
|
162
|
+
const autoAcceptedTools = ["Edit", "MultiEdit", "Delete", "Write"];
|
|
163
|
+
if (autoAcceptedTools.includes(context.toolName)) {
|
|
164
|
+
this.logger?.debug(
|
|
165
|
+
"Permission automatically accepted for tool in acceptEdits mode",
|
|
166
|
+
{
|
|
167
|
+
toolName: context.toolName,
|
|
168
|
+
},
|
|
169
|
+
);
|
|
170
|
+
return { behavior: "allow" };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 1.2 Check if tool call matches any allowed rule
|
|
175
|
+
if (this.isAllowedByRule(context)) {
|
|
176
|
+
this.logger?.debug("Permission allowed by persistent rule", {
|
|
177
|
+
toolName: context.toolName,
|
|
178
|
+
});
|
|
179
|
+
return { behavior: "allow" };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 2. If not a restricted tool, always allow
|
|
183
|
+
if (!this.isRestrictedTool(context.toolName)) {
|
|
184
|
+
this.logger?.debug("Tool is not restricted, allowing", {
|
|
185
|
+
toolName: context.toolName,
|
|
186
|
+
});
|
|
187
|
+
return { behavior: "allow" };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 3. If custom callback provided, call it and return result
|
|
191
|
+
if (context.canUseToolCallback) {
|
|
192
|
+
try {
|
|
193
|
+
this.logger?.debug("Calling custom permission callback for tool", {
|
|
194
|
+
toolName: context.toolName,
|
|
195
|
+
});
|
|
196
|
+
const decision = await context.canUseToolCallback(context);
|
|
197
|
+
this.logger?.debug("Custom callback returned decision", {
|
|
198
|
+
toolName: context.toolName,
|
|
199
|
+
decision,
|
|
200
|
+
});
|
|
201
|
+
return decision;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
const errorMessage =
|
|
204
|
+
error instanceof Error ? error.message : String(error);
|
|
205
|
+
this.logger?.error("Error in permission callback", {
|
|
206
|
+
toolName: context.toolName,
|
|
207
|
+
error: errorMessage,
|
|
208
|
+
});
|
|
209
|
+
return {
|
|
210
|
+
behavior: "deny",
|
|
211
|
+
message: "Error in permission callback",
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 4. For default mode on restricted tools without callback, integrate with CLI confirmation
|
|
217
|
+
// Note: CLI confirmation integration will be implemented in Phase 2
|
|
218
|
+
this.logger?.warn(
|
|
219
|
+
"No permission callback provided for restricted tool in default mode",
|
|
220
|
+
{
|
|
221
|
+
toolName: context.toolName,
|
|
222
|
+
},
|
|
223
|
+
);
|
|
224
|
+
return {
|
|
225
|
+
behavior: "deny",
|
|
226
|
+
message: `Tool '${context.toolName}' requires permission approval. No permission callback configured.`,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Determine if a tool requires permission checks based on its name
|
|
232
|
+
*/
|
|
233
|
+
isRestrictedTool(toolName: string): boolean {
|
|
234
|
+
const isRestricted = (RESTRICTED_TOOLS as readonly string[]).includes(
|
|
235
|
+
toolName,
|
|
236
|
+
);
|
|
237
|
+
this.logger?.debug("Checking if tool is restricted", {
|
|
238
|
+
toolName,
|
|
239
|
+
isRestricted,
|
|
240
|
+
});
|
|
241
|
+
return isRestricted;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Helper method to create a permission context for CLI integration
|
|
246
|
+
*/
|
|
247
|
+
createContext(
|
|
248
|
+
toolName: string,
|
|
249
|
+
permissionMode: PermissionMode,
|
|
250
|
+
callback?: PermissionCallback,
|
|
251
|
+
toolInput?: Record<string, unknown>,
|
|
252
|
+
): ToolPermissionContext {
|
|
253
|
+
let suggestedPrefix: string | undefined;
|
|
254
|
+
if (toolName === "Bash" && toolInput?.command) {
|
|
255
|
+
const command = String(toolInput.command);
|
|
256
|
+
const parts = splitBashCommand(command);
|
|
257
|
+
// Only suggest prefix for single commands to avoid confusion with complex chains
|
|
258
|
+
if (parts.length === 1) {
|
|
259
|
+
const processedPart = stripRedirections(stripEnvVars(parts[0]));
|
|
260
|
+
suggestedPrefix = getSmartPrefix(processedPart) ?? undefined;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const context: ToolPermissionContext = {
|
|
265
|
+
toolName,
|
|
266
|
+
permissionMode,
|
|
267
|
+
canUseToolCallback: callback,
|
|
268
|
+
toolInput,
|
|
269
|
+
suggestedPrefix,
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// Set hidePersistentOption for dangerous or out-of-bounds bash commands
|
|
273
|
+
if (toolName === "Bash" && toolInput?.command) {
|
|
274
|
+
const command = String(toolInput.command);
|
|
275
|
+
const workdir = toolInput.workdir as string | undefined;
|
|
276
|
+
const parts = splitBashCommand(command);
|
|
277
|
+
|
|
278
|
+
const isDangerous = parts.some((part) => {
|
|
279
|
+
const processedPart = stripRedirections(stripEnvVars(part));
|
|
280
|
+
const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
|
|
281
|
+
if (commandMatch) {
|
|
282
|
+
const cmd = commandMatch[1];
|
|
283
|
+
const args = commandMatch[2]?.trim() || "";
|
|
284
|
+
|
|
285
|
+
// Check blacklist
|
|
286
|
+
if (DANGEROUS_COMMANDS.includes(cmd)) {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Check out-of-bounds for cd and ls
|
|
291
|
+
if (workdir && (cmd === "cd" || cmd === "ls")) {
|
|
292
|
+
const pathArgs =
|
|
293
|
+
(args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
|
|
294
|
+
(arg) => !arg.startsWith("-"),
|
|
295
|
+
) || [];
|
|
296
|
+
|
|
297
|
+
return pathArgs.some((pathArg) => {
|
|
298
|
+
const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
|
|
299
|
+
const absolutePath = path.resolve(workdir, cleanPath);
|
|
300
|
+
return !isPathInside(absolutePath, workdir);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return false;
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
if (isDangerous) {
|
|
308
|
+
context.hidePersistentOption = true;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
this.logger?.debug("Created permission context", {
|
|
313
|
+
toolName,
|
|
314
|
+
permissionMode,
|
|
315
|
+
hasCallback: !!callback,
|
|
316
|
+
hasToolInput: !!toolInput,
|
|
317
|
+
suggestedPrefix,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
return context;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Check if a tool call is allowed by persistent rules
|
|
325
|
+
*/
|
|
326
|
+
private isAllowedByRule(context: ToolPermissionContext): boolean {
|
|
327
|
+
if (context.toolName === "Bash" && context.toolInput?.command) {
|
|
328
|
+
const command = String(context.toolInput.command);
|
|
329
|
+
const parts = splitBashCommand(command);
|
|
330
|
+
if (parts.length === 0) return false;
|
|
331
|
+
|
|
332
|
+
const workdir = context.toolInput?.workdir as string | undefined;
|
|
333
|
+
|
|
334
|
+
return parts.every((part) => {
|
|
335
|
+
const processedPart = stripRedirections(stripEnvVars(part));
|
|
336
|
+
|
|
337
|
+
// Check for safe commands
|
|
338
|
+
const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
|
|
339
|
+
if (commandMatch) {
|
|
340
|
+
const cmd = commandMatch[1];
|
|
341
|
+
const args = commandMatch[2]?.trim() || "";
|
|
342
|
+
|
|
343
|
+
if (SAFE_COMMANDS.includes(cmd)) {
|
|
344
|
+
if (workdir) {
|
|
345
|
+
if (cmd === "pwd") {
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// For cd and ls, check paths
|
|
350
|
+
const pathArgs =
|
|
351
|
+
(args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
|
|
352
|
+
(arg) => !arg.startsWith("-"),
|
|
353
|
+
) || [];
|
|
354
|
+
|
|
355
|
+
if (pathArgs.length === 0) {
|
|
356
|
+
// cd or ls without arguments operates on current dir (workdir)
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const allPathsSafe = pathArgs.every((pathArg) => {
|
|
361
|
+
// Remove quotes if present
|
|
362
|
+
const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
|
|
363
|
+
const absolutePath = path.resolve(workdir, cleanPath);
|
|
364
|
+
return isPathInside(absolutePath, workdir);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
if (allPathsSafe) {
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const action = `${context.toolName}(${processedPart})`;
|
|
375
|
+
const allowedByRule = this.allowedRules.some((rule) => {
|
|
376
|
+
if (rule.endsWith(":*)")) {
|
|
377
|
+
const prefix = rule.slice(0, -3);
|
|
378
|
+
return action.startsWith(prefix);
|
|
379
|
+
}
|
|
380
|
+
return action === rule;
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
if (allowedByRule) return true;
|
|
384
|
+
return !this.isRestrictedTool(context.toolName);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
// Add other tools if needed in the future
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Expand a bash command into individual permission rules, filtering out safe commands.
|
|
393
|
+
* Used when saving permissions to the allow list.
|
|
394
|
+
*
|
|
395
|
+
* @param command The full bash command string
|
|
396
|
+
* @param workdir The working directory for path safety checks
|
|
397
|
+
* @returns Array of permission rules in "Bash(cmd)" format
|
|
398
|
+
*/
|
|
399
|
+
public expandBashRule(command: string, workdir: string): string[] {
|
|
400
|
+
const parts = splitBashCommand(command);
|
|
401
|
+
const rules: string[] = [];
|
|
402
|
+
|
|
403
|
+
for (const part of parts) {
|
|
404
|
+
const processedPart = stripRedirections(stripEnvVars(part));
|
|
405
|
+
|
|
406
|
+
// Check for safe commands
|
|
407
|
+
const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
|
|
408
|
+
let isSafe = false;
|
|
409
|
+
|
|
410
|
+
if (commandMatch) {
|
|
411
|
+
const cmd = commandMatch[1];
|
|
412
|
+
const args = commandMatch[2]?.trim() || "";
|
|
413
|
+
|
|
414
|
+
if (SAFE_COMMANDS.includes(cmd)) {
|
|
415
|
+
if (cmd === "pwd") {
|
|
416
|
+
isSafe = true;
|
|
417
|
+
} else {
|
|
418
|
+
// For cd and ls, check paths
|
|
419
|
+
const pathArgs =
|
|
420
|
+
(args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
|
|
421
|
+
(arg) => !arg.startsWith("-"),
|
|
422
|
+
) || [];
|
|
423
|
+
|
|
424
|
+
if (pathArgs.length === 0) {
|
|
425
|
+
isSafe = true;
|
|
426
|
+
} else {
|
|
427
|
+
const allPathsSafe = pathArgs.every((pathArg) => {
|
|
428
|
+
const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
|
|
429
|
+
const absolutePath = path.resolve(workdir, cleanPath);
|
|
430
|
+
return isPathInside(absolutePath, workdir);
|
|
431
|
+
});
|
|
432
|
+
if (allPathsSafe) {
|
|
433
|
+
isSafe = true;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (!isSafe) {
|
|
441
|
+
// Check if command is dangerous or out-of-bounds
|
|
442
|
+
const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
|
|
443
|
+
if (commandMatch) {
|
|
444
|
+
const cmd = commandMatch[1];
|
|
445
|
+
const args = commandMatch[2]?.trim() || "";
|
|
446
|
+
|
|
447
|
+
if (DANGEROUS_COMMANDS.includes(cmd)) {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (cmd === "cd" || cmd === "ls") {
|
|
452
|
+
const pathArgs =
|
|
453
|
+
(args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
|
|
454
|
+
(arg) => !arg.startsWith("-"),
|
|
455
|
+
) || [];
|
|
456
|
+
|
|
457
|
+
const isOutOfBounds = pathArgs.some((pathArg) => {
|
|
458
|
+
const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
|
|
459
|
+
const absolutePath = path.resolve(workdir, cleanPath);
|
|
460
|
+
return !isPathInside(absolutePath, workdir);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
if (isOutOfBounds) {
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const smartPrefix = getSmartPrefix(processedPart);
|
|
470
|
+
if (smartPrefix) {
|
|
471
|
+
rules.push(`Bash(${smartPrefix}:*)`);
|
|
472
|
+
} else {
|
|
473
|
+
rules.push(`Bash(${processedPart})`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return rules;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
@@ -20,6 +20,7 @@ export class SkillManager {
|
|
|
20
20
|
private personalSkillsPath: string;
|
|
21
21
|
private scanTimeout: number;
|
|
22
22
|
private logger?: Logger;
|
|
23
|
+
private workdir: string;
|
|
23
24
|
|
|
24
25
|
private skillMetadata = new Map<string, SkillMetadata>();
|
|
25
26
|
private skillContent = new Map<string, Skill>();
|
|
@@ -30,6 +31,7 @@ export class SkillManager {
|
|
|
30
31
|
options.personalSkillsPath || join(homedir(), ".wave", "skills");
|
|
31
32
|
this.scanTimeout = options.scanTimeout || 5000;
|
|
32
33
|
this.logger = options.logger;
|
|
34
|
+
this.workdir = options.workdir || process.cwd();
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
/**
|
|
@@ -123,7 +125,7 @@ export class SkillManager {
|
|
|
123
125
|
);
|
|
124
126
|
|
|
125
127
|
const projectCollection = await this.discoverSkillCollection(
|
|
126
|
-
|
|
128
|
+
this.workdir,
|
|
127
129
|
"project",
|
|
128
130
|
);
|
|
129
131
|
|
|
@@ -218,7 +218,7 @@ export class SlashCommandManager {
|
|
|
218
218
|
private async executeCustomCommandInMainAgent(
|
|
219
219
|
commandName: string,
|
|
220
220
|
content: string,
|
|
221
|
-
config?: { model?: string
|
|
221
|
+
config?: { model?: string },
|
|
222
222
|
args?: string,
|
|
223
223
|
): Promise<void> {
|
|
224
224
|
try {
|
|
@@ -272,7 +272,6 @@ export class SlashCommandManager {
|
|
|
272
272
|
// Execute the AI conversation with custom configuration
|
|
273
273
|
await this.aiManager.sendAIMessage({
|
|
274
274
|
model: config?.model,
|
|
275
|
-
allowedTools: config?.allowedTools,
|
|
276
275
|
});
|
|
277
276
|
} catch (error) {
|
|
278
277
|
this.logger?.error(
|