@trops/dash-core 0.1.637 → 0.1.639
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/electron/index.js +642 -64
- package/dist/electron/index.js.map +1 -1
- package/dist/index.esm.js +326 -121
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +326 -121
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/electron/index.js
CHANGED
|
@@ -21,7 +21,7 @@ var require$$11 = require('https');
|
|
|
21
21
|
var require$$0$8 = require('@modelcontextprotocol/sdk/client/index.js');
|
|
22
22
|
var require$$1$6 = require('@modelcontextprotocol/sdk/client/stdio.js');
|
|
23
23
|
var require$$0$7 = require('pkce-challenge');
|
|
24
|
-
var require$$
|
|
24
|
+
var require$$13 = require('child_process');
|
|
25
25
|
var require$$1$5 = require('http');
|
|
26
26
|
var require$$2$2 = require('algoliasearch');
|
|
27
27
|
var require$$3$2 = require('node:path');
|
|
@@ -982,6 +982,9 @@ const BOTS_RESUME_ALL$1 = "bots-resume-all";
|
|
|
982
982
|
const BOTS_PAUSE_BOT$1 = "bots-pause-bot";
|
|
983
983
|
const BOTS_RESUME_BOT$1 = "bots-resume-bot";
|
|
984
984
|
const BOTS_GET_PAUSE_STATE$1 = "bots-get-pause-state";
|
|
985
|
+
const BOTS_LIST_TOOL_SOURCES$1 = "bots-list-tool-sources";
|
|
986
|
+
const BOTS_GET_GRANTS$1 = "bots-get-grants";
|
|
987
|
+
const BOTS_REVOKE_GRANT$1 = "bots-revoke-grant";
|
|
985
988
|
|
|
986
989
|
// --- Main → Renderer (send) ---
|
|
987
990
|
const BOT_STREAM$2 = "bot-stream"; // { botId, event: BotEvent }
|
|
@@ -1008,6 +1011,9 @@ var botEvents$1 = {
|
|
|
1008
1011
|
BOTS_PAUSE_BOT: BOTS_PAUSE_BOT$1,
|
|
1009
1012
|
BOTS_RESUME_BOT: BOTS_RESUME_BOT$1,
|
|
1010
1013
|
BOTS_GET_PAUSE_STATE: BOTS_GET_PAUSE_STATE$1,
|
|
1014
|
+
BOTS_LIST_TOOL_SOURCES: BOTS_LIST_TOOL_SOURCES$1,
|
|
1015
|
+
BOTS_GET_GRANTS: BOTS_GET_GRANTS$1,
|
|
1016
|
+
BOTS_REVOKE_GRANT: BOTS_REVOKE_GRANT$1,
|
|
1011
1017
|
BOT_STREAM: BOT_STREAM$2,
|
|
1012
1018
|
BOT_APPROVAL_PENDING: BOT_APPROVAL_PENDING$2,
|
|
1013
1019
|
BOT_BUDGET_ALERT: BOT_BUDGET_ALERT$2,
|
|
@@ -19490,7 +19496,7 @@ util$E.isObject = isObject;
|
|
|
19490
19496
|
util$E.isPlainObject = isPlainObject;
|
|
19491
19497
|
util$E.numKeys = numKeys;
|
|
19492
19498
|
util$E.escapeRegex = escapeRegex;
|
|
19493
|
-
util$E.clone = clone;
|
|
19499
|
+
util$E.clone = clone$1;
|
|
19494
19500
|
util$E.normalizeParams = normalizeParams;
|
|
19495
19501
|
util$E.createTransparentProxy = createTransparentProxy;
|
|
19496
19502
|
util$E.stringifyPrimitive = stringifyPrimitive;
|
|
@@ -19713,7 +19719,7 @@ function escapeRegex(str) {
|
|
|
19713
19719
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
19714
19720
|
}
|
|
19715
19721
|
// zod-specific utils
|
|
19716
|
-
function clone(inst, def, params) {
|
|
19722
|
+
function clone$1(inst, def, params) {
|
|
19717
19723
|
const cl = new inst._zod.constr(def ?? inst._zod.def);
|
|
19718
19724
|
if (!def || params?.parent)
|
|
19719
19725
|
cl._zod.parent = inst;
|
|
@@ -19803,7 +19809,7 @@ function pick$1(schema, mask) {
|
|
|
19803
19809
|
// pick key
|
|
19804
19810
|
newShape[key] = currDef.shape[key];
|
|
19805
19811
|
}
|
|
19806
|
-
return clone(schema, {
|
|
19812
|
+
return clone$1(schema, {
|
|
19807
19813
|
...schema._zod.def,
|
|
19808
19814
|
shape: newShape,
|
|
19809
19815
|
checks: [],
|
|
@@ -19820,7 +19826,7 @@ function omit(schema, mask) {
|
|
|
19820
19826
|
continue;
|
|
19821
19827
|
delete newShape[key];
|
|
19822
19828
|
}
|
|
19823
|
-
return clone(schema, {
|
|
19829
|
+
return clone$1(schema, {
|
|
19824
19830
|
...schema._zod.def,
|
|
19825
19831
|
shape: newShape,
|
|
19826
19832
|
checks: [],
|
|
@@ -19839,10 +19845,10 @@ function extend(schema, shape) {
|
|
|
19839
19845
|
},
|
|
19840
19846
|
checks: [], // delete existing checks
|
|
19841
19847
|
};
|
|
19842
|
-
return clone(schema, def);
|
|
19848
|
+
return clone$1(schema, def);
|
|
19843
19849
|
}
|
|
19844
19850
|
function merge(a, b) {
|
|
19845
|
-
return clone(a, {
|
|
19851
|
+
return clone$1(a, {
|
|
19846
19852
|
...a._zod.def,
|
|
19847
19853
|
get shape() {
|
|
19848
19854
|
const _shape = { ...a._zod.def.shape, ...b._zod.def.shape };
|
|
@@ -19883,7 +19889,7 @@ function partial(Class, schema, mask) {
|
|
|
19883
19889
|
: oldShape[key];
|
|
19884
19890
|
}
|
|
19885
19891
|
}
|
|
19886
|
-
return clone(schema, {
|
|
19892
|
+
return clone$1(schema, {
|
|
19887
19893
|
...schema._zod.def,
|
|
19888
19894
|
shape,
|
|
19889
19895
|
checks: [],
|
|
@@ -19915,7 +19921,7 @@ function required$3(Class, schema, mask) {
|
|
|
19915
19921
|
});
|
|
19916
19922
|
}
|
|
19917
19923
|
}
|
|
19918
|
-
return clone(schema, {
|
|
19924
|
+
return clone$1(schema, {
|
|
19919
19925
|
...schema._zod.def,
|
|
19920
19926
|
shape,
|
|
19921
19927
|
// optional: [],
|
|
@@ -36672,7 +36678,7 @@ function _resolveIdentity({ token, widgetId }) {
|
|
|
36672
36678
|
// Argument keys that look like paths. Different MCP servers use
|
|
36673
36679
|
// different conventions; this list covers the common filesystem-style
|
|
36674
36680
|
// servers. Extensible — add as new patterns surface.
|
|
36675
|
-
const PATH_ARG_KEYS = ["path", "uri", "filepath", "file", "directory"];
|
|
36681
|
+
const PATH_ARG_KEYS$1 = ["path", "uri", "filepath", "file", "directory"];
|
|
36676
36682
|
|
|
36677
36683
|
// Heuristic: tool names matching this regex are treated as writes for
|
|
36678
36684
|
// purposes of choosing readPaths vs writePaths. The match is intentionally
|
|
@@ -36681,7 +36687,7 @@ const PATH_ARG_KEYS = ["path", "uri", "filepath", "file", "directory"];
|
|
|
36681
36687
|
const WRITE_TOOL_PATTERN =
|
|
36682
36688
|
/(^|_)(write|create|edit|delete|remove|append|move|rename|chmod|chown|mkdir)/i;
|
|
36683
36689
|
|
|
36684
|
-
function isWriteTool(toolName) {
|
|
36690
|
+
function isWriteTool$1(toolName) {
|
|
36685
36691
|
if (typeof toolName !== "string") return false;
|
|
36686
36692
|
return WRITE_TOOL_PATTERN.test(toolName);
|
|
36687
36693
|
}
|
|
@@ -36755,7 +36761,7 @@ function _evaluateGrant({
|
|
|
36755
36761
|
|
|
36756
36762
|
// Path-argument containment. Only checked when the tool's args
|
|
36757
36763
|
// include a path-shaped key.
|
|
36758
|
-
const isWrite = isWriteTool(toolName);
|
|
36764
|
+
const isWrite = isWriteTool$1(toolName);
|
|
36759
36765
|
// Write tools must use writePaths; read tools may use either
|
|
36760
36766
|
// readPaths or writePaths (write access implies read access).
|
|
36761
36767
|
const allowedPaths = isWrite
|
|
@@ -36763,7 +36769,7 @@ function _evaluateGrant({
|
|
|
36763
36769
|
: [...serverPerms.readPaths, ...serverPerms.writePaths];
|
|
36764
36770
|
|
|
36765
36771
|
if (args && typeof args === "object") {
|
|
36766
|
-
for (const key of PATH_ARG_KEYS) {
|
|
36772
|
+
for (const key of PATH_ARG_KEYS$1) {
|
|
36767
36773
|
const v = args[key];
|
|
36768
36774
|
if (typeof v !== "string" || !v) continue;
|
|
36769
36775
|
if (allowedPaths.length === 0) {
|
|
@@ -37132,8 +37138,8 @@ var permissionGate = {
|
|
|
37132
37138
|
gateToolCall: gateToolCall$1,
|
|
37133
37139
|
gateBotToolCall,
|
|
37134
37140
|
gateToolCallWithJit: gateToolCallWithJit$1,
|
|
37135
|
-
isWriteTool,
|
|
37136
|
-
PATH_ARG_KEYS,
|
|
37141
|
+
isWriteTool: isWriteTool$1,
|
|
37142
|
+
PATH_ARG_KEYS: PATH_ARG_KEYS$1,
|
|
37137
37143
|
};
|
|
37138
37144
|
|
|
37139
37145
|
/**
|
|
@@ -37164,7 +37170,7 @@ var permissionGate = {
|
|
|
37164
37170
|
* the trust boundary appears.
|
|
37165
37171
|
*/
|
|
37166
37172
|
|
|
37167
|
-
const NO_WORKSPACE = "__no_workspace__";
|
|
37173
|
+
const NO_WORKSPACE$1 = "__no_workspace__";
|
|
37168
37174
|
const SEP = "::";
|
|
37169
37175
|
|
|
37170
37176
|
function serverKey$1(workspaceId, serverName) {
|
|
@@ -37172,11 +37178,11 @@ function serverKey$1(workspaceId, serverName) {
|
|
|
37172
37178
|
throw new Error("serverKey: serverName is required");
|
|
37173
37179
|
}
|
|
37174
37180
|
const wid =
|
|
37175
|
-
typeof workspaceId === "string" && workspaceId ? workspaceId : NO_WORKSPACE;
|
|
37181
|
+
typeof workspaceId === "string" && workspaceId ? workspaceId : NO_WORKSPACE$1;
|
|
37176
37182
|
return wid + SEP + serverName;
|
|
37177
37183
|
}
|
|
37178
37184
|
|
|
37179
|
-
function parseServerKey$
|
|
37185
|
+
function parseServerKey$2(key) {
|
|
37180
37186
|
if (typeof key !== "string") {
|
|
37181
37187
|
throw new Error("parseServerKey: key must be a string");
|
|
37182
37188
|
}
|
|
@@ -37194,11 +37200,265 @@ function parseServerKey$1(key) {
|
|
|
37194
37200
|
|
|
37195
37201
|
var mcpServerKey = {
|
|
37196
37202
|
serverKey: serverKey$1,
|
|
37197
|
-
parseServerKey: parseServerKey$
|
|
37198
|
-
NO_WORKSPACE,
|
|
37203
|
+
parseServerKey: parseServerKey$2,
|
|
37204
|
+
NO_WORKSPACE: NO_WORKSPACE$1,
|
|
37199
37205
|
SEP,
|
|
37200
37206
|
};
|
|
37201
37207
|
|
|
37208
|
+
/**
|
|
37209
|
+
* toolSources.js — how a bot finds, starts, and uses the user's Dash MCP
|
|
37210
|
+
* providers (the integrations configured under Settings → Providers).
|
|
37211
|
+
*
|
|
37212
|
+
* Portable (no electron import — NFR-006): the MCP controller, provider store
|
|
37213
|
+
* and server map are injected by botController.
|
|
37214
|
+
*
|
|
37215
|
+
* Server reuse goes through the same `mcpController.startServer` factory the
|
|
37216
|
+
* dashboards use: a server already connected — or already starting — for a
|
|
37217
|
+
* given (workspace, server) bucket is reused, never spawned twice. Buckets are
|
|
37218
|
+
* per-workspace on purpose (each workspace's processes carry that workspace's
|
|
37219
|
+
* grants), so a bot uses its OWN bucket (its workspaceId, or the no-workspace
|
|
37220
|
+
* bucket) and never borrows a dashboard workspace's process.
|
|
37221
|
+
*/
|
|
37222
|
+
|
|
37223
|
+
const { parseServerKey: parseServerKey$1 } = mcpServerKey;
|
|
37224
|
+
|
|
37225
|
+
const NO_WORKSPACE = "__no_workspace__";
|
|
37226
|
+
|
|
37227
|
+
const normalizeWorkspace = (wid) =>
|
|
37228
|
+
typeof wid === "string" && wid && wid !== NO_WORKSPACE ? wid : null;
|
|
37229
|
+
|
|
37230
|
+
/**
|
|
37231
|
+
* Summarize mcpController's `activeServers` Map (keyed "<workspace>::<server>")
|
|
37232
|
+
* into connected servers with their REAL server name + workspace.
|
|
37233
|
+
*/
|
|
37234
|
+
function connectedServersFromMap$1(activeServers, connectedStatus) {
|
|
37235
|
+
const out = [];
|
|
37236
|
+
if (!activeServers || typeof activeServers.entries !== "function") return out;
|
|
37237
|
+
for (const [key, server] of activeServers.entries()) {
|
|
37238
|
+
if (!server || server.status !== connectedStatus) continue;
|
|
37239
|
+
let serverName = key;
|
|
37240
|
+
let workspaceId = null;
|
|
37241
|
+
try {
|
|
37242
|
+
const parsed = parseServerKey$1(key);
|
|
37243
|
+
serverName = parsed.serverName;
|
|
37244
|
+
workspaceId = normalizeWorkspace(parsed.workspaceId);
|
|
37245
|
+
} catch (_) {
|
|
37246
|
+
// Legacy un-prefixed key: treat as the no-workspace bucket.
|
|
37247
|
+
}
|
|
37248
|
+
out.push({
|
|
37249
|
+
serverName,
|
|
37250
|
+
workspaceId,
|
|
37251
|
+
tools: server.tools || [],
|
|
37252
|
+
resources: server.resources || [],
|
|
37253
|
+
status: server.status,
|
|
37254
|
+
});
|
|
37255
|
+
}
|
|
37256
|
+
return out;
|
|
37257
|
+
}
|
|
37258
|
+
|
|
37259
|
+
const inBucket = (server, workspaceId) =>
|
|
37260
|
+
normalizeWorkspace(server.workspaceId) === normalizeWorkspace(workspaceId);
|
|
37261
|
+
|
|
37262
|
+
/**
|
|
37263
|
+
* The bot form's provider list: every configured MCP provider (credential-only
|
|
37264
|
+
* providers give a bot nothing to call, so they're excluded), with whether it's
|
|
37265
|
+
* running in the bot's bucket and its tool count when known.
|
|
37266
|
+
*
|
|
37267
|
+
* `tools` is what the form offers for per-bot narrowing: the provider's
|
|
37268
|
+
* declared `allowedTools` (set in Settings → Providers — known without starting
|
|
37269
|
+
* the server); for a provider with no declared limit, the live tool names if
|
|
37270
|
+
* it's running, else null (every tool, list not known yet). Never credentials.
|
|
37271
|
+
*/
|
|
37272
|
+
function listToolSources$1({
|
|
37273
|
+
providers,
|
|
37274
|
+
connected = [],
|
|
37275
|
+
workspaceId = null,
|
|
37276
|
+
} = {}) {
|
|
37277
|
+
if (!Array.isArray(providers)) return [];
|
|
37278
|
+
return providers
|
|
37279
|
+
.filter((p) => p && p.providerClass === "mcp" && p.name)
|
|
37280
|
+
.map((p) => {
|
|
37281
|
+
const live = (connected || []).find(
|
|
37282
|
+
(s) => s.serverName === p.name && inBucket(s, workspaceId),
|
|
37283
|
+
);
|
|
37284
|
+
const declared = Array.isArray(p.allowedTools);
|
|
37285
|
+
const liveNames = live
|
|
37286
|
+
? (live.tools || []).map((t) => t && t.name).filter(Boolean)
|
|
37287
|
+
: null;
|
|
37288
|
+
return {
|
|
37289
|
+
name: p.name,
|
|
37290
|
+
type: p.type || null,
|
|
37291
|
+
running: !!live,
|
|
37292
|
+
toolCount: live ? (live.tools || []).length : null,
|
|
37293
|
+
declared,
|
|
37294
|
+
tools: declared ? [...p.allowedTools] : liveNames,
|
|
37295
|
+
};
|
|
37296
|
+
})
|
|
37297
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
37298
|
+
}
|
|
37299
|
+
|
|
37300
|
+
/**
|
|
37301
|
+
* The tools a bot may use on one provider: the provider's declared limit
|
|
37302
|
+
* (Settings → Providers) is the ceiling; the bot's own selection narrows within
|
|
37303
|
+
* it. null = no restriction (every tool the server exposes).
|
|
37304
|
+
*
|
|
37305
|
+
* @param {string[]|null|undefined} providerLimit provider.allowedTools
|
|
37306
|
+
* @param {string[]|null|undefined} selection bot.toolSelections[provider]
|
|
37307
|
+
* @returns {string[]|null}
|
|
37308
|
+
*/
|
|
37309
|
+
function effectiveAllowedTools(providerLimit, selection) {
|
|
37310
|
+
const limit = Array.isArray(providerLimit) ? providerLimit : null;
|
|
37311
|
+
const sel = Array.isArray(selection) ? selection : null;
|
|
37312
|
+
if (limit && sel) return sel.filter((t) => limit.includes(t));
|
|
37313
|
+
if (limit) return [...limit];
|
|
37314
|
+
if (sel) return [...sel];
|
|
37315
|
+
return null;
|
|
37316
|
+
}
|
|
37317
|
+
|
|
37318
|
+
/**
|
|
37319
|
+
* Before a run: start each of the bot's selected providers that isn't already
|
|
37320
|
+
* connected in the bot's bucket, via the shared startServer factory (which
|
|
37321
|
+
* itself dedupes concurrent starts). Never throws — returns per-provider
|
|
37322
|
+
* failures so the run can surface them instead of silently running without
|
|
37323
|
+
* those tools.
|
|
37324
|
+
*/
|
|
37325
|
+
async function ensureBotServers$1({
|
|
37326
|
+
bot,
|
|
37327
|
+
connected = [],
|
|
37328
|
+
getProvider,
|
|
37329
|
+
startServer,
|
|
37330
|
+
}) {
|
|
37331
|
+
const failed = [];
|
|
37332
|
+
const wanted = Array.isArray(bot && bot.mcpServers) ? bot.mcpServers : [];
|
|
37333
|
+
const workspaceId = normalizeWorkspace(bot && bot.workspaceId);
|
|
37334
|
+
for (const serverName of wanted) {
|
|
37335
|
+
const running = (connected || []).some(
|
|
37336
|
+
(s) => s.serverName === serverName && inBucket(s, workspaceId),
|
|
37337
|
+
);
|
|
37338
|
+
if (running) continue;
|
|
37339
|
+
try {
|
|
37340
|
+
// providerController.getProvider returns { provider } or
|
|
37341
|
+
// { error, message }; accept a bare provider too.
|
|
37342
|
+
const result = await getProvider(serverName);
|
|
37343
|
+
if (result && result.error) {
|
|
37344
|
+
failed.push({
|
|
37345
|
+
serverName,
|
|
37346
|
+
message:
|
|
37347
|
+
result.message ||
|
|
37348
|
+
`Provider "${serverName}" was not found in Settings → Providers`,
|
|
37349
|
+
});
|
|
37350
|
+
continue;
|
|
37351
|
+
}
|
|
37352
|
+
const provider = result && result.provider ? result.provider : result;
|
|
37353
|
+
if (
|
|
37354
|
+
!provider ||
|
|
37355
|
+
provider.providerClass !== "mcp" ||
|
|
37356
|
+
!provider.mcpConfig
|
|
37357
|
+
) {
|
|
37358
|
+
failed.push({
|
|
37359
|
+
serverName,
|
|
37360
|
+
message: `Provider "${serverName}" was not found in Settings → Providers`,
|
|
37361
|
+
});
|
|
37362
|
+
continue;
|
|
37363
|
+
}
|
|
37364
|
+
const res = await startServer(
|
|
37365
|
+
serverName,
|
|
37366
|
+
provider.mcpConfig,
|
|
37367
|
+
provider.credentials || {},
|
|
37368
|
+
workspaceId,
|
|
37369
|
+
);
|
|
37370
|
+
if (res && res.error) {
|
|
37371
|
+
failed.push({
|
|
37372
|
+
serverName,
|
|
37373
|
+
message: res.message || `Provider "${serverName}" failed to start`,
|
|
37374
|
+
});
|
|
37375
|
+
}
|
|
37376
|
+
} catch (err) {
|
|
37377
|
+
failed.push({
|
|
37378
|
+
serverName,
|
|
37379
|
+
message: (err && err.message) || String(err),
|
|
37380
|
+
});
|
|
37381
|
+
}
|
|
37382
|
+
}
|
|
37383
|
+
return { failed };
|
|
37384
|
+
}
|
|
37385
|
+
|
|
37386
|
+
/**
|
|
37387
|
+
* The bot's MCP tools: only its selected providers, only from its own bucket,
|
|
37388
|
+
* and only the tools each provider allows (narrowed by the bot's own
|
|
37389
|
+
* selection). Also returns `allowedFor` — the per-provider whitelist the
|
|
37390
|
+
* caller passes to mcpController.callTool so a disallowed call is rejected
|
|
37391
|
+
* there too (same double enforcement widgets get).
|
|
37392
|
+
*
|
|
37393
|
+
* When `providerLimits` is given it is authoritative and fails closed: a
|
|
37394
|
+
* server with no entry (provider deleted, or provider info unreadable) gets no
|
|
37395
|
+
* tools. Omit it only where no provider info exists at all (unit callers).
|
|
37396
|
+
*
|
|
37397
|
+
* @param {{ bot, connected, providerLimits?: Record<string, string[]|null> }} args
|
|
37398
|
+
*/
|
|
37399
|
+
function resolveBotTools$1({ bot, connected = [], providerLimits }) {
|
|
37400
|
+
const wanted = new Set(
|
|
37401
|
+
Array.isArray(bot && bot.mcpServers) ? bot.mcpServers : [],
|
|
37402
|
+
);
|
|
37403
|
+
const selections = (bot && bot.toolSelections) || {};
|
|
37404
|
+
const workspaceId = bot && bot.workspaceId;
|
|
37405
|
+
const tools = [];
|
|
37406
|
+
const toolServer = Object.create(null);
|
|
37407
|
+
const allowedFor = Object.create(null);
|
|
37408
|
+
for (const server of connected || []) {
|
|
37409
|
+
const name = server.serverName;
|
|
37410
|
+
if (!wanted.has(name) || !inBucket(server, workspaceId)) continue;
|
|
37411
|
+
if (providerLimits && !(name in providerLimits)) continue; // fail closed
|
|
37412
|
+
const allowed = effectiveAllowedTools(
|
|
37413
|
+
providerLimits ? providerLimits[name] : undefined,
|
|
37414
|
+
selections[name],
|
|
37415
|
+
);
|
|
37416
|
+
allowedFor[name] = allowed;
|
|
37417
|
+
for (const tool of server.tools || []) {
|
|
37418
|
+
if (allowed && !allowed.includes(tool.name)) continue;
|
|
37419
|
+
tools.push(tool);
|
|
37420
|
+
toolServer[tool.name] = name;
|
|
37421
|
+
}
|
|
37422
|
+
}
|
|
37423
|
+
return { tools, toolServer, allowedFor };
|
|
37424
|
+
}
|
|
37425
|
+
|
|
37426
|
+
/**
|
|
37427
|
+
* Last-line enforcement for a bot tool call. `allowedMap` is the
|
|
37428
|
+
* `allowedFor` from this bot's resolveBotTools. Fails closed: unknown bot,
|
|
37429
|
+
* unknown provider, or a tool outside the whitelist → rejected.
|
|
37430
|
+
*
|
|
37431
|
+
* @returns {{ ok: true, allowed: string[]|null } | { ok: false, message: string }}
|
|
37432
|
+
*/
|
|
37433
|
+
function checkToolCall$1(allowedMap, serverName, toolName) {
|
|
37434
|
+
if (
|
|
37435
|
+
!allowedMap ||
|
|
37436
|
+
!Object.prototype.hasOwnProperty.call(allowedMap, serverName)
|
|
37437
|
+
) {
|
|
37438
|
+
return {
|
|
37439
|
+
ok: false,
|
|
37440
|
+
message: `Tool "${toolName}" is not available to this bot (provider "${serverName}" isn't one of its providers).`,
|
|
37441
|
+
};
|
|
37442
|
+
}
|
|
37443
|
+
const allowed = allowedMap[serverName];
|
|
37444
|
+
if (Array.isArray(allowed) && !allowed.includes(toolName)) {
|
|
37445
|
+
return {
|
|
37446
|
+
ok: false,
|
|
37447
|
+
message: `Tool "${toolName}" is not allowed for this bot on "${serverName}".`,
|
|
37448
|
+
};
|
|
37449
|
+
}
|
|
37450
|
+
return { ok: true, allowed: Array.isArray(allowed) ? allowed : null };
|
|
37451
|
+
}
|
|
37452
|
+
|
|
37453
|
+
var toolSources = {
|
|
37454
|
+
checkToolCall: checkToolCall$1,
|
|
37455
|
+
connectedServersFromMap: connectedServersFromMap$1,
|
|
37456
|
+
listToolSources: listToolSources$1,
|
|
37457
|
+
ensureBotServers: ensureBotServers$1,
|
|
37458
|
+
resolveBotTools: resolveBotTools$1,
|
|
37459
|
+
effectiveAllowedTools,
|
|
37460
|
+
};
|
|
37461
|
+
|
|
37202
37462
|
/**
|
|
37203
37463
|
* mcpScopeResolver.js
|
|
37204
37464
|
*
|
|
@@ -37577,6 +37837,7 @@ const os$2 = require$$2;
|
|
|
37577
37837
|
const responseCache$2 = responseCache_1;
|
|
37578
37838
|
const { gateToolCall, gateToolCallWithJit } = permissionGate;
|
|
37579
37839
|
const { serverKey, parseServerKey } = mcpServerKey;
|
|
37840
|
+
const { connectedServersFromMap } = toolSources;
|
|
37580
37841
|
const { applyPathScopeToCredentials } = mcpScopeResolver;
|
|
37581
37842
|
const { readEnforceFlag: readEnforceFlag$1, readJitFlag: readJitFlag$1 } = securityFlags;
|
|
37582
37843
|
const { app: app$8 } = require$$0$2;
|
|
@@ -37750,7 +38011,7 @@ function getShellPath$1() {
|
|
|
37750
38011
|
return _shellPath$1;
|
|
37751
38012
|
}
|
|
37752
38013
|
|
|
37753
|
-
const { execSync } = require$$
|
|
38014
|
+
const { execSync } = require$$13;
|
|
37754
38015
|
const fallbackDirs = ["/usr/local/bin", "/opt/homebrew/bin"];
|
|
37755
38016
|
|
|
37756
38017
|
// Scan nvm versions, tracking both latest and best compatible version
|
|
@@ -38854,24 +39115,16 @@ const mcpController$3 = {
|
|
|
38854
39115
|
/**
|
|
38855
39116
|
* listConnectedServers
|
|
38856
39117
|
* Returns all connected servers with their cached tool lists.
|
|
38857
|
-
* Used by
|
|
39118
|
+
* Used by bots + the LLM tool picker to discover available MCP tools.
|
|
38858
39119
|
*
|
|
38859
|
-
*
|
|
39120
|
+
* `serverName` is the real server name and `workspaceId` its bucket
|
|
39121
|
+
* (null = no workspace) — activeServers is keyed "<workspace>::<server>",
|
|
39122
|
+
* and returning that compound key as `serverName` broke name matching.
|
|
39123
|
+
*
|
|
39124
|
+
* @returns {Array<{ serverName, workspaceId, tools, resources, status }>}
|
|
38860
39125
|
*/
|
|
38861
|
-
listConnectedServers: () =>
|
|
38862
|
-
|
|
38863
|
-
for (const [serverName, server] of activeServers) {
|
|
38864
|
-
if (server.status === STATUS$1.CONNECTED) {
|
|
38865
|
-
servers.push({
|
|
38866
|
-
serverName,
|
|
38867
|
-
tools: server.tools || [],
|
|
38868
|
-
resources: server.resources || [],
|
|
38869
|
-
status: server.status,
|
|
38870
|
-
});
|
|
38871
|
-
}
|
|
38872
|
-
}
|
|
38873
|
-
return servers;
|
|
38874
|
-
},
|
|
39126
|
+
listConnectedServers: () =>
|
|
39127
|
+
connectedServersFromMap(activeServers, STATUS$1.CONNECTED),
|
|
38875
39128
|
|
|
38876
39129
|
/**
|
|
38877
39130
|
* runAuth
|
|
@@ -38884,7 +39137,7 @@ const mcpController$3 = {
|
|
|
38884
39137
|
* @returns {{ success } | { error, message }}
|
|
38885
39138
|
*/
|
|
38886
39139
|
runAuth: async (win, mcpConfig, credentials, authCommand) => {
|
|
38887
|
-
const { spawn } = require$$
|
|
39140
|
+
const { spawn } = require$$13;
|
|
38888
39141
|
|
|
38889
39142
|
const env = cleanEnvForChildProcess();
|
|
38890
39143
|
|
|
@@ -59749,7 +60002,7 @@ var mcpDashServerController_1 = mcpDashServerController$4;
|
|
|
59749
60002
|
* can use the Chat widget without a separate API key.
|
|
59750
60003
|
*/
|
|
59751
60004
|
|
|
59752
|
-
const { spawn, execSync } = require$$
|
|
60005
|
+
const { spawn, execSync } = require$$13;
|
|
59753
60006
|
const {
|
|
59754
60007
|
LLM_STREAM_DELTA: LLM_STREAM_DELTA$2,
|
|
59755
60008
|
LLM_STREAM_TOOL_CALL: LLM_STREAM_TOOL_CALL$2,
|
|
@@ -73788,6 +74041,10 @@ function withDefaults$1(def = {}) {
|
|
|
73788
74041
|
// specific engine id here (e.g. "claude-agent").
|
|
73789
74042
|
engine: null,
|
|
73790
74043
|
mcpServers: [],
|
|
74044
|
+
// Per-provider narrowing within each provider's declared tools:
|
|
74045
|
+
// { [providerName]: string[] }. No entry → every tool the provider allows.
|
|
74046
|
+
toolSelections: {},
|
|
74047
|
+
// Tools that skip the approval prompt (NOT the tools the bot may use).
|
|
73791
74048
|
allowedTools: [],
|
|
73792
74049
|
approvalPolicy: "ask",
|
|
73793
74050
|
schedules: [],
|
|
@@ -73849,6 +74106,22 @@ function validateBotDefinition$1(def) {
|
|
|
73849
74106
|
errors.push(`${field} must be an array`);
|
|
73850
74107
|
}
|
|
73851
74108
|
}
|
|
74109
|
+
// toolSelections: { [providerName]: string[] } — per-bot tool narrowing.
|
|
74110
|
+
if (def.toolSelections !== undefined) {
|
|
74111
|
+
const ts = def.toolSelections;
|
|
74112
|
+
const ok =
|
|
74113
|
+
ts &&
|
|
74114
|
+
typeof ts === "object" &&
|
|
74115
|
+
!Array.isArray(ts) &&
|
|
74116
|
+
Object.values(ts).every(
|
|
74117
|
+
(v) => Array.isArray(v) && v.every((t) => typeof t === "string"),
|
|
74118
|
+
);
|
|
74119
|
+
if (!ok) {
|
|
74120
|
+
errors.push(
|
|
74121
|
+
"toolSelections must map provider names to arrays of tool names",
|
|
74122
|
+
);
|
|
74123
|
+
}
|
|
74124
|
+
}
|
|
73852
74125
|
// Each subscription must name an eventType (PRD FR-009).
|
|
73853
74126
|
if (Array.isArray(def.subscriptions)) {
|
|
73854
74127
|
for (const sub of def.subscriptions) {
|
|
@@ -74915,6 +75188,9 @@ var agentToolBridge = {
|
|
|
74915
75188
|
const { createEventStream } = eventStream;
|
|
74916
75189
|
const { BOT_MCP_SERVER, buildBotMcpServer } = agentToolBridge;
|
|
74917
75190
|
|
|
75191
|
+
// The SDK names bridged tools "mcp__<server>__<tool>".
|
|
75192
|
+
const BRIDGED_PREFIX = `mcp__${BOT_MCP_SERVER}__`;
|
|
75193
|
+
|
|
74918
75194
|
// Test seam: injected fake `query` implementation (see __setQueryForTest).
|
|
74919
75195
|
let _queryImpl = null;
|
|
74920
75196
|
|
|
@@ -74974,7 +75250,27 @@ function _makeCanUseTool(ctx) {
|
|
|
74974
75250
|
if (ctx.approvalPolicy === "allow") {
|
|
74975
75251
|
return { behavior: "allow", updatedInput: input };
|
|
74976
75252
|
}
|
|
74977
|
-
//
|
|
75253
|
+
// Bridged Dash provider tools go through the SAME Dash permission gate as
|
|
75254
|
+
// the tool-loop engine — grants, remembered approvals ("Always allow"),
|
|
75255
|
+
// and the approval queue all apply, keyed by the real provider.
|
|
75256
|
+
if (
|
|
75257
|
+
typeof toolName === "string" &&
|
|
75258
|
+
toolName.startsWith(BRIDGED_PREFIX) &&
|
|
75259
|
+
typeof ctx.requestPermission === "function"
|
|
75260
|
+
) {
|
|
75261
|
+
const decision = await ctx.requestPermission(
|
|
75262
|
+
toolName.slice(BRIDGED_PREFIX.length),
|
|
75263
|
+
input,
|
|
75264
|
+
);
|
|
75265
|
+
return decision && decision.allow
|
|
75266
|
+
? { behavior: "allow", updatedInput: input }
|
|
75267
|
+
: {
|
|
75268
|
+
behavior: "deny",
|
|
75269
|
+
message: (decision && decision.reason) || "not permitted",
|
|
75270
|
+
};
|
|
75271
|
+
}
|
|
75272
|
+
// Built-in tools (Bash, Write, …) ask every time via the approval queue
|
|
75273
|
+
// (shown in the Activity panel) — they're never remembered.
|
|
74978
75274
|
if (typeof ctx.createApproval !== "function") {
|
|
74979
75275
|
return { behavior: "deny", message: "no approval channel available" };
|
|
74980
75276
|
}
|
|
@@ -74998,18 +75294,29 @@ function _makeCanUseTool(ctx) {
|
|
|
74998
75294
|
async function _runAgent(ctx, stream) {
|
|
74999
75295
|
const query = await _loadQuery();
|
|
75000
75296
|
|
|
75001
|
-
//
|
|
75002
|
-
// bot's Anthropic API key
|
|
75003
|
-
//
|
|
75297
|
+
// Per-run environment for the agent process. Auth: the Claude Code login by
|
|
75298
|
+
// default; a bot's Anthropic API key, when set, goes in THIS run's env only —
|
|
75299
|
+
// never process.env, which would leak it to the rest of the main process and
|
|
75300
|
+
// to other bots.
|
|
75301
|
+
const env = { ...process.env };
|
|
75004
75302
|
if (ctx.credentials && ctx.credentials.apiKey) {
|
|
75005
|
-
|
|
75303
|
+
env.ANTHROPIC_API_KEY = ctx.credentials.apiKey;
|
|
75006
75304
|
}
|
|
75305
|
+
// No claude.ai connectors from the user's Claude login.
|
|
75306
|
+
env.ENABLE_CLAUDEAI_MCP_SERVERS = "false";
|
|
75007
75307
|
|
|
75008
75308
|
const options = {
|
|
75009
75309
|
model: ctx.model,
|
|
75010
75310
|
systemPrompt: ctx.systemPrompt || { type: "preset", preset: "claude_code" },
|
|
75011
75311
|
permissionMode: "default",
|
|
75012
75312
|
canUseTool: _makeCanUseTool(ctx),
|
|
75313
|
+
// Isolation: a bot sees only the SDK's built-in tools + the Dash providers
|
|
75314
|
+
// it was granted (bridged below as "bot-mcp"). Don't load the user's
|
|
75315
|
+
// ~/.claude settings/plugins/skills/hooks, and ignore every MCP config
|
|
75316
|
+
// other than the servers passed in `mcpServers`.
|
|
75317
|
+
settingSources: [],
|
|
75318
|
+
strictMcpConfig: true,
|
|
75319
|
+
env,
|
|
75013
75320
|
};
|
|
75014
75321
|
if (ctx.maxTurns) options.maxTurns = ctx.maxTurns;
|
|
75015
75322
|
if (ctx.workingDir) options.cwd = ctx.workingDir;
|
|
@@ -75445,12 +75752,17 @@ let BotRunner$1 = class BotRunner {
|
|
|
75445
75752
|
}
|
|
75446
75753
|
return this._callTool(serverName, toolName, input, {
|
|
75447
75754
|
workspaceId: bot.workspaceId,
|
|
75755
|
+
// Lets the caller apply this bot's per-provider tool limits.
|
|
75756
|
+
botId,
|
|
75448
75757
|
});
|
|
75449
75758
|
};
|
|
75450
75759
|
|
|
75451
|
-
//
|
|
75760
|
+
// A run does the bot's job against current data → fresh conversation.
|
|
75761
|
+
// Resume only when explicitly continuing (reply-to-continue) AND the
|
|
75762
|
+
// stored session was produced by this engine. Anything a bot should
|
|
75763
|
+
// carry between runs belongs in its memory tools.
|
|
75452
75764
|
const session =
|
|
75453
|
-
bot.session && bot.session.engine === engine.id
|
|
75765
|
+
opts.continueSession && bot.session && bot.session.engine === engine.id
|
|
75454
75766
|
? bot.session.state
|
|
75455
75767
|
: null;
|
|
75456
75768
|
|
|
@@ -76253,6 +76565,111 @@ function normalizeMcpResult$1(mcpResult) {
|
|
|
76253
76565
|
|
|
76254
76566
|
var mcpResult = { normalizeMcpResult: normalizeMcpResult$1 };
|
|
76255
76567
|
|
|
76568
|
+
/**
|
|
76569
|
+
* rememberGrant.js — turn an "Always allow" bot approval into a durable grant,
|
|
76570
|
+
* and revoke it.
|
|
76571
|
+
*
|
|
76572
|
+
* Bot grants live in the shared grant store (electron/mcp/grantedPermissions)
|
|
76573
|
+
* keyed by botId, shape `{ servers: { [provider]: { tools, readPaths,
|
|
76574
|
+
* writePaths } } }`, and are enforced by the bot permission gate
|
|
76575
|
+
* (gateBotToolCall). A remembered approval is scoped to this bot + this
|
|
76576
|
+
* provider + this tool, plus — when the tool call carries a path argument —
|
|
76577
|
+
* the folder of that path (write folder for write tools, read folder
|
|
76578
|
+
* otherwise). Nothing broader.
|
|
76579
|
+
*
|
|
76580
|
+
* Pure (NFR-006): the gate's path rules (isWriteTool, path arg keys) and
|
|
76581
|
+
* `dirname` are injected so this matches the real gate exactly.
|
|
76582
|
+
*/
|
|
76583
|
+
|
|
76584
|
+
const LISTING_TOOL = /(list|director|tree|search)/i;
|
|
76585
|
+
|
|
76586
|
+
/**
|
|
76587
|
+
* The folder to remember for a path argument: a `directory` arg, or a
|
|
76588
|
+
* directory-listing tool, names the folder itself; any other path is a file,
|
|
76589
|
+
* so its parent folder is remembered.
|
|
76590
|
+
*/
|
|
76591
|
+
function folderForPathArg(key, value, toolName, dirname) {
|
|
76592
|
+
if (key === "directory" || LISTING_TOOL.test(String(toolName || ""))) {
|
|
76593
|
+
return value;
|
|
76594
|
+
}
|
|
76595
|
+
return dirname(value);
|
|
76596
|
+
}
|
|
76597
|
+
|
|
76598
|
+
const clone = (perms) =>
|
|
76599
|
+
perms && typeof perms === "object" ? JSON.parse(JSON.stringify(perms)) : {};
|
|
76600
|
+
|
|
76601
|
+
const addUnique = (list, value) =>
|
|
76602
|
+
list.includes(value) ? list : [...list, value];
|
|
76603
|
+
|
|
76604
|
+
/**
|
|
76605
|
+
* @param {object|null} perms the bot's current grant (or null)
|
|
76606
|
+
* @param {{ serverName: string, toolName: string, args?: object }} call
|
|
76607
|
+
* @param {{ isWriteTool: Function, pathArgKeys: string[], dirname: Function }} helpers
|
|
76608
|
+
* @returns {object} the new grant (input is not mutated)
|
|
76609
|
+
*/
|
|
76610
|
+
function rememberToolGrant$1(perms, call, helpers) {
|
|
76611
|
+
const { serverName, toolName, args } = call;
|
|
76612
|
+
const next = clone(perms);
|
|
76613
|
+
next.grantOrigin = next.grantOrigin || "manual";
|
|
76614
|
+
next.servers = next.servers || {};
|
|
76615
|
+
const server = next.servers[serverName] || {
|
|
76616
|
+
tools: [],
|
|
76617
|
+
readPaths: [],
|
|
76618
|
+
writePaths: [],
|
|
76619
|
+
};
|
|
76620
|
+
server.tools = addUnique(server.tools || [], toolName);
|
|
76621
|
+
server.readPaths = server.readPaths || [];
|
|
76622
|
+
server.writePaths = server.writePaths || [];
|
|
76623
|
+
|
|
76624
|
+
if (args && typeof args === "object") {
|
|
76625
|
+
const isWrite = helpers.isWriteTool(toolName);
|
|
76626
|
+
for (const key of helpers.pathArgKeys) {
|
|
76627
|
+
const v = args[key];
|
|
76628
|
+
if (typeof v !== "string" || !v) continue;
|
|
76629
|
+
const folder = folderForPathArg(key, v, toolName, helpers.dirname);
|
|
76630
|
+
if (isWrite) server.writePaths = addUnique(server.writePaths, folder);
|
|
76631
|
+
else server.readPaths = addUnique(server.readPaths, folder);
|
|
76632
|
+
}
|
|
76633
|
+
}
|
|
76634
|
+
|
|
76635
|
+
next.servers[serverName] = server;
|
|
76636
|
+
return next;
|
|
76637
|
+
}
|
|
76638
|
+
|
|
76639
|
+
/**
|
|
76640
|
+
* Revoke one remembered tool. When it was the provider's last remembered
|
|
76641
|
+
* tool, the provider entry (and its folders) is removed entirely.
|
|
76642
|
+
*/
|
|
76643
|
+
function forgetToolGrant$1(perms, serverName, toolName) {
|
|
76644
|
+
const server = perms && perms.servers && perms.servers[serverName];
|
|
76645
|
+
if (!server || !(server.tools || []).includes(toolName)) return perms;
|
|
76646
|
+
const next = clone(perms);
|
|
76647
|
+
const tools = next.servers[serverName].tools.filter((t) => t !== toolName);
|
|
76648
|
+
if (tools.length) next.servers[serverName].tools = tools;
|
|
76649
|
+
else delete next.servers[serverName];
|
|
76650
|
+
return next;
|
|
76651
|
+
}
|
|
76652
|
+
|
|
76653
|
+
/** Remembered tools + folders per provider, for display in the bot form. */
|
|
76654
|
+
function summarizeGrants$1(perms) {
|
|
76655
|
+
const out = {};
|
|
76656
|
+
const servers = (perms && perms.servers) || {};
|
|
76657
|
+
for (const [name, s] of Object.entries(servers)) {
|
|
76658
|
+
out[name] = {
|
|
76659
|
+
tools: [...(s.tools || [])],
|
|
76660
|
+
folders: [...(s.readPaths || []), ...(s.writePaths || [])],
|
|
76661
|
+
};
|
|
76662
|
+
}
|
|
76663
|
+
return out;
|
|
76664
|
+
}
|
|
76665
|
+
|
|
76666
|
+
var rememberGrant = {
|
|
76667
|
+
rememberToolGrant: rememberToolGrant$1,
|
|
76668
|
+
forgetToolGrant: forgetToolGrant$1,
|
|
76669
|
+
summarizeGrants: summarizeGrants$1,
|
|
76670
|
+
folderForPathArg,
|
|
76671
|
+
};
|
|
76672
|
+
|
|
76256
76673
|
/**
|
|
76257
76674
|
* botController.js
|
|
76258
76675
|
*
|
|
@@ -76284,6 +76701,19 @@ const {
|
|
|
76284
76701
|
const { matchSubscribedBots } = eventMatcher;
|
|
76285
76702
|
const { EventDispatcher } = EventDispatcher_1;
|
|
76286
76703
|
const { normalizeMcpResult } = mcpResult;
|
|
76704
|
+
const {
|
|
76705
|
+
listToolSources,
|
|
76706
|
+
ensureBotServers,
|
|
76707
|
+
resolveBotTools,
|
|
76708
|
+
checkToolCall,
|
|
76709
|
+
} = toolSources;
|
|
76710
|
+
const {
|
|
76711
|
+
rememberToolGrant,
|
|
76712
|
+
forgetToolGrant,
|
|
76713
|
+
summarizeGrants,
|
|
76714
|
+
} = rememberGrant;
|
|
76715
|
+
const grantStore = grantedPermissions;
|
|
76716
|
+
const { isWriteTool, PATH_ARG_KEYS } = permissionGate;
|
|
76287
76717
|
const {
|
|
76288
76718
|
getProvider: getProvider$1,
|
|
76289
76719
|
getDefaultModel,
|
|
@@ -76387,6 +76817,10 @@ const botController$1 = {
|
|
|
76387
76817
|
// flood the runner with event-triggered runs.
|
|
76388
76818
|
this._dispatcher = new EventDispatcher();
|
|
76389
76819
|
|
|
76820
|
+
// botId → { [providerName]: allowed tool names | null }, set on each run's
|
|
76821
|
+
// tool resolution and enforced in _callTool.
|
|
76822
|
+
this._allowedByBot = new Map();
|
|
76823
|
+
|
|
76390
76824
|
this._runner = new BotRunner({
|
|
76391
76825
|
engines,
|
|
76392
76826
|
store: this._store,
|
|
@@ -76472,6 +76906,26 @@ const botController$1 = {
|
|
|
76472
76906
|
return this._store.list();
|
|
76473
76907
|
},
|
|
76474
76908
|
|
|
76909
|
+
/**
|
|
76910
|
+
* The user's Dash MCP providers a bot can use (Settings → Providers), with
|
|
76911
|
+
* running status + tool count. Names/types only — never credentials.
|
|
76912
|
+
*/
|
|
76913
|
+
listToolSources(workspaceId = null) {
|
|
76914
|
+
let providers = [];
|
|
76915
|
+
try {
|
|
76916
|
+
const win = this._getMainWindow();
|
|
76917
|
+
({ providers = [] } =
|
|
76918
|
+
this._providers.listProviders(win, this._appId) || {});
|
|
76919
|
+
} catch (_e) {
|
|
76920
|
+
providers = [];
|
|
76921
|
+
}
|
|
76922
|
+
return listToolSources({
|
|
76923
|
+
providers,
|
|
76924
|
+
connected: this._connectedServers(),
|
|
76925
|
+
workspaceId,
|
|
76926
|
+
});
|
|
76927
|
+
},
|
|
76928
|
+
|
|
76475
76929
|
get(botId) {
|
|
76476
76930
|
return this._store.get(botId);
|
|
76477
76931
|
},
|
|
@@ -76487,6 +76941,8 @@ const botController$1 = {
|
|
|
76487
76941
|
|
|
76488
76942
|
delete(botId) {
|
|
76489
76943
|
this._scheduler.unregister(botId);
|
|
76944
|
+
// A deleted bot's remembered approvals go with it.
|
|
76945
|
+
grantStore.revokeGrant(botId);
|
|
76490
76946
|
return this._store.delete(botId);
|
|
76491
76947
|
},
|
|
76492
76948
|
|
|
@@ -76498,10 +76954,50 @@ const botController$1 = {
|
|
|
76498
76954
|
return { stopped: this._runner.abort(botId) };
|
|
76499
76955
|
},
|
|
76500
76956
|
|
|
76957
|
+
/**
|
|
76958
|
+
* Resolve a pending approval. `decision.remember` ("Always allow") also
|
|
76959
|
+
* saves a durable grant for this bot + provider + tool (+ the folder of a
|
|
76960
|
+
* path argument), so later runs use that tool without asking. Only provider
|
|
76961
|
+
* tool approvals can be remembered — built-in agent tools have no provider
|
|
76962
|
+
* and always ask.
|
|
76963
|
+
*/
|
|
76501
76964
|
approve(approvalId, decision = {}) {
|
|
76502
|
-
|
|
76503
|
-
|
|
76504
|
-
|
|
76965
|
+
if (!decision.allow) {
|
|
76966
|
+
return { ok: this._approvals.deny(approvalId, decision.reason) };
|
|
76967
|
+
}
|
|
76968
|
+
let remembered = false;
|
|
76969
|
+
if (decision.remember) {
|
|
76970
|
+
const pending = this._approvals.get(approvalId);
|
|
76971
|
+
const req = pending && pending.request;
|
|
76972
|
+
if (req && req.botId && req.serverName && req.toolName) {
|
|
76973
|
+
const next = rememberToolGrant(
|
|
76974
|
+
grantStore.getGrant(req.botId),
|
|
76975
|
+
{
|
|
76976
|
+
serverName: req.serverName,
|
|
76977
|
+
toolName: req.toolName,
|
|
76978
|
+
args: req.input,
|
|
76979
|
+
},
|
|
76980
|
+
{ isWriteTool, pathArgKeys: PATH_ARG_KEYS, dirname: path$2.dirname },
|
|
76981
|
+
);
|
|
76982
|
+
remembered = grantStore.setGrant(req.botId, next) === true;
|
|
76983
|
+
}
|
|
76984
|
+
}
|
|
76985
|
+
return { ok: this._approvals.resolve(approvalId, decision), remembered };
|
|
76986
|
+
},
|
|
76987
|
+
|
|
76988
|
+
/** Remembered approvals for a bot: { [provider]: { tools, folders } }. */
|
|
76989
|
+
getGrants(botId) {
|
|
76990
|
+
return summarizeGrants(grantStore.getGrant(botId));
|
|
76991
|
+
},
|
|
76992
|
+
|
|
76993
|
+
/** Revoke one remembered tool approval; returns the updated summary. */
|
|
76994
|
+
revokeGrant(botId, serverName, toolName) {
|
|
76995
|
+
const current = grantStore.getGrant(botId);
|
|
76996
|
+
if (current) {
|
|
76997
|
+
const next = forgetToolGrant(current, serverName, toolName);
|
|
76998
|
+
if (next !== current) grantStore.setGrant(botId, next);
|
|
76999
|
+
}
|
|
77000
|
+
return this.getGrants(botId);
|
|
76505
77001
|
},
|
|
76506
77002
|
|
|
76507
77003
|
listApprovals() {
|
|
@@ -76638,23 +77134,58 @@ const botController$1 = {
|
|
|
76638
77134
|
};
|
|
76639
77135
|
},
|
|
76640
77136
|
|
|
76641
|
-
/**
|
|
76642
|
-
|
|
76643
|
-
|
|
77137
|
+
/** Connected MCP servers with real server names + workspace buckets. */
|
|
77138
|
+
_connectedServers() {
|
|
77139
|
+
return this._mcp && this._mcp.listConnectedServers
|
|
76644
77140
|
? this._mcp.listConnectedServers()
|
|
76645
77141
|
: [];
|
|
76646
|
-
|
|
76647
|
-
|
|
76648
|
-
|
|
76649
|
-
|
|
76650
|
-
|
|
76651
|
-
|
|
76652
|
-
|
|
76653
|
-
|
|
76654
|
-
|
|
76655
|
-
|
|
77142
|
+
},
|
|
77143
|
+
|
|
77144
|
+
/**
|
|
77145
|
+
* Build the bot's tool set + tool→server resolver. First starts any of the
|
|
77146
|
+
* bot's selected providers that aren't running in its bucket — through the
|
|
77147
|
+
* same deduping startServer factory the dashboards use — so a scheduled or
|
|
77148
|
+
* event-triggered bot doesn't silently run without its tools. Start
|
|
77149
|
+
* failures are surfaced in the run feed as warnings.
|
|
77150
|
+
*/
|
|
77151
|
+
async _resolveTools(bot) {
|
|
77152
|
+
if (this._mcp && this._mcp.startServer && this._providers) {
|
|
77153
|
+
const win = this._getMainWindow();
|
|
77154
|
+
const { failed } = await ensureBotServers({
|
|
77155
|
+
bot,
|
|
77156
|
+
connected: this._connectedServers(),
|
|
77157
|
+
getProvider: async (name) =>
|
|
77158
|
+
this._providers.getProvider(win, this._appId, name),
|
|
77159
|
+
startServer: (name, mcpConfig, credentials, workspaceId) =>
|
|
77160
|
+
this._mcp.startServer(
|
|
77161
|
+
win,
|
|
77162
|
+
name,
|
|
77163
|
+
mcpConfig,
|
|
77164
|
+
credentials,
|
|
77165
|
+
workspaceId,
|
|
77166
|
+
null,
|
|
77167
|
+
this._appId,
|
|
77168
|
+
),
|
|
77169
|
+
});
|
|
77170
|
+
for (const f of failed) {
|
|
77171
|
+
this._broadcast(BOT_STREAM$1, {
|
|
77172
|
+
botId: bot.id,
|
|
77173
|
+
event: {
|
|
77174
|
+
type: "warning",
|
|
77175
|
+
message: `${f.serverName} couldn't start, so its tools aren't available this run: ${f.message}`,
|
|
77176
|
+
},
|
|
77177
|
+
});
|
|
76656
77178
|
}
|
|
76657
77179
|
}
|
|
77180
|
+
// Each provider's declared tools (Settings → Providers) are the ceiling;
|
|
77181
|
+
// the bot's toolSelections narrow within them.
|
|
77182
|
+
const { tools, toolServer, allowedFor } = resolveBotTools({
|
|
77183
|
+
bot,
|
|
77184
|
+
connected: this._connectedServers(),
|
|
77185
|
+
providerLimits: this._providerToolLimits(),
|
|
77186
|
+
});
|
|
77187
|
+
// Remembered per bot so _callTool can hard-enforce the same whitelist.
|
|
77188
|
+
this._allowedByBot.set(bot.id, allowedFor);
|
|
76658
77189
|
// Always-available in-process memory tools (P1: FR-010).
|
|
76659
77190
|
for (const tool of MEMORY_TOOLS) {
|
|
76660
77191
|
tools.push(tool);
|
|
@@ -76674,20 +77205,54 @@ const botController$1 = {
|
|
|
76674
77205
|
);
|
|
76675
77206
|
}
|
|
76676
77207
|
const win = this._getMainWindow();
|
|
77208
|
+
// The bot's per-provider whitelist (provider limit ∩ bot selection), from
|
|
77209
|
+
// the last _resolveTools for this bot. A tool outside it is rejected —
|
|
77210
|
+
// mirroring the allowedTools enforcement widgets get. If no entry exists
|
|
77211
|
+
// for this provider, the bot never had it resolved → deny.
|
|
77212
|
+
const check = checkToolCall(
|
|
77213
|
+
this._allowedByBot.get(opts.botId),
|
|
77214
|
+
serverName,
|
|
77215
|
+
toolName,
|
|
77216
|
+
);
|
|
77217
|
+
if (!check.ok) return { text: check.message, isError: true };
|
|
77218
|
+
const { allowed } = check;
|
|
76677
77219
|
// widgetId + token are null → the widget permission gate is bypassed; bot
|
|
76678
|
-
// tool calls are gated upstream by the bot PermissionGate.
|
|
77220
|
+
// tool calls are gated upstream by the bot PermissionGate. `allowed` is
|
|
77221
|
+
// also passed as callTool's whitelist (defense in depth).
|
|
76679
77222
|
const result = await this._mcp.callTool(
|
|
76680
77223
|
win,
|
|
76681
77224
|
serverName,
|
|
76682
77225
|
toolName,
|
|
76683
77226
|
args,
|
|
76684
|
-
null,
|
|
77227
|
+
allowed || null,
|
|
76685
77228
|
null,
|
|
76686
77229
|
opts.workspaceId || null,
|
|
76687
77230
|
null,
|
|
76688
77231
|
);
|
|
76689
77232
|
return normalizeMcpResult(result);
|
|
76690
77233
|
},
|
|
77234
|
+
|
|
77235
|
+
/** { [providerName]: allowedTools | null } for the user's MCP providers. */
|
|
77236
|
+
_providerToolLimits() {
|
|
77237
|
+
const limits = Object.create(null);
|
|
77238
|
+
try {
|
|
77239
|
+
const win = this._getMainWindow();
|
|
77240
|
+
const { providers = [] } =
|
|
77241
|
+
this._providers.listProviders(win, this._appId) || {};
|
|
77242
|
+
for (const p of providers) {
|
|
77243
|
+
if (p && p.providerClass === "mcp" && p.name) {
|
|
77244
|
+
limits[p.name] = Array.isArray(p.allowedTools)
|
|
77245
|
+
? p.allowedTools
|
|
77246
|
+
: null;
|
|
77247
|
+
}
|
|
77248
|
+
}
|
|
77249
|
+
} catch (_e) {
|
|
77250
|
+
// Provider info unreadable → empty map. resolveBotTools treats a
|
|
77251
|
+
// provider with no entry as not configured, so the bot gets NO tools
|
|
77252
|
+
// from it (fails closed) rather than every tool the server exposes.
|
|
77253
|
+
}
|
|
77254
|
+
return limits;
|
|
77255
|
+
},
|
|
76691
77256
|
};
|
|
76692
77257
|
|
|
76693
77258
|
var botController_1 = botController$1;
|
|
@@ -80600,6 +81165,9 @@ const {
|
|
|
80600
81165
|
BOTS_PAUSE_BOT,
|
|
80601
81166
|
BOTS_RESUME_BOT,
|
|
80602
81167
|
BOTS_GET_PAUSE_STATE,
|
|
81168
|
+
BOTS_LIST_TOOL_SOURCES,
|
|
81169
|
+
BOTS_GET_GRANTS,
|
|
81170
|
+
BOTS_REVOKE_GRANT,
|
|
80603
81171
|
BOT_STREAM,
|
|
80604
81172
|
BOT_APPROVAL_PENDING,
|
|
80605
81173
|
BOT_BUDGET_ALERT,
|
|
@@ -80648,6 +81216,16 @@ const botApi$1 = {
|
|
|
80648
81216
|
resumeBot: (botId) => ipcRenderer$3.invoke(BOTS_RESUME_BOT, { botId }),
|
|
80649
81217
|
getPauseState: () => ipcRenderer$3.invoke(BOTS_GET_PAUSE_STATE),
|
|
80650
81218
|
|
|
81219
|
+
/** The user's Dash MCP providers a bot can use: [{ name, type, running, toolCount }]. */
|
|
81220
|
+
listToolSources: (workspaceId = null) =>
|
|
81221
|
+
ipcRenderer$3.invoke(BOTS_LIST_TOOL_SOURCES, { workspaceId }),
|
|
81222
|
+
|
|
81223
|
+
/** Remembered approvals ("Always allow"): { [provider]: { tools, folders } }. */
|
|
81224
|
+
getGrants: (botId) => ipcRenderer$3.invoke(BOTS_GET_GRANTS, { botId }),
|
|
81225
|
+
/** Revoke one remembered tool approval; resolves to the updated summary. */
|
|
81226
|
+
revokeGrant: (botId, serverName, toolName) =>
|
|
81227
|
+
ipcRenderer$3.invoke(BOTS_REVOKE_GRANT, { botId, serverName, toolName }),
|
|
81228
|
+
|
|
80651
81229
|
/** Subscribe to streamed run events: { botId, event }. */
|
|
80652
81230
|
onStream: (callback) => _addListener(BOT_STREAM, callback),
|
|
80653
81231
|
/** Subscribe to new pending approvals: { id, request }. */
|