@dexto/core 1.8.12 → 1.9.1
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/approval/manager.cjs +37 -325
- package/dist/approval/manager.d.ts +10 -109
- package/dist/approval/manager.d.ts.map +1 -1
- package/dist/approval/manager.js +37 -316
- package/dist/approval/schemas.cjs +8 -51
- package/dist/approval/schemas.d.ts +5 -179
- package/dist/approval/schemas.d.ts.map +1 -1
- package/dist/approval/schemas.js +8 -47
- package/dist/approval/types.cjs +0 -6
- package/dist/approval/types.d.ts +2 -27
- package/dist/approval/types.d.ts.map +1 -1
- package/dist/approval/types.js +0 -6
- package/dist/context/compaction/strategies/reactive-overflow-compaction.cjs +2 -0
- package/dist/context/compaction/strategies/reactive-overflow-compaction.d.ts.map +1 -1
- package/dist/context/compaction/strategies/reactive-overflow-compaction.js +2 -0
- package/dist/context/content-clone.cjs +55 -10
- package/dist/context/content-clone.d.ts +1 -0
- package/dist/context/content-clone.d.ts.map +1 -1
- package/dist/context/content-clone.js +53 -9
- package/dist/context/manager.cjs +25 -4
- package/dist/context/manager.d.ts +4 -2
- package/dist/context/manager.d.ts.map +1 -1
- package/dist/context/manager.js +25 -4
- package/dist/context/schemas.cjs +15 -1
- package/dist/context/schemas.d.ts.map +1 -1
- package/dist/context/schemas.js +15 -1
- package/dist/context/types.d.ts +11 -0
- package/dist/context/types.d.ts.map +1 -1
- package/dist/errors/DextoRuntimeError.cjs +3 -1
- package/dist/errors/DextoRuntimeError.d.ts +5 -3
- package/dist/errors/DextoRuntimeError.d.ts.map +1 -1
- package/dist/errors/DextoRuntimeError.js +3 -1
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/types.d.ts +1 -0
- package/dist/errors/types.d.ts.map +1 -1
- package/dist/events/index.d.ts +2 -2
- package/dist/events/index.d.ts.map +1 -1
- package/dist/llm/executor/provider-error.cjs +25 -4
- package/dist/llm/executor/provider-error.d.ts.map +1 -1
- package/dist/llm/executor/provider-error.js +25 -4
- package/dist/llm/executor/stream-processor.cjs +98 -7
- package/dist/llm/executor/stream-processor.d.ts.map +1 -1
- package/dist/llm/executor/stream-processor.js +98 -7
- package/dist/llm/executor/turn-executor.cjs +96 -28
- package/dist/llm/executor/turn-executor.d.ts +2 -0
- package/dist/llm/executor/turn-executor.d.ts.map +1 -1
- package/dist/llm/executor/turn-executor.js +96 -28
- package/dist/llm/providers/local/schemas.d.ts +2 -2
- package/dist/llm/services/factory.cjs +3 -2
- package/dist/llm/services/factory.d.ts.map +1 -1
- package/dist/llm/services/factory.js +3 -2
- package/dist/llm/services/types.d.ts +8 -0
- package/dist/llm/services/types.d.ts.map +1 -1
- package/dist/llm/services/vercel.cjs +6 -1
- package/dist/llm/services/vercel.d.ts +3 -2
- package/dist/llm/services/vercel.d.ts.map +1 -1
- package/dist/llm/services/vercel.js +6 -1
- package/dist/session/chat-session.cjs +4 -0
- package/dist/session/chat-session.d.ts +2 -1
- package/dist/session/chat-session.d.ts.map +1 -1
- package/dist/session/chat-session.js +4 -0
- package/dist/session/message-queue.cjs +48 -75
- package/dist/session/message-queue.d.ts +1 -2
- package/dist/session/message-queue.d.ts.map +1 -1
- package/dist/session/message-queue.js +48 -75
- package/dist/session/session-manager.cjs +14 -32
- package/dist/session/session-manager.d.ts +5 -2
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +14 -32
- package/dist/storage/approvals/types.cjs +2 -9
- package/dist/storage/approvals/types.d.ts +4 -16
- package/dist/storage/approvals/types.d.ts.map +1 -1
- package/dist/storage/approvals/types.js +2 -8
- package/dist/storage/database/types.d.ts +5 -0
- package/dist/storage/database/types.d.ts.map +1 -1
- package/dist/storage/index.cjs +0 -2
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +1 -2
- package/dist/storage/message-queue/types.d.ts +14 -6
- package/dist/storage/message-queue/types.d.ts.map +1 -1
- package/dist/storage/stores/backend.cjs +68 -32
- package/dist/storage/stores/backend.d.ts +17 -8
- package/dist/storage/stores/backend.d.ts.map +1 -1
- package/dist/storage/stores/backend.js +69 -33
- package/dist/storage/stores/in-memory.cjs +26 -11
- package/dist/storage/stores/in-memory.d.ts.map +1 -1
- package/dist/storage/stores/in-memory.js +27 -12
- package/dist/test-utils/in-memory-storage.cjs +11 -0
- package/dist/test-utils/in-memory-storage.js +11 -0
- package/dist/test-utils/session-state-stores.cjs +25 -9
- package/dist/test-utils/session-state-stores.js +26 -10
- package/dist/tools/approval/tool-approval-policy.cjs +87 -0
- package/dist/tools/approval/tool-approval-policy.d.ts +34 -0
- package/dist/tools/approval/tool-approval-policy.d.ts.map +1 -0
- package/dist/tools/approval/tool-approval-policy.js +64 -0
- package/dist/tools/error-codes.cjs +0 -1
- package/dist/tools/error-codes.d.ts +0 -1
- package/dist/tools/error-codes.d.ts.map +1 -1
- package/dist/tools/error-codes.js +0 -1
- package/dist/tools/errors.cjs +0 -15
- package/dist/tools/errors.d.ts +0 -9
- package/dist/tools/errors.d.ts.map +1 -1
- package/dist/tools/errors.js +0 -15
- package/dist/tools/index.cjs +2 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +5 -1
- package/dist/tools/tool-manager.cjs +94 -312
- package/dist/tools/tool-manager.d.ts +4 -19
- package/dist/tools/tool-manager.d.ts.map +1 -1
- package/dist/tools/tool-manager.js +95 -316
- package/dist/tools/types.d.ts +10 -9
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/utils/service-initializer.cjs +3 -0
- package/dist/utils/service-initializer.d.ts +2 -1
- package/dist/utils/service-initializer.d.ts.map +1 -1
- package/dist/utils/service-initializer.js +3 -0
- package/package.json +2 -2
- package/dist/tools/pattern-utils.cjs +0 -33
- package/dist/tools/pattern-utils.d.ts +0 -20
- package/dist/tools/pattern-utils.d.ts.map +0 -1
- package/dist/tools/pattern-utils.js +0 -10
package/dist/approval/manager.js
CHANGED
|
@@ -1,44 +1,16 @@
|
|
|
1
1
|
import "../chunk-C6A6W6XS.js";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { realpathSync } from "node:fs";
|
|
4
2
|
import { isDeepStrictEqual } from "node:util";
|
|
5
3
|
import { ApprovalType, ApprovalStatus } from "./types.js";
|
|
6
4
|
import {
|
|
7
5
|
CommandApprovalResponseSchema,
|
|
8
6
|
CustomApprovalResponseSchema,
|
|
9
|
-
DirectoryAccessResponseSchema,
|
|
10
7
|
ElicitationResponseSchema,
|
|
11
8
|
ToolApprovalResponseSchema
|
|
12
9
|
} from "./schemas.js";
|
|
13
10
|
import { createApprovalRequest, createDeterministicApprovalId } from "./factory.js";
|
|
14
11
|
import { DextoLogComponent } from "../logger/v2/types.js";
|
|
15
12
|
import { ApprovalError } from "./errors.js";
|
|
16
|
-
import { patternCovers } from "../tools/pattern-utils.js";
|
|
17
13
|
const GLOBAL_APPROVAL_SCOPE = "__global__";
|
|
18
|
-
function tryRealpathSync(targetPath) {
|
|
19
|
-
try {
|
|
20
|
-
return realpathSync(targetPath);
|
|
21
|
-
} catch {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function tryRealpathSyncWithExistingParent(resolvedPath) {
|
|
26
|
-
const direct = tryRealpathSync(resolvedPath);
|
|
27
|
-
if (direct) return direct;
|
|
28
|
-
let currentDir = path.dirname(resolvedPath);
|
|
29
|
-
while (true) {
|
|
30
|
-
const realDir = tryRealpathSync(currentDir);
|
|
31
|
-
if (realDir) {
|
|
32
|
-
const suffix = path.relative(currentDir, resolvedPath);
|
|
33
|
-
return path.join(realDir, suffix);
|
|
34
|
-
}
|
|
35
|
-
const parent = path.dirname(currentDir);
|
|
36
|
-
if (parent === currentDir) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
currentDir = parent;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
14
|
class ApprovalManager {
|
|
43
15
|
constructor(config, logger, approvalStore) {
|
|
44
16
|
this.approvalStore = approvalStore;
|
|
@@ -72,8 +44,7 @@ class ApprovalManager {
|
|
|
72
44
|
}
|
|
73
45
|
createEmptyScopeState() {
|
|
74
46
|
return {
|
|
75
|
-
|
|
76
|
-
approvedDirectories: /* @__PURE__ */ new Map()
|
|
47
|
+
approvedKeys: /* @__PURE__ */ new Map()
|
|
77
48
|
};
|
|
78
49
|
}
|
|
79
50
|
getOrCreateScope(scopeKey) {
|
|
@@ -120,26 +91,13 @@ class ApprovalManager {
|
|
|
120
91
|
}
|
|
121
92
|
}
|
|
122
93
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
for (const [toolName, patterns] of this.getScope(scopeKey).toolPatterns) {
|
|
126
|
-
snapshot[toolName] = Array.from(patterns);
|
|
127
|
-
}
|
|
128
|
-
return snapshot;
|
|
129
|
-
}
|
|
130
|
-
snapshotApprovedDirectories(scopeKey) {
|
|
131
|
-
return Array.from(this.getScope(scopeKey).approvedDirectories.entries()).map(
|
|
132
|
-
([path2, type]) => ({
|
|
133
|
-
path: path2,
|
|
134
|
-
type
|
|
135
|
-
})
|
|
136
|
-
);
|
|
94
|
+
snapshotApprovedKeys(scopeKey) {
|
|
95
|
+
return Object.fromEntries(this.getScope(scopeKey).approvedKeys.entries());
|
|
137
96
|
}
|
|
138
97
|
async persistScope(sessionId) {
|
|
139
98
|
const scopeKey = this.getScopeKey(sessionId);
|
|
140
99
|
const state = {
|
|
141
|
-
|
|
142
|
-
approvedDirectories: this.snapshotApprovedDirectories(scopeKey)
|
|
100
|
+
approvedKeys: this.snapshotApprovedKeys(scopeKey)
|
|
143
101
|
};
|
|
144
102
|
await this.approvalStore.saveSessionState({
|
|
145
103
|
...this.sessionScope(sessionId),
|
|
@@ -148,17 +106,12 @@ class ApprovalManager {
|
|
|
148
106
|
}
|
|
149
107
|
hydrateScope(sessionId, state) {
|
|
150
108
|
const scopeKey = this.getScopeKey(sessionId);
|
|
151
|
-
const
|
|
152
|
-
for (const [
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
const approvedDirectories = /* @__PURE__ */ new Map();
|
|
156
|
-
for (const entry of state.approvedDirectories) {
|
|
157
|
-
approvedDirectories.set(entry.path, entry.type);
|
|
109
|
+
const approvedKeys = /* @__PURE__ */ new Map();
|
|
110
|
+
for (const [key, type] of Object.entries(state.approvedKeys ?? {})) {
|
|
111
|
+
approvedKeys.set(key, type);
|
|
158
112
|
}
|
|
159
113
|
this.scopes.set(scopeKey, {
|
|
160
|
-
|
|
161
|
-
approvedDirectories
|
|
114
|
+
approvedKeys
|
|
162
115
|
});
|
|
163
116
|
}
|
|
164
117
|
async restoreSessionState(sessionId) {
|
|
@@ -175,8 +128,7 @@ class ApprovalManager {
|
|
|
175
128
|
this.loadedScopes.add(scopeKey);
|
|
176
129
|
this.logger.debug("Restored persisted approval state", {
|
|
177
130
|
sessionId: this.getScopeLabel(sessionId),
|
|
178
|
-
|
|
179
|
-
directoryCount: state.approvedDirectories.length
|
|
131
|
+
keyCount: Object.keys(state.approvedKeys ?? {}).length
|
|
180
132
|
});
|
|
181
133
|
});
|
|
182
134
|
}
|
|
@@ -192,94 +144,37 @@ class ApprovalManager {
|
|
|
192
144
|
await this.approvalStore.deleteSessionState(this.sessionScope(sessionId));
|
|
193
145
|
});
|
|
194
146
|
}
|
|
195
|
-
// ====================
|
|
196
|
-
|
|
197
|
-
const scope = this.getOrCreateScope(scopeKey).toolPatterns;
|
|
198
|
-
const existing = scope.get(toolName);
|
|
199
|
-
if (existing) return existing;
|
|
200
|
-
const created = /* @__PURE__ */ new Set();
|
|
201
|
-
scope.set(toolName, created);
|
|
202
|
-
return created;
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Add an approval pattern for a tool.
|
|
206
|
-
*/
|
|
207
|
-
async addPattern(toolName, pattern, sessionId) {
|
|
147
|
+
// ==================== Generic Approval Key Methods ====================
|
|
148
|
+
async addApprovedKey(key, type = "session", sessionId) {
|
|
208
149
|
await this.restoreSessionState(sessionId);
|
|
209
150
|
const scopeKey = this.getScopeKey(sessionId);
|
|
210
151
|
await this.runWithScopeLock(scopeKey, async () => {
|
|
211
|
-
this.
|
|
152
|
+
const approvedKeys = this.getOrCreateScope(scopeKey).approvedKeys;
|
|
153
|
+
const existing = approvedKeys.get(key);
|
|
154
|
+
const effectiveType = type === "session" || existing === "session" ? "session" : "once";
|
|
155
|
+
approvedKeys.set(key, effectiveType);
|
|
212
156
|
await this.persistScope(sessionId);
|
|
213
157
|
});
|
|
214
|
-
this.logger.debug(
|
|
215
|
-
`Added pattern for '${toolName}' in '${this.getScopeLabel(sessionId)}': "${pattern}"`
|
|
216
|
-
);
|
|
217
158
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
*
|
|
221
|
-
* Note: This expects a pattern key (e.g. "git push *"), not raw arguments.
|
|
222
|
-
* Tools are responsible for generating the key via `tool.approval.patternKey()`.
|
|
223
|
-
*/
|
|
224
|
-
matchesPattern(toolName, patternKey, sessionId) {
|
|
225
|
-
const scopeKey = this.getScopeKey(sessionId);
|
|
226
|
-
const patterns = this.getScope(scopeKey).toolPatterns.get(toolName);
|
|
227
|
-
if (!patterns || patterns.size === 0) return false;
|
|
228
|
-
for (const storedPattern of patterns) {
|
|
229
|
-
if (patternCovers(storedPattern, patternKey)) {
|
|
230
|
-
this.logger.debug(
|
|
231
|
-
`Pattern key "${patternKey}" is covered by approved pattern "${storedPattern}" (tool: ${toolName})`
|
|
232
|
-
);
|
|
233
|
-
return true;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return false;
|
|
159
|
+
isApprovalKeySessionApproved(key, sessionId) {
|
|
160
|
+
return this.getScope(this.getScopeKey(sessionId)).approvedKeys.get(key) === "session";
|
|
237
161
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
162
|
+
isApprovalKeyApproved(key, sessionId) {
|
|
163
|
+
return this.getScope(this.getScopeKey(sessionId)).approvedKeys.has(key);
|
|
164
|
+
}
|
|
165
|
+
getApprovedKeys(sessionId) {
|
|
166
|
+
return this.getScope(this.getScopeKey(sessionId)).approvedKeys;
|
|
167
|
+
}
|
|
168
|
+
async clearApprovedKeys(sessionId) {
|
|
242
169
|
await this.restoreSessionState(sessionId);
|
|
243
170
|
const scopeKey = this.getScopeKey(sessionId);
|
|
244
171
|
await this.runWithScopeLock(scopeKey, async () => {
|
|
245
|
-
const
|
|
246
|
-
if (
|
|
247
|
-
|
|
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;
|
|
258
|
-
}
|
|
259
|
-
const count = Array.from(scope.values()).reduce((sum, set) => sum + set.size, 0);
|
|
260
|
-
scope.clear();
|
|
172
|
+
const approvedKeys = this.getOrCreateScope(scopeKey).approvedKeys;
|
|
173
|
+
if (approvedKeys.size === 0) return;
|
|
174
|
+
approvedKeys.clear();
|
|
261
175
|
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
176
|
});
|
|
268
177
|
}
|
|
269
|
-
/**
|
|
270
|
-
* Get patterns for a tool (for debugging/display).
|
|
271
|
-
*/
|
|
272
|
-
getToolPatterns(toolName, sessionId) {
|
|
273
|
-
const scopeKey = this.getScopeKey(sessionId);
|
|
274
|
-
return this.getScope(scopeKey).toolPatterns.get(toolName) ?? /* @__PURE__ */ new Set();
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Get all tool patterns (for debugging/display).
|
|
278
|
-
*/
|
|
279
|
-
getAllToolPatterns(sessionId) {
|
|
280
|
-
const scopeKey = this.getScopeKey(sessionId);
|
|
281
|
-
return this.getScope(scopeKey).toolPatterns;
|
|
282
|
-
}
|
|
283
178
|
// ==================== Directory Access Methods ====================
|
|
284
179
|
/**
|
|
285
180
|
* Resolve a directory path for use as an approval key.
|
|
@@ -288,150 +183,8 @@ class ApprovalManager {
|
|
|
288
183
|
* continue to work even when other subsystems canonicalize paths via realpath
|
|
289
184
|
* (e.g. macOS /tmp -> /private/tmp or custom symlinked directories).
|
|
290
185
|
*/
|
|
291
|
-
getPathApprovalKeys(targetPath) {
|
|
292
|
-
const resolved = path.resolve(targetPath);
|
|
293
|
-
const real = tryRealpathSyncWithExistingParent(resolved);
|
|
294
|
-
if (real && real !== resolved) {
|
|
295
|
-
return [resolved, real];
|
|
296
|
-
}
|
|
297
|
-
return [resolved];
|
|
298
|
-
}
|
|
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 = path.relative(approvedDir, normalized);
|
|
308
|
-
if (!relative.startsWith("..") && !path.isAbsolute(relative)) {
|
|
309
|
-
this.logger.debug(
|
|
310
|
-
`Path "${normalized}" is within approved directory "${approvedDir}" (type: ${type})`
|
|
311
|
-
);
|
|
312
|
-
return true;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return false;
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Initialize the working directory as a session-approved directory.
|
|
320
|
-
* This should be called once during setup to ensure the working directory
|
|
321
|
-
* never triggers directory access prompts.
|
|
322
|
-
*
|
|
323
|
-
* @param workingDir The working directory path
|
|
324
|
-
*/
|
|
325
|
-
async initializeWorkingDirectory(workingDir, sessionId) {
|
|
326
|
-
await this.addApprovedDirectory(workingDir, "session", sessionId);
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Add a directory to the approved list for this session.
|
|
330
|
-
* Files within this directory (including subdirectories) will be allowed.
|
|
331
|
-
*
|
|
332
|
-
* @param directory Absolute path to the directory to approve
|
|
333
|
-
* @param type The approval type:
|
|
334
|
-
* - 'session': No directory prompt on future accesses, follows tool config
|
|
335
|
-
* - 'once': Will prompt again on future accesses, but tool can execute this time
|
|
336
|
-
* @example
|
|
337
|
-
* ```typescript
|
|
338
|
-
* manager.addApprovedDirectory("/external/project", 'session');
|
|
339
|
-
* // Now /external/project/src/file.ts is accessible without directory prompt
|
|
340
|
-
*
|
|
341
|
-
* manager.addApprovedDirectory("/tmp/files", 'once');
|
|
342
|
-
* // Tool can access, but will prompt again next time
|
|
343
|
-
* ```
|
|
344
|
-
*/
|
|
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);
|
|
360
|
-
}
|
|
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;
|
|
370
|
-
this.logger.debug(
|
|
371
|
-
`Added approved directory in '${this.getScopeLabel(sessionId)}': "${resolvedKey}" (type: ${effectiveType})${realKey ? `, realpath: "${realKey}"` : ""}`
|
|
372
|
-
);
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
186
|
/**
|
|
376
|
-
*
|
|
377
|
-
* This is used for PROMPTING decisions - only 'session' type directories count.
|
|
378
|
-
* Working directory and user session-approved directories return true.
|
|
379
|
-
*
|
|
380
|
-
* @param filePath The file path to check (can be relative or absolute)
|
|
381
|
-
* @returns true if the path is within a session-approved directory
|
|
382
|
-
*/
|
|
383
|
-
isDirectorySessionApproved(filePath, sessionId) {
|
|
384
|
-
return this.isPathWithinApprovedDirectory(filePath, sessionId, /* @__PURE__ */ new Set(["session"]));
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* Check if a file path is within any approved directory (session OR once).
|
|
388
|
-
* This is used for EXECUTION decisions - both 'session' and 'once' types count.
|
|
389
|
-
* PathValidator uses this to determine if a tool can access the path.
|
|
390
|
-
*
|
|
391
|
-
* @param filePath The file path to check (can be relative or absolute)
|
|
392
|
-
* @returns true if the path is within any approved directory
|
|
393
|
-
*/
|
|
394
|
-
isDirectoryApproved(filePath, sessionId) {
|
|
395
|
-
return this.isPathWithinApprovedDirectory(
|
|
396
|
-
filePath,
|
|
397
|
-
sessionId,
|
|
398
|
-
/* @__PURE__ */ new Set(["session", "once"])
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
/**
|
|
402
|
-
* Clear all approved directories.
|
|
403
|
-
* Should be called when session ends.
|
|
404
|
-
*/
|
|
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
|
-
});
|
|
419
|
-
}
|
|
420
|
-
/**
|
|
421
|
-
* Get the current map of approved directories with their types (for debugging/display).
|
|
422
|
-
*/
|
|
423
|
-
getApprovedDirectories(sessionId) {
|
|
424
|
-
const scopeKey = this.getScopeKey(sessionId);
|
|
425
|
-
return this.getScope(scopeKey).approvedDirectories;
|
|
426
|
-
}
|
|
427
|
-
/**
|
|
428
|
-
* Get just the directory paths that are approved (for debugging/display).
|
|
429
|
-
*/
|
|
430
|
-
getApprovedDirectoryPaths(sessionId) {
|
|
431
|
-
return Array.from(this.getApprovedDirectories(sessionId).keys());
|
|
432
|
-
}
|
|
433
|
-
/**
|
|
434
|
-
* Clear all session-scoped approvals (tool patterns and directories).
|
|
187
|
+
* Clear all session-scoped approvals.
|
|
435
188
|
* Convenience method for clearing all session state at once.
|
|
436
189
|
*/
|
|
437
190
|
async clearSessionApprovals(sessionId) {
|
|
@@ -439,17 +192,12 @@ class ApprovalManager {
|
|
|
439
192
|
const scopeKey = this.getScopeKey(sessionId);
|
|
440
193
|
await this.runWithScopeLock(scopeKey, async () => {
|
|
441
194
|
const scope = this.getOrCreateScope(scopeKey);
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
0
|
|
445
|
-
);
|
|
446
|
-
const directoryCount = scope.approvedDirectories.size;
|
|
447
|
-
scope.toolPatterns.clear();
|
|
448
|
-
scope.approvedDirectories.clear();
|
|
195
|
+
const keyCount = scope.approvedKeys.size;
|
|
196
|
+
scope.approvedKeys.clear();
|
|
449
197
|
await this.persistScope(sessionId);
|
|
450
|
-
if (
|
|
198
|
+
if (keyCount > 0) {
|
|
451
199
|
this.logger.debug(
|
|
452
|
-
`Cleared ${
|
|
200
|
+
`Cleared ${keyCount} approval key(s) in '${this.getScopeLabel(sessionId)}'`
|
|
453
201
|
);
|
|
454
202
|
}
|
|
455
203
|
});
|
|
@@ -529,8 +277,6 @@ class ApprovalManager {
|
|
|
529
277
|
return ElicitationResponseSchema.parse(response);
|
|
530
278
|
case ApprovalType.CUSTOM:
|
|
531
279
|
return CustomApprovalResponseSchema.parse(response);
|
|
532
|
-
case ApprovalType.DIRECTORY_ACCESS:
|
|
533
|
-
return DirectoryAccessResponseSchema.parse(response);
|
|
534
280
|
}
|
|
535
281
|
}
|
|
536
282
|
getElicitationFormData(response) {
|
|
@@ -546,33 +292,6 @@ class ApprovalManager {
|
|
|
546
292
|
field: "formData"
|
|
547
293
|
});
|
|
548
294
|
}
|
|
549
|
-
/**
|
|
550
|
-
* Request directory access approval.
|
|
551
|
-
* Convenience method for directory access requests.
|
|
552
|
-
*
|
|
553
|
-
* @example
|
|
554
|
-
* ```typescript
|
|
555
|
-
* const response = await manager.requestDirectoryAccess({
|
|
556
|
-
* path: '/external/project/src/file.ts',
|
|
557
|
-
* parentDir: '/external/project',
|
|
558
|
-
* operation: 'write',
|
|
559
|
-
* toolName: 'write_file',
|
|
560
|
-
* sessionId: 'session-123'
|
|
561
|
-
* });
|
|
562
|
-
* ```
|
|
563
|
-
*/
|
|
564
|
-
async requestDirectoryAccess(metadata) {
|
|
565
|
-
const { sessionId, hostRuntime, timeout, ...directoryMetadata } = metadata;
|
|
566
|
-
return this.requestApproval(
|
|
567
|
-
this.createApprovalDetails(
|
|
568
|
-
ApprovalType.DIRECTORY_ACCESS,
|
|
569
|
-
directoryMetadata,
|
|
570
|
-
sessionId,
|
|
571
|
-
hostRuntime,
|
|
572
|
-
timeout
|
|
573
|
-
)
|
|
574
|
-
);
|
|
575
|
-
}
|
|
576
295
|
/**
|
|
577
296
|
* Request a generic approval
|
|
578
297
|
*/
|
|
@@ -824,8 +543,8 @@ class ApprovalManager {
|
|
|
824
543
|
}
|
|
825
544
|
/**
|
|
826
545
|
* Auto-approve pending requests that match a predicate.
|
|
827
|
-
* Used when a
|
|
828
|
-
*
|
|
546
|
+
* Used when a remembered approval should auto-approve other parallel requests
|
|
547
|
+
* selected by the caller.
|
|
829
548
|
*
|
|
830
549
|
* @param predicate Function that returns true for requests that should be auto-approved
|
|
831
550
|
* @param responseData Optional data to include in the auto-approval response
|
|
@@ -834,7 +553,9 @@ class ApprovalManager {
|
|
|
834
553
|
autoApprovePendingRequests(predicate, responseData) {
|
|
835
554
|
const count = this.handler?.autoApprovePending?.(predicate, responseData) ?? 0;
|
|
836
555
|
if (count > 0) {
|
|
837
|
-
this.logger.info(
|
|
556
|
+
this.logger.info(
|
|
557
|
+
`Auto-approved ${count} pending request(s) due to remembered approval`
|
|
558
|
+
);
|
|
838
559
|
}
|
|
839
560
|
return count;
|
|
840
561
|
}
|
|
@@ -34,10 +34,6 @@ __export(schemas_exports, {
|
|
|
34
34
|
CustomApprovalResponseDataSchema: () => CustomApprovalResponseDataSchema,
|
|
35
35
|
CustomApprovalResponseSchema: () => CustomApprovalResponseSchema,
|
|
36
36
|
DenialReasonSchema: () => DenialReasonSchema,
|
|
37
|
-
DirectoryAccessMetadataSchema: () => DirectoryAccessMetadataSchema,
|
|
38
|
-
DirectoryAccessRequestSchema: () => DirectoryAccessRequestSchema,
|
|
39
|
-
DirectoryAccessResponseDataSchema: () => DirectoryAccessResponseDataSchema,
|
|
40
|
-
DirectoryAccessResponseSchema: () => DirectoryAccessResponseSchema,
|
|
41
37
|
ElicitationMetadataSchema: () => ElicitationMetadataSchema,
|
|
42
38
|
ElicitationRequestSchema: () => ElicitationRequestSchema,
|
|
43
39
|
ElicitationResponseDataSchema: () => ElicitationResponseDataSchema,
|
|
@@ -65,14 +61,11 @@ const ToolPresentationSnapshotV1Schema = import_zod.z.custom(
|
|
|
65
61
|
message: "Invalid ToolPresentationSnapshotV1"
|
|
66
62
|
}
|
|
67
63
|
);
|
|
68
|
-
const DirectoryAccessMetadataSchema = import_zod.z.object({
|
|
69
|
-
path: import_zod.z.string().describe("Full path being accessed"),
|
|
70
|
-
parentDir: import_zod.z.string().describe("Parent directory (what gets approved for session)"),
|
|
71
|
-
operation: import_zod.z.enum(["read", "write", "edit"]).describe("Type of file operation"),
|
|
72
|
-
toolName: import_zod.z.string().describe("Name of the tool requesting access")
|
|
73
|
-
}).strict().describe("Directory access metadata");
|
|
74
64
|
const ToolApprovalMetadataSchema = import_zod.z.object({
|
|
75
65
|
toolName: import_zod.z.string().describe("Name of the tool to approve"),
|
|
66
|
+
approvalKey: import_zod.z.string().min(1).optional().describe(
|
|
67
|
+
"Optional opaque key identifying the approval scope. Core stores keys exactly and does not interpret them."
|
|
68
|
+
),
|
|
76
69
|
presentationSnapshot: ToolPresentationSnapshotV1Schema.optional().describe(
|
|
77
70
|
"Optional UI-agnostic presentation snapshot for the tool call. Clients MUST ignore unknown fields."
|
|
78
71
|
),
|
|
@@ -81,12 +74,6 @@ const ToolApprovalMetadataSchema = import_zod.z.object({
|
|
|
81
74
|
description: import_zod.z.string().optional().describe("Description of the tool"),
|
|
82
75
|
displayPreview: ToolDisplayDataSchema.optional().describe(
|
|
83
76
|
"Preview display data for approval UI (e.g., diff preview)"
|
|
84
|
-
),
|
|
85
|
-
directoryAccess: DirectoryAccessMetadataSchema.optional().describe(
|
|
86
|
-
"Optional directory access metadata when the tool targets a path outside config-allowed roots"
|
|
87
|
-
),
|
|
88
|
-
suggestedPatterns: import_zod.z.array(import_zod.z.string()).optional().describe(
|
|
89
|
-
'Suggested patterns for session approval. Tools may provide patterns to allow approving a broader subset of future calls (e.g., ["git push *", "git *"]).'
|
|
90
77
|
)
|
|
91
78
|
}).strict().describe("Tool approval metadata");
|
|
92
79
|
const CommandApprovalMetadataSchema = import_zod.z.object({
|
|
@@ -127,24 +114,15 @@ const CustomApprovalRequestSchema = BaseApprovalRequestSchema.extend({
|
|
|
127
114
|
type: import_zod.z.literal(import_types.ApprovalType.CUSTOM),
|
|
128
115
|
metadata: CustomApprovalMetadataSchema
|
|
129
116
|
}).strict();
|
|
130
|
-
const DirectoryAccessRequestSchema = BaseApprovalRequestSchema.extend({
|
|
131
|
-
type: import_zod.z.literal(import_types.ApprovalType.DIRECTORY_ACCESS),
|
|
132
|
-
metadata: DirectoryAccessMetadataSchema
|
|
133
|
-
}).strict();
|
|
134
117
|
const ApprovalRequestSchema = import_zod.z.discriminatedUnion("type", [
|
|
135
118
|
ToolApprovalRequestSchema,
|
|
136
119
|
CommandApprovalRequestSchema,
|
|
137
120
|
ElicitationRequestSchema,
|
|
138
|
-
CustomApprovalRequestSchema
|
|
139
|
-
DirectoryAccessRequestSchema
|
|
121
|
+
CustomApprovalRequestSchema
|
|
140
122
|
]);
|
|
141
123
|
const ToolApprovalResponseDataSchema = import_zod.z.object({
|
|
142
|
-
rememberChoice: import_zod.z.boolean().optional().describe(
|
|
143
|
-
|
|
144
|
-
'Remember an approval pattern (e.g., "git *"). Only applicable when the tool provides pattern-based approval support.'
|
|
145
|
-
),
|
|
146
|
-
rememberDirectory: import_zod.z.boolean().optional().describe(
|
|
147
|
-
"Remember this directory for the session (allows future access without prompting again)"
|
|
124
|
+
rememberChoice: import_zod.z.boolean().optional().describe(
|
|
125
|
+
"Remember this approval scope for the session. If an approval key is present, only matching keys are remembered; otherwise all uses of this tool are approved."
|
|
148
126
|
)
|
|
149
127
|
}).strict().describe("Tool approval response data");
|
|
150
128
|
const CommandApprovalResponseDataSchema = import_zod.z.object({
|
|
@@ -155,9 +133,6 @@ const ElicitationResponseDataSchema = import_zod.z.object({
|
|
|
155
133
|
formData: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).describe("Form data matching schema")
|
|
156
134
|
}).strict().describe("Elicitation response data");
|
|
157
135
|
const CustomApprovalResponseDataSchema = import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).describe("Custom response data");
|
|
158
|
-
const DirectoryAccessResponseDataSchema = import_zod.z.object({
|
|
159
|
-
rememberDirectory: import_zod.z.boolean().optional().describe("Remember this directory for the session (allows all file access within it)")
|
|
160
|
-
}).strict().describe("Directory access response data");
|
|
161
136
|
const BaseApprovalResponseSchema = import_zod.z.object({
|
|
162
137
|
approvalId: import_zod.z.string().uuid().describe("Must match request approvalId"),
|
|
163
138
|
status: ApprovalStatusSchema.describe("Approval status"),
|
|
@@ -183,15 +158,11 @@ const ElicitationResponseSchema = BaseApprovalResponseSchema.extend({
|
|
|
183
158
|
const CustomApprovalResponseSchema = BaseApprovalResponseSchema.extend({
|
|
184
159
|
data: CustomApprovalResponseDataSchema.optional()
|
|
185
160
|
}).strict();
|
|
186
|
-
const DirectoryAccessResponseSchema = BaseApprovalResponseSchema.extend({
|
|
187
|
-
data: DirectoryAccessResponseDataSchema.optional()
|
|
188
|
-
}).strict();
|
|
189
161
|
const ApprovalResponseSchema = import_zod.z.union([
|
|
190
162
|
ToolApprovalResponseSchema,
|
|
191
163
|
CommandApprovalResponseSchema,
|
|
192
164
|
ElicitationResponseSchema,
|
|
193
|
-
CustomApprovalResponseSchema
|
|
194
|
-
DirectoryAccessResponseSchema
|
|
165
|
+
CustomApprovalResponseSchema
|
|
195
166
|
]);
|
|
196
167
|
const ApprovalRequestDetailsSchema = import_zod.z.object({
|
|
197
168
|
type: ApprovalTypeSchema,
|
|
@@ -204,8 +175,7 @@ const ApprovalRequestDetailsSchema = import_zod.z.object({
|
|
|
204
175
|
ToolApprovalMetadataSchema,
|
|
205
176
|
CommandApprovalMetadataSchema,
|
|
206
177
|
ElicitationMetadataSchema,
|
|
207
|
-
CustomApprovalMetadataSchema
|
|
208
|
-
DirectoryAccessMetadataSchema
|
|
178
|
+
CustomApprovalMetadataSchema
|
|
209
179
|
])
|
|
210
180
|
}).superRefine((data, ctx) => {
|
|
211
181
|
if (data.type === import_types.ApprovalType.TOOL_APPROVAL) {
|
|
@@ -235,15 +205,6 @@ const ApprovalRequestDetailsSchema = import_zod.z.object({
|
|
|
235
205
|
path: ["metadata"]
|
|
236
206
|
});
|
|
237
207
|
}
|
|
238
|
-
} else if (data.type === import_types.ApprovalType.DIRECTORY_ACCESS) {
|
|
239
|
-
const result = DirectoryAccessMetadataSchema.safeParse(data.metadata);
|
|
240
|
-
if (!result.success) {
|
|
241
|
-
ctx.addIssue({
|
|
242
|
-
code: import_zod.z.ZodIssueCode.custom,
|
|
243
|
-
message: "Metadata must match DirectoryAccessMetadataSchema for DIRECTORY_ACCESS type",
|
|
244
|
-
path: ["metadata"]
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
208
|
} else if (data.type === import_types.ApprovalType.CUSTOM) {
|
|
248
209
|
const result = CustomApprovalMetadataSchema.safeParse(data.metadata);
|
|
249
210
|
if (!result.success) {
|
|
@@ -273,10 +234,6 @@ const ApprovalRequestDetailsSchema = import_zod.z.object({
|
|
|
273
234
|
CustomApprovalResponseDataSchema,
|
|
274
235
|
CustomApprovalResponseSchema,
|
|
275
236
|
DenialReasonSchema,
|
|
276
|
-
DirectoryAccessMetadataSchema,
|
|
277
|
-
DirectoryAccessRequestSchema,
|
|
278
|
-
DirectoryAccessResponseDataSchema,
|
|
279
|
-
DirectoryAccessResponseSchema,
|
|
280
237
|
ElicitationMetadataSchema,
|
|
281
238
|
ElicitationRequestSchema,
|
|
282
239
|
ElicitationResponseDataSchema,
|