@dexto/core 1.6.25 → 1.6.26
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/DextoAgent.cjs +27 -14
- package/dist/agent/DextoAgent.d.ts +7 -6
- package/dist/agent/DextoAgent.d.ts.map +1 -1
- package/dist/agent/DextoAgent.js +27 -14
- package/dist/approval/manager.cjs +328 -178
- package/dist/approval/manager.d.ts +39 -31
- package/dist/approval/manager.d.ts.map +1 -1
- package/dist/approval/manager.js +328 -178
- package/dist/approval/session-approval-store.cjs +91 -0
- package/dist/approval/session-approval-store.d.ts +55 -0
- package/dist/approval/session-approval-store.d.ts.map +1 -0
- package/dist/approval/session-approval-store.js +68 -0
- package/dist/llm/executor/turn-executor.cjs +7 -3
- package/dist/llm/executor/turn-executor.d.ts.map +1 -1
- package/dist/llm/executor/turn-executor.js +7 -3
- package/dist/llm/services/factory.cjs +10 -4
- package/dist/llm/services/factory.d.ts +2 -21
- package/dist/llm/services/factory.d.ts.map +1 -1
- package/dist/llm/services/factory.js +11 -7
- package/dist/llm/services/types.d.ts +33 -2
- package/dist/llm/services/types.d.ts.map +1 -1
- package/dist/llm/services/vercel.cjs +4 -5
- package/dist/llm/services/vercel.d.ts +3 -3
- package/dist/llm/services/vercel.d.ts.map +1 -1
- package/dist/llm/services/vercel.js +2 -3
- package/dist/logger/default-logger-factory.d.ts +12 -12
- package/dist/logger/v2/schemas.d.ts +6 -6
- package/dist/mcp/schemas.d.ts +10 -10
- package/dist/session/chat-session.cjs +39 -41
- package/dist/session/chat-session.d.ts +22 -12
- package/dist/session/chat-session.d.ts.map +1 -1
- package/dist/session/chat-session.js +39 -41
- package/dist/session/message-queue-store.cjs +75 -0
- package/dist/session/message-queue-store.d.ts +16 -0
- package/dist/session/message-queue-store.d.ts.map +1 -0
- package/dist/session/message-queue-store.js +52 -0
- package/dist/session/message-queue.cjs +140 -46
- package/dist/session/message-queue.d.ts +18 -6
- package/dist/session/message-queue.d.ts.map +1 -1
- package/dist/session/message-queue.js +140 -46
- package/dist/session/session-manager.cjs +130 -25
- package/dist/session/session-manager.d.ts +18 -1
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +130 -25
- package/dist/session/title-generator.cjs +9 -2
- package/dist/session/title-generator.d.ts +2 -0
- package/dist/session/title-generator.d.ts.map +1 -1
- package/dist/session/title-generator.js +9 -2
- package/dist/telemetry/errors.cjs +2 -2
- package/dist/telemetry/errors.js +2 -2
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/telemetry/index.d.ts.map +1 -1
- package/dist/telemetry/index.js +3 -1
- package/dist/telemetry/telemetry.cjs +62 -21
- package/dist/telemetry/telemetry.d.ts +14 -0
- package/dist/telemetry/telemetry.d.ts.map +1 -1
- package/dist/telemetry/telemetry.js +62 -21
- package/dist/test-utils/session-state-stores.cjs +68 -0
- package/dist/test-utils/session-state-stores.js +42 -0
- package/dist/tools/session-tool-preferences-store.cjs +86 -0
- package/dist/tools/session-tool-preferences-store.d.ts +29 -0
- package/dist/tools/session-tool-preferences-store.d.ts.map +1 -0
- package/dist/tools/session-tool-preferences-store.js +63 -0
- package/dist/tools/tool-manager.cjs +131 -32
- package/dist/tools/tool-manager.d.ts +17 -6
- package/dist/tools/tool-manager.d.ts.map +1 -1
- package/dist/tools/tool-manager.js +131 -32
- package/dist/utils/service-initializer.cjs +38 -5
- package/dist/utils/service-initializer.d.ts +11 -1
- package/dist/utils/service-initializer.d.ts.map +1 -1
- package/dist/utils/service-initializer.js +36 -4
- package/package.json +1 -1
|
@@ -38,6 +38,7 @@ var import_factory = require("./factory.js");
|
|
|
38
38
|
var import_types2 = require("../logger/v2/types.js");
|
|
39
39
|
var import_errors = require("./errors.js");
|
|
40
40
|
var import_pattern_utils = require("../tools/pattern-utils.js");
|
|
41
|
+
const GLOBAL_APPROVAL_SCOPE = "__global__";
|
|
41
42
|
function tryRealpathSync(targetPath) {
|
|
42
43
|
try {
|
|
43
44
|
return (0, import_node_fs.realpathSync)(targetPath);
|
|
@@ -63,45 +64,156 @@ function tryRealpathSyncWithExistingParent(resolvedPath) {
|
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
class ApprovalManager {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
logger;
|
|
69
|
-
/**
|
|
70
|
-
* Tool approval patterns, keyed by tool id.
|
|
71
|
-
*
|
|
72
|
-
* Patterns use simple glob syntax (e.g. "git *", "npm install *") and are matched
|
|
73
|
-
* using pattern-to-pattern covering (see {@link patternCovers}).
|
|
74
|
-
*/
|
|
75
|
-
toolPatterns = /* @__PURE__ */ new Map();
|
|
76
|
-
/**
|
|
77
|
-
* Directories approved for file access for the current session.
|
|
78
|
-
* Stores normalized absolute paths mapped to their approval type:
|
|
79
|
-
* - 'session': No directory prompt, follows tool config (working dir + user session-approved)
|
|
80
|
-
* - 'once': Prompts each time, but tool can execute
|
|
81
|
-
* Cleared when session ends.
|
|
82
|
-
*/
|
|
83
|
-
approvedDirectories = /* @__PURE__ */ new Map();
|
|
84
|
-
constructor(config, logger) {
|
|
67
|
+
constructor(config, logger, sessionApprovalStore) {
|
|
68
|
+
this.sessionApprovalStore = sessionApprovalStore;
|
|
85
69
|
this.config = config;
|
|
86
70
|
this.logger = logger.createChild(import_types2.DextoLogComponent.APPROVAL);
|
|
87
71
|
this.logger.debug(
|
|
88
72
|
`ApprovalManager initialized with permissions.mode: ${config.permissions.mode}, elicitation.enabled: ${config.elicitation.enabled}`
|
|
89
73
|
);
|
|
90
74
|
}
|
|
75
|
+
handler;
|
|
76
|
+
config;
|
|
77
|
+
logger;
|
|
78
|
+
loadedScopes = /* @__PURE__ */ new Set();
|
|
79
|
+
scopeLocks = /* @__PURE__ */ new Map();
|
|
80
|
+
scopes = /* @__PURE__ */ new Map();
|
|
81
|
+
getScopeKey(sessionId) {
|
|
82
|
+
return sessionId ?? GLOBAL_APPROVAL_SCOPE;
|
|
83
|
+
}
|
|
84
|
+
getScopeLabel(sessionId) {
|
|
85
|
+
return sessionId ?? "global";
|
|
86
|
+
}
|
|
87
|
+
getApprovalTimeout(type, timeout) {
|
|
88
|
+
return timeout ?? this.getDefaultTimeout(type);
|
|
89
|
+
}
|
|
90
|
+
getDefaultTimeout(type) {
|
|
91
|
+
return type === import_types.ApprovalType.ELICITATION ? this.config.elicitation.timeout : this.config.permissions.timeout;
|
|
92
|
+
}
|
|
93
|
+
createEmptyScopeState() {
|
|
94
|
+
return {
|
|
95
|
+
toolPatterns: /* @__PURE__ */ new Map(),
|
|
96
|
+
approvedDirectories: /* @__PURE__ */ new Map()
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
getOrCreateScope(scopeKey) {
|
|
100
|
+
const existing = this.scopes.get(scopeKey);
|
|
101
|
+
if (existing) return existing;
|
|
102
|
+
const created = this.createEmptyScopeState();
|
|
103
|
+
this.scopes.set(scopeKey, created);
|
|
104
|
+
return created;
|
|
105
|
+
}
|
|
106
|
+
getScope(scopeKey) {
|
|
107
|
+
return this.scopes.get(scopeKey) ?? this.createEmptyScopeState();
|
|
108
|
+
}
|
|
109
|
+
async runWithScopeLock(scopeKey, fn) {
|
|
110
|
+
const previousLock = this.scopeLocks.get(scopeKey) ?? Promise.resolve();
|
|
111
|
+
const currentResult = previousLock.catch(() => {
|
|
112
|
+
}).then(() => fn());
|
|
113
|
+
const currentLock = currentResult.then(
|
|
114
|
+
() => void 0,
|
|
115
|
+
() => void 0
|
|
116
|
+
);
|
|
117
|
+
this.scopeLocks.set(scopeKey, currentLock);
|
|
118
|
+
try {
|
|
119
|
+
return await currentResult;
|
|
120
|
+
} finally {
|
|
121
|
+
if (this.scopeLocks.get(scopeKey) === currentLock) {
|
|
122
|
+
this.scopeLocks.delete(scopeKey);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
snapshotToolPatterns(scopeKey) {
|
|
127
|
+
const snapshot = {};
|
|
128
|
+
for (const [toolName, patterns] of this.getScope(scopeKey).toolPatterns) {
|
|
129
|
+
snapshot[toolName] = Array.from(patterns);
|
|
130
|
+
}
|
|
131
|
+
return snapshot;
|
|
132
|
+
}
|
|
133
|
+
snapshotApprovedDirectories(scopeKey) {
|
|
134
|
+
return Array.from(this.getScope(scopeKey).approvedDirectories.entries()).map(
|
|
135
|
+
([path2, type]) => ({
|
|
136
|
+
path: path2,
|
|
137
|
+
type
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
async persistScope(sessionId) {
|
|
142
|
+
const scopeKey = this.getScopeKey(sessionId);
|
|
143
|
+
const state = {
|
|
144
|
+
toolPatterns: this.snapshotToolPatterns(scopeKey),
|
|
145
|
+
approvedDirectories: this.snapshotApprovedDirectories(scopeKey)
|
|
146
|
+
};
|
|
147
|
+
await this.sessionApprovalStore.save(sessionId, state);
|
|
148
|
+
}
|
|
149
|
+
hydrateScope(sessionId, state) {
|
|
150
|
+
const scopeKey = this.getScopeKey(sessionId);
|
|
151
|
+
const toolPatterns = /* @__PURE__ */ new Map();
|
|
152
|
+
for (const [toolName, patterns] of Object.entries(state.toolPatterns)) {
|
|
153
|
+
toolPatterns.set(toolName, new Set(patterns));
|
|
154
|
+
}
|
|
155
|
+
const approvedDirectories = /* @__PURE__ */ new Map();
|
|
156
|
+
for (const entry of state.approvedDirectories) {
|
|
157
|
+
approvedDirectories.set(entry.path, entry.type);
|
|
158
|
+
}
|
|
159
|
+
this.scopes.set(scopeKey, {
|
|
160
|
+
toolPatterns,
|
|
161
|
+
approvedDirectories
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async restoreSessionState(sessionId) {
|
|
165
|
+
const scopeKey = this.getScopeKey(sessionId);
|
|
166
|
+
if (this.loadedScopes.has(scopeKey)) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
await this.runWithScopeLock(scopeKey, async () => {
|
|
170
|
+
if (this.loadedScopes.has(scopeKey)) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const state = await this.sessionApprovalStore.load(sessionId);
|
|
174
|
+
this.hydrateScope(sessionId, state);
|
|
175
|
+
this.loadedScopes.add(scopeKey);
|
|
176
|
+
this.logger.debug("Restored persisted approval state", {
|
|
177
|
+
sessionId: this.getScopeLabel(sessionId),
|
|
178
|
+
toolCount: Object.keys(state.toolPatterns).length,
|
|
179
|
+
directoryCount: state.approvedDirectories.length
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
evictSessionState(sessionId) {
|
|
184
|
+
const scopeKey = this.getScopeKey(sessionId);
|
|
185
|
+
this.scopes.delete(scopeKey);
|
|
186
|
+
this.loadedScopes.delete(scopeKey);
|
|
187
|
+
}
|
|
188
|
+
async deleteSessionState(sessionId) {
|
|
189
|
+
const scopeKey = this.getScopeKey(sessionId);
|
|
190
|
+
await this.runWithScopeLock(scopeKey, async () => {
|
|
191
|
+
this.evictSessionState(sessionId);
|
|
192
|
+
await this.sessionApprovalStore.delete(sessionId);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
91
195
|
// ==================== Pattern Methods ====================
|
|
92
|
-
getOrCreateToolPatternSet(toolName) {
|
|
93
|
-
const
|
|
196
|
+
getOrCreateToolPatternSet(toolName, scopeKey) {
|
|
197
|
+
const scope = this.getOrCreateScope(scopeKey).toolPatterns;
|
|
198
|
+
const existing = scope.get(toolName);
|
|
94
199
|
if (existing) return existing;
|
|
95
200
|
const created = /* @__PURE__ */ new Set();
|
|
96
|
-
|
|
201
|
+
scope.set(toolName, created);
|
|
97
202
|
return created;
|
|
98
203
|
}
|
|
99
204
|
/**
|
|
100
205
|
* Add an approval pattern for a tool.
|
|
101
206
|
*/
|
|
102
|
-
addPattern(toolName, pattern) {
|
|
103
|
-
this.
|
|
104
|
-
this.
|
|
207
|
+
async addPattern(toolName, pattern, sessionId) {
|
|
208
|
+
await this.restoreSessionState(sessionId);
|
|
209
|
+
const scopeKey = this.getScopeKey(sessionId);
|
|
210
|
+
await this.runWithScopeLock(scopeKey, async () => {
|
|
211
|
+
this.getOrCreateToolPatternSet(toolName, scopeKey).add(pattern);
|
|
212
|
+
await this.persistScope(sessionId);
|
|
213
|
+
});
|
|
214
|
+
this.logger.debug(
|
|
215
|
+
`Added pattern for '${toolName}' in '${this.getScopeLabel(sessionId)}': "${pattern}"`
|
|
216
|
+
);
|
|
105
217
|
}
|
|
106
218
|
/**
|
|
107
219
|
* Check if a pattern key is covered by any approved pattern for a tool.
|
|
@@ -109,8 +221,9 @@ class ApprovalManager {
|
|
|
109
221
|
* Note: This expects a pattern key (e.g. "git push *"), not raw arguments.
|
|
110
222
|
* Tools are responsible for generating the key via `tool.approval.patternKey()`.
|
|
111
223
|
*/
|
|
112
|
-
matchesPattern(toolName, patternKey) {
|
|
113
|
-
const
|
|
224
|
+
matchesPattern(toolName, patternKey, sessionId) {
|
|
225
|
+
const scopeKey = this.getScopeKey(sessionId);
|
|
226
|
+
const patterns = this.getScope(scopeKey).toolPatterns.get(toolName);
|
|
114
227
|
if (!patterns || patterns.size === 0) return false;
|
|
115
228
|
for (const storedPattern of patterns) {
|
|
116
229
|
if ((0, import_pattern_utils.patternCovers)(storedPattern, patternKey)) {
|
|
@@ -125,37 +238,47 @@ class ApprovalManager {
|
|
|
125
238
|
/**
|
|
126
239
|
* Clear all patterns for a tool (or all tools when omitted).
|
|
127
240
|
*/
|
|
128
|
-
clearPatterns(toolName) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
241
|
+
async clearPatterns(toolName, sessionId) {
|
|
242
|
+
await this.restoreSessionState(sessionId);
|
|
243
|
+
const scopeKey = this.getScopeKey(sessionId);
|
|
244
|
+
await this.runWithScopeLock(scopeKey, async () => {
|
|
245
|
+
const scope = this.getOrCreateScope(scopeKey).toolPatterns;
|
|
246
|
+
if (toolName) {
|
|
247
|
+
const patterns = scope.get(toolName);
|
|
248
|
+
if (!patterns) return;
|
|
249
|
+
const count2 = patterns.size;
|
|
250
|
+
scope.delete(toolName);
|
|
251
|
+
await this.persistScope(sessionId);
|
|
252
|
+
if (count2 > 0) {
|
|
253
|
+
this.logger.debug(
|
|
254
|
+
`Cleared ${count2} pattern(s) for '${toolName}' in '${this.getScopeLabel(sessionId)}'`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
136
258
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
259
|
+
const count = Array.from(scope.values()).reduce((sum, set) => sum + set.size, 0);
|
|
260
|
+
scope.clear();
|
|
261
|
+
await this.persistScope(sessionId);
|
|
262
|
+
if (count > 0) {
|
|
263
|
+
this.logger.debug(
|
|
264
|
+
`Cleared ${count} total tool pattern(s) in '${this.getScopeLabel(sessionId)}'`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
147
268
|
}
|
|
148
269
|
/**
|
|
149
270
|
* Get patterns for a tool (for debugging/display).
|
|
150
271
|
*/
|
|
151
|
-
getToolPatterns(toolName) {
|
|
152
|
-
|
|
272
|
+
getToolPatterns(toolName, sessionId) {
|
|
273
|
+
const scopeKey = this.getScopeKey(sessionId);
|
|
274
|
+
return this.getScope(scopeKey).toolPatterns.get(toolName) ?? /* @__PURE__ */ new Set();
|
|
153
275
|
}
|
|
154
276
|
/**
|
|
155
277
|
* Get all tool patterns (for debugging/display).
|
|
156
278
|
*/
|
|
157
|
-
getAllToolPatterns() {
|
|
158
|
-
|
|
279
|
+
getAllToolPatterns(sessionId) {
|
|
280
|
+
const scopeKey = this.getScopeKey(sessionId);
|
|
281
|
+
return this.getScope(scopeKey).toolPatterns;
|
|
159
282
|
}
|
|
160
283
|
// ==================== Directory Access Methods ====================
|
|
161
284
|
/**
|
|
@@ -165,21 +288,32 @@ class ApprovalManager {
|
|
|
165
288
|
* continue to work even when other subsystems canonicalize paths via realpath
|
|
166
289
|
* (e.g. macOS /tmp -> /private/tmp or custom symlinked directories).
|
|
167
290
|
*/
|
|
168
|
-
|
|
169
|
-
const resolved = import_node_path.default.resolve(
|
|
291
|
+
getPathApprovalKeys(targetPath) {
|
|
292
|
+
const resolved = import_node_path.default.resolve(targetPath);
|
|
170
293
|
const real = tryRealpathSyncWithExistingParent(resolved);
|
|
171
294
|
if (real && real !== resolved) {
|
|
172
295
|
return [resolved, real];
|
|
173
296
|
}
|
|
174
297
|
return [resolved];
|
|
175
298
|
}
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
299
|
+
isPathWithinApprovedDirectory(targetPath, sessionId, approvedTypes) {
|
|
300
|
+
const scopeKey = this.getScopeKey(sessionId);
|
|
301
|
+
const directoryScope = this.getScope(scopeKey).approvedDirectories;
|
|
302
|
+
for (const normalized of this.getPathApprovalKeys(targetPath)) {
|
|
303
|
+
for (const [approvedDir, type] of directoryScope) {
|
|
304
|
+
if (!approvedTypes.has(type)) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const relative = import_node_path.default.relative(approvedDir, normalized);
|
|
308
|
+
if (!relative.startsWith("..") && !import_node_path.default.isAbsolute(relative)) {
|
|
309
|
+
this.logger.debug(
|
|
310
|
+
`Path "${normalized}" is within approved directory "${approvedDir}" (type: ${type})`
|
|
311
|
+
);
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
181
315
|
}
|
|
182
|
-
return
|
|
316
|
+
return false;
|
|
183
317
|
}
|
|
184
318
|
/**
|
|
185
319
|
* Initialize the working directory as a session-approved directory.
|
|
@@ -188,8 +322,8 @@ class ApprovalManager {
|
|
|
188
322
|
*
|
|
189
323
|
* @param workingDir The working directory path
|
|
190
324
|
*/
|
|
191
|
-
initializeWorkingDirectory(workingDir) {
|
|
192
|
-
this.addApprovedDirectory(workingDir, "session");
|
|
325
|
+
async initializeWorkingDirectory(workingDir, sessionId) {
|
|
326
|
+
await this.addApprovedDirectory(workingDir, "session", sessionId);
|
|
193
327
|
}
|
|
194
328
|
/**
|
|
195
329
|
* Add a directory to the approved list for this session.
|
|
@@ -208,29 +342,35 @@ class ApprovalManager {
|
|
|
208
342
|
* // Tool can access, but will prompt again next time
|
|
209
343
|
* ```
|
|
210
344
|
*/
|
|
211
|
-
addApprovedDirectory(directory, type = "session") {
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
345
|
+
async addApprovedDirectory(directory, type = "session", sessionId) {
|
|
346
|
+
await this.restoreSessionState(sessionId);
|
|
347
|
+
const scopeKey = this.getScopeKey(sessionId);
|
|
348
|
+
await this.runWithScopeLock(scopeKey, async () => {
|
|
349
|
+
const keys = this.getPathApprovalKeys(directory);
|
|
350
|
+
const directoryScope = this.getOrCreateScope(scopeKey).approvedDirectories;
|
|
351
|
+
const existingTypes = keys.map((key) => directoryScope.get(key)).filter((value) => value !== void 0);
|
|
352
|
+
const hasSessionApproval = existingTypes.includes("session");
|
|
353
|
+
const effectiveType = type === "session" || hasSessionApproval ? "session" : "once";
|
|
354
|
+
for (const key of keys) {
|
|
355
|
+
const existing = directoryScope.get(key);
|
|
356
|
+
if (existing === "session") {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
directoryScope.set(key, effectiveType);
|
|
220
360
|
}
|
|
221
|
-
this.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
361
|
+
await this.persistScope(sessionId);
|
|
362
|
+
const resolvedKey = keys[0];
|
|
363
|
+
if (effectiveType === "session" && type === "once" && hasSessionApproval) {
|
|
364
|
+
this.logger.debug(
|
|
365
|
+
`Directory "${resolvedKey}" already approved as 'session', not downgrading to 'once'`
|
|
366
|
+
);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const realKey = keys.length > 1 ? keys[1] : null;
|
|
225
370
|
this.logger.debug(
|
|
226
|
-
`
|
|
371
|
+
`Added approved directory in '${this.getScopeLabel(sessionId)}': "${resolvedKey}" (type: ${effectiveType})${realKey ? `, realpath: "${realKey}"` : ""}`
|
|
227
372
|
);
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
const realKey = keys.length > 1 ? keys[1] : null;
|
|
231
|
-
this.logger.debug(
|
|
232
|
-
`Added approved directory: "${resolvedKey}" (type: ${effectiveType})${realKey ? `, realpath: "${realKey}"` : ""}`
|
|
233
|
-
);
|
|
373
|
+
});
|
|
234
374
|
}
|
|
235
375
|
/**
|
|
236
376
|
* Check if a file path is within any session-approved directory.
|
|
@@ -240,20 +380,8 @@ class ApprovalManager {
|
|
|
240
380
|
* @param filePath The file path to check (can be relative or absolute)
|
|
241
381
|
* @returns true if the path is within a session-approved directory
|
|
242
382
|
*/
|
|
243
|
-
isDirectorySessionApproved(filePath) {
|
|
244
|
-
|
|
245
|
-
for (const [approvedDir, type] of this.approvedDirectories) {
|
|
246
|
-
if (type !== "session") continue;
|
|
247
|
-
const relative = import_node_path.default.relative(approvedDir, normalized);
|
|
248
|
-
if (!relative.startsWith("..") && !import_node_path.default.isAbsolute(relative)) {
|
|
249
|
-
this.logger.debug(
|
|
250
|
-
`Path "${normalized}" is within session-approved directory "${approvedDir}"`
|
|
251
|
-
);
|
|
252
|
-
return true;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
return false;
|
|
383
|
+
isDirectorySessionApproved(filePath, sessionId) {
|
|
384
|
+
return this.isPathWithinApprovedDirectory(filePath, sessionId, /* @__PURE__ */ new Set(["session"]));
|
|
257
385
|
}
|
|
258
386
|
/**
|
|
259
387
|
* Check if a file path is within any approved directory (session OR once).
|
|
@@ -263,51 +391,99 @@ class ApprovalManager {
|
|
|
263
391
|
* @param filePath The file path to check (can be relative or absolute)
|
|
264
392
|
* @returns true if the path is within any approved directory
|
|
265
393
|
*/
|
|
266
|
-
isDirectoryApproved(filePath) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
`Path "${normalized}" is within approved directory "${approvedDir}"`
|
|
273
|
-
);
|
|
274
|
-
return true;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
return false;
|
|
394
|
+
isDirectoryApproved(filePath, sessionId) {
|
|
395
|
+
return this.isPathWithinApprovedDirectory(
|
|
396
|
+
filePath,
|
|
397
|
+
sessionId,
|
|
398
|
+
/* @__PURE__ */ new Set(["session", "once"])
|
|
399
|
+
);
|
|
279
400
|
}
|
|
280
401
|
/**
|
|
281
402
|
* Clear all approved directories.
|
|
282
403
|
* Should be called when session ends.
|
|
283
404
|
*/
|
|
284
|
-
clearApprovedDirectories() {
|
|
285
|
-
|
|
286
|
-
this.
|
|
287
|
-
|
|
288
|
-
this.
|
|
289
|
-
|
|
405
|
+
async clearApprovedDirectories(sessionId) {
|
|
406
|
+
await this.restoreSessionState(sessionId);
|
|
407
|
+
const scopeKey = this.getScopeKey(sessionId);
|
|
408
|
+
await this.runWithScopeLock(scopeKey, async () => {
|
|
409
|
+
const scope = this.getOrCreateScope(scopeKey).approvedDirectories;
|
|
410
|
+
const count = scope.size;
|
|
411
|
+
scope.clear();
|
|
412
|
+
await this.persistScope(sessionId);
|
|
413
|
+
if (count > 0) {
|
|
414
|
+
this.logger.debug(
|
|
415
|
+
`Cleared ${count} approved directories in '${this.getScopeLabel(sessionId)}'`
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
290
419
|
}
|
|
291
420
|
/**
|
|
292
421
|
* Get the current map of approved directories with their types (for debugging/display).
|
|
293
422
|
*/
|
|
294
|
-
getApprovedDirectories() {
|
|
295
|
-
|
|
423
|
+
getApprovedDirectories(sessionId) {
|
|
424
|
+
const scopeKey = this.getScopeKey(sessionId);
|
|
425
|
+
return this.getScope(scopeKey).approvedDirectories;
|
|
296
426
|
}
|
|
297
427
|
/**
|
|
298
428
|
* Get just the directory paths that are approved (for debugging/display).
|
|
299
429
|
*/
|
|
300
|
-
getApprovedDirectoryPaths() {
|
|
301
|
-
return Array.from(this.
|
|
430
|
+
getApprovedDirectoryPaths(sessionId) {
|
|
431
|
+
return Array.from(this.getApprovedDirectories(sessionId).keys());
|
|
302
432
|
}
|
|
303
433
|
/**
|
|
304
434
|
* Clear all session-scoped approvals (tool patterns and directories).
|
|
305
435
|
* Convenience method for clearing all session state at once.
|
|
306
436
|
*/
|
|
307
|
-
clearSessionApprovals() {
|
|
308
|
-
this.
|
|
309
|
-
this.
|
|
310
|
-
this.
|
|
437
|
+
async clearSessionApprovals(sessionId) {
|
|
438
|
+
await this.restoreSessionState(sessionId);
|
|
439
|
+
const scopeKey = this.getScopeKey(sessionId);
|
|
440
|
+
await this.runWithScopeLock(scopeKey, async () => {
|
|
441
|
+
const scope = this.getOrCreateScope(scopeKey);
|
|
442
|
+
const patternCount = Array.from(scope.toolPatterns.values()).reduce(
|
|
443
|
+
(sum, set) => sum + set.size,
|
|
444
|
+
0
|
|
445
|
+
);
|
|
446
|
+
const directoryCount = scope.approvedDirectories.size;
|
|
447
|
+
scope.toolPatterns.clear();
|
|
448
|
+
scope.approvedDirectories.clear();
|
|
449
|
+
await this.persistScope(sessionId);
|
|
450
|
+
if (patternCount > 0 || directoryCount > 0) {
|
|
451
|
+
this.logger.debug(
|
|
452
|
+
`Cleared ${patternCount} tool pattern(s) and ${directoryCount} approved director${directoryCount === 1 ? "y" : "ies"} in '${this.getScopeLabel(sessionId)}'`
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
createApprovalDetails(type, metadata, sessionId, timeout) {
|
|
458
|
+
const details = {
|
|
459
|
+
type,
|
|
460
|
+
timeout: this.getApprovalTimeout(type, timeout),
|
|
461
|
+
metadata
|
|
462
|
+
};
|
|
463
|
+
if (sessionId !== void 0) {
|
|
464
|
+
details.sessionId = sessionId;
|
|
465
|
+
}
|
|
466
|
+
return details;
|
|
467
|
+
}
|
|
468
|
+
createResponse(request, response) {
|
|
469
|
+
return {
|
|
470
|
+
approvalId: request.approvalId,
|
|
471
|
+
...request.sessionId !== void 0 ? { sessionId: request.sessionId } : {},
|
|
472
|
+
...response
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
getElicitationFormData(response) {
|
|
476
|
+
if (response.data && typeof response.data === "object" && "formData" in response.data && typeof response.data.formData === "object" && response.data.formData !== null) {
|
|
477
|
+
return response.data.formData;
|
|
478
|
+
}
|
|
479
|
+
if (response.data === void 0 || typeof response.data === "object" && response.data !== null && !("formData" in response.data)) {
|
|
480
|
+
return {};
|
|
481
|
+
}
|
|
482
|
+
throw import_errors.ApprovalError.invalidResponse("Approved elicitation response is missing formData", {
|
|
483
|
+
approvalId: response.approvalId,
|
|
484
|
+
type: import_types.ApprovalType.ELICITATION,
|
|
485
|
+
field: "formData"
|
|
486
|
+
});
|
|
311
487
|
}
|
|
312
488
|
/**
|
|
313
489
|
* Request directory access approval.
|
|
@@ -326,16 +502,14 @@ class ApprovalManager {
|
|
|
326
502
|
*/
|
|
327
503
|
async requestDirectoryAccess(metadata) {
|
|
328
504
|
const { sessionId, timeout, ...directoryMetadata } = metadata;
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
return this.requestApproval(details);
|
|
505
|
+
return this.requestApproval(
|
|
506
|
+
this.createApprovalDetails(
|
|
507
|
+
import_types.ApprovalType.DIRECTORY_ACCESS,
|
|
508
|
+
directoryMetadata,
|
|
509
|
+
sessionId,
|
|
510
|
+
timeout
|
|
511
|
+
)
|
|
512
|
+
);
|
|
339
513
|
}
|
|
340
514
|
/**
|
|
341
515
|
* Request a generic approval
|
|
@@ -366,29 +540,19 @@ class ApprovalManager {
|
|
|
366
540
|
this.logger.info(
|
|
367
541
|
`Auto-approve approval '${request.type}', approvalId: ${request.approvalId}`
|
|
368
542
|
);
|
|
369
|
-
|
|
370
|
-
approvalId: request.approvalId,
|
|
543
|
+
return this.createResponse(request, {
|
|
371
544
|
status: import_types.ApprovalStatus.APPROVED
|
|
372
|
-
};
|
|
373
|
-
if (request.sessionId !== void 0) {
|
|
374
|
-
response.sessionId = request.sessionId;
|
|
375
|
-
}
|
|
376
|
-
return response;
|
|
545
|
+
});
|
|
377
546
|
}
|
|
378
547
|
if (mode === "auto-deny") {
|
|
379
548
|
this.logger.info(
|
|
380
549
|
`Auto-deny approval '${request.type}', approvalId: ${request.approvalId}`
|
|
381
550
|
);
|
|
382
|
-
|
|
383
|
-
approvalId: request.approvalId,
|
|
551
|
+
return this.createResponse(request, {
|
|
384
552
|
status: import_types.ApprovalStatus.DENIED,
|
|
385
553
|
reason: import_types.DenialReason.SYSTEM_DENIED,
|
|
386
554
|
message: `Approval automatically denied by system policy (auto-deny mode)`
|
|
387
|
-
};
|
|
388
|
-
if (request.sessionId !== void 0) {
|
|
389
|
-
response.sessionId = request.sessionId;
|
|
390
|
-
}
|
|
391
|
-
return response;
|
|
555
|
+
});
|
|
392
556
|
}
|
|
393
557
|
const handler = this.ensureHandler();
|
|
394
558
|
this.logger.info(
|
|
@@ -405,16 +569,9 @@ class ApprovalManager {
|
|
|
405
569
|
*/
|
|
406
570
|
async requestToolApproval(metadata) {
|
|
407
571
|
const { sessionId, timeout, ...toolMetadata } = metadata;
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
timeout: timeout !== void 0 ? timeout : this.config.permissions.timeout,
|
|
412
|
-
metadata: toolMetadata
|
|
413
|
-
};
|
|
414
|
-
if (sessionId !== void 0) {
|
|
415
|
-
details.sessionId = sessionId;
|
|
416
|
-
}
|
|
417
|
-
return this.requestApproval(details);
|
|
572
|
+
return this.requestApproval(
|
|
573
|
+
this.createApprovalDetails(import_types.ApprovalType.TOOL_APPROVAL, toolMetadata, sessionId, timeout)
|
|
574
|
+
);
|
|
418
575
|
}
|
|
419
576
|
/**
|
|
420
577
|
* Request command confirmation approval
|
|
@@ -439,16 +596,14 @@ class ApprovalManager {
|
|
|
439
596
|
*/
|
|
440
597
|
async requestCommandConfirmation(metadata) {
|
|
441
598
|
const { sessionId, timeout, ...commandMetadata } = metadata;
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
return this.requestApproval(details);
|
|
599
|
+
return this.requestApproval(
|
|
600
|
+
this.createApprovalDetails(
|
|
601
|
+
import_types.ApprovalType.COMMAND_CONFIRMATION,
|
|
602
|
+
commandMetadata,
|
|
603
|
+
sessionId,
|
|
604
|
+
timeout
|
|
605
|
+
)
|
|
606
|
+
);
|
|
452
607
|
}
|
|
453
608
|
/**
|
|
454
609
|
* Request elicitation from MCP server
|
|
@@ -459,16 +614,14 @@ class ApprovalManager {
|
|
|
459
614
|
*/
|
|
460
615
|
async requestElicitation(metadata) {
|
|
461
616
|
const { sessionId, timeout, ...elicitationMetadata } = metadata;
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
return this.requestApproval(details);
|
|
617
|
+
return this.requestApproval(
|
|
618
|
+
this.createApprovalDetails(
|
|
619
|
+
import_types.ApprovalType.ELICITATION,
|
|
620
|
+
elicitationMetadata,
|
|
621
|
+
sessionId,
|
|
622
|
+
timeout
|
|
623
|
+
)
|
|
624
|
+
);
|
|
472
625
|
}
|
|
473
626
|
/**
|
|
474
627
|
* Check if tool approval was approved
|
|
@@ -500,10 +653,7 @@ class ApprovalManager {
|
|
|
500
653
|
async getElicitationData(metadata) {
|
|
501
654
|
const response = await this.requestElicitation(metadata);
|
|
502
655
|
if (response.status === import_types.ApprovalStatus.APPROVED) {
|
|
503
|
-
|
|
504
|
-
return response.data.formData;
|
|
505
|
-
}
|
|
506
|
-
return {};
|
|
656
|
+
return this.getElicitationFormData(response);
|
|
507
657
|
} else if (response.status === import_types.ApprovalStatus.DENIED) {
|
|
508
658
|
throw import_errors.ApprovalError.elicitationDenied(
|
|
509
659
|
metadata.serverName,
|