@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
|
@@ -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,103 @@ 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, hostRuntime, 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
|
+
if (hostRuntime !== void 0) {
|
|
467
|
+
details.hostRuntime = hostRuntime;
|
|
468
|
+
}
|
|
469
|
+
return details;
|
|
470
|
+
}
|
|
471
|
+
createResponse(request, response) {
|
|
472
|
+
return {
|
|
473
|
+
approvalId: request.approvalId,
|
|
474
|
+
...request.sessionId !== void 0 ? { sessionId: request.sessionId } : {},
|
|
475
|
+
...request.hostRuntime !== void 0 ? { hostRuntime: request.hostRuntime } : {},
|
|
476
|
+
...response
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
getElicitationFormData(response) {
|
|
480
|
+
if (response.data && typeof response.data === "object" && "formData" in response.data && typeof response.data.formData === "object" && response.data.formData !== null) {
|
|
481
|
+
return response.data.formData;
|
|
482
|
+
}
|
|
483
|
+
if (response.data === void 0 || typeof response.data === "object" && response.data !== null && !("formData" in response.data)) {
|
|
484
|
+
return {};
|
|
485
|
+
}
|
|
486
|
+
throw import_errors.ApprovalError.invalidResponse("Approved elicitation response is missing formData", {
|
|
487
|
+
approvalId: response.approvalId,
|
|
488
|
+
type: import_types.ApprovalType.ELICITATION,
|
|
489
|
+
field: "formData"
|
|
490
|
+
});
|
|
311
491
|
}
|
|
312
492
|
/**
|
|
313
493
|
* Request directory access approval.
|
|
@@ -325,17 +505,16 @@ class ApprovalManager {
|
|
|
325
505
|
* ```
|
|
326
506
|
*/
|
|
327
507
|
async requestDirectoryAccess(metadata) {
|
|
328
|
-
const { sessionId, timeout, ...directoryMetadata } = metadata;
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return this.requestApproval(details);
|
|
508
|
+
const { sessionId, hostRuntime, timeout, ...directoryMetadata } = metadata;
|
|
509
|
+
return this.requestApproval(
|
|
510
|
+
this.createApprovalDetails(
|
|
511
|
+
import_types.ApprovalType.DIRECTORY_ACCESS,
|
|
512
|
+
directoryMetadata,
|
|
513
|
+
sessionId,
|
|
514
|
+
hostRuntime,
|
|
515
|
+
timeout
|
|
516
|
+
)
|
|
517
|
+
);
|
|
339
518
|
}
|
|
340
519
|
/**
|
|
341
520
|
* Request a generic approval
|
|
@@ -366,29 +545,19 @@ class ApprovalManager {
|
|
|
366
545
|
this.logger.info(
|
|
367
546
|
`Auto-approve approval '${request.type}', approvalId: ${request.approvalId}`
|
|
368
547
|
);
|
|
369
|
-
|
|
370
|
-
approvalId: request.approvalId,
|
|
548
|
+
return this.createResponse(request, {
|
|
371
549
|
status: import_types.ApprovalStatus.APPROVED
|
|
372
|
-
};
|
|
373
|
-
if (request.sessionId !== void 0) {
|
|
374
|
-
response.sessionId = request.sessionId;
|
|
375
|
-
}
|
|
376
|
-
return response;
|
|
550
|
+
});
|
|
377
551
|
}
|
|
378
552
|
if (mode === "auto-deny") {
|
|
379
553
|
this.logger.info(
|
|
380
554
|
`Auto-deny approval '${request.type}', approvalId: ${request.approvalId}`
|
|
381
555
|
);
|
|
382
|
-
|
|
383
|
-
approvalId: request.approvalId,
|
|
556
|
+
return this.createResponse(request, {
|
|
384
557
|
status: import_types.ApprovalStatus.DENIED,
|
|
385
558
|
reason: import_types.DenialReason.SYSTEM_DENIED,
|
|
386
559
|
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;
|
|
560
|
+
});
|
|
392
561
|
}
|
|
393
562
|
const handler = this.ensureHandler();
|
|
394
563
|
this.logger.info(
|
|
@@ -404,17 +573,16 @@ class ApprovalManager {
|
|
|
404
573
|
* Tool confirmations always happen in session context during LLM execution
|
|
405
574
|
*/
|
|
406
575
|
async requestToolApproval(metadata) {
|
|
407
|
-
const { sessionId, timeout, ...toolMetadata } = metadata;
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
return this.requestApproval(details);
|
|
576
|
+
const { sessionId, hostRuntime, timeout, ...toolMetadata } = metadata;
|
|
577
|
+
return this.requestApproval(
|
|
578
|
+
this.createApprovalDetails(
|
|
579
|
+
import_types.ApprovalType.TOOL_APPROVAL,
|
|
580
|
+
toolMetadata,
|
|
581
|
+
sessionId,
|
|
582
|
+
hostRuntime,
|
|
583
|
+
timeout
|
|
584
|
+
)
|
|
585
|
+
);
|
|
418
586
|
}
|
|
419
587
|
/**
|
|
420
588
|
* Request command confirmation approval
|
|
@@ -438,17 +606,16 @@ class ApprovalManager {
|
|
|
438
606
|
* ```
|
|
439
607
|
*/
|
|
440
608
|
async requestCommandConfirmation(metadata) {
|
|
441
|
-
const { sessionId, timeout, ...commandMetadata } = metadata;
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
return this.requestApproval(details);
|
|
609
|
+
const { sessionId, hostRuntime, timeout, ...commandMetadata } = metadata;
|
|
610
|
+
return this.requestApproval(
|
|
611
|
+
this.createApprovalDetails(
|
|
612
|
+
import_types.ApprovalType.COMMAND_CONFIRMATION,
|
|
613
|
+
commandMetadata,
|
|
614
|
+
sessionId,
|
|
615
|
+
hostRuntime,
|
|
616
|
+
timeout
|
|
617
|
+
)
|
|
618
|
+
);
|
|
452
619
|
}
|
|
453
620
|
/**
|
|
454
621
|
* Request elicitation from MCP server
|
|
@@ -458,17 +625,16 @@ class ApprovalManager {
|
|
|
458
625
|
* and the MCP protocol doesn't include session context in elicitation requests.
|
|
459
626
|
*/
|
|
460
627
|
async requestElicitation(metadata) {
|
|
461
|
-
const { sessionId, timeout, ...elicitationMetadata } = metadata;
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
return this.requestApproval(details);
|
|
628
|
+
const { sessionId, hostRuntime, timeout, ...elicitationMetadata } = metadata;
|
|
629
|
+
return this.requestApproval(
|
|
630
|
+
this.createApprovalDetails(
|
|
631
|
+
import_types.ApprovalType.ELICITATION,
|
|
632
|
+
elicitationMetadata,
|
|
633
|
+
sessionId,
|
|
634
|
+
hostRuntime,
|
|
635
|
+
timeout
|
|
636
|
+
)
|
|
637
|
+
);
|
|
472
638
|
}
|
|
473
639
|
/**
|
|
474
640
|
* Check if tool approval was approved
|
|
@@ -500,10 +666,7 @@ class ApprovalManager {
|
|
|
500
666
|
async getElicitationData(metadata) {
|
|
501
667
|
const response = await this.requestElicitation(metadata);
|
|
502
668
|
if (response.status === import_types.ApprovalStatus.APPROVED) {
|
|
503
|
-
|
|
504
|
-
return response.data.formData;
|
|
505
|
-
}
|
|
506
|
-
return {};
|
|
669
|
+
return this.getElicitationFormData(response);
|
|
507
670
|
} else if (response.status === import_types.ApprovalStatus.DENIED) {
|
|
508
671
|
throw import_errors.ApprovalError.elicitationDenied(
|
|
509
672
|
metadata.serverName,
|