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