@trops/dash-core 0.1.636 → 0.1.638
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 +435 -46
- package/dist/electron/index.js.map +1 -1
- package/dist/index.esm.js +274 -124
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +274 -124
- 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,7 @@ 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";
|
|
985
986
|
|
|
986
987
|
// --- Main → Renderer (send) ---
|
|
987
988
|
const BOT_STREAM$2 = "bot-stream"; // { botId, event: BotEvent }
|
|
@@ -1008,6 +1009,7 @@ var botEvents$1 = {
|
|
|
1008
1009
|
BOTS_PAUSE_BOT: BOTS_PAUSE_BOT$1,
|
|
1009
1010
|
BOTS_RESUME_BOT: BOTS_RESUME_BOT$1,
|
|
1010
1011
|
BOTS_GET_PAUSE_STATE: BOTS_GET_PAUSE_STATE$1,
|
|
1012
|
+
BOTS_LIST_TOOL_SOURCES: BOTS_LIST_TOOL_SOURCES$1,
|
|
1011
1013
|
BOT_STREAM: BOT_STREAM$2,
|
|
1012
1014
|
BOT_APPROVAL_PENDING: BOT_APPROVAL_PENDING$2,
|
|
1013
1015
|
BOT_BUDGET_ALERT: BOT_BUDGET_ALERT$2,
|
|
@@ -37164,7 +37166,7 @@ var permissionGate = {
|
|
|
37164
37166
|
* the trust boundary appears.
|
|
37165
37167
|
*/
|
|
37166
37168
|
|
|
37167
|
-
const NO_WORKSPACE = "__no_workspace__";
|
|
37169
|
+
const NO_WORKSPACE$1 = "__no_workspace__";
|
|
37168
37170
|
const SEP = "::";
|
|
37169
37171
|
|
|
37170
37172
|
function serverKey$1(workspaceId, serverName) {
|
|
@@ -37172,11 +37174,11 @@ function serverKey$1(workspaceId, serverName) {
|
|
|
37172
37174
|
throw new Error("serverKey: serverName is required");
|
|
37173
37175
|
}
|
|
37174
37176
|
const wid =
|
|
37175
|
-
typeof workspaceId === "string" && workspaceId ? workspaceId : NO_WORKSPACE;
|
|
37177
|
+
typeof workspaceId === "string" && workspaceId ? workspaceId : NO_WORKSPACE$1;
|
|
37176
37178
|
return wid + SEP + serverName;
|
|
37177
37179
|
}
|
|
37178
37180
|
|
|
37179
|
-
function parseServerKey$
|
|
37181
|
+
function parseServerKey$2(key) {
|
|
37180
37182
|
if (typeof key !== "string") {
|
|
37181
37183
|
throw new Error("parseServerKey: key must be a string");
|
|
37182
37184
|
}
|
|
@@ -37194,11 +37196,265 @@ function parseServerKey$1(key) {
|
|
|
37194
37196
|
|
|
37195
37197
|
var mcpServerKey = {
|
|
37196
37198
|
serverKey: serverKey$1,
|
|
37197
|
-
parseServerKey: parseServerKey$
|
|
37198
|
-
NO_WORKSPACE,
|
|
37199
|
+
parseServerKey: parseServerKey$2,
|
|
37200
|
+
NO_WORKSPACE: NO_WORKSPACE$1,
|
|
37199
37201
|
SEP,
|
|
37200
37202
|
};
|
|
37201
37203
|
|
|
37204
|
+
/**
|
|
37205
|
+
* toolSources.js — how a bot finds, starts, and uses the user's Dash MCP
|
|
37206
|
+
* providers (the integrations configured under Settings → Providers).
|
|
37207
|
+
*
|
|
37208
|
+
* Portable (no electron import — NFR-006): the MCP controller, provider store
|
|
37209
|
+
* and server map are injected by botController.
|
|
37210
|
+
*
|
|
37211
|
+
* Server reuse goes through the same `mcpController.startServer` factory the
|
|
37212
|
+
* dashboards use: a server already connected — or already starting — for a
|
|
37213
|
+
* given (workspace, server) bucket is reused, never spawned twice. Buckets are
|
|
37214
|
+
* per-workspace on purpose (each workspace's processes carry that workspace's
|
|
37215
|
+
* grants), so a bot uses its OWN bucket (its workspaceId, or the no-workspace
|
|
37216
|
+
* bucket) and never borrows a dashboard workspace's process.
|
|
37217
|
+
*/
|
|
37218
|
+
|
|
37219
|
+
const { parseServerKey: parseServerKey$1 } = mcpServerKey;
|
|
37220
|
+
|
|
37221
|
+
const NO_WORKSPACE = "__no_workspace__";
|
|
37222
|
+
|
|
37223
|
+
const normalizeWorkspace = (wid) =>
|
|
37224
|
+
typeof wid === "string" && wid && wid !== NO_WORKSPACE ? wid : null;
|
|
37225
|
+
|
|
37226
|
+
/**
|
|
37227
|
+
* Summarize mcpController's `activeServers` Map (keyed "<workspace>::<server>")
|
|
37228
|
+
* into connected servers with their REAL server name + workspace.
|
|
37229
|
+
*/
|
|
37230
|
+
function connectedServersFromMap$1(activeServers, connectedStatus) {
|
|
37231
|
+
const out = [];
|
|
37232
|
+
if (!activeServers || typeof activeServers.entries !== "function") return out;
|
|
37233
|
+
for (const [key, server] of activeServers.entries()) {
|
|
37234
|
+
if (!server || server.status !== connectedStatus) continue;
|
|
37235
|
+
let serverName = key;
|
|
37236
|
+
let workspaceId = null;
|
|
37237
|
+
try {
|
|
37238
|
+
const parsed = parseServerKey$1(key);
|
|
37239
|
+
serverName = parsed.serverName;
|
|
37240
|
+
workspaceId = normalizeWorkspace(parsed.workspaceId);
|
|
37241
|
+
} catch (_) {
|
|
37242
|
+
// Legacy un-prefixed key: treat as the no-workspace bucket.
|
|
37243
|
+
}
|
|
37244
|
+
out.push({
|
|
37245
|
+
serverName,
|
|
37246
|
+
workspaceId,
|
|
37247
|
+
tools: server.tools || [],
|
|
37248
|
+
resources: server.resources || [],
|
|
37249
|
+
status: server.status,
|
|
37250
|
+
});
|
|
37251
|
+
}
|
|
37252
|
+
return out;
|
|
37253
|
+
}
|
|
37254
|
+
|
|
37255
|
+
const inBucket = (server, workspaceId) =>
|
|
37256
|
+
normalizeWorkspace(server.workspaceId) === normalizeWorkspace(workspaceId);
|
|
37257
|
+
|
|
37258
|
+
/**
|
|
37259
|
+
* The bot form's provider list: every configured MCP provider (credential-only
|
|
37260
|
+
* providers give a bot nothing to call, so they're excluded), with whether it's
|
|
37261
|
+
* running in the bot's bucket and its tool count when known.
|
|
37262
|
+
*
|
|
37263
|
+
* `tools` is what the form offers for per-bot narrowing: the provider's
|
|
37264
|
+
* declared `allowedTools` (set in Settings → Providers — known without starting
|
|
37265
|
+
* the server); for a provider with no declared limit, the live tool names if
|
|
37266
|
+
* it's running, else null (every tool, list not known yet). Never credentials.
|
|
37267
|
+
*/
|
|
37268
|
+
function listToolSources$1({
|
|
37269
|
+
providers,
|
|
37270
|
+
connected = [],
|
|
37271
|
+
workspaceId = null,
|
|
37272
|
+
} = {}) {
|
|
37273
|
+
if (!Array.isArray(providers)) return [];
|
|
37274
|
+
return providers
|
|
37275
|
+
.filter((p) => p && p.providerClass === "mcp" && p.name)
|
|
37276
|
+
.map((p) => {
|
|
37277
|
+
const live = (connected || []).find(
|
|
37278
|
+
(s) => s.serverName === p.name && inBucket(s, workspaceId),
|
|
37279
|
+
);
|
|
37280
|
+
const declared = Array.isArray(p.allowedTools);
|
|
37281
|
+
const liveNames = live
|
|
37282
|
+
? (live.tools || []).map((t) => t && t.name).filter(Boolean)
|
|
37283
|
+
: null;
|
|
37284
|
+
return {
|
|
37285
|
+
name: p.name,
|
|
37286
|
+
type: p.type || null,
|
|
37287
|
+
running: !!live,
|
|
37288
|
+
toolCount: live ? (live.tools || []).length : null,
|
|
37289
|
+
declared,
|
|
37290
|
+
tools: declared ? [...p.allowedTools] : liveNames,
|
|
37291
|
+
};
|
|
37292
|
+
})
|
|
37293
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
37294
|
+
}
|
|
37295
|
+
|
|
37296
|
+
/**
|
|
37297
|
+
* The tools a bot may use on one provider: the provider's declared limit
|
|
37298
|
+
* (Settings → Providers) is the ceiling; the bot's own selection narrows within
|
|
37299
|
+
* it. null = no restriction (every tool the server exposes).
|
|
37300
|
+
*
|
|
37301
|
+
* @param {string[]|null|undefined} providerLimit provider.allowedTools
|
|
37302
|
+
* @param {string[]|null|undefined} selection bot.toolSelections[provider]
|
|
37303
|
+
* @returns {string[]|null}
|
|
37304
|
+
*/
|
|
37305
|
+
function effectiveAllowedTools(providerLimit, selection) {
|
|
37306
|
+
const limit = Array.isArray(providerLimit) ? providerLimit : null;
|
|
37307
|
+
const sel = Array.isArray(selection) ? selection : null;
|
|
37308
|
+
if (limit && sel) return sel.filter((t) => limit.includes(t));
|
|
37309
|
+
if (limit) return [...limit];
|
|
37310
|
+
if (sel) return [...sel];
|
|
37311
|
+
return null;
|
|
37312
|
+
}
|
|
37313
|
+
|
|
37314
|
+
/**
|
|
37315
|
+
* Before a run: start each of the bot's selected providers that isn't already
|
|
37316
|
+
* connected in the bot's bucket, via the shared startServer factory (which
|
|
37317
|
+
* itself dedupes concurrent starts). Never throws — returns per-provider
|
|
37318
|
+
* failures so the run can surface them instead of silently running without
|
|
37319
|
+
* those tools.
|
|
37320
|
+
*/
|
|
37321
|
+
async function ensureBotServers$1({
|
|
37322
|
+
bot,
|
|
37323
|
+
connected = [],
|
|
37324
|
+
getProvider,
|
|
37325
|
+
startServer,
|
|
37326
|
+
}) {
|
|
37327
|
+
const failed = [];
|
|
37328
|
+
const wanted = Array.isArray(bot && bot.mcpServers) ? bot.mcpServers : [];
|
|
37329
|
+
const workspaceId = normalizeWorkspace(bot && bot.workspaceId);
|
|
37330
|
+
for (const serverName of wanted) {
|
|
37331
|
+
const running = (connected || []).some(
|
|
37332
|
+
(s) => s.serverName === serverName && inBucket(s, workspaceId),
|
|
37333
|
+
);
|
|
37334
|
+
if (running) continue;
|
|
37335
|
+
try {
|
|
37336
|
+
// providerController.getProvider returns { provider } or
|
|
37337
|
+
// { error, message }; accept a bare provider too.
|
|
37338
|
+
const result = await getProvider(serverName);
|
|
37339
|
+
if (result && result.error) {
|
|
37340
|
+
failed.push({
|
|
37341
|
+
serverName,
|
|
37342
|
+
message:
|
|
37343
|
+
result.message ||
|
|
37344
|
+
`Provider "${serverName}" was not found in Settings → Providers`,
|
|
37345
|
+
});
|
|
37346
|
+
continue;
|
|
37347
|
+
}
|
|
37348
|
+
const provider = result && result.provider ? result.provider : result;
|
|
37349
|
+
if (
|
|
37350
|
+
!provider ||
|
|
37351
|
+
provider.providerClass !== "mcp" ||
|
|
37352
|
+
!provider.mcpConfig
|
|
37353
|
+
) {
|
|
37354
|
+
failed.push({
|
|
37355
|
+
serverName,
|
|
37356
|
+
message: `Provider "${serverName}" was not found in Settings → Providers`,
|
|
37357
|
+
});
|
|
37358
|
+
continue;
|
|
37359
|
+
}
|
|
37360
|
+
const res = await startServer(
|
|
37361
|
+
serverName,
|
|
37362
|
+
provider.mcpConfig,
|
|
37363
|
+
provider.credentials || {},
|
|
37364
|
+
workspaceId,
|
|
37365
|
+
);
|
|
37366
|
+
if (res && res.error) {
|
|
37367
|
+
failed.push({
|
|
37368
|
+
serverName,
|
|
37369
|
+
message: res.message || `Provider "${serverName}" failed to start`,
|
|
37370
|
+
});
|
|
37371
|
+
}
|
|
37372
|
+
} catch (err) {
|
|
37373
|
+
failed.push({
|
|
37374
|
+
serverName,
|
|
37375
|
+
message: (err && err.message) || String(err),
|
|
37376
|
+
});
|
|
37377
|
+
}
|
|
37378
|
+
}
|
|
37379
|
+
return { failed };
|
|
37380
|
+
}
|
|
37381
|
+
|
|
37382
|
+
/**
|
|
37383
|
+
* The bot's MCP tools: only its selected providers, only from its own bucket,
|
|
37384
|
+
* and only the tools each provider allows (narrowed by the bot's own
|
|
37385
|
+
* selection). Also returns `allowedFor` — the per-provider whitelist the
|
|
37386
|
+
* caller passes to mcpController.callTool so a disallowed call is rejected
|
|
37387
|
+
* there too (same double enforcement widgets get).
|
|
37388
|
+
*
|
|
37389
|
+
* When `providerLimits` is given it is authoritative and fails closed: a
|
|
37390
|
+
* server with no entry (provider deleted, or provider info unreadable) gets no
|
|
37391
|
+
* tools. Omit it only where no provider info exists at all (unit callers).
|
|
37392
|
+
*
|
|
37393
|
+
* @param {{ bot, connected, providerLimits?: Record<string, string[]|null> }} args
|
|
37394
|
+
*/
|
|
37395
|
+
function resolveBotTools$1({ bot, connected = [], providerLimits }) {
|
|
37396
|
+
const wanted = new Set(
|
|
37397
|
+
Array.isArray(bot && bot.mcpServers) ? bot.mcpServers : [],
|
|
37398
|
+
);
|
|
37399
|
+
const selections = (bot && bot.toolSelections) || {};
|
|
37400
|
+
const workspaceId = bot && bot.workspaceId;
|
|
37401
|
+
const tools = [];
|
|
37402
|
+
const toolServer = Object.create(null);
|
|
37403
|
+
const allowedFor = Object.create(null);
|
|
37404
|
+
for (const server of connected || []) {
|
|
37405
|
+
const name = server.serverName;
|
|
37406
|
+
if (!wanted.has(name) || !inBucket(server, workspaceId)) continue;
|
|
37407
|
+
if (providerLimits && !(name in providerLimits)) continue; // fail closed
|
|
37408
|
+
const allowed = effectiveAllowedTools(
|
|
37409
|
+
providerLimits ? providerLimits[name] : undefined,
|
|
37410
|
+
selections[name],
|
|
37411
|
+
);
|
|
37412
|
+
allowedFor[name] = allowed;
|
|
37413
|
+
for (const tool of server.tools || []) {
|
|
37414
|
+
if (allowed && !allowed.includes(tool.name)) continue;
|
|
37415
|
+
tools.push(tool);
|
|
37416
|
+
toolServer[tool.name] = name;
|
|
37417
|
+
}
|
|
37418
|
+
}
|
|
37419
|
+
return { tools, toolServer, allowedFor };
|
|
37420
|
+
}
|
|
37421
|
+
|
|
37422
|
+
/**
|
|
37423
|
+
* Last-line enforcement for a bot tool call. `allowedMap` is the
|
|
37424
|
+
* `allowedFor` from this bot's resolveBotTools. Fails closed: unknown bot,
|
|
37425
|
+
* unknown provider, or a tool outside the whitelist → rejected.
|
|
37426
|
+
*
|
|
37427
|
+
* @returns {{ ok: true, allowed: string[]|null } | { ok: false, message: string }}
|
|
37428
|
+
*/
|
|
37429
|
+
function checkToolCall$1(allowedMap, serverName, toolName) {
|
|
37430
|
+
if (
|
|
37431
|
+
!allowedMap ||
|
|
37432
|
+
!Object.prototype.hasOwnProperty.call(allowedMap, serverName)
|
|
37433
|
+
) {
|
|
37434
|
+
return {
|
|
37435
|
+
ok: false,
|
|
37436
|
+
message: `Tool "${toolName}" is not available to this bot (provider "${serverName}" isn't one of its providers).`,
|
|
37437
|
+
};
|
|
37438
|
+
}
|
|
37439
|
+
const allowed = allowedMap[serverName];
|
|
37440
|
+
if (Array.isArray(allowed) && !allowed.includes(toolName)) {
|
|
37441
|
+
return {
|
|
37442
|
+
ok: false,
|
|
37443
|
+
message: `Tool "${toolName}" is not allowed for this bot on "${serverName}".`,
|
|
37444
|
+
};
|
|
37445
|
+
}
|
|
37446
|
+
return { ok: true, allowed: Array.isArray(allowed) ? allowed : null };
|
|
37447
|
+
}
|
|
37448
|
+
|
|
37449
|
+
var toolSources = {
|
|
37450
|
+
checkToolCall: checkToolCall$1,
|
|
37451
|
+
connectedServersFromMap: connectedServersFromMap$1,
|
|
37452
|
+
listToolSources: listToolSources$1,
|
|
37453
|
+
ensureBotServers: ensureBotServers$1,
|
|
37454
|
+
resolveBotTools: resolveBotTools$1,
|
|
37455
|
+
effectiveAllowedTools,
|
|
37456
|
+
};
|
|
37457
|
+
|
|
37202
37458
|
/**
|
|
37203
37459
|
* mcpScopeResolver.js
|
|
37204
37460
|
*
|
|
@@ -37577,6 +37833,7 @@ const os$2 = require$$2;
|
|
|
37577
37833
|
const responseCache$2 = responseCache_1;
|
|
37578
37834
|
const { gateToolCall, gateToolCallWithJit } = permissionGate;
|
|
37579
37835
|
const { serverKey, parseServerKey } = mcpServerKey;
|
|
37836
|
+
const { connectedServersFromMap } = toolSources;
|
|
37580
37837
|
const { applyPathScopeToCredentials } = mcpScopeResolver;
|
|
37581
37838
|
const { readEnforceFlag: readEnforceFlag$1, readJitFlag: readJitFlag$1 } = securityFlags;
|
|
37582
37839
|
const { app: app$8 } = require$$0$2;
|
|
@@ -37750,7 +38007,7 @@ function getShellPath$1() {
|
|
|
37750
38007
|
return _shellPath$1;
|
|
37751
38008
|
}
|
|
37752
38009
|
|
|
37753
|
-
const { execSync } = require$$
|
|
38010
|
+
const { execSync } = require$$13;
|
|
37754
38011
|
const fallbackDirs = ["/usr/local/bin", "/opt/homebrew/bin"];
|
|
37755
38012
|
|
|
37756
38013
|
// Scan nvm versions, tracking both latest and best compatible version
|
|
@@ -38854,24 +39111,16 @@ const mcpController$3 = {
|
|
|
38854
39111
|
/**
|
|
38855
39112
|
* listConnectedServers
|
|
38856
39113
|
* Returns all connected servers with their cached tool lists.
|
|
38857
|
-
* Used by
|
|
39114
|
+
* Used by bots + the LLM tool picker to discover available MCP tools.
|
|
39115
|
+
*
|
|
39116
|
+
* `serverName` is the real server name and `workspaceId` its bucket
|
|
39117
|
+
* (null = no workspace) — activeServers is keyed "<workspace>::<server>",
|
|
39118
|
+
* and returning that compound key as `serverName` broke name matching.
|
|
38858
39119
|
*
|
|
38859
|
-
* @returns {Array<{ serverName, tools, resources, status }>}
|
|
39120
|
+
* @returns {Array<{ serverName, workspaceId, tools, resources, status }>}
|
|
38860
39121
|
*/
|
|
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
|
-
},
|
|
39122
|
+
listConnectedServers: () =>
|
|
39123
|
+
connectedServersFromMap(activeServers, STATUS$1.CONNECTED),
|
|
38875
39124
|
|
|
38876
39125
|
/**
|
|
38877
39126
|
* runAuth
|
|
@@ -38884,7 +39133,7 @@ const mcpController$3 = {
|
|
|
38884
39133
|
* @returns {{ success } | { error, message }}
|
|
38885
39134
|
*/
|
|
38886
39135
|
runAuth: async (win, mcpConfig, credentials, authCommand) => {
|
|
38887
|
-
const { spawn } = require$$
|
|
39136
|
+
const { spawn } = require$$13;
|
|
38888
39137
|
|
|
38889
39138
|
const env = cleanEnvForChildProcess();
|
|
38890
39139
|
|
|
@@ -59749,7 +59998,7 @@ var mcpDashServerController_1 = mcpDashServerController$4;
|
|
|
59749
59998
|
* can use the Chat widget without a separate API key.
|
|
59750
59999
|
*/
|
|
59751
60000
|
|
|
59752
|
-
const { spawn, execSync } = require$$
|
|
60001
|
+
const { spawn, execSync } = require$$13;
|
|
59753
60002
|
const {
|
|
59754
60003
|
LLM_STREAM_DELTA: LLM_STREAM_DELTA$2,
|
|
59755
60004
|
LLM_STREAM_TOOL_CALL: LLM_STREAM_TOOL_CALL$2,
|
|
@@ -73788,6 +74037,10 @@ function withDefaults$1(def = {}) {
|
|
|
73788
74037
|
// specific engine id here (e.g. "claude-agent").
|
|
73789
74038
|
engine: null,
|
|
73790
74039
|
mcpServers: [],
|
|
74040
|
+
// Per-provider narrowing within each provider's declared tools:
|
|
74041
|
+
// { [providerName]: string[] }. No entry → every tool the provider allows.
|
|
74042
|
+
toolSelections: {},
|
|
74043
|
+
// Tools that skip the approval prompt (NOT the tools the bot may use).
|
|
73791
74044
|
allowedTools: [],
|
|
73792
74045
|
approvalPolicy: "ask",
|
|
73793
74046
|
schedules: [],
|
|
@@ -73849,6 +74102,22 @@ function validateBotDefinition$1(def) {
|
|
|
73849
74102
|
errors.push(`${field} must be an array`);
|
|
73850
74103
|
}
|
|
73851
74104
|
}
|
|
74105
|
+
// toolSelections: { [providerName]: string[] } — per-bot tool narrowing.
|
|
74106
|
+
if (def.toolSelections !== undefined) {
|
|
74107
|
+
const ts = def.toolSelections;
|
|
74108
|
+
const ok =
|
|
74109
|
+
ts &&
|
|
74110
|
+
typeof ts === "object" &&
|
|
74111
|
+
!Array.isArray(ts) &&
|
|
74112
|
+
Object.values(ts).every(
|
|
74113
|
+
(v) => Array.isArray(v) && v.every((t) => typeof t === "string"),
|
|
74114
|
+
);
|
|
74115
|
+
if (!ok) {
|
|
74116
|
+
errors.push(
|
|
74117
|
+
"toolSelections must map provider names to arrays of tool names",
|
|
74118
|
+
);
|
|
74119
|
+
}
|
|
74120
|
+
}
|
|
73852
74121
|
// Each subscription must name an eventType (PRD FR-009).
|
|
73853
74122
|
if (Array.isArray(def.subscriptions)) {
|
|
73854
74123
|
for (const sub of def.subscriptions) {
|
|
@@ -74998,18 +75267,29 @@ function _makeCanUseTool(ctx) {
|
|
|
74998
75267
|
async function _runAgent(ctx, stream) {
|
|
74999
75268
|
const query = await _loadQuery();
|
|
75000
75269
|
|
|
75001
|
-
//
|
|
75002
|
-
// bot's Anthropic API key
|
|
75003
|
-
//
|
|
75270
|
+
// Per-run environment for the agent process. Auth: the Claude Code login by
|
|
75271
|
+
// default; a bot's Anthropic API key, when set, goes in THIS run's env only —
|
|
75272
|
+
// never process.env, which would leak it to the rest of the main process and
|
|
75273
|
+
// to other bots.
|
|
75274
|
+
const env = { ...process.env };
|
|
75004
75275
|
if (ctx.credentials && ctx.credentials.apiKey) {
|
|
75005
|
-
|
|
75276
|
+
env.ANTHROPIC_API_KEY = ctx.credentials.apiKey;
|
|
75006
75277
|
}
|
|
75278
|
+
// No claude.ai connectors from the user's Claude login.
|
|
75279
|
+
env.ENABLE_CLAUDEAI_MCP_SERVERS = "false";
|
|
75007
75280
|
|
|
75008
75281
|
const options = {
|
|
75009
75282
|
model: ctx.model,
|
|
75010
75283
|
systemPrompt: ctx.systemPrompt || { type: "preset", preset: "claude_code" },
|
|
75011
75284
|
permissionMode: "default",
|
|
75012
75285
|
canUseTool: _makeCanUseTool(ctx),
|
|
75286
|
+
// Isolation: a bot sees only the SDK's built-in tools + the Dash providers
|
|
75287
|
+
// it was granted (bridged below as "bot-mcp"). Don't load the user's
|
|
75288
|
+
// ~/.claude settings/plugins/skills/hooks, and ignore every MCP config
|
|
75289
|
+
// other than the servers passed in `mcpServers`.
|
|
75290
|
+
settingSources: [],
|
|
75291
|
+
strictMcpConfig: true,
|
|
75292
|
+
env,
|
|
75013
75293
|
};
|
|
75014
75294
|
if (ctx.maxTurns) options.maxTurns = ctx.maxTurns;
|
|
75015
75295
|
if (ctx.workingDir) options.cwd = ctx.workingDir;
|
|
@@ -75445,12 +75725,17 @@ let BotRunner$1 = class BotRunner {
|
|
|
75445
75725
|
}
|
|
75446
75726
|
return this._callTool(serverName, toolName, input, {
|
|
75447
75727
|
workspaceId: bot.workspaceId,
|
|
75728
|
+
// Lets the caller apply this bot's per-provider tool limits.
|
|
75729
|
+
botId,
|
|
75448
75730
|
});
|
|
75449
75731
|
};
|
|
75450
75732
|
|
|
75451
|
-
//
|
|
75733
|
+
// A run does the bot's job against current data → fresh conversation.
|
|
75734
|
+
// Resume only when explicitly continuing (reply-to-continue) AND the
|
|
75735
|
+
// stored session was produced by this engine. Anything a bot should
|
|
75736
|
+
// carry between runs belongs in its memory tools.
|
|
75452
75737
|
const session =
|
|
75453
|
-
bot.session && bot.session.engine === engine.id
|
|
75738
|
+
opts.continueSession && bot.session && bot.session.engine === engine.id
|
|
75454
75739
|
? bot.session.state
|
|
75455
75740
|
: null;
|
|
75456
75741
|
|
|
@@ -76284,6 +76569,12 @@ const {
|
|
|
76284
76569
|
const { matchSubscribedBots } = eventMatcher;
|
|
76285
76570
|
const { EventDispatcher } = EventDispatcher_1;
|
|
76286
76571
|
const { normalizeMcpResult } = mcpResult;
|
|
76572
|
+
const {
|
|
76573
|
+
listToolSources,
|
|
76574
|
+
ensureBotServers,
|
|
76575
|
+
resolveBotTools,
|
|
76576
|
+
checkToolCall,
|
|
76577
|
+
} = toolSources;
|
|
76287
76578
|
const {
|
|
76288
76579
|
getProvider: getProvider$1,
|
|
76289
76580
|
getDefaultModel,
|
|
@@ -76387,6 +76678,10 @@ const botController$1 = {
|
|
|
76387
76678
|
// flood the runner with event-triggered runs.
|
|
76388
76679
|
this._dispatcher = new EventDispatcher();
|
|
76389
76680
|
|
|
76681
|
+
// botId → { [providerName]: allowed tool names | null }, set on each run's
|
|
76682
|
+
// tool resolution and enforced in _callTool.
|
|
76683
|
+
this._allowedByBot = new Map();
|
|
76684
|
+
|
|
76390
76685
|
this._runner = new BotRunner({
|
|
76391
76686
|
engines,
|
|
76392
76687
|
store: this._store,
|
|
@@ -76472,6 +76767,26 @@ const botController$1 = {
|
|
|
76472
76767
|
return this._store.list();
|
|
76473
76768
|
},
|
|
76474
76769
|
|
|
76770
|
+
/**
|
|
76771
|
+
* The user's Dash MCP providers a bot can use (Settings → Providers), with
|
|
76772
|
+
* running status + tool count. Names/types only — never credentials.
|
|
76773
|
+
*/
|
|
76774
|
+
listToolSources(workspaceId = null) {
|
|
76775
|
+
let providers = [];
|
|
76776
|
+
try {
|
|
76777
|
+
const win = this._getMainWindow();
|
|
76778
|
+
({ providers = [] } =
|
|
76779
|
+
this._providers.listProviders(win, this._appId) || {});
|
|
76780
|
+
} catch (_e) {
|
|
76781
|
+
providers = [];
|
|
76782
|
+
}
|
|
76783
|
+
return listToolSources({
|
|
76784
|
+
providers,
|
|
76785
|
+
connected: this._connectedServers(),
|
|
76786
|
+
workspaceId,
|
|
76787
|
+
});
|
|
76788
|
+
},
|
|
76789
|
+
|
|
76475
76790
|
get(botId) {
|
|
76476
76791
|
return this._store.get(botId);
|
|
76477
76792
|
},
|
|
@@ -76638,23 +76953,58 @@ const botController$1 = {
|
|
|
76638
76953
|
};
|
|
76639
76954
|
},
|
|
76640
76955
|
|
|
76641
|
-
/**
|
|
76642
|
-
|
|
76643
|
-
|
|
76956
|
+
/** Connected MCP servers with real server names + workspace buckets. */
|
|
76957
|
+
_connectedServers() {
|
|
76958
|
+
return this._mcp && this._mcp.listConnectedServers
|
|
76644
76959
|
? this._mcp.listConnectedServers()
|
|
76645
76960
|
: [];
|
|
76646
|
-
|
|
76647
|
-
|
|
76648
|
-
|
|
76649
|
-
|
|
76650
|
-
|
|
76651
|
-
|
|
76652
|
-
|
|
76653
|
-
|
|
76654
|
-
|
|
76655
|
-
|
|
76961
|
+
},
|
|
76962
|
+
|
|
76963
|
+
/**
|
|
76964
|
+
* Build the bot's tool set + tool→server resolver. First starts any of the
|
|
76965
|
+
* bot's selected providers that aren't running in its bucket — through the
|
|
76966
|
+
* same deduping startServer factory the dashboards use — so a scheduled or
|
|
76967
|
+
* event-triggered bot doesn't silently run without its tools. Start
|
|
76968
|
+
* failures are surfaced in the run feed as warnings.
|
|
76969
|
+
*/
|
|
76970
|
+
async _resolveTools(bot) {
|
|
76971
|
+
if (this._mcp && this._mcp.startServer && this._providers) {
|
|
76972
|
+
const win = this._getMainWindow();
|
|
76973
|
+
const { failed } = await ensureBotServers({
|
|
76974
|
+
bot,
|
|
76975
|
+
connected: this._connectedServers(),
|
|
76976
|
+
getProvider: async (name) =>
|
|
76977
|
+
this._providers.getProvider(win, this._appId, name),
|
|
76978
|
+
startServer: (name, mcpConfig, credentials, workspaceId) =>
|
|
76979
|
+
this._mcp.startServer(
|
|
76980
|
+
win,
|
|
76981
|
+
name,
|
|
76982
|
+
mcpConfig,
|
|
76983
|
+
credentials,
|
|
76984
|
+
workspaceId,
|
|
76985
|
+
null,
|
|
76986
|
+
this._appId,
|
|
76987
|
+
),
|
|
76988
|
+
});
|
|
76989
|
+
for (const f of failed) {
|
|
76990
|
+
this._broadcast(BOT_STREAM$1, {
|
|
76991
|
+
botId: bot.id,
|
|
76992
|
+
event: {
|
|
76993
|
+
type: "warning",
|
|
76994
|
+
message: `${f.serverName} couldn't start, so its tools aren't available this run: ${f.message}`,
|
|
76995
|
+
},
|
|
76996
|
+
});
|
|
76656
76997
|
}
|
|
76657
76998
|
}
|
|
76999
|
+
// Each provider's declared tools (Settings → Providers) are the ceiling;
|
|
77000
|
+
// the bot's toolSelections narrow within them.
|
|
77001
|
+
const { tools, toolServer, allowedFor } = resolveBotTools({
|
|
77002
|
+
bot,
|
|
77003
|
+
connected: this._connectedServers(),
|
|
77004
|
+
providerLimits: this._providerToolLimits(),
|
|
77005
|
+
});
|
|
77006
|
+
// Remembered per bot so _callTool can hard-enforce the same whitelist.
|
|
77007
|
+
this._allowedByBot.set(bot.id, allowedFor);
|
|
76658
77008
|
// Always-available in-process memory tools (P1: FR-010).
|
|
76659
77009
|
for (const tool of MEMORY_TOOLS) {
|
|
76660
77010
|
tools.push(tool);
|
|
@@ -76674,20 +77024,54 @@ const botController$1 = {
|
|
|
76674
77024
|
);
|
|
76675
77025
|
}
|
|
76676
77026
|
const win = this._getMainWindow();
|
|
77027
|
+
// The bot's per-provider whitelist (provider limit ∩ bot selection), from
|
|
77028
|
+
// the last _resolveTools for this bot. A tool outside it is rejected —
|
|
77029
|
+
// mirroring the allowedTools enforcement widgets get. If no entry exists
|
|
77030
|
+
// for this provider, the bot never had it resolved → deny.
|
|
77031
|
+
const check = checkToolCall(
|
|
77032
|
+
this._allowedByBot.get(opts.botId),
|
|
77033
|
+
serverName,
|
|
77034
|
+
toolName,
|
|
77035
|
+
);
|
|
77036
|
+
if (!check.ok) return { text: check.message, isError: true };
|
|
77037
|
+
const { allowed } = check;
|
|
76677
77038
|
// widgetId + token are null → the widget permission gate is bypassed; bot
|
|
76678
|
-
// tool calls are gated upstream by the bot PermissionGate.
|
|
77039
|
+
// tool calls are gated upstream by the bot PermissionGate. `allowed` is
|
|
77040
|
+
// also passed as callTool's whitelist (defense in depth).
|
|
76679
77041
|
const result = await this._mcp.callTool(
|
|
76680
77042
|
win,
|
|
76681
77043
|
serverName,
|
|
76682
77044
|
toolName,
|
|
76683
77045
|
args,
|
|
76684
|
-
null,
|
|
77046
|
+
allowed || null,
|
|
76685
77047
|
null,
|
|
76686
77048
|
opts.workspaceId || null,
|
|
76687
77049
|
null,
|
|
76688
77050
|
);
|
|
76689
77051
|
return normalizeMcpResult(result);
|
|
76690
77052
|
},
|
|
77053
|
+
|
|
77054
|
+
/** { [providerName]: allowedTools | null } for the user's MCP providers. */
|
|
77055
|
+
_providerToolLimits() {
|
|
77056
|
+
const limits = Object.create(null);
|
|
77057
|
+
try {
|
|
77058
|
+
const win = this._getMainWindow();
|
|
77059
|
+
const { providers = [] } =
|
|
77060
|
+
this._providers.listProviders(win, this._appId) || {};
|
|
77061
|
+
for (const p of providers) {
|
|
77062
|
+
if (p && p.providerClass === "mcp" && p.name) {
|
|
77063
|
+
limits[p.name] = Array.isArray(p.allowedTools)
|
|
77064
|
+
? p.allowedTools
|
|
77065
|
+
: null;
|
|
77066
|
+
}
|
|
77067
|
+
}
|
|
77068
|
+
} catch (_e) {
|
|
77069
|
+
// Provider info unreadable → empty map. resolveBotTools treats a
|
|
77070
|
+
// provider with no entry as not configured, so the bot gets NO tools
|
|
77071
|
+
// from it (fails closed) rather than every tool the server exposes.
|
|
77072
|
+
}
|
|
77073
|
+
return limits;
|
|
77074
|
+
},
|
|
76691
77075
|
};
|
|
76692
77076
|
|
|
76693
77077
|
var botController_1 = botController$1;
|
|
@@ -80600,6 +80984,7 @@ const {
|
|
|
80600
80984
|
BOTS_PAUSE_BOT,
|
|
80601
80985
|
BOTS_RESUME_BOT,
|
|
80602
80986
|
BOTS_GET_PAUSE_STATE,
|
|
80987
|
+
BOTS_LIST_TOOL_SOURCES,
|
|
80603
80988
|
BOT_STREAM,
|
|
80604
80989
|
BOT_APPROVAL_PENDING,
|
|
80605
80990
|
BOT_BUDGET_ALERT,
|
|
@@ -80648,6 +81033,10 @@ const botApi$1 = {
|
|
|
80648
81033
|
resumeBot: (botId) => ipcRenderer$3.invoke(BOTS_RESUME_BOT, { botId }),
|
|
80649
81034
|
getPauseState: () => ipcRenderer$3.invoke(BOTS_GET_PAUSE_STATE),
|
|
80650
81035
|
|
|
81036
|
+
/** The user's Dash MCP providers a bot can use: [{ name, type, running, toolCount }]. */
|
|
81037
|
+
listToolSources: (workspaceId = null) =>
|
|
81038
|
+
ipcRenderer$3.invoke(BOTS_LIST_TOOL_SOURCES, { workspaceId }),
|
|
81039
|
+
|
|
80651
81040
|
/** Subscribe to streamed run events: { botId, event }. */
|
|
80652
81041
|
onStream: (callback) => _addListener(BOT_STREAM, callback),
|
|
80653
81042
|
/** Subscribe to new pending approvals: { id, request }. */
|