@trops/dash-core 0.1.489 → 0.1.491
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 +835 -357
- package/dist/electron/index.js.map +1 -1
- package/dist/index.esm.js +325 -42
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +325 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/electron/index.js
CHANGED
|
@@ -17,7 +17,7 @@ var require$$0$5 = require('@modelcontextprotocol/sdk/client/index.js');
|
|
|
17
17
|
var require$$1$4 = require('@modelcontextprotocol/sdk/client/stdio.js');
|
|
18
18
|
var require$$0$4 = require('pkce-challenge');
|
|
19
19
|
var require$$2$1 = require('os');
|
|
20
|
-
var require$$
|
|
20
|
+
var require$$10 = require('child_process');
|
|
21
21
|
var require$$3$2 = require('adm-zip');
|
|
22
22
|
var require$$4$1 = require('url');
|
|
23
23
|
var require$$2$2 = require('vm');
|
|
@@ -1107,7 +1107,7 @@ var secureStoreController$1 = {
|
|
|
1107
1107
|
getData: getData$1,
|
|
1108
1108
|
};
|
|
1109
1109
|
|
|
1110
|
-
const path$
|
|
1110
|
+
const path$m = require$$1$2;
|
|
1111
1111
|
const {
|
|
1112
1112
|
readFileSync,
|
|
1113
1113
|
writeFileSync: writeFileSync$4,
|
|
@@ -1125,7 +1125,7 @@ const {
|
|
|
1125
1125
|
function ensureDirectoryExistence$2(filePath) {
|
|
1126
1126
|
try {
|
|
1127
1127
|
// isDirectory
|
|
1128
|
-
var dirname = path$
|
|
1128
|
+
var dirname = path$m.dirname(filePath);
|
|
1129
1129
|
// check if the directory exists...return true
|
|
1130
1130
|
// if not, we can pass in the dirname as the filepath
|
|
1131
1131
|
// and check each directory recursively.
|
|
@@ -1240,7 +1240,7 @@ function removeFilesFromDirectory(directory, excludeFiles = []) {
|
|
|
1240
1240
|
|
|
1241
1241
|
for (const file of files) {
|
|
1242
1242
|
if (!excludeFiles.includes(file)) {
|
|
1243
|
-
unlinkSync(path$
|
|
1243
|
+
unlinkSync(path$m.join(directory, file), (err) => {
|
|
1244
1244
|
if (err) throw err;
|
|
1245
1245
|
});
|
|
1246
1246
|
}
|
|
@@ -1257,8 +1257,8 @@ var file = {
|
|
|
1257
1257
|
checkDirectory: checkDirectory$1,
|
|
1258
1258
|
};
|
|
1259
1259
|
|
|
1260
|
-
const { app: app$
|
|
1261
|
-
const path$
|
|
1260
|
+
const { app: app$f } = require$$0$1;
|
|
1261
|
+
const path$l = require$$1$2;
|
|
1262
1262
|
const { writeFileSync: writeFileSync$3 } = require$$0$2;
|
|
1263
1263
|
const { getFileContents: getFileContents$7 } = file;
|
|
1264
1264
|
|
|
@@ -1305,8 +1305,8 @@ const workspaceController$3 = {
|
|
|
1305
1305
|
saveWorkspaceForApplication: (win, appId, workspaceObject) => {
|
|
1306
1306
|
try {
|
|
1307
1307
|
// filename to the pages file (live pages)
|
|
1308
|
-
const filename = path$
|
|
1309
|
-
app$
|
|
1308
|
+
const filename = path$l.join(
|
|
1309
|
+
app$f.getPath("userData"),
|
|
1310
1310
|
appName$7,
|
|
1311
1311
|
appId,
|
|
1312
1312
|
configFilename$5,
|
|
@@ -1354,8 +1354,8 @@ const workspaceController$3 = {
|
|
|
1354
1354
|
saveMenuItemsForApplication: (win, appId, menuItems) => {
|
|
1355
1355
|
try {
|
|
1356
1356
|
// filename to the workspaces file
|
|
1357
|
-
const filename = path$
|
|
1358
|
-
app$
|
|
1357
|
+
const filename = path$l.join(
|
|
1358
|
+
app$f.getPath("userData"),
|
|
1359
1359
|
appName$7,
|
|
1360
1360
|
appId,
|
|
1361
1361
|
configFilename$5,
|
|
@@ -1403,8 +1403,8 @@ const workspaceController$3 = {
|
|
|
1403
1403
|
*/
|
|
1404
1404
|
deleteWorkspaceForApplication: (win, appId, workspaceId) => {
|
|
1405
1405
|
try {
|
|
1406
|
-
const filename = path$
|
|
1407
|
-
app$
|
|
1406
|
+
const filename = path$l.join(
|
|
1407
|
+
app$f.getPath("userData"),
|
|
1408
1408
|
appName$7,
|
|
1409
1409
|
appId,
|
|
1410
1410
|
configFilename$5,
|
|
@@ -1437,8 +1437,8 @@ const workspaceController$3 = {
|
|
|
1437
1437
|
|
|
1438
1438
|
listWorkspacesForApplication: (win, appId) => {
|
|
1439
1439
|
try {
|
|
1440
|
-
const filename = path$
|
|
1441
|
-
app$
|
|
1440
|
+
const filename = path$l.join(
|
|
1441
|
+
app$f.getPath("userData"),
|
|
1442
1442
|
appName$7,
|
|
1443
1443
|
appId,
|
|
1444
1444
|
configFilename$5,
|
|
@@ -1465,8 +1465,8 @@ const workspaceController$3 = {
|
|
|
1465
1465
|
|
|
1466
1466
|
listMenuItemsForApplication: (win, appId) => {
|
|
1467
1467
|
try {
|
|
1468
|
-
const filename = path$
|
|
1469
|
-
app$
|
|
1468
|
+
const filename = path$l.join(
|
|
1469
|
+
app$f.getPath("userData"),
|
|
1470
1470
|
appName$7,
|
|
1471
1471
|
appId,
|
|
1472
1472
|
configFilename$5,
|
|
@@ -1509,8 +1509,8 @@ const workspaceController$3 = {
|
|
|
1509
1509
|
|
|
1510
1510
|
var workspaceController_1 = workspaceController$3;
|
|
1511
1511
|
|
|
1512
|
-
const { app: app$
|
|
1513
|
-
const path$
|
|
1512
|
+
const { app: app$e } = require$$0$1;
|
|
1513
|
+
const path$k = require$$1$2;
|
|
1514
1514
|
const { writeFileSync: writeFileSync$2 } = require$$0$2;
|
|
1515
1515
|
const { getFileContents: getFileContents$6 } = file;
|
|
1516
1516
|
|
|
@@ -1530,8 +1530,8 @@ const themeController$5 = {
|
|
|
1530
1530
|
saveThemeForApplication: (win, appId, name, obj) => {
|
|
1531
1531
|
try {
|
|
1532
1532
|
// filename to the pages file (live pages)
|
|
1533
|
-
const filename = path$
|
|
1534
|
-
app$
|
|
1533
|
+
const filename = path$k.join(
|
|
1534
|
+
app$e.getPath("userData"),
|
|
1535
1535
|
appName$6,
|
|
1536
1536
|
appId,
|
|
1537
1537
|
configFilename$4,
|
|
@@ -1576,8 +1576,8 @@ const themeController$5 = {
|
|
|
1576
1576
|
*/
|
|
1577
1577
|
listThemesForApplication: (win, appId) => {
|
|
1578
1578
|
try {
|
|
1579
|
-
const filename = path$
|
|
1580
|
-
app$
|
|
1579
|
+
const filename = path$k.join(
|
|
1580
|
+
app$e.getPath("userData"),
|
|
1581
1581
|
appName$6,
|
|
1582
1582
|
appId,
|
|
1583
1583
|
configFilename$4,
|
|
@@ -1618,8 +1618,8 @@ const themeController$5 = {
|
|
|
1618
1618
|
*/
|
|
1619
1619
|
deleteThemeForApplication: (win, appId, themeKey) => {
|
|
1620
1620
|
try {
|
|
1621
|
-
const filename = path$
|
|
1622
|
-
app$
|
|
1621
|
+
const filename = path$k.join(
|
|
1622
|
+
app$e.getPath("userData"),
|
|
1623
1623
|
appName$6,
|
|
1624
1624
|
appId,
|
|
1625
1625
|
configFilename$4,
|
|
@@ -1695,9 +1695,9 @@ var themeController_1 = themeController$5;
|
|
|
1695
1695
|
* `/data/`.
|
|
1696
1696
|
*/
|
|
1697
1697
|
|
|
1698
|
-
const path$
|
|
1699
|
-
const fs$
|
|
1700
|
-
const { app: app$
|
|
1698
|
+
const path$j = require$$1$2;
|
|
1699
|
+
const fs$e = require$$0$2;
|
|
1700
|
+
const { app: app$d } = require$$0$1;
|
|
1701
1701
|
|
|
1702
1702
|
const APP_NAME = "Dashboard";
|
|
1703
1703
|
|
|
@@ -1706,10 +1706,10 @@ const APP_NAME = "Dashboard";
|
|
|
1706
1706
|
* @returns {string[]} ordered allowed roots for that category
|
|
1707
1707
|
*/
|
|
1708
1708
|
function getAllowedRoots$2(category) {
|
|
1709
|
-
const userData = app$
|
|
1709
|
+
const userData = app$d.getPath("userData");
|
|
1710
1710
|
switch (category) {
|
|
1711
1711
|
case "data": {
|
|
1712
|
-
const def = path$
|
|
1712
|
+
const def = path$j.join(userData, APP_NAME, "data");
|
|
1713
1713
|
// The user can configure a custom data directory in
|
|
1714
1714
|
// Settings → General → Data Directory. If set, that
|
|
1715
1715
|
// location is ALSO an allowed root. We don't replace the
|
|
@@ -1719,13 +1719,13 @@ function getAllowedRoots$2(category) {
|
|
|
1719
1719
|
return override ? [def, override] : [def];
|
|
1720
1720
|
}
|
|
1721
1721
|
case "themes":
|
|
1722
|
-
return [path$
|
|
1722
|
+
return [path$j.join(userData, APP_NAME, "themes")];
|
|
1723
1723
|
case "widgets":
|
|
1724
|
-
return [path$
|
|
1724
|
+
return [path$j.join(userData, "widgets")];
|
|
1725
1725
|
case "plugins":
|
|
1726
|
-
return [path$
|
|
1726
|
+
return [path$j.join(userData, "plugins")];
|
|
1727
1727
|
case "downloads":
|
|
1728
|
-
return [app$
|
|
1728
|
+
return [app$d.getPath("downloads")];
|
|
1729
1729
|
default:
|
|
1730
1730
|
throw new Error("safePath: unknown allowed-roots category: " + category);
|
|
1731
1731
|
}
|
|
@@ -1740,13 +1740,13 @@ function getAllowedRoots$2(category) {
|
|
|
1740
1740
|
*/
|
|
1741
1741
|
function readDataDirectoryFromSettings() {
|
|
1742
1742
|
try {
|
|
1743
|
-
const settingsPath = path$
|
|
1744
|
-
app$
|
|
1743
|
+
const settingsPath = path$j.join(
|
|
1744
|
+
app$d.getPath("userData"),
|
|
1745
1745
|
APP_NAME,
|
|
1746
1746
|
"settings.json",
|
|
1747
1747
|
);
|
|
1748
|
-
if (!fs$
|
|
1749
|
-
const raw = fs$
|
|
1748
|
+
if (!fs$e.existsSync(settingsPath)) return undefined;
|
|
1749
|
+
const raw = fs$e.readFileSync(settingsPath, "utf8");
|
|
1750
1750
|
const settings = JSON.parse(raw);
|
|
1751
1751
|
const dir = settings && settings.dataDirectory;
|
|
1752
1752
|
if (typeof dir === "string" && dir) return dir;
|
|
@@ -1772,18 +1772,18 @@ function safePath$3(requested, allowedRoots) {
|
|
|
1772
1772
|
throw new Error("safePath: allowedRoots must be a non-empty array");
|
|
1773
1773
|
}
|
|
1774
1774
|
|
|
1775
|
-
const resolved = path$
|
|
1775
|
+
const resolved = path$j.resolve(requested);
|
|
1776
1776
|
|
|
1777
1777
|
// Real-path through symlinks. If the file doesn't exist yet (a
|
|
1778
1778
|
// create-new operation), real-path the parent so a symlink in the
|
|
1779
1779
|
// parent chain can't trick us.
|
|
1780
1780
|
let real = resolved;
|
|
1781
1781
|
try {
|
|
1782
|
-
real = fs$
|
|
1782
|
+
real = fs$e.realpathSync(resolved);
|
|
1783
1783
|
} catch (_e) {
|
|
1784
1784
|
try {
|
|
1785
|
-
const parent = fs$
|
|
1786
|
-
real = path$
|
|
1785
|
+
const parent = fs$e.realpathSync(path$j.dirname(resolved));
|
|
1786
|
+
real = path$j.join(parent, path$j.basename(resolved));
|
|
1787
1787
|
} catch (_e2) {
|
|
1788
1788
|
// Parent doesn't exist either. Use the resolved-but-not-
|
|
1789
1789
|
// real path; the caller's mkdirSync will happen inside the
|
|
@@ -1795,14 +1795,14 @@ function safePath$3(requested, allowedRoots) {
|
|
|
1795
1795
|
for (const root of allowedRoots) {
|
|
1796
1796
|
let realRoot = root;
|
|
1797
1797
|
try {
|
|
1798
|
-
if (fs$
|
|
1798
|
+
if (fs$e.existsSync(root)) realRoot = fs$e.realpathSync(root);
|
|
1799
1799
|
} catch (_e) {
|
|
1800
1800
|
// root doesn't exist or isn't reachable — keep as-is for
|
|
1801
1801
|
// the comparison below
|
|
1802
1802
|
}
|
|
1803
1803
|
// Exact match OR strictly-inside (with separator to prevent
|
|
1804
1804
|
// /data-evil/ matching /data/).
|
|
1805
|
-
if (real === realRoot || real.startsWith(realRoot + path$
|
|
1805
|
+
if (real === realRoot || real.startsWith(realRoot + path$j.sep)) {
|
|
1806
1806
|
return real;
|
|
1807
1807
|
}
|
|
1808
1808
|
}
|
|
@@ -2060,15 +2060,15 @@ var safeJsExecutor$1 = {
|
|
|
2060
2060
|
* - CSV -> JSON
|
|
2061
2061
|
*/
|
|
2062
2062
|
|
|
2063
|
-
var fs$
|
|
2063
|
+
var fs$d = require$$0$2;
|
|
2064
2064
|
var readline = require$$1$3;
|
|
2065
2065
|
const xtreamer = require$$2;
|
|
2066
2066
|
var xmlParser = require$$3$1;
|
|
2067
2067
|
var JSONStream$1 = require$$4;
|
|
2068
2068
|
const stream$1 = require$$5;
|
|
2069
2069
|
var csv = require$$6;
|
|
2070
|
-
const path$
|
|
2071
|
-
const { app: app$
|
|
2070
|
+
const path$i = require$$1$2;
|
|
2071
|
+
const { app: app$c } = require$$0$1;
|
|
2072
2072
|
const { ensureDirectoryExistence: ensureDirectoryExistence$1 } = file;
|
|
2073
2073
|
const safeJsExecutor = safeJsExecutor$1;
|
|
2074
2074
|
|
|
@@ -2122,7 +2122,7 @@ let Transform$1 = class Transform {
|
|
|
2122
2122
|
let lineObject = [];
|
|
2123
2123
|
|
|
2124
2124
|
const readInterface = readline.createInterface({
|
|
2125
|
-
input: fs$
|
|
2125
|
+
input: fs$d.createReadStream(filepath),
|
|
2126
2126
|
output: process.stdout,
|
|
2127
2127
|
console: false,
|
|
2128
2128
|
});
|
|
@@ -2157,7 +2157,7 @@ let Transform$1 = class Transform {
|
|
|
2157
2157
|
return new Promise((resolve, reject) => {
|
|
2158
2158
|
try {
|
|
2159
2159
|
const parser = JSONStream$1.parse("*");
|
|
2160
|
-
const readStream = fs$
|
|
2160
|
+
const readStream = fs$d.createReadStream(file).pipe(parser);
|
|
2161
2161
|
|
|
2162
2162
|
let count = 0;
|
|
2163
2163
|
|
|
@@ -2210,7 +2210,7 @@ let Transform$1 = class Transform {
|
|
|
2210
2210
|
parseXMLStream = (filepath, outpath, start) => {
|
|
2211
2211
|
return new Promise((resolve, reject) => {
|
|
2212
2212
|
try {
|
|
2213
|
-
const xmlFileReadStream = fs$
|
|
2213
|
+
const xmlFileReadStream = fs$d.createReadStream(filepath);
|
|
2214
2214
|
|
|
2215
2215
|
xmlFileReadStream.on("end", () => {
|
|
2216
2216
|
writeStream.write("\n]");
|
|
@@ -2221,7 +2221,7 @@ let Transform$1 = class Transform {
|
|
|
2221
2221
|
resolve("Read Finish");
|
|
2222
2222
|
});
|
|
2223
2223
|
|
|
2224
|
-
const writeStream = fs$
|
|
2224
|
+
const writeStream = fs$d.createWriteStream(outpath);
|
|
2225
2225
|
writeStream.write("[\n");
|
|
2226
2226
|
|
|
2227
2227
|
const options = {
|
|
@@ -2273,10 +2273,10 @@ let Transform$1 = class Transform {
|
|
|
2273
2273
|
) => {
|
|
2274
2274
|
return new Promise((resolve, reject) => {
|
|
2275
2275
|
try {
|
|
2276
|
-
const readStream = fs$
|
|
2276
|
+
const readStream = fs$d
|
|
2277
2277
|
.createReadStream(filepath)
|
|
2278
2278
|
.pipe(csv({ separator: delimiter }));
|
|
2279
|
-
const writeStream = fs$
|
|
2279
|
+
const writeStream = fs$d.createWriteStream(outpath);
|
|
2280
2280
|
|
|
2281
2281
|
let canParse = true;
|
|
2282
2282
|
|
|
@@ -2362,18 +2362,18 @@ let Transform$1 = class Transform {
|
|
|
2362
2362
|
}
|
|
2363
2363
|
|
|
2364
2364
|
// Validate file paths are within app data directory
|
|
2365
|
-
const appDataDir = path$
|
|
2366
|
-
const resolvedFilepath = path$
|
|
2367
|
-
const resolvedOutFilepath = path$
|
|
2365
|
+
const appDataDir = path$i.join(app$c.getPath("userData"), TRANSFORM_APP_NAME);
|
|
2366
|
+
const resolvedFilepath = path$i.resolve(filepath);
|
|
2367
|
+
const resolvedOutFilepath = path$i.resolve(outFilepath);
|
|
2368
2368
|
|
|
2369
|
-
if (!resolvedFilepath.startsWith(appDataDir + path$
|
|
2369
|
+
if (!resolvedFilepath.startsWith(appDataDir + path$i.sep)) {
|
|
2370
2370
|
return reject(
|
|
2371
2371
|
new Error(
|
|
2372
2372
|
"Input file path must be within the application data directory",
|
|
2373
2373
|
),
|
|
2374
2374
|
);
|
|
2375
2375
|
}
|
|
2376
|
-
if (!resolvedOutFilepath.startsWith(appDataDir + path$
|
|
2376
|
+
if (!resolvedOutFilepath.startsWith(appDataDir + path$i.sep)) {
|
|
2377
2377
|
return reject(
|
|
2378
2378
|
new Error(
|
|
2379
2379
|
"Output file path must be within the application data directory",
|
|
@@ -2384,16 +2384,16 @@ let Transform$1 = class Transform {
|
|
|
2384
2384
|
// JSON parser
|
|
2385
2385
|
var parser = JSONStream$1.parse("*");
|
|
2386
2386
|
|
|
2387
|
-
if (!fs$
|
|
2387
|
+
if (!fs$d.existsSync(resolvedFilepath)) {
|
|
2388
2388
|
return reject(new Error("File doesnt exist"));
|
|
2389
2389
|
}
|
|
2390
2390
|
console.log("file exists ", resolvedFilepath);
|
|
2391
2391
|
// create the readStream to parse the large file (json)
|
|
2392
|
-
var readStream = fs$
|
|
2392
|
+
var readStream = fs$d.createReadStream(resolvedFilepath).pipe(parser);
|
|
2393
2393
|
|
|
2394
2394
|
ensureDirectoryExistence$1(resolvedOutFilepath);
|
|
2395
2395
|
|
|
2396
|
-
var writeStream = fs$
|
|
2396
|
+
var writeStream = fs$d.createWriteStream(resolvedOutFilepath);
|
|
2397
2397
|
|
|
2398
2398
|
let sep = "";
|
|
2399
2399
|
let count = 0;
|
|
@@ -2505,9 +2505,9 @@ let Transform$1 = class Transform {
|
|
|
2505
2505
|
|
|
2506
2506
|
var transform = Transform$1;
|
|
2507
2507
|
|
|
2508
|
-
const { app: app$
|
|
2509
|
-
var fs$
|
|
2510
|
-
const path$
|
|
2508
|
+
const { app: app$b } = require$$0$1;
|
|
2509
|
+
var fs$c = require$$0$2;
|
|
2510
|
+
const path$h = require$$1$2;
|
|
2511
2511
|
const events$5 = events$8;
|
|
2512
2512
|
const { getFileContents: getFileContents$5, writeToFile: writeToFile$2 } = file;
|
|
2513
2513
|
const { safePath: safePath$2, getAllowedRoots: getAllowedRoots$1 } = safePath_1;
|
|
@@ -2532,8 +2532,8 @@ const dataController$1 = {
|
|
|
2532
2532
|
// Validate the renderer-supplied filename is contained within
|
|
2533
2533
|
// the data directory. path.join doesn't reject `..` segments;
|
|
2534
2534
|
// safePath does.
|
|
2535
|
-
const candidate = path$
|
|
2536
|
-
app$
|
|
2535
|
+
const candidate = path$h.join(
|
|
2536
|
+
app$b.getPath("userData"),
|
|
2537
2537
|
appName$5,
|
|
2538
2538
|
appId,
|
|
2539
2539
|
"data",
|
|
@@ -2691,7 +2691,7 @@ const dataController$1 = {
|
|
|
2691
2691
|
// intent, plus realpath/symlink protection.
|
|
2692
2692
|
const resolvedFilepath = safePath$2(toFilepath, getAllowedRoots$1("data"));
|
|
2693
2693
|
|
|
2694
|
-
const writeStream = fs$
|
|
2694
|
+
const writeStream = fs$c.createWriteStream(resolvedFilepath);
|
|
2695
2695
|
|
|
2696
2696
|
https$3
|
|
2697
2697
|
.get(url, (resp) => {
|
|
@@ -2855,8 +2855,8 @@ const dataController$1 = {
|
|
|
2855
2855
|
if (data) {
|
|
2856
2856
|
// Validate filename is contained within the data directory.
|
|
2857
2857
|
// path.join doesn't reject `..` segments; safePath does.
|
|
2858
|
-
const candidate = path$
|
|
2859
|
-
app$
|
|
2858
|
+
const candidate = path$h.join(
|
|
2859
|
+
app$b.getPath("userData"),
|
|
2860
2860
|
appName$5,
|
|
2861
2861
|
"data",
|
|
2862
2862
|
filename,
|
|
@@ -2948,8 +2948,8 @@ const dataController$1 = {
|
|
|
2948
2948
|
try {
|
|
2949
2949
|
if (filename) {
|
|
2950
2950
|
// filename to the pages file (live pages)
|
|
2951
|
-
const fromFilename = path$
|
|
2952
|
-
app$
|
|
2951
|
+
const fromFilename = path$h.join(
|
|
2952
|
+
app$b.getPath("userData"),
|
|
2953
2953
|
appName$5,
|
|
2954
2954
|
"data",
|
|
2955
2955
|
filename,
|
|
@@ -3029,9 +3029,9 @@ var dataController_1 = dataController$1;
|
|
|
3029
3029
|
* settingsController
|
|
3030
3030
|
*/
|
|
3031
3031
|
|
|
3032
|
-
const { app: app$
|
|
3033
|
-
const path$
|
|
3034
|
-
const fs$
|
|
3032
|
+
const { app: app$a } = require$$0$1;
|
|
3033
|
+
const path$g = require$$1$2;
|
|
3034
|
+
const fs$b = require$$0$2;
|
|
3035
3035
|
const { getFileContents: getFileContents$4, writeToFile: writeToFile$1 } = file;
|
|
3036
3036
|
|
|
3037
3037
|
const configFilename$3 = "settings.json";
|
|
@@ -3039,15 +3039,15 @@ const appName$4 = "Dashboard";
|
|
|
3039
3039
|
|
|
3040
3040
|
// Helper function to recursively copy directory
|
|
3041
3041
|
function copyDirectory(source, destination) {
|
|
3042
|
-
if (!fs$
|
|
3043
|
-
fs$
|
|
3042
|
+
if (!fs$b.existsSync(destination)) {
|
|
3043
|
+
fs$b.mkdirSync(destination, { recursive: true });
|
|
3044
3044
|
}
|
|
3045
3045
|
|
|
3046
|
-
const files = fs$
|
|
3046
|
+
const files = fs$b.readdirSync(source);
|
|
3047
3047
|
for (const file of files) {
|
|
3048
|
-
const srcPath = path$
|
|
3049
|
-
const destPath = path$
|
|
3050
|
-
const stat = fs$
|
|
3048
|
+
const srcPath = path$g.join(source, file);
|
|
3049
|
+
const destPath = path$g.join(destination, file);
|
|
3050
|
+
const stat = fs$b.lstatSync(srcPath);
|
|
3051
3051
|
|
|
3052
3052
|
// Skip symlinks to prevent following links to sensitive files
|
|
3053
3053
|
if (stat.isSymbolicLink()) {
|
|
@@ -3058,7 +3058,7 @@ function copyDirectory(source, destination) {
|
|
|
3058
3058
|
if (stat.isDirectory()) {
|
|
3059
3059
|
copyDirectory(srcPath, destPath);
|
|
3060
3060
|
} else {
|
|
3061
|
-
fs$
|
|
3061
|
+
fs$b.copyFileSync(srcPath, destPath);
|
|
3062
3062
|
}
|
|
3063
3063
|
}
|
|
3064
3064
|
}
|
|
@@ -3074,8 +3074,8 @@ const settingsController$4 = {
|
|
|
3074
3074
|
try {
|
|
3075
3075
|
if (data) {
|
|
3076
3076
|
// <appId>/settings.json
|
|
3077
|
-
const filename = path$
|
|
3078
|
-
app$
|
|
3077
|
+
const filename = path$g.join(
|
|
3078
|
+
app$a.getPath("userData"),
|
|
3079
3079
|
appName$4,
|
|
3080
3080
|
configFilename$3,
|
|
3081
3081
|
);
|
|
@@ -3110,8 +3110,8 @@ const settingsController$4 = {
|
|
|
3110
3110
|
getSettingsForApplication: (win) => {
|
|
3111
3111
|
try {
|
|
3112
3112
|
// <appId>/settings.json
|
|
3113
|
-
const filename = path$
|
|
3114
|
-
app$
|
|
3113
|
+
const filename = path$g.join(
|
|
3114
|
+
app$a.getPath("userData"),
|
|
3115
3115
|
appName$4,
|
|
3116
3116
|
configFilename$3,
|
|
3117
3117
|
);
|
|
@@ -3141,15 +3141,15 @@ const settingsController$4 = {
|
|
|
3141
3141
|
*/
|
|
3142
3142
|
getDataDirectory: (win) => {
|
|
3143
3143
|
try {
|
|
3144
|
-
const settingsPath = path$
|
|
3145
|
-
app$
|
|
3144
|
+
const settingsPath = path$g.join(
|
|
3145
|
+
app$a.getPath("userData"),
|
|
3146
3146
|
appName$4,
|
|
3147
3147
|
configFilename$3,
|
|
3148
3148
|
);
|
|
3149
3149
|
const settings = getFileContents$4(settingsPath, {});
|
|
3150
3150
|
const userDataDir =
|
|
3151
3151
|
settings.userDataDirectory ||
|
|
3152
|
-
path$
|
|
3152
|
+
path$g.join(app$a.getPath("userData"), appName$4);
|
|
3153
3153
|
|
|
3154
3154
|
console.log("[settingsController] Data directory retrieved successfully");
|
|
3155
3155
|
// Return the data for ipcMain.handle() - modern promise-based approach
|
|
@@ -3176,18 +3176,18 @@ const settingsController$4 = {
|
|
|
3176
3176
|
setDataDirectory: (win, newPath) => {
|
|
3177
3177
|
try {
|
|
3178
3178
|
// Validate the path exists and is a directory
|
|
3179
|
-
if (!fs$
|
|
3180
|
-
fs$
|
|
3179
|
+
if (!fs$b.existsSync(newPath)) {
|
|
3180
|
+
fs$b.mkdirSync(newPath, { recursive: true });
|
|
3181
3181
|
}
|
|
3182
3182
|
|
|
3183
|
-
const stats = fs$
|
|
3183
|
+
const stats = fs$b.statSync(newPath);
|
|
3184
3184
|
if (!stats.isDirectory()) {
|
|
3185
3185
|
throw new Error("Path is not a directory");
|
|
3186
3186
|
}
|
|
3187
3187
|
|
|
3188
3188
|
// Update settings
|
|
3189
|
-
const settingsPath = path$
|
|
3190
|
-
app$
|
|
3189
|
+
const settingsPath = path$g.join(
|
|
3190
|
+
app$a.getPath("userData"),
|
|
3191
3191
|
appName$4,
|
|
3192
3192
|
configFilename$3,
|
|
3193
3193
|
);
|
|
@@ -3220,20 +3220,20 @@ const settingsController$4 = {
|
|
|
3220
3220
|
migrateDataDirectory: (win, oldPath, newPath) => {
|
|
3221
3221
|
try {
|
|
3222
3222
|
// Resolve paths to prevent traversal
|
|
3223
|
-
const resolvedOldPath = path$
|
|
3224
|
-
const resolvedNewPath = path$
|
|
3223
|
+
const resolvedOldPath = path$g.resolve(oldPath);
|
|
3224
|
+
const resolvedNewPath = path$g.resolve(newPath);
|
|
3225
3225
|
|
|
3226
3226
|
// Validate oldPath is the current configured data directory
|
|
3227
|
-
const settingsCheckPath = path$
|
|
3228
|
-
app$
|
|
3227
|
+
const settingsCheckPath = path$g.join(
|
|
3228
|
+
app$a.getPath("userData"),
|
|
3229
3229
|
appName$4,
|
|
3230
3230
|
configFilename$3,
|
|
3231
3231
|
);
|
|
3232
3232
|
const currentSettings = getFileContents$4(settingsCheckPath, {});
|
|
3233
3233
|
const currentDataDir =
|
|
3234
3234
|
currentSettings.userDataDirectory ||
|
|
3235
|
-
path$
|
|
3236
|
-
if (resolvedOldPath !== path$
|
|
3235
|
+
path$g.join(app$a.getPath("userData"), appName$4);
|
|
3236
|
+
if (resolvedOldPath !== path$g.resolve(currentDataDir)) {
|
|
3237
3237
|
throw new Error("Source path must be the current data directory");
|
|
3238
3238
|
}
|
|
3239
3239
|
|
|
@@ -3257,20 +3257,20 @@ const settingsController$4 = {
|
|
|
3257
3257
|
}
|
|
3258
3258
|
|
|
3259
3259
|
// Validate paths
|
|
3260
|
-
if (!fs$
|
|
3260
|
+
if (!fs$b.existsSync(resolvedOldPath)) {
|
|
3261
3261
|
throw new Error("Source directory does not exist");
|
|
3262
3262
|
}
|
|
3263
3263
|
|
|
3264
|
-
if (!fs$
|
|
3265
|
-
fs$
|
|
3264
|
+
if (!fs$b.existsSync(resolvedNewPath)) {
|
|
3265
|
+
fs$b.mkdirSync(resolvedNewPath, { recursive: true });
|
|
3266
3266
|
}
|
|
3267
3267
|
|
|
3268
3268
|
// Copy files
|
|
3269
3269
|
copyDirectory(resolvedOldPath, resolvedNewPath);
|
|
3270
3270
|
|
|
3271
3271
|
// Update settings to use new path
|
|
3272
|
-
const settingsPath = path$
|
|
3273
|
-
app$
|
|
3272
|
+
const settingsPath = path$g.join(
|
|
3273
|
+
app$a.getPath("userData"),
|
|
3274
3274
|
appName$4,
|
|
3275
3275
|
configFilename$3,
|
|
3276
3276
|
);
|
|
@@ -3934,8 +3934,8 @@ function requireProviderController () {
|
|
|
3934
3934
|
return providerController_1;
|
|
3935
3935
|
}
|
|
3936
3936
|
|
|
3937
|
-
const { app: app$
|
|
3938
|
-
const path$
|
|
3937
|
+
const { app: app$9 } = require$$0$1;
|
|
3938
|
+
const path$f = require$$1$2;
|
|
3939
3939
|
const { writeFileSync: writeFileSync$1 } = require$$0$2;
|
|
3940
3940
|
const events$4 = events$8;
|
|
3941
3941
|
const { getFileContents: getFileContents$3 } = file;
|
|
@@ -3955,8 +3955,8 @@ const layoutController$1 = {
|
|
|
3955
3955
|
saveLayoutForApplication: (win, appId, layoutObject) => {
|
|
3956
3956
|
try {
|
|
3957
3957
|
// filename to the pages file (live pages)
|
|
3958
|
-
const filename = path$
|
|
3959
|
-
app$
|
|
3958
|
+
const filename = path$f.join(
|
|
3959
|
+
app$9.getPath("userData"),
|
|
3960
3960
|
appName$3,
|
|
3961
3961
|
appId,
|
|
3962
3962
|
configFilename$2,
|
|
@@ -3988,8 +3988,8 @@ const layoutController$1 = {
|
|
|
3988
3988
|
*/
|
|
3989
3989
|
listLayoutsForApplication: (win, appId) => {
|
|
3990
3990
|
try {
|
|
3991
|
-
const filename = path$
|
|
3992
|
-
app$
|
|
3991
|
+
const filename = path$f.join(
|
|
3992
|
+
app$9.getPath("userData"),
|
|
3993
3993
|
appName$3,
|
|
3994
3994
|
appId,
|
|
3995
3995
|
configFilename$2,
|
|
@@ -21153,154 +21153,211 @@ let StreamableHTTPClientTransport$1 = class StreamableHTTPClientTransport {
|
|
|
21153
21153
|
streamableHttp$1.StreamableHTTPClientTransport = StreamableHTTPClientTransport$1;
|
|
21154
21154
|
|
|
21155
21155
|
/**
|
|
21156
|
-
*
|
|
21156
|
+
* grantedPermissions.js
|
|
21157
21157
|
*
|
|
21158
|
-
*
|
|
21159
|
-
* widget's
|
|
21158
|
+
* Stores the user's actual MCP permission grants per widget. This is the
|
|
21159
|
+
* Slice-2 enforcement source of truth — separate from the widget's declared
|
|
21160
|
+
* `dash.permissions.mcp` block (which is just a request).
|
|
21160
21161
|
*
|
|
21161
|
-
*
|
|
21162
|
+
* The runtime gate (permissionGate.gateToolCall) reads from here only.
|
|
21163
|
+
* A widget with a declared manifest but no grant entry has no access:
|
|
21164
|
+
* fail-closed. The user grants permissions at install time (consent modal)
|
|
21165
|
+
* or later in Settings → Privacy & Security.
|
|
21162
21166
|
*
|
|
21167
|
+
* Storage: userData/widgetMcpGrants.json. Atomic writes via tmp + rename.
|
|
21168
|
+
*
|
|
21169
|
+
* Shape on disk:
|
|
21163
21170
|
* {
|
|
21164
|
-
* "
|
|
21165
|
-
*
|
|
21166
|
-
*
|
|
21167
|
-
*
|
|
21168
|
-
* "
|
|
21169
|
-
*
|
|
21170
|
-
* "readPaths": ["~/Documents/notes"],
|
|
21171
|
-
* "writePaths": []
|
|
21172
|
-
* },
|
|
21173
|
-
* "github": {
|
|
21174
|
-
* "tools": ["search_repositories", "get_file_contents"]
|
|
21175
|
-
* }
|
|
21171
|
+
* "@trops/notes-summarizer": {
|
|
21172
|
+
* "servers": {
|
|
21173
|
+
* "filesystem": {
|
|
21174
|
+
* "tools": ["read_file"],
|
|
21175
|
+
* "readPaths": ["/Users/jane/Documents/notes"],
|
|
21176
|
+
* "writePaths": []
|
|
21176
21177
|
* }
|
|
21177
21178
|
* }
|
|
21178
21179
|
* }
|
|
21179
21180
|
* }
|
|
21180
21181
|
*
|
|
21181
|
-
*
|
|
21182
|
-
*
|
|
21183
|
-
*
|
|
21182
|
+
* Note: paths are stored as-is (already tilde-expanded by the manifest
|
|
21183
|
+
* parser before grants are written). Tests can re-expand via
|
|
21184
|
+
* widgetPermissions.expandHome if they store ~ literals.
|
|
21184
21185
|
*
|
|
21185
21186
|
* Public API:
|
|
21186
|
-
*
|
|
21187
|
-
*
|
|
21188
|
-
*
|
|
21189
|
-
*
|
|
21190
|
-
*
|
|
21191
|
-
*
|
|
21192
|
-
* Pure function — exposed for tests.
|
|
21193
|
-
*
|
|
21194
|
-
* clearCache() → void
|
|
21195
|
-
* Test-only. Drops the in-process cache so tests can re-read.
|
|
21187
|
+
* getGrant(widgetId) → grant | null
|
|
21188
|
+
* setGrant(widgetId, perms) → boolean
|
|
21189
|
+
* revokeGrant(widgetId) → boolean
|
|
21190
|
+
* revokeServer(widgetId, serverName) → boolean
|
|
21191
|
+
* listAllGrants() → [{ widgetId, granted }]
|
|
21192
|
+
* clearCache() → void // test-only
|
|
21196
21193
|
*/
|
|
21197
21194
|
|
|
21198
|
-
const fs$
|
|
21199
|
-
const path$
|
|
21200
|
-
const
|
|
21201
|
-
const { app: app$7 } = require$$0$1;
|
|
21195
|
+
const fs$a = require$$0$2;
|
|
21196
|
+
const path$e = require$$1$2;
|
|
21197
|
+
const { app: app$8 } = require$$0$1;
|
|
21202
21198
|
|
|
21203
|
-
|
|
21204
|
-
// lookup; invalidated when a widget is installed/uninstalled (the
|
|
21205
|
-
// install/uninstall paths call clearCache()).
|
|
21206
|
-
const _cache = new Map();
|
|
21199
|
+
const FILE_NAME = "widgetMcpGrants.json";
|
|
21207
21200
|
|
|
21208
|
-
|
|
21209
|
-
|
|
21210
|
-
|
|
21211
|
-
|
|
21212
|
-
function
|
|
21213
|
-
|
|
21214
|
-
|
|
21215
|
-
|
|
21216
|
-
|
|
21201
|
+
// In-process cache of the entire grants file. Lazily loaded; invalidated
|
|
21202
|
+
// on every write.
|
|
21203
|
+
let _cache$1 = null;
|
|
21204
|
+
|
|
21205
|
+
function grantsFilePath() {
|
|
21206
|
+
return path$e.join(app$8.getPath("userData"), FILE_NAME);
|
|
21207
|
+
}
|
|
21208
|
+
|
|
21209
|
+
function loadFromDisk() {
|
|
21210
|
+
const p = grantsFilePath();
|
|
21211
|
+
if (!fs$a.existsSync(p)) return {};
|
|
21212
|
+
try {
|
|
21213
|
+
const raw = fs$a.readFileSync(p, "utf8");
|
|
21214
|
+
const parsed = JSON.parse(raw);
|
|
21215
|
+
if (!parsed || typeof parsed !== "object") return {};
|
|
21216
|
+
return parsed;
|
|
21217
|
+
} catch (e) {
|
|
21218
|
+
console.warn("[grantedPermissions] failed to read " + p + ": " + e.message);
|
|
21219
|
+
return {};
|
|
21217
21220
|
}
|
|
21218
|
-
|
|
21221
|
+
}
|
|
21222
|
+
|
|
21223
|
+
function ensureCache() {
|
|
21224
|
+
if (_cache$1 === null) _cache$1 = loadFromDisk();
|
|
21225
|
+
return _cache$1;
|
|
21226
|
+
}
|
|
21227
|
+
|
|
21228
|
+
function writeToDisk(data) {
|
|
21229
|
+
const p = grantsFilePath();
|
|
21230
|
+
const tmp = p + ".tmp";
|
|
21231
|
+
// Ensure parent dir exists (userData should already, but be defensive
|
|
21232
|
+
// for first-launch / freshly-cleared profile cases).
|
|
21233
|
+
fs$a.mkdirSync(path$e.dirname(p), { recursive: true });
|
|
21234
|
+
fs$a.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf8");
|
|
21235
|
+
fs$a.renameSync(tmp, p);
|
|
21219
21236
|
}
|
|
21220
21237
|
|
|
21221
21238
|
/**
|
|
21222
|
-
*
|
|
21223
|
-
*
|
|
21239
|
+
* Sanitize a perms object before persisting. Drops unknown keys, coerces
|
|
21240
|
+
* arrays of strings, and silently ignores malformed servers. Mirrors the
|
|
21241
|
+
* shape produced by parseManifestPermissions so the gate reads either
|
|
21242
|
+
* declared or granted with the same code path.
|
|
21224
21243
|
*/
|
|
21225
|
-
function
|
|
21226
|
-
if (!
|
|
21227
|
-
const
|
|
21228
|
-
|
|
21244
|
+
function sanitizePerms(perms) {
|
|
21245
|
+
if (!perms || typeof perms !== "object") return null;
|
|
21246
|
+
const rawServers =
|
|
21247
|
+
perms.servers && typeof perms.servers === "object" ? perms.servers : null;
|
|
21248
|
+
if (!rawServers) return null;
|
|
21229
21249
|
|
|
21230
21250
|
const servers = {};
|
|
21231
|
-
for (const [
|
|
21251
|
+
for (const [name, raw] of Object.entries(rawServers)) {
|
|
21232
21252
|
if (!raw || typeof raw !== "object") continue;
|
|
21233
|
-
|
|
21234
|
-
|
|
21235
|
-
|
|
21236
|
-
|
|
21237
|
-
|
|
21238
|
-
|
|
21239
|
-
|
|
21240
|
-
|
|
21241
|
-
|
|
21242
|
-
|
|
21253
|
+
servers[name] = {
|
|
21254
|
+
tools: Array.isArray(raw.tools)
|
|
21255
|
+
? raw.tools.filter((t) => typeof t === "string")
|
|
21256
|
+
: [],
|
|
21257
|
+
readPaths: Array.isArray(raw.readPaths)
|
|
21258
|
+
? raw.readPaths.filter((p) => typeof p === "string")
|
|
21259
|
+
: [],
|
|
21260
|
+
writePaths: Array.isArray(raw.writePaths)
|
|
21261
|
+
? raw.writePaths.filter((p) => typeof p === "string")
|
|
21262
|
+
: [],
|
|
21263
|
+
};
|
|
21243
21264
|
}
|
|
21244
|
-
|
|
21245
21265
|
return { servers };
|
|
21246
21266
|
}
|
|
21247
21267
|
|
|
21248
|
-
|
|
21249
|
-
* Find a widget's installed package.json on disk. Widgets live under
|
|
21250
|
-
* userData/widgets/<scope>/<name>/ for scoped packages or
|
|
21251
|
-
* userData/widgets/<name>/ for unscoped. The widgetId is the npm
|
|
21252
|
-
* package name (e.g. "@trops/notes-summarizer" or "notes-summarizer").
|
|
21253
|
-
*/
|
|
21254
|
-
function resolveWidgetPackagePath(widgetId) {
|
|
21268
|
+
function getGrant$2(widgetId) {
|
|
21255
21269
|
if (typeof widgetId !== "string" || !widgetId) return null;
|
|
21256
|
-
const
|
|
21257
|
-
|
|
21258
|
-
const parts = widgetId.startsWith("@") ? widgetId.split("/") : [widgetId];
|
|
21259
|
-
return path$d.join(widgetsRoot, ...parts, "package.json");
|
|
21270
|
+
const all = ensureCache();
|
|
21271
|
+
return all[widgetId] || null;
|
|
21260
21272
|
}
|
|
21261
21273
|
|
|
21262
|
-
|
|
21263
|
-
|
|
21264
|
-
|
|
21265
|
-
|
|
21266
|
-
|
|
21267
|
-
|
|
21268
|
-
|
|
21269
|
-
|
|
21270
|
-
|
|
21271
|
-
|
|
21272
|
-
|
|
21273
|
-
|
|
21274
|
-
|
|
21275
|
-
|
|
21274
|
+
function setGrant$1(widgetId, perms) {
|
|
21275
|
+
if (typeof widgetId !== "string" || !widgetId) return false;
|
|
21276
|
+
const sanitized = sanitizePerms(perms);
|
|
21277
|
+
if (!sanitized) return false;
|
|
21278
|
+
const all = ensureCache();
|
|
21279
|
+
all[widgetId] = sanitized;
|
|
21280
|
+
try {
|
|
21281
|
+
writeToDisk(all);
|
|
21282
|
+
return true;
|
|
21283
|
+
} catch (e) {
|
|
21284
|
+
console.warn(
|
|
21285
|
+
"[grantedPermissions] failed to write grant for " +
|
|
21286
|
+
widgetId +
|
|
21287
|
+
": " +
|
|
21288
|
+
e.message,
|
|
21289
|
+
);
|
|
21290
|
+
// Roll back the cache entry so memory matches disk.
|
|
21291
|
+
_cache$1 = loadFromDisk();
|
|
21292
|
+
return false;
|
|
21276
21293
|
}
|
|
21294
|
+
}
|
|
21295
|
+
|
|
21296
|
+
function revokeGrant$1(widgetId) {
|
|
21297
|
+
if (typeof widgetId !== "string" || !widgetId) return false;
|
|
21298
|
+
const all = ensureCache();
|
|
21299
|
+
if (!Object.prototype.hasOwnProperty.call(all, widgetId)) return false;
|
|
21300
|
+
delete all[widgetId];
|
|
21277
21301
|
try {
|
|
21278
|
-
|
|
21279
|
-
|
|
21280
|
-
const perms = parseManifestPermissions(pkg);
|
|
21281
|
-
_cache.set(widgetId, perms);
|
|
21282
|
-
return perms;
|
|
21302
|
+
writeToDisk(all);
|
|
21303
|
+
return true;
|
|
21283
21304
|
} catch (e) {
|
|
21284
21305
|
console.warn(
|
|
21285
|
-
"[
|
|
21306
|
+
"[grantedPermissions] failed to revoke grant for " +
|
|
21286
21307
|
widgetId +
|
|
21287
21308
|
": " +
|
|
21288
21309
|
e.message,
|
|
21289
21310
|
);
|
|
21290
|
-
_cache
|
|
21291
|
-
return
|
|
21311
|
+
_cache$1 = loadFromDisk();
|
|
21312
|
+
return false;
|
|
21292
21313
|
}
|
|
21293
21314
|
}
|
|
21294
21315
|
|
|
21295
|
-
function
|
|
21296
|
-
|
|
21316
|
+
function revokeServer$1(widgetId, serverName) {
|
|
21317
|
+
if (typeof widgetId !== "string" || !widgetId) return false;
|
|
21318
|
+
if (typeof serverName !== "string" || !serverName) return false;
|
|
21319
|
+
const all = ensureCache();
|
|
21320
|
+
const widgetEntry = all[widgetId];
|
|
21321
|
+
if (!widgetEntry || !widgetEntry.servers) return false;
|
|
21322
|
+
if (!Object.prototype.hasOwnProperty.call(widgetEntry.servers, serverName))
|
|
21323
|
+
return false;
|
|
21324
|
+
delete widgetEntry.servers[serverName];
|
|
21325
|
+
try {
|
|
21326
|
+
writeToDisk(all);
|
|
21327
|
+
return true;
|
|
21328
|
+
} catch (e) {
|
|
21329
|
+
console.warn(
|
|
21330
|
+
"[grantedPermissions] failed to revoke server " +
|
|
21331
|
+
serverName +
|
|
21332
|
+
" for " +
|
|
21333
|
+
widgetId +
|
|
21334
|
+
": " +
|
|
21335
|
+
e.message,
|
|
21336
|
+
);
|
|
21337
|
+
_cache$1 = loadFromDisk();
|
|
21338
|
+
return false;
|
|
21339
|
+
}
|
|
21297
21340
|
}
|
|
21298
21341
|
|
|
21299
|
-
|
|
21300
|
-
|
|
21301
|
-
|
|
21302
|
-
|
|
21303
|
-
|
|
21342
|
+
function listAllGrants$1() {
|
|
21343
|
+
const all = ensureCache();
|
|
21344
|
+
return Object.entries(all).map(([widgetId, granted]) => ({
|
|
21345
|
+
widgetId,
|
|
21346
|
+
granted,
|
|
21347
|
+
}));
|
|
21348
|
+
}
|
|
21349
|
+
|
|
21350
|
+
function clearCache$1() {
|
|
21351
|
+
_cache$1 = null;
|
|
21352
|
+
}
|
|
21353
|
+
|
|
21354
|
+
var grantedPermissions = {
|
|
21355
|
+
getGrant: getGrant$2,
|
|
21356
|
+
setGrant: setGrant$1,
|
|
21357
|
+
revokeGrant: revokeGrant$1,
|
|
21358
|
+
revokeServer: revokeServer$1,
|
|
21359
|
+
listAllGrants: listAllGrants$1,
|
|
21360
|
+
clearCache: clearCache$1,
|
|
21304
21361
|
};
|
|
21305
21362
|
|
|
21306
21363
|
/**
|
|
@@ -21309,32 +21366,40 @@ var widgetPermissions = {
|
|
|
21309
21366
|
* Per-widget gating for MCP tool calls.
|
|
21310
21367
|
*
|
|
21311
21368
|
* When `gateToolCall` is invoked with a widget identity, server name,
|
|
21312
|
-
* tool name, and tool arguments, it consults the widget's
|
|
21313
|
-
*
|
|
21314
|
-
*
|
|
21369
|
+
* tool name, and tool arguments, it consults the widget's GRANTED
|
|
21370
|
+
* permissions (electron/mcp/grantedPermissions.js) and either permits
|
|
21371
|
+
* the call or returns a clear denial reason.
|
|
21372
|
+
*
|
|
21373
|
+
* **Granted vs declared (Slice 2):** the widget's package.json
|
|
21374
|
+
* `dash.permissions.mcp` block is the *request* — read by
|
|
21375
|
+
* widgetPermissions.js and shown to the user at install time. The
|
|
21376
|
+
* *grant* is what the user actually approved. The runtime gate reads
|
|
21377
|
+
* grants only. A widget with a declared manifest but no grant entry
|
|
21378
|
+
* has no MCP access — fail-closed. The user grants permissions via
|
|
21379
|
+
* the install consent modal or Settings → Privacy & Security.
|
|
21315
21380
|
*
|
|
21316
21381
|
* Two layers:
|
|
21317
21382
|
*
|
|
21318
|
-
* 1. **Tool-name allowlist** — the
|
|
21383
|
+
* 1. **Tool-name allowlist** — the granted `tools[]` array for the
|
|
21319
21384
|
* target server determines which tool names this widget may
|
|
21320
21385
|
* invoke. Anything outside the list is rejected.
|
|
21321
21386
|
*
|
|
21322
21387
|
* 2. **Path-argument containment** — for tools whose arguments
|
|
21323
21388
|
* include a path-shaped key (`path`, `uri`, `filepath`, `file`,
|
|
21324
21389
|
* `directory`), the supplied path is validated with safePath()
|
|
21325
|
-
* against the widget's
|
|
21390
|
+
* against the widget's granted `readPaths` or `writePaths` for
|
|
21326
21391
|
* the target server. The read/write distinction is heuristic
|
|
21327
21392
|
* based on the tool name (e.g. `write_file` is treated as a
|
|
21328
21393
|
* write).
|
|
21329
21394
|
*
|
|
21330
|
-
* This is the runtime enforcement layer. Install-time consent UI
|
|
21331
|
-
*
|
|
21332
|
-
*
|
|
21333
|
-
*
|
|
21334
|
-
*
|
|
21395
|
+
* This is the runtime enforcement layer. Install-time consent UI is
|
|
21396
|
+
* Slice 2. Per-dashboard MCP-server scope reconfiguration is Slice 3.
|
|
21397
|
+
* When the feature flag is OFF (default), this gate is bypassed
|
|
21398
|
+
* entirely; mcpController behaves as before. When ON, every callTool
|
|
21399
|
+
* dispatch goes through this gate.
|
|
21335
21400
|
*/
|
|
21336
21401
|
|
|
21337
|
-
const {
|
|
21402
|
+
const { getGrant: getGrant$1 } = grantedPermissions;
|
|
21338
21403
|
const { safePath: safePath$1 } = safePath_1;
|
|
21339
21404
|
|
|
21340
21405
|
// Argument keys that look like paths. Different MCP servers use
|
|
@@ -21365,18 +21430,18 @@ function gateToolCall$1({ widgetId, serverName, toolName, args }) {
|
|
|
21365
21430
|
};
|
|
21366
21431
|
}
|
|
21367
21432
|
|
|
21368
|
-
const perms =
|
|
21433
|
+
const perms = getGrant$1(widgetId);
|
|
21369
21434
|
if (!perms) {
|
|
21370
21435
|
return {
|
|
21371
21436
|
allow: false,
|
|
21372
21437
|
reason:
|
|
21373
21438
|
"widget '" +
|
|
21374
21439
|
widgetId +
|
|
21375
|
-
"' has no MCP
|
|
21440
|
+
"' has no MCP permissions granted; user must approve at install time or in Settings → Privacy & Security",
|
|
21376
21441
|
};
|
|
21377
21442
|
}
|
|
21378
21443
|
|
|
21379
|
-
const serverPerms = perms.servers[serverName];
|
|
21444
|
+
const serverPerms = perms.servers && perms.servers[serverName];
|
|
21380
21445
|
if (!serverPerms) {
|
|
21381
21446
|
return {
|
|
21382
21447
|
allow: false,
|
|
@@ -21457,6 +21522,69 @@ var permissionGate = {
|
|
|
21457
21522
|
PATH_ARG_KEYS,
|
|
21458
21523
|
};
|
|
21459
21524
|
|
|
21525
|
+
/**
|
|
21526
|
+
* mcpServerKey.js
|
|
21527
|
+
*
|
|
21528
|
+
* Slice 3a: per-workspace MCP server process isolation.
|
|
21529
|
+
*
|
|
21530
|
+
* `mcpController.activeServers` was historically keyed by `serverName`
|
|
21531
|
+
* alone, so two workspaces using the same MCP server type (e.g. both
|
|
21532
|
+
* using `filesystem`) shared one process. Slice 3a keys by the
|
|
21533
|
+
* compound `(workspaceId, serverName)` so each workspace gets its
|
|
21534
|
+
* own process. Slice 3b will configure each process with the union
|
|
21535
|
+
* of grants from widgets on that workspace; for now, processes are
|
|
21536
|
+
* simply isolated.
|
|
21537
|
+
*
|
|
21538
|
+
* Format: `<workspaceId>::<serverName>`. The `::` separator is
|
|
21539
|
+
* unlikely to appear in a UUID-shaped workspace id; serverName is
|
|
21540
|
+
* everything after the first `::` so server names containing `::`
|
|
21541
|
+
* round-trip cleanly.
|
|
21542
|
+
*
|
|
21543
|
+
* Callers without a workspace context (legacy IPC, dash MCP server
|
|
21544
|
+
* tools, AI Builder previews) supply `null`/`undefined` and land on
|
|
21545
|
+
* the `NO_WORKSPACE` sentinel bucket — that's the pre-Slice-3 bucket.
|
|
21546
|
+
*
|
|
21547
|
+
* NOTE: workspaceId is renderer-supplied. Slice 3a uses it only as a
|
|
21548
|
+
* process-isolation key, NOT as a trust boundary. Slice 3b will tie
|
|
21549
|
+
* server scope (e.g. filesystem `--allowed` paths) to it; that's when
|
|
21550
|
+
* the trust boundary appears.
|
|
21551
|
+
*/
|
|
21552
|
+
|
|
21553
|
+
const NO_WORKSPACE = "__no_workspace__";
|
|
21554
|
+
const SEP = "::";
|
|
21555
|
+
|
|
21556
|
+
function serverKey$1(workspaceId, serverName) {
|
|
21557
|
+
if (typeof serverName !== "string" || !serverName) {
|
|
21558
|
+
throw new Error("serverKey: serverName is required");
|
|
21559
|
+
}
|
|
21560
|
+
const wid =
|
|
21561
|
+
typeof workspaceId === "string" && workspaceId ? workspaceId : NO_WORKSPACE;
|
|
21562
|
+
return wid + SEP + serverName;
|
|
21563
|
+
}
|
|
21564
|
+
|
|
21565
|
+
function parseServerKey$1(key) {
|
|
21566
|
+
if (typeof key !== "string") {
|
|
21567
|
+
throw new Error("parseServerKey: key must be a string");
|
|
21568
|
+
}
|
|
21569
|
+
const idx = key.indexOf(SEP);
|
|
21570
|
+
if (idx < 0) {
|
|
21571
|
+
throw new Error(
|
|
21572
|
+
"parseServerKey: malformed key (no '::' separator): " + key,
|
|
21573
|
+
);
|
|
21574
|
+
}
|
|
21575
|
+
return {
|
|
21576
|
+
workspaceId: key.slice(0, idx),
|
|
21577
|
+
serverName: key.slice(idx + SEP.length),
|
|
21578
|
+
};
|
|
21579
|
+
}
|
|
21580
|
+
|
|
21581
|
+
var mcpServerKey = {
|
|
21582
|
+
serverKey: serverKey$1,
|
|
21583
|
+
parseServerKey: parseServerKey$1,
|
|
21584
|
+
NO_WORKSPACE,
|
|
21585
|
+
SEP,
|
|
21586
|
+
};
|
|
21587
|
+
|
|
21460
21588
|
/**
|
|
21461
21589
|
* mcpController.js
|
|
21462
21590
|
*
|
|
@@ -21477,12 +21605,13 @@ const {
|
|
|
21477
21605
|
const {
|
|
21478
21606
|
StreamableHTTPClientTransport,
|
|
21479
21607
|
} = streamableHttp$1;
|
|
21480
|
-
const path$
|
|
21481
|
-
const fs$
|
|
21482
|
-
const os$
|
|
21608
|
+
const path$d = require$$1$2;
|
|
21609
|
+
const fs$9 = require$$0$2;
|
|
21610
|
+
const os$2 = require$$2$1;
|
|
21483
21611
|
const responseCache$2 = responseCache_1;
|
|
21484
21612
|
const { gateToolCall } = permissionGate;
|
|
21485
|
-
const {
|
|
21613
|
+
const { serverKey, parseServerKey } = mcpServerKey;
|
|
21614
|
+
const { app: app$7 } = require$$0$1;
|
|
21486
21615
|
|
|
21487
21616
|
// Read the widget-MCP-enforcement feature flag from settings.json.
|
|
21488
21617
|
// Default is OFF — flipping ON activates per-widget gating in
|
|
@@ -21490,13 +21619,13 @@ const { app: app$6 } = require$$0$1;
|
|
|
21490
21619
|
// and electron/mcp/permissionGate.js for context.
|
|
21491
21620
|
function isWidgetPermissionEnforcementEnabled() {
|
|
21492
21621
|
try {
|
|
21493
|
-
const settingsPath = path$
|
|
21494
|
-
app$
|
|
21622
|
+
const settingsPath = path$d.join(
|
|
21623
|
+
app$7.getPath("userData"),
|
|
21495
21624
|
"Dashboard",
|
|
21496
21625
|
"settings.json",
|
|
21497
21626
|
);
|
|
21498
|
-
if (!fs$
|
|
21499
|
-
const raw = fs$
|
|
21627
|
+
if (!fs$9.existsSync(settingsPath)) return false;
|
|
21628
|
+
const raw = fs$9.readFileSync(settingsPath, "utf8");
|
|
21500
21629
|
const settings = JSON.parse(raw);
|
|
21501
21630
|
return Boolean(settings?.security?.enforceWidgetMcpPermissions);
|
|
21502
21631
|
} catch (_e) {
|
|
@@ -21638,18 +21767,18 @@ function getShellPath$1() {
|
|
|
21638
21767
|
return _shellPath$1;
|
|
21639
21768
|
}
|
|
21640
21769
|
|
|
21641
|
-
const { execSync } = require$$
|
|
21770
|
+
const { execSync } = require$$10;
|
|
21642
21771
|
const fallbackDirs = ["/usr/local/bin", "/opt/homebrew/bin"];
|
|
21643
21772
|
|
|
21644
21773
|
// Scan nvm versions, tracking both latest and best compatible version
|
|
21645
|
-
const home = os$
|
|
21774
|
+
const home = os$2.homedir();
|
|
21646
21775
|
let compatibleNvmBin = null;
|
|
21647
21776
|
if (home) {
|
|
21648
21777
|
fallbackDirs.push(`${home}/.volta/bin`);
|
|
21649
21778
|
fallbackDirs.push(`${home}/.nodenv/shims`);
|
|
21650
21779
|
try {
|
|
21651
21780
|
const nvmDir = `${home}/.nvm/versions/node`;
|
|
21652
|
-
const versions = fs$
|
|
21781
|
+
const versions = fs$9.readdirSync(nvmDir).sort();
|
|
21653
21782
|
if (versions.length > 0) {
|
|
21654
21783
|
// Find the highest compatible version (v18/v20/v22)
|
|
21655
21784
|
for (let i = versions.length - 1; i >= 0; i--) {
|
|
@@ -21781,19 +21910,19 @@ function interpolate$1(template, credentials) {
|
|
|
21781
21910
|
* @param {object} tokenRefresh { credentialsPath, oauthKeysPath }
|
|
21782
21911
|
*/
|
|
21783
21912
|
async function refreshGoogleOAuthToken(tokenRefresh) {
|
|
21784
|
-
const home = os$
|
|
21913
|
+
const home = os$2.homedir();
|
|
21785
21914
|
const credPath = tokenRefresh.credentialsPath.replace(/^~/, home);
|
|
21786
21915
|
const keysPath = tokenRefresh.oauthKeysPath.replace(/^~/, home);
|
|
21787
21916
|
|
|
21788
|
-
if (!fs$
|
|
21917
|
+
if (!fs$9.existsSync(credPath) || !fs$9.existsSync(keysPath)) {
|
|
21789
21918
|
console.log(
|
|
21790
21919
|
"[mcpController] Token refresh skipped: credential files not found",
|
|
21791
21920
|
);
|
|
21792
21921
|
return;
|
|
21793
21922
|
}
|
|
21794
21923
|
|
|
21795
|
-
const credentials = JSON.parse(fs$
|
|
21796
|
-
const keysFile = JSON.parse(fs$
|
|
21924
|
+
const credentials = JSON.parse(fs$9.readFileSync(credPath, "utf8"));
|
|
21925
|
+
const keysFile = JSON.parse(fs$9.readFileSync(keysPath, "utf8"));
|
|
21797
21926
|
const keyData = keysFile.installed || keysFile.web;
|
|
21798
21927
|
|
|
21799
21928
|
if (
|
|
@@ -21862,7 +21991,7 @@ async function refreshGoogleOAuthToken(tokenRefresh) {
|
|
|
21862
21991
|
credentials.refresh_token = body.refresh_token;
|
|
21863
21992
|
}
|
|
21864
21993
|
|
|
21865
|
-
fs$
|
|
21994
|
+
fs$9.writeFileSync(credPath, JSON.stringify(credentials, null, 2));
|
|
21866
21995
|
console.log("[mcpController] Google OAuth token refreshed successfully");
|
|
21867
21996
|
}
|
|
21868
21997
|
|
|
@@ -21871,17 +22000,24 @@ const mcpController$3 = {
|
|
|
21871
22000
|
* startServer
|
|
21872
22001
|
* Start an MCP server with the given config and credentials
|
|
21873
22002
|
*
|
|
22003
|
+
* Slice 3a: server instances are keyed by `(workspaceId, serverName)`
|
|
22004
|
+
* so two workspaces using the same server type get separate processes.
|
|
22005
|
+
* Pass `null`/`undefined` workspaceId to land on the legacy
|
|
22006
|
+
* NO_WORKSPACE bucket (e.g. dash MCP server tools, AI Builder previews).
|
|
22007
|
+
*
|
|
21874
22008
|
* @param {BrowserWindow} win the main window
|
|
21875
22009
|
* @param {string} serverName unique name for this server instance
|
|
21876
22010
|
* @param {object} mcpConfig { transport, command, args, envMapping }
|
|
21877
22011
|
* @param {object} credentials decrypted credentials object
|
|
22012
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
21878
22013
|
* @returns {{ success, serverName, tools, status } | { error, message }}
|
|
21879
22014
|
*/
|
|
21880
|
-
startServer: async (win, serverName, mcpConfig, credentials) => {
|
|
22015
|
+
startServer: async (win, serverName, mcpConfig, credentials, workspaceId) => {
|
|
22016
|
+
const key = serverKey(workspaceId, serverName);
|
|
21881
22017
|
// 1. Already connected? Return existing connection
|
|
21882
|
-
const existing = activeServers.get(
|
|
22018
|
+
const existing = activeServers.get(key);
|
|
21883
22019
|
if (existing && existing.status === STATUS$1.CONNECTED && existing.client) {
|
|
21884
|
-
console.log(`[mcpController] Server already connected: ${
|
|
22020
|
+
console.log(`[mcpController] Server already connected: ${key}`);
|
|
21885
22021
|
return {
|
|
21886
22022
|
success: true,
|
|
21887
22023
|
serverName,
|
|
@@ -21892,19 +22028,19 @@ const mcpController$3 = {
|
|
|
21892
22028
|
}
|
|
21893
22029
|
|
|
21894
22030
|
// 2. Already starting? Piggyback on the pending promise
|
|
21895
|
-
if (pendingStarts.has(
|
|
22031
|
+
if (pendingStarts.has(key)) {
|
|
21896
22032
|
console.log(
|
|
21897
|
-
`[mcpController] Server already starting, deduplicating: ${
|
|
22033
|
+
`[mcpController] Server already starting, deduplicating: ${key}`,
|
|
21898
22034
|
);
|
|
21899
|
-
return pendingStarts.get(
|
|
22035
|
+
return pendingStarts.get(key);
|
|
21900
22036
|
}
|
|
21901
22037
|
|
|
21902
22038
|
// 3. Fresh start — wrap in a promise and track it
|
|
21903
22039
|
const startPromise = (async () => {
|
|
21904
22040
|
try {
|
|
21905
22041
|
// Stop if in stale/error state
|
|
21906
|
-
if (activeServers.has(
|
|
21907
|
-
await mcpController$3.stopServer(win, serverName);
|
|
22042
|
+
if (activeServers.has(key)) {
|
|
22043
|
+
await mcpController$3.stopServer(win, serverName, workspaceId);
|
|
21908
22044
|
}
|
|
21909
22045
|
|
|
21910
22046
|
// Merge with catalog entry to pick up updated command/args
|
|
@@ -21977,7 +22113,7 @@ const mcpController$3 = {
|
|
|
21977
22113
|
// Merge static env vars from mcpConfig (with ~ expansion)
|
|
21978
22114
|
if (mcpConfig.staticEnv) {
|
|
21979
22115
|
Object.entries(mcpConfig.staticEnv).forEach(([envVar, value]) => {
|
|
21980
|
-
env[envVar] = value.replace(/^~/, os$
|
|
22116
|
+
env[envVar] = value.replace(/^~/, os$2.homedir());
|
|
21981
22117
|
});
|
|
21982
22118
|
}
|
|
21983
22119
|
|
|
@@ -22016,7 +22152,7 @@ const mcpController$3 = {
|
|
|
22016
22152
|
}
|
|
22017
22153
|
|
|
22018
22154
|
// Interpolate {{MCP_DIR}} in args to resolve local MCP server scripts
|
|
22019
|
-
const mcpDir = path$
|
|
22155
|
+
const mcpDir = path$d.join(__dirname, "..", "mcp");
|
|
22020
22156
|
for (let i = 0; i < args.length; i++) {
|
|
22021
22157
|
if (
|
|
22022
22158
|
typeof args[i] === "string" &&
|
|
@@ -22035,12 +22171,14 @@ const mcpController$3 = {
|
|
|
22035
22171
|
}
|
|
22036
22172
|
|
|
22037
22173
|
// Update status to connecting
|
|
22038
|
-
activeServers.set(
|
|
22174
|
+
activeServers.set(key, {
|
|
22039
22175
|
client: null,
|
|
22040
22176
|
transport,
|
|
22041
22177
|
tools: [],
|
|
22042
22178
|
resources: [],
|
|
22043
22179
|
status: STATUS$1.CONNECTING,
|
|
22180
|
+
workspaceId: workspaceId || null,
|
|
22181
|
+
serverName,
|
|
22044
22182
|
});
|
|
22045
22183
|
|
|
22046
22184
|
// Create MCP client
|
|
@@ -22074,16 +22212,18 @@ const mcpController$3 = {
|
|
|
22074
22212
|
}
|
|
22075
22213
|
|
|
22076
22214
|
// Store the active connection
|
|
22077
|
-
activeServers.set(
|
|
22215
|
+
activeServers.set(key, {
|
|
22078
22216
|
client,
|
|
22079
22217
|
transport,
|
|
22080
22218
|
tools,
|
|
22081
22219
|
resources,
|
|
22082
22220
|
status: STATUS$1.CONNECTED,
|
|
22221
|
+
workspaceId: workspaceId || null,
|
|
22222
|
+
serverName,
|
|
22083
22223
|
});
|
|
22084
22224
|
|
|
22085
22225
|
console.log(
|
|
22086
|
-
`[mcpController] Server connected: ${
|
|
22226
|
+
`[mcpController] Server connected: ${key} (${tools.length} tools, ${resources.length} resources)`,
|
|
22087
22227
|
);
|
|
22088
22228
|
|
|
22089
22229
|
return {
|
|
@@ -22108,13 +22248,15 @@ const mcpController$3 = {
|
|
|
22108
22248
|
}
|
|
22109
22249
|
|
|
22110
22250
|
// Mark as error state
|
|
22111
|
-
activeServers.set(
|
|
22251
|
+
activeServers.set(key, {
|
|
22112
22252
|
client: null,
|
|
22113
22253
|
transport: null,
|
|
22114
22254
|
tools: [],
|
|
22115
22255
|
resources: [],
|
|
22116
22256
|
status: STATUS$1.ERROR,
|
|
22117
22257
|
error: errorMessage,
|
|
22258
|
+
workspaceId: workspaceId || null,
|
|
22259
|
+
serverName,
|
|
22118
22260
|
});
|
|
22119
22261
|
|
|
22120
22262
|
return {
|
|
@@ -22124,11 +22266,11 @@ const mcpController$3 = {
|
|
|
22124
22266
|
status: STATUS$1.ERROR,
|
|
22125
22267
|
};
|
|
22126
22268
|
} finally {
|
|
22127
|
-
pendingStarts.delete(
|
|
22269
|
+
pendingStarts.delete(key);
|
|
22128
22270
|
}
|
|
22129
22271
|
})();
|
|
22130
22272
|
|
|
22131
|
-
pendingStarts.set(
|
|
22273
|
+
pendingStarts.set(key, startPromise);
|
|
22132
22274
|
return startPromise;
|
|
22133
22275
|
},
|
|
22134
22276
|
|
|
@@ -22138,20 +22280,22 @@ const mcpController$3 = {
|
|
|
22138
22280
|
*
|
|
22139
22281
|
* @param {BrowserWindow} win the main window
|
|
22140
22282
|
* @param {string} serverName the server to stop
|
|
22283
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
22141
22284
|
* @returns {{ success, serverName } | { error, message }}
|
|
22142
22285
|
*/
|
|
22143
|
-
stopServer: async (win, serverName) => {
|
|
22286
|
+
stopServer: async (win, serverName, workspaceId) => {
|
|
22287
|
+
const key = serverKey(workspaceId, serverName);
|
|
22144
22288
|
try {
|
|
22145
22289
|
// Wait for any in-flight start to finish before stopping
|
|
22146
|
-
if (pendingStarts.has(
|
|
22290
|
+
if (pendingStarts.has(key)) {
|
|
22147
22291
|
try {
|
|
22148
|
-
await pendingStarts.get(
|
|
22292
|
+
await pendingStarts.get(key);
|
|
22149
22293
|
} catch (e) {
|
|
22150
22294
|
/* stopping anyway */
|
|
22151
22295
|
}
|
|
22152
22296
|
}
|
|
22153
22297
|
|
|
22154
|
-
const server = activeServers.get(
|
|
22298
|
+
const server = activeServers.get(key);
|
|
22155
22299
|
if (!server) {
|
|
22156
22300
|
return {
|
|
22157
22301
|
success: true,
|
|
@@ -22160,7 +22304,7 @@ const mcpController$3 = {
|
|
|
22160
22304
|
};
|
|
22161
22305
|
}
|
|
22162
22306
|
|
|
22163
|
-
console.log(`[mcpController] Stopping server: ${
|
|
22307
|
+
console.log(`[mcpController] Stopping server: ${key}`);
|
|
22164
22308
|
|
|
22165
22309
|
// Close the client connection
|
|
22166
22310
|
if (server.client) {
|
|
@@ -22168,27 +22312,24 @@ const mcpController$3 = {
|
|
|
22168
22312
|
await server.client.close();
|
|
22169
22313
|
} catch (closeError) {
|
|
22170
22314
|
console.warn(
|
|
22171
|
-
`[mcpController] Error closing client for ${
|
|
22315
|
+
`[mcpController] Error closing client for ${key}:`,
|
|
22172
22316
|
closeError.message,
|
|
22173
22317
|
);
|
|
22174
22318
|
}
|
|
22175
22319
|
}
|
|
22176
22320
|
|
|
22177
|
-
activeServers.delete(
|
|
22321
|
+
activeServers.delete(key);
|
|
22178
22322
|
|
|
22179
|
-
console.log(`[mcpController] Server stopped: ${
|
|
22323
|
+
console.log(`[mcpController] Server stopped: ${key}`);
|
|
22180
22324
|
|
|
22181
22325
|
return {
|
|
22182
22326
|
success: true,
|
|
22183
22327
|
serverName,
|
|
22184
22328
|
};
|
|
22185
22329
|
} catch (error) {
|
|
22186
|
-
console.error(
|
|
22187
|
-
`[mcpController] Error stopping server ${serverName}:`,
|
|
22188
|
-
error,
|
|
22189
|
-
);
|
|
22330
|
+
console.error(`[mcpController] Error stopping server ${key}:`, error);
|
|
22190
22331
|
// Clean up anyway
|
|
22191
|
-
activeServers.delete(
|
|
22332
|
+
activeServers.delete(key);
|
|
22192
22333
|
return {
|
|
22193
22334
|
error: true,
|
|
22194
22335
|
message: error.message,
|
|
@@ -22205,6 +22346,10 @@ const mcpController$3 = {
|
|
|
22205
22346
|
* @param {string} toolName the tool to call
|
|
22206
22347
|
* @param {object} args arguments for the tool
|
|
22207
22348
|
* @param {Array<string>} allowedTools optional whitelist of allowed tool names
|
|
22349
|
+
* @param {string|null} widgetId the widget originating the call (Slice 1+2)
|
|
22350
|
+
* @param {string|null} workspaceId the active workspace (Slice 3a) — used
|
|
22351
|
+
* to scope the server process per workspace.
|
|
22352
|
+
* Slice 3b will tie path scope to this id.
|
|
22208
22353
|
* @returns {{ result } | { error, message }}
|
|
22209
22354
|
*/
|
|
22210
22355
|
callTool: async (
|
|
@@ -22214,11 +22359,13 @@ const mcpController$3 = {
|
|
|
22214
22359
|
args,
|
|
22215
22360
|
allowedTools = null,
|
|
22216
22361
|
widgetId = null,
|
|
22362
|
+
workspaceId = null,
|
|
22217
22363
|
) => {
|
|
22364
|
+
const key = serverKey(workspaceId, serverName);
|
|
22218
22365
|
try {
|
|
22219
|
-
const server = activeServers.get(
|
|
22366
|
+
const server = activeServers.get(key);
|
|
22220
22367
|
if (!server || !server.client) {
|
|
22221
|
-
throw new Error(`Server not connected: ${
|
|
22368
|
+
throw new Error(`Server not connected: ${key}`);
|
|
22222
22369
|
}
|
|
22223
22370
|
|
|
22224
22371
|
// Per-widget manifest gate. Activated by the
|
|
@@ -22251,7 +22398,7 @@ const mcpController$3 = {
|
|
|
22251
22398
|
}
|
|
22252
22399
|
|
|
22253
22400
|
const doCall = async () => {
|
|
22254
|
-
console.log(`[mcpController] Calling tool: ${
|
|
22401
|
+
console.log(`[mcpController] Calling tool: ${key}/${toolName}`);
|
|
22255
22402
|
const result = await server.client.callTool({
|
|
22256
22403
|
name: toolName,
|
|
22257
22404
|
arguments: args || {},
|
|
@@ -22262,18 +22409,21 @@ const mcpController$3 = {
|
|
|
22262
22409
|
};
|
|
22263
22410
|
};
|
|
22264
22411
|
|
|
22265
|
-
// Cache read-only tool calls with in-flight dedup.
|
|
22266
|
-
//
|
|
22412
|
+
// Cache read-only tool calls with in-flight dedup. Cache key is
|
|
22413
|
+
// scoped per (workspace, server, tool, args) so two workspaces
|
|
22414
|
+
// calling the same read on the same server type don't share a
|
|
22415
|
+
// cached response — they're separate processes with potentially
|
|
22416
|
+
// different scopes (Slice 3b will make that explicit).
|
|
22267
22417
|
if (isReadOnlyTool(toolName)) {
|
|
22268
|
-
const
|
|
22269
|
-
return responseCache$2.get(
|
|
22418
|
+
const cacheKey = `mcp:${key}:${toolName}:${JSON.stringify(args || {})}`;
|
|
22419
|
+
return responseCache$2.get(cacheKey, doCall, {
|
|
22270
22420
|
ttl: DEFAULT_TOOL_CACHE_TTL,
|
|
22271
22421
|
});
|
|
22272
22422
|
}
|
|
22273
22423
|
|
|
22274
22424
|
// Write/mutation: invalidate any cached reads for this server
|
|
22275
22425
|
// (safest default — broad invalidation when state changes)
|
|
22276
|
-
responseCache$2.invalidatePrefix(`mcp:${
|
|
22426
|
+
responseCache$2.invalidatePrefix(`mcp:${key}:`);
|
|
22277
22427
|
return doCall();
|
|
22278
22428
|
} catch (error) {
|
|
22279
22429
|
console.error(
|
|
@@ -22293,13 +22443,15 @@ const mcpController$3 = {
|
|
|
22293
22443
|
*
|
|
22294
22444
|
* @param {BrowserWindow} win the main window
|
|
22295
22445
|
* @param {string} serverName the server name
|
|
22446
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
22296
22447
|
* @returns {{ tools } | { error, message }}
|
|
22297
22448
|
*/
|
|
22298
|
-
listTools: async (win, serverName) => {
|
|
22449
|
+
listTools: async (win, serverName, workspaceId) => {
|
|
22450
|
+
const key = serverKey(workspaceId, serverName);
|
|
22299
22451
|
try {
|
|
22300
|
-
const server = activeServers.get(
|
|
22452
|
+
const server = activeServers.get(key);
|
|
22301
22453
|
if (!server || !server.client) {
|
|
22302
|
-
throw new Error(`Server not connected: ${
|
|
22454
|
+
throw new Error(`Server not connected: ${key}`);
|
|
22303
22455
|
}
|
|
22304
22456
|
|
|
22305
22457
|
// Refresh tool list from server
|
|
@@ -22331,13 +22483,15 @@ const mcpController$3 = {
|
|
|
22331
22483
|
*
|
|
22332
22484
|
* @param {BrowserWindow} win the main window
|
|
22333
22485
|
* @param {string} serverName the server name
|
|
22486
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
22334
22487
|
* @returns {{ resources } | { error, message }}
|
|
22335
22488
|
*/
|
|
22336
|
-
listResources: async (win, serverName) => {
|
|
22489
|
+
listResources: async (win, serverName, workspaceId) => {
|
|
22490
|
+
const key = serverKey(workspaceId, serverName);
|
|
22337
22491
|
try {
|
|
22338
|
-
const server = activeServers.get(
|
|
22492
|
+
const server = activeServers.get(key);
|
|
22339
22493
|
if (!server || !server.client) {
|
|
22340
|
-
throw new Error(`Server not connected: ${
|
|
22494
|
+
throw new Error(`Server not connected: ${key}`);
|
|
22341
22495
|
}
|
|
22342
22496
|
|
|
22343
22497
|
const resourcesResult = await server.client.listResources();
|
|
@@ -22369,13 +22523,15 @@ const mcpController$3 = {
|
|
|
22369
22523
|
* @param {BrowserWindow} win the main window
|
|
22370
22524
|
* @param {string} serverName the server name
|
|
22371
22525
|
* @param {string} uri the resource URI
|
|
22526
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
22372
22527
|
* @returns {{ resource } | { error, message }}
|
|
22373
22528
|
*/
|
|
22374
|
-
readResource: async (win, serverName, uri) => {
|
|
22529
|
+
readResource: async (win, serverName, uri, workspaceId) => {
|
|
22530
|
+
const key = serverKey(workspaceId, serverName);
|
|
22375
22531
|
try {
|
|
22376
|
-
const server = activeServers.get(
|
|
22532
|
+
const server = activeServers.get(key);
|
|
22377
22533
|
if (!server || !server.client) {
|
|
22378
|
-
throw new Error(`Server not connected: ${
|
|
22534
|
+
throw new Error(`Server not connected: ${key}`);
|
|
22379
22535
|
}
|
|
22380
22536
|
|
|
22381
22537
|
const result = await server.client.readResource({ uri });
|
|
@@ -22404,8 +22560,9 @@ const mcpController$3 = {
|
|
|
22404
22560
|
* @param {string} serverName the server name
|
|
22405
22561
|
* @returns {{ status, tools, error }}
|
|
22406
22562
|
*/
|
|
22407
|
-
getServerStatus: (win, serverName) => {
|
|
22408
|
-
const
|
|
22563
|
+
getServerStatus: (win, serverName, workspaceId) => {
|
|
22564
|
+
const key = serverKey(workspaceId, serverName);
|
|
22565
|
+
const server = activeServers.get(key);
|
|
22409
22566
|
if (!server) {
|
|
22410
22567
|
return {
|
|
22411
22568
|
serverName,
|
|
@@ -22433,20 +22590,20 @@ const mcpController$3 = {
|
|
|
22433
22590
|
*/
|
|
22434
22591
|
getCatalog: (win) => {
|
|
22435
22592
|
try {
|
|
22436
|
-
const catalogPath = path$
|
|
22593
|
+
const catalogPath = path$d.join(
|
|
22437
22594
|
__dirname,
|
|
22438
22595
|
"..",
|
|
22439
22596
|
"mcp",
|
|
22440
22597
|
"mcpServerCatalog.json",
|
|
22441
22598
|
);
|
|
22442
22599
|
|
|
22443
|
-
if (!fs$
|
|
22600
|
+
if (!fs$9.existsSync(catalogPath)) {
|
|
22444
22601
|
return {
|
|
22445
22602
|
catalog: [],
|
|
22446
22603
|
};
|
|
22447
22604
|
}
|
|
22448
22605
|
|
|
22449
|
-
const catalogData = fs$
|
|
22606
|
+
const catalogData = fs$9.readFileSync(catalogPath, "utf8");
|
|
22450
22607
|
const catalog = JSON.parse(catalogData);
|
|
22451
22608
|
|
|
22452
22609
|
return {
|
|
@@ -22474,18 +22631,18 @@ const mcpController$3 = {
|
|
|
22474
22631
|
*/
|
|
22475
22632
|
getKnownExternalCatalog: () => {
|
|
22476
22633
|
try {
|
|
22477
|
-
const catalogPath = path$
|
|
22634
|
+
const catalogPath = path$d.join(
|
|
22478
22635
|
__dirname,
|
|
22479
22636
|
"..",
|
|
22480
22637
|
"mcp",
|
|
22481
22638
|
"knownExternalMcpServers.json",
|
|
22482
22639
|
);
|
|
22483
22640
|
|
|
22484
|
-
if (!fs$
|
|
22641
|
+
if (!fs$9.existsSync(catalogPath)) {
|
|
22485
22642
|
return { success: true, servers: [] };
|
|
22486
22643
|
}
|
|
22487
22644
|
|
|
22488
|
-
const catalogData = fs$
|
|
22645
|
+
const catalogData = fs$9.readFileSync(catalogPath, "utf8");
|
|
22489
22646
|
const catalog = JSON.parse(catalogData);
|
|
22490
22647
|
|
|
22491
22648
|
return {
|
|
@@ -22538,7 +22695,7 @@ const mcpController$3 = {
|
|
|
22538
22695
|
* @returns {{ success } | { error, message }}
|
|
22539
22696
|
*/
|
|
22540
22697
|
runAuth: async (win, mcpConfig, credentials, authCommand) => {
|
|
22541
|
-
const { spawn } = require$$
|
|
22698
|
+
const { spawn } = require$$10;
|
|
22542
22699
|
|
|
22543
22700
|
const env = cleanEnvForChildProcess();
|
|
22544
22701
|
|
|
@@ -22547,11 +22704,11 @@ const mcpController$3 = {
|
|
|
22547
22704
|
const { from, to } = authCommand.setup.copyCredential;
|
|
22548
22705
|
const sourcePath = credentials?.[from];
|
|
22549
22706
|
if (sourcePath) {
|
|
22550
|
-
const destPath = to.replace(/^~/, os$
|
|
22707
|
+
const destPath = to.replace(/^~/, os$2.homedir());
|
|
22551
22708
|
const destDir = require$$1$2.dirname(destPath);
|
|
22552
22709
|
try {
|
|
22553
|
-
fs$
|
|
22554
|
-
fs$
|
|
22710
|
+
fs$9.mkdirSync(destDir, { recursive: true });
|
|
22711
|
+
fs$9.copyFileSync(sourcePath, destPath);
|
|
22555
22712
|
} catch (err) {
|
|
22556
22713
|
return {
|
|
22557
22714
|
error: true,
|
|
@@ -22582,12 +22739,12 @@ const mcpController$3 = {
|
|
|
22582
22739
|
// Merge static env vars from authCommand with ~ expansion
|
|
22583
22740
|
if (authCommand.staticEnv) {
|
|
22584
22741
|
Object.entries(authCommand.staticEnv).forEach(([key, value]) => {
|
|
22585
|
-
env[key] = value.replace(/^~/, os$
|
|
22742
|
+
env[key] = value.replace(/^~/, os$2.homedir());
|
|
22586
22743
|
});
|
|
22587
22744
|
}
|
|
22588
22745
|
|
|
22589
22746
|
// Interpolate {{MCP_DIR}} in authCommand args (same as startServer)
|
|
22590
|
-
const mcpDir = path$
|
|
22747
|
+
const mcpDir = path$d.join(__dirname, "..", "mcp");
|
|
22591
22748
|
const resolvedArgs = (authCommand.args || []).map((arg) =>
|
|
22592
22749
|
typeof arg === "string" && arg.includes("{{MCP_DIR}}")
|
|
22593
22750
|
? arg.replace(/\{\{MCP_DIR\}\}/g, mcpDir)
|
|
@@ -22667,12 +22824,39 @@ const mcpController$3 = {
|
|
|
22667
22824
|
`[mcpController] Stopping all servers (${activeServers.size} active)`,
|
|
22668
22825
|
);
|
|
22669
22826
|
const promises = [];
|
|
22670
|
-
|
|
22671
|
-
|
|
22827
|
+
// Slice 3a: keys are compound `(workspaceId, serverName)`. Parse
|
|
22828
|
+
// to call stopServer with the original args.
|
|
22829
|
+
for (const [key] of activeServers) {
|
|
22830
|
+
const { workspaceId, serverName } = parseServerKey(key);
|
|
22831
|
+
promises.push(mcpController$3.stopServer(null, serverName, workspaceId));
|
|
22672
22832
|
}
|
|
22673
22833
|
await Promise.allSettled(promises);
|
|
22674
22834
|
console.log("[mcpController] All servers stopped");
|
|
22675
22835
|
},
|
|
22836
|
+
|
|
22837
|
+
/**
|
|
22838
|
+
* stopServersForWorkspace
|
|
22839
|
+
* Stop every server keyed under the given workspaceId. Called when
|
|
22840
|
+
* a workspace unmounts so its scoped MCP processes don't leak.
|
|
22841
|
+
*
|
|
22842
|
+
* @param {string} workspaceId the workspace whose servers to stop
|
|
22843
|
+
*/
|
|
22844
|
+
stopServersForWorkspace: async (workspaceId) => {
|
|
22845
|
+
if (!workspaceId) return;
|
|
22846
|
+
const promises = [];
|
|
22847
|
+
for (const [key] of activeServers) {
|
|
22848
|
+
const parsed = parseServerKey(key);
|
|
22849
|
+
if (parsed.workspaceId !== workspaceId) continue;
|
|
22850
|
+
promises.push(
|
|
22851
|
+
mcpController$3.stopServer(null, parsed.serverName, workspaceId),
|
|
22852
|
+
);
|
|
22853
|
+
}
|
|
22854
|
+
if (promises.length === 0) return;
|
|
22855
|
+
console.log(
|
|
22856
|
+
`[mcpController] Stopping ${promises.length} server(s) for workspace ${workspaceId}`,
|
|
22857
|
+
);
|
|
22858
|
+
await Promise.allSettled(promises);
|
|
22859
|
+
},
|
|
22676
22860
|
};
|
|
22677
22861
|
|
|
22678
22862
|
mcpController$4.exports = mcpController$3;
|
|
@@ -23108,8 +23292,8 @@ function commonjsRequire(path) {
|
|
|
23108
23292
|
* Runs in the Electron main process at widget install time.
|
|
23109
23293
|
*/
|
|
23110
23294
|
|
|
23111
|
-
const fs$
|
|
23112
|
-
const path$
|
|
23295
|
+
const fs$8 = require$$0$2;
|
|
23296
|
+
const path$c = require$$1$2;
|
|
23113
23297
|
|
|
23114
23298
|
/**
|
|
23115
23299
|
* Structured error thrown by compileWidget() when the underlying
|
|
@@ -23144,7 +23328,7 @@ function getEsbuildDiagnostics() {
|
|
|
23144
23328
|
|
|
23145
23329
|
try {
|
|
23146
23330
|
const pkgJsonPath = require.resolve("esbuild/package.json");
|
|
23147
|
-
diagnostics.esbuildPackageDir = path$
|
|
23331
|
+
diagnostics.esbuildPackageDir = path$c.dirname(pkgJsonPath);
|
|
23148
23332
|
diagnostics.esbuildVersion = commonjsRequire(pkgJsonPath).version;
|
|
23149
23333
|
} catch (err) {
|
|
23150
23334
|
diagnostics.esbuildResolveError = err.message;
|
|
@@ -23154,15 +23338,15 @@ function getEsbuildDiagnostics() {
|
|
|
23154
23338
|
const archPkgJson = require.resolve(
|
|
23155
23339
|
`${diagnostics.archPackage}/package.json`,
|
|
23156
23340
|
);
|
|
23157
|
-
const archDir = path$
|
|
23341
|
+
const archDir = path$c.dirname(archPkgJson);
|
|
23158
23342
|
// esbuild's native binary on macOS/Linux is bin/esbuild;
|
|
23159
23343
|
// on Windows it's esbuild.exe at the package root.
|
|
23160
23344
|
const candidate =
|
|
23161
23345
|
process.platform === "win32"
|
|
23162
|
-
? path$
|
|
23163
|
-
: path$
|
|
23346
|
+
? path$c.join(archDir, "esbuild.exe")
|
|
23347
|
+
: path$c.join(archDir, "bin", "esbuild");
|
|
23164
23348
|
diagnostics.nativeBinaryPath = candidate;
|
|
23165
|
-
diagnostics.nativeBinaryExists = fs$
|
|
23349
|
+
diagnostics.nativeBinaryExists = fs$8.existsSync(candidate);
|
|
23166
23350
|
} catch (err) {
|
|
23167
23351
|
diagnostics.archResolveError = err.message;
|
|
23168
23352
|
}
|
|
@@ -23208,26 +23392,26 @@ async function healthCheck() {
|
|
|
23208
23392
|
* @returns {string|null} Path to the widgets/ directory, or null
|
|
23209
23393
|
*/
|
|
23210
23394
|
function findWidgetsDir$2(widgetPath) {
|
|
23211
|
-
const direct = path$
|
|
23212
|
-
if (fs$
|
|
23395
|
+
const direct = path$c.join(widgetPath, "widgets");
|
|
23396
|
+
if (fs$8.existsSync(direct)) {
|
|
23213
23397
|
return direct;
|
|
23214
23398
|
}
|
|
23215
23399
|
|
|
23216
23400
|
// Check configs/widgets/ (packageZip.js nests .dash.js files here)
|
|
23217
|
-
const configsWidgets = path$
|
|
23218
|
-
if (fs$
|
|
23401
|
+
const configsWidgets = path$c.join(widgetPath, "configs", "widgets");
|
|
23402
|
+
if (fs$8.existsSync(configsWidgets)) {
|
|
23219
23403
|
return configsWidgets;
|
|
23220
23404
|
}
|
|
23221
23405
|
|
|
23222
23406
|
// Check configs/ directory (used by packageZip.js for distributed widgets)
|
|
23223
|
-
const configs = path$
|
|
23224
|
-
if (fs$
|
|
23407
|
+
const configs = path$c.join(widgetPath, "configs");
|
|
23408
|
+
if (fs$8.existsSync(configs)) {
|
|
23225
23409
|
return configs;
|
|
23226
23410
|
}
|
|
23227
23411
|
|
|
23228
23412
|
// Check one level deeper for nested ZIP extraction
|
|
23229
23413
|
try {
|
|
23230
|
-
const entries = fs$
|
|
23414
|
+
const entries = fs$8.readdirSync(widgetPath, { withFileTypes: true });
|
|
23231
23415
|
const subdirs = entries.filter(
|
|
23232
23416
|
(e) =>
|
|
23233
23417
|
e.isDirectory() &&
|
|
@@ -23237,8 +23421,8 @@ function findWidgetsDir$2(widgetPath) {
|
|
|
23237
23421
|
);
|
|
23238
23422
|
|
|
23239
23423
|
for (const subdir of subdirs) {
|
|
23240
|
-
const nested = path$
|
|
23241
|
-
if (fs$
|
|
23424
|
+
const nested = path$c.join(widgetPath, subdir.name, "widgets");
|
|
23425
|
+
if (fs$8.existsSync(nested)) {
|
|
23242
23426
|
console.log(`[WidgetCompiler] Found nested widgets/ at ${nested}`);
|
|
23243
23427
|
return nested;
|
|
23244
23428
|
}
|
|
@@ -23272,7 +23456,7 @@ async function compileWidget$1(widgetPath) {
|
|
|
23272
23456
|
}
|
|
23273
23457
|
|
|
23274
23458
|
// Discover .dash.js config files
|
|
23275
|
-
const files = fs$
|
|
23459
|
+
const files = fs$8.readdirSync(widgetsDir);
|
|
23276
23460
|
const dashFiles = files.filter((f) => f.endsWith(".dash.js"));
|
|
23277
23461
|
|
|
23278
23462
|
if (dashFiles.length === 0) {
|
|
@@ -23286,15 +23470,15 @@ async function compileWidget$1(widgetPath) {
|
|
|
23286
23470
|
// Compute relative path from the entry file (in widgetPath) to widgetsDir,
|
|
23287
23471
|
// since widgetsDir may be nested (e.g., ./weather-widget/widgets/).
|
|
23288
23472
|
const relWidgetsDir =
|
|
23289
|
-
"./" + path$
|
|
23473
|
+
"./" + path$c.relative(widgetPath, widgetsDir).split(path$c.sep).join("/");
|
|
23290
23474
|
const imports = [];
|
|
23291
23475
|
const exportParts = [];
|
|
23292
23476
|
|
|
23293
23477
|
for (const dashFile of dashFiles) {
|
|
23294
23478
|
const componentName = dashFile.replace(".dash.js", "");
|
|
23295
23479
|
const componentFile = `${componentName}.js`;
|
|
23296
|
-
const componentFilePath = path$
|
|
23297
|
-
const hasComponent = fs$
|
|
23480
|
+
const componentFilePath = path$c.join(widgetsDir, componentFile);
|
|
23481
|
+
const hasComponent = fs$8.existsSync(componentFilePath);
|
|
23298
23482
|
|
|
23299
23483
|
// Import the config (always)
|
|
23300
23484
|
imports.push(
|
|
@@ -23326,17 +23510,17 @@ async function compileWidget$1(widgetPath) {
|
|
|
23326
23510
|
const entryContent = [...imports, "", ...exportParts, ""].join("\n");
|
|
23327
23511
|
|
|
23328
23512
|
// Write temporary entry file in the widget root
|
|
23329
|
-
const entryPath = path$
|
|
23330
|
-
const distDir = path$
|
|
23331
|
-
const outPath = path$
|
|
23513
|
+
const entryPath = path$c.join(widgetPath, "__compile_entry.js");
|
|
23514
|
+
const distDir = path$c.join(widgetPath, "dist");
|
|
23515
|
+
const outPath = path$c.join(distDir, "index.cjs.js");
|
|
23332
23516
|
|
|
23333
23517
|
try {
|
|
23334
23518
|
// Ensure dist/ directory exists
|
|
23335
|
-
if (!fs$
|
|
23336
|
-
fs$
|
|
23519
|
+
if (!fs$8.existsSync(distDir)) {
|
|
23520
|
+
fs$8.mkdirSync(distDir, { recursive: true });
|
|
23337
23521
|
}
|
|
23338
23522
|
|
|
23339
|
-
fs$
|
|
23523
|
+
fs$8.writeFileSync(entryPath, entryContent, "utf8");
|
|
23340
23524
|
|
|
23341
23525
|
console.log(
|
|
23342
23526
|
`[WidgetCompiler] Compiling ${dashFiles.length} component(s) from ${widgetPath}`,
|
|
@@ -23392,8 +23576,8 @@ async function compileWidget$1(widgetPath) {
|
|
|
23392
23576
|
} finally {
|
|
23393
23577
|
// Clean up temporary entry file
|
|
23394
23578
|
try {
|
|
23395
|
-
if (fs$
|
|
23396
|
-
fs$
|
|
23579
|
+
if (fs$8.existsSync(entryPath)) {
|
|
23580
|
+
fs$8.unlinkSync(entryPath);
|
|
23397
23581
|
}
|
|
23398
23582
|
} catch (cleanupError) {
|
|
23399
23583
|
// Non-fatal
|
|
@@ -23425,8 +23609,8 @@ var widgetCompiler$1 = {
|
|
|
23425
23609
|
* Integrates with ComponentManager for automatic registration
|
|
23426
23610
|
*/
|
|
23427
23611
|
|
|
23428
|
-
const fs$
|
|
23429
|
-
const path$
|
|
23612
|
+
const fs$7 = require$$0$2;
|
|
23613
|
+
const path$b = require$$1$2;
|
|
23430
23614
|
const vm = require$$2$2;
|
|
23431
23615
|
const { findWidgetsDir: findWidgetsDir$1 } = widgetCompiler$1;
|
|
23432
23616
|
|
|
@@ -23514,14 +23698,14 @@ class DynamicWidgetLoader {
|
|
|
23514
23698
|
);
|
|
23515
23699
|
|
|
23516
23700
|
const widgetsDir =
|
|
23517
|
-
findWidgetsDir$1(widgetPath) || path$
|
|
23518
|
-
const componentPath = path$
|
|
23519
|
-
const configPath = path$
|
|
23701
|
+
findWidgetsDir$1(widgetPath) || path$b.join(widgetPath, "widgets");
|
|
23702
|
+
const componentPath = path$b.join(widgetsDir, `${componentName}.js`);
|
|
23703
|
+
const configPath = path$b.join(widgetsDir, `${componentName}.dash.js`);
|
|
23520
23704
|
|
|
23521
|
-
if (!fs$
|
|
23705
|
+
if (!fs$7.existsSync(componentPath)) {
|
|
23522
23706
|
throw new Error(`Component file not found: ${componentPath}`);
|
|
23523
23707
|
}
|
|
23524
|
-
if (!fs$
|
|
23708
|
+
if (!fs$7.existsSync(configPath)) {
|
|
23525
23709
|
throw new Error(`Config file not found: ${configPath}`);
|
|
23526
23710
|
}
|
|
23527
23711
|
|
|
@@ -23572,7 +23756,7 @@ class DynamicWidgetLoader {
|
|
|
23572
23756
|
*/
|
|
23573
23757
|
async loadConfigFile(configPath) {
|
|
23574
23758
|
try {
|
|
23575
|
-
const source = fs$
|
|
23759
|
+
const source = fs$7.readFileSync(configPath, "utf8");
|
|
23576
23760
|
|
|
23577
23761
|
let exportMatch = source.match(/export\s+default\s+({[\s\S]*});?\s*$/);
|
|
23578
23762
|
|
|
@@ -23640,7 +23824,7 @@ class DynamicWidgetLoader {
|
|
|
23640
23824
|
return [];
|
|
23641
23825
|
}
|
|
23642
23826
|
|
|
23643
|
-
const files = fs$
|
|
23827
|
+
const files = fs$7.readdirSync(widgetsDir);
|
|
23644
23828
|
const widgets = new Set();
|
|
23645
23829
|
|
|
23646
23830
|
files.forEach((file) => {
|
|
@@ -23673,6 +23857,157 @@ dynamicWidgetLoader$3.exports.dynamicWidgetLoader = dynamicWidgetLoader$2;
|
|
|
23673
23857
|
|
|
23674
23858
|
var dynamicWidgetLoaderExports = dynamicWidgetLoader$3.exports;
|
|
23675
23859
|
|
|
23860
|
+
/**
|
|
23861
|
+
* widgetPermissions.js
|
|
23862
|
+
*
|
|
23863
|
+
* Read and parse the `dash.permissions.mcp` block from an installed
|
|
23864
|
+
* widget's package.json.
|
|
23865
|
+
*
|
|
23866
|
+
* Manifest format (declared by widget authors in their package.json):
|
|
23867
|
+
*
|
|
23868
|
+
* {
|
|
23869
|
+
* "name": "@trops/notes-summarizer",
|
|
23870
|
+
* "dash": {
|
|
23871
|
+
* "permissions": {
|
|
23872
|
+
* "mcp": {
|
|
23873
|
+
* "filesystem": {
|
|
23874
|
+
* "tools": ["read_file", "list_directory"],
|
|
23875
|
+
* "readPaths": ["~/Documents/notes"],
|
|
23876
|
+
* "writePaths": []
|
|
23877
|
+
* },
|
|
23878
|
+
* "github": {
|
|
23879
|
+
* "tools": ["search_repositories", "get_file_contents"]
|
|
23880
|
+
* }
|
|
23881
|
+
* }
|
|
23882
|
+
* }
|
|
23883
|
+
* }
|
|
23884
|
+
* }
|
|
23885
|
+
*
|
|
23886
|
+
* Path strings beginning with `~` are expanded to the user's home
|
|
23887
|
+
* directory at parse time. Tool-only servers (no path I/O, e.g.
|
|
23888
|
+
* github) omit the `readPaths`/`writePaths` keys.
|
|
23889
|
+
*
|
|
23890
|
+
* Public API:
|
|
23891
|
+
*
|
|
23892
|
+
* getWidgetMcpPermissions(widgetId) → permissions | null
|
|
23893
|
+
* Returns the parsed permissions for a widget, or null if the
|
|
23894
|
+
* widget is unmanifested. Cached per process.
|
|
23895
|
+
*
|
|
23896
|
+
* parseManifestPermissions(packageJson) → permissions | null
|
|
23897
|
+
* Pure function — exposed for tests.
|
|
23898
|
+
*
|
|
23899
|
+
* clearCache() → void
|
|
23900
|
+
* Test-only. Drops the in-process cache so tests can re-read.
|
|
23901
|
+
*/
|
|
23902
|
+
|
|
23903
|
+
const fs$6 = require$$0$2;
|
|
23904
|
+
const path$a = require$$1$2;
|
|
23905
|
+
const os$1 = require$$2$1;
|
|
23906
|
+
const { app: app$6 } = require$$0$1;
|
|
23907
|
+
|
|
23908
|
+
// Cache: widgetId → permissions | null. Populated lazily on first
|
|
23909
|
+
// lookup; invalidated when a widget is installed/uninstalled (the
|
|
23910
|
+
// install/uninstall paths call clearCache()).
|
|
23911
|
+
const _cache = new Map();
|
|
23912
|
+
|
|
23913
|
+
/**
|
|
23914
|
+
* Expand a leading "~" to the user's home directory. Other paths are
|
|
23915
|
+
* returned as-is.
|
|
23916
|
+
*/
|
|
23917
|
+
function expandHome(p) {
|
|
23918
|
+
if (typeof p !== "string" || !p) return p;
|
|
23919
|
+
if (p === "~") return os$1.homedir();
|
|
23920
|
+
if (p.startsWith("~/") || p.startsWith("~\\")) {
|
|
23921
|
+
return path$a.join(os$1.homedir(), p.slice(2));
|
|
23922
|
+
}
|
|
23923
|
+
return p;
|
|
23924
|
+
}
|
|
23925
|
+
|
|
23926
|
+
/**
|
|
23927
|
+
* Parse a widget's package.json contents into a normalized permissions
|
|
23928
|
+
* object. Returns null if no `dash.permissions.mcp` block exists.
|
|
23929
|
+
*/
|
|
23930
|
+
function parseManifestPermissions(packageJson) {
|
|
23931
|
+
if (!packageJson || typeof packageJson !== "object") return null;
|
|
23932
|
+
const mcp = packageJson?.dash?.permissions?.mcp;
|
|
23933
|
+
if (!mcp || typeof mcp !== "object") return null;
|
|
23934
|
+
|
|
23935
|
+
const servers = {};
|
|
23936
|
+
for (const [serverName, raw] of Object.entries(mcp)) {
|
|
23937
|
+
if (!raw || typeof raw !== "object") continue;
|
|
23938
|
+
const tools = Array.isArray(raw.tools)
|
|
23939
|
+
? raw.tools.filter((t) => typeof t === "string")
|
|
23940
|
+
: [];
|
|
23941
|
+
const readPaths = Array.isArray(raw.readPaths)
|
|
23942
|
+
? raw.readPaths.filter((p) => typeof p === "string").map(expandHome)
|
|
23943
|
+
: [];
|
|
23944
|
+
const writePaths = Array.isArray(raw.writePaths)
|
|
23945
|
+
? raw.writePaths.filter((p) => typeof p === "string").map(expandHome)
|
|
23946
|
+
: [];
|
|
23947
|
+
servers[serverName] = { tools, readPaths, writePaths };
|
|
23948
|
+
}
|
|
23949
|
+
|
|
23950
|
+
return { servers };
|
|
23951
|
+
}
|
|
23952
|
+
|
|
23953
|
+
/**
|
|
23954
|
+
* Find a widget's installed package.json on disk. Widgets live under
|
|
23955
|
+
* userData/widgets/<scope>/<name>/ for scoped packages or
|
|
23956
|
+
* userData/widgets/<name>/ for unscoped. The widgetId is the npm
|
|
23957
|
+
* package name (e.g. "@trops/notes-summarizer" or "notes-summarizer").
|
|
23958
|
+
*/
|
|
23959
|
+
function resolveWidgetPackagePath(widgetId) {
|
|
23960
|
+
if (typeof widgetId !== "string" || !widgetId) return null;
|
|
23961
|
+
const widgetsRoot = path$a.join(app$6.getPath("userData"), "widgets");
|
|
23962
|
+
// Split scope from name for "@scope/name" form.
|
|
23963
|
+
const parts = widgetId.startsWith("@") ? widgetId.split("/") : [widgetId];
|
|
23964
|
+
return path$a.join(widgetsRoot, ...parts, "package.json");
|
|
23965
|
+
}
|
|
23966
|
+
|
|
23967
|
+
/**
|
|
23968
|
+
* Read and parse a widget's MCP permissions. Returns null if:
|
|
23969
|
+
* - the widget directory doesn't exist
|
|
23970
|
+
* - package.json is unreadable / malformed
|
|
23971
|
+
* - the widget hasn't declared dash.permissions.mcp
|
|
23972
|
+
*
|
|
23973
|
+
* Result is cached per widgetId for the lifetime of this process.
|
|
23974
|
+
*/
|
|
23975
|
+
function getWidgetMcpPermissions$1(widgetId) {
|
|
23976
|
+
if (_cache.has(widgetId)) return _cache.get(widgetId);
|
|
23977
|
+
const pkgPath = resolveWidgetPackagePath(widgetId);
|
|
23978
|
+
if (!pkgPath || !fs$6.existsSync(pkgPath)) {
|
|
23979
|
+
_cache.set(widgetId, null);
|
|
23980
|
+
return null;
|
|
23981
|
+
}
|
|
23982
|
+
try {
|
|
23983
|
+
const raw = fs$6.readFileSync(pkgPath, "utf8");
|
|
23984
|
+
const pkg = JSON.parse(raw);
|
|
23985
|
+
const perms = parseManifestPermissions(pkg);
|
|
23986
|
+
_cache.set(widgetId, perms);
|
|
23987
|
+
return perms;
|
|
23988
|
+
} catch (e) {
|
|
23989
|
+
console.warn(
|
|
23990
|
+
"[widgetPermissions] failed to read package.json for " +
|
|
23991
|
+
widgetId +
|
|
23992
|
+
": " +
|
|
23993
|
+
e.message,
|
|
23994
|
+
);
|
|
23995
|
+
_cache.set(widgetId, null);
|
|
23996
|
+
return null;
|
|
23997
|
+
}
|
|
23998
|
+
}
|
|
23999
|
+
|
|
24000
|
+
function clearCache() {
|
|
24001
|
+
_cache.clear();
|
|
24002
|
+
}
|
|
24003
|
+
|
|
24004
|
+
var widgetPermissions = {
|
|
24005
|
+
getWidgetMcpPermissions: getWidgetMcpPermissions$1,
|
|
24006
|
+
parseManifestPermissions,
|
|
24007
|
+
expandHome,
|
|
24008
|
+
clearCache,
|
|
24009
|
+
};
|
|
24010
|
+
|
|
23676
24011
|
/**
|
|
23677
24012
|
* schedulerController.js
|
|
23678
24013
|
*
|
|
@@ -24213,6 +24548,10 @@ var schedulerController_1 = schedulerController$2;
|
|
|
24213
24548
|
const { compileWidget, findWidgetsDir } = widgetCompiler$1;
|
|
24214
24549
|
const { toPackageId, parsePackageId } = packageId;
|
|
24215
24550
|
const { getStoredToken } = registryAuthController$2;
|
|
24551
|
+
const {
|
|
24552
|
+
getWidgetMcpPermissions,
|
|
24553
|
+
clearCache: clearWidgetPermsCache,
|
|
24554
|
+
} = widgetPermissions;
|
|
24216
24555
|
|
|
24217
24556
|
let WIDGETS_CACHE_DIR = null;
|
|
24218
24557
|
let REGISTRY_CONFIG_FILE = null;
|
|
@@ -25287,6 +25626,38 @@ var schedulerController_1 = schedulerController$2;
|
|
|
25287
25626
|
return null;
|
|
25288
25627
|
}
|
|
25289
25628
|
|
|
25629
|
+
/**
|
|
25630
|
+
* If a freshly-installed widget declares dash.permissions.mcp, ping the
|
|
25631
|
+
* renderer so the consent modal can pop up. The renderer subscribes via
|
|
25632
|
+
* `widget:mcp-consent-required` and either calls widget-mcp:set-grant
|
|
25633
|
+
* with the user's selections (Slice 2) or quietly drops the message
|
|
25634
|
+
* (older renderer pre-Slice-2 — the gate still fail-closes).
|
|
25635
|
+
*
|
|
25636
|
+
* Cache invalidation: widgetPermissions caches per-process, so an upgrade
|
|
25637
|
+
* over a stale cached entry would otherwise keep the old manifest. Drop
|
|
25638
|
+
* the whole cache here — cheap, infrequent.
|
|
25639
|
+
*/
|
|
25640
|
+
function maybeEmitMcpConsentRequired(widgetName) {
|
|
25641
|
+
try {
|
|
25642
|
+
clearWidgetPermsCache();
|
|
25643
|
+
const declared = getWidgetMcpPermissions(widgetName);
|
|
25644
|
+
if (!declared) return;
|
|
25645
|
+
BrowserWindow.getAllWindows().forEach((win) => {
|
|
25646
|
+
win.webContents.send("widget:mcp-consent-required", {
|
|
25647
|
+
widgetId: widgetName,
|
|
25648
|
+
declared,
|
|
25649
|
+
});
|
|
25650
|
+
});
|
|
25651
|
+
} catch (e) {
|
|
25652
|
+
console.warn(
|
|
25653
|
+
"[widgetRegistry] mcp consent emit failed for " +
|
|
25654
|
+
widgetName +
|
|
25655
|
+
": " +
|
|
25656
|
+
e.message,
|
|
25657
|
+
);
|
|
25658
|
+
}
|
|
25659
|
+
}
|
|
25660
|
+
|
|
25290
25661
|
/**
|
|
25291
25662
|
* Setup IPC handlers for widget management (use in main.js)
|
|
25292
25663
|
*/
|
|
@@ -25313,6 +25684,8 @@ var schedulerController_1 = schedulerController$2;
|
|
|
25313
25684
|
});
|
|
25314
25685
|
});
|
|
25315
25686
|
|
|
25687
|
+
maybeEmitMcpConsentRequired(widgetName);
|
|
25688
|
+
|
|
25316
25689
|
return config;
|
|
25317
25690
|
},
|
|
25318
25691
|
);
|
|
@@ -25334,6 +25707,8 @@ var schedulerController_1 = schedulerController$2;
|
|
|
25334
25707
|
});
|
|
25335
25708
|
});
|
|
25336
25709
|
|
|
25710
|
+
maybeEmitMcpConsentRequired(widgetName);
|
|
25711
|
+
|
|
25337
25712
|
return config;
|
|
25338
25713
|
},
|
|
25339
25714
|
);
|
|
@@ -47487,7 +47862,7 @@ var mcpDashServerController_1 = mcpDashServerController$4;
|
|
|
47487
47862
|
* can use the Chat widget without a separate API key.
|
|
47488
47863
|
*/
|
|
47489
47864
|
|
|
47490
|
-
const { spawn, execSync } = require$$
|
|
47865
|
+
const { spawn, execSync } = require$$10;
|
|
47491
47866
|
const {
|
|
47492
47867
|
LLM_STREAM_DELTA: LLM_STREAM_DELTA$2,
|
|
47493
47868
|
LLM_STREAM_TOOL_CALL: LLM_STREAM_TOOL_CALL$2,
|
|
@@ -50796,7 +51171,7 @@ async function handleGetAppStats$1() {
|
|
|
50796
51171
|
|
|
50797
51172
|
const registryController$2 = registryController$3;
|
|
50798
51173
|
const registryAuthController$1 = registryAuthController$2;
|
|
50799
|
-
const { getWidgetRegistry } = widgetRegistryExports;
|
|
51174
|
+
const { getWidgetRegistry: getWidgetRegistry$1 } = widgetRegistryExports;
|
|
50800
51175
|
|
|
50801
51176
|
/**
|
|
50802
51177
|
* Helper: find a workspace by ID or return the first (active) one.
|
|
@@ -51324,7 +51699,7 @@ async function handleConfigureWidget$1({ dashboardId, widgetId, config }) {
|
|
|
51324
51699
|
function getInstalledPackageNames() {
|
|
51325
51700
|
const installed = new Set();
|
|
51326
51701
|
try {
|
|
51327
|
-
const registry = getWidgetRegistry();
|
|
51702
|
+
const registry = getWidgetRegistry$1();
|
|
51328
51703
|
const widgets = registry.getWidgets();
|
|
51329
51704
|
for (const w of widgets) {
|
|
51330
51705
|
const name = w.name || w.packageId || "";
|
|
@@ -51605,7 +51980,7 @@ async function handleInstallWidget$1({ packageName }) {
|
|
|
51605
51980
|
}
|
|
51606
51981
|
|
|
51607
51982
|
// Download and install
|
|
51608
|
-
const registry = getWidgetRegistry();
|
|
51983
|
+
const registry = getWidgetRegistry$1();
|
|
51609
51984
|
const config = await registry.downloadWidget(pkg.name, pkg.downloadUrl);
|
|
51610
51985
|
|
|
51611
51986
|
// Notify all renderer windows
|
|
@@ -59564,6 +59939,93 @@ const webSocketController$1 = {
|
|
|
59564
59939
|
|
|
59565
59940
|
var webSocketController_1 = webSocketController$1;
|
|
59566
59941
|
|
|
59942
|
+
/**
|
|
59943
|
+
* widgetMcpGrantsController.js
|
|
59944
|
+
*
|
|
59945
|
+
* IPC handlers for the Slice-2 user-grant store. The renderer reads/writes
|
|
59946
|
+
* grants via these channels: install consent modal calls `widget-mcp:set-grant`
|
|
59947
|
+
* with the user's selections, Settings → Privacy & Security calls
|
|
59948
|
+
* `widget-mcp:list-all` to render the grant audit, and either entry point can
|
|
59949
|
+
* call `widget-mcp:revoke` / `widget-mcp:revoke-server`.
|
|
59950
|
+
*
|
|
59951
|
+
* The list-all handler joins each grant with its declared manifest so the UI
|
|
59952
|
+
* can show declared-vs-granted diffs (declared paths the user did NOT grant
|
|
59953
|
+
* are rendered struck through, etc.). Widgets that have a manifest but no
|
|
59954
|
+
* grant are also surfaced — those are the install-consent retroactive prompts.
|
|
59955
|
+
*/
|
|
59956
|
+
|
|
59957
|
+
const { ipcMain: ipcMain$1 } = require$$0$1;
|
|
59958
|
+
const {
|
|
59959
|
+
getGrant,
|
|
59960
|
+
setGrant,
|
|
59961
|
+
revokeGrant,
|
|
59962
|
+
revokeServer,
|
|
59963
|
+
listAllGrants,
|
|
59964
|
+
} = grantedPermissions;
|
|
59965
|
+
const { getWidgetMcpPermissions } = widgetPermissions;
|
|
59966
|
+
const { getWidgetRegistry } = widgetRegistryExports;
|
|
59967
|
+
|
|
59968
|
+
function setupWidgetMcpGrantsHandlers() {
|
|
59969
|
+
ipcMain$1.handle("widget-mcp:get-grant", (event, widgetId) => {
|
|
59970
|
+
return getGrant(widgetId);
|
|
59971
|
+
});
|
|
59972
|
+
|
|
59973
|
+
ipcMain$1.handle("widget-mcp:set-grant", (event, widgetId, perms) => {
|
|
59974
|
+
return setGrant(widgetId, perms);
|
|
59975
|
+
});
|
|
59976
|
+
|
|
59977
|
+
ipcMain$1.handle("widget-mcp:revoke", (event, widgetId) => {
|
|
59978
|
+
return revokeGrant(widgetId);
|
|
59979
|
+
});
|
|
59980
|
+
|
|
59981
|
+
ipcMain$1.handle("widget-mcp:revoke-server", (event, widgetId, serverName) => {
|
|
59982
|
+
return revokeServer(widgetId, serverName);
|
|
59983
|
+
});
|
|
59984
|
+
|
|
59985
|
+
// Joins all installed widgets with their declared and granted permission
|
|
59986
|
+
// blocks. Returns one row per widget that has a declared manifest OR a
|
|
59987
|
+
// grant — this surfaces both "freshly installed, awaiting consent" and
|
|
59988
|
+
// "previously granted, now reviewable" cases in Settings.
|
|
59989
|
+
ipcMain$1.handle("widget-mcp:list-all", () => {
|
|
59990
|
+
const grantsByWidget = new Map();
|
|
59991
|
+
for (const { widgetId, granted } of listAllGrants()) {
|
|
59992
|
+
grantsByWidget.set(widgetId, granted);
|
|
59993
|
+
}
|
|
59994
|
+
|
|
59995
|
+
const rows = [];
|
|
59996
|
+
const seen = new Set();
|
|
59997
|
+
|
|
59998
|
+
let installedWidgets = [];
|
|
59999
|
+
try {
|
|
60000
|
+
installedWidgets = getWidgetRegistry().getWidgets() || [];
|
|
60001
|
+
} catch (_e) {
|
|
60002
|
+
// Registry not initialized yet; fall back to grants-only listing.
|
|
60003
|
+
}
|
|
60004
|
+
|
|
60005
|
+
for (const w of installedWidgets) {
|
|
60006
|
+
const widgetId = w?.name;
|
|
60007
|
+
if (!widgetId || seen.has(widgetId)) continue;
|
|
60008
|
+
seen.add(widgetId);
|
|
60009
|
+
const declared = getWidgetMcpPermissions(widgetId);
|
|
60010
|
+
const granted = grantsByWidget.get(widgetId) || null;
|
|
60011
|
+
// Skip widgets with neither — they have nothing to show.
|
|
60012
|
+
if (!declared && !granted) continue;
|
|
60013
|
+
rows.push({ widgetId, declared, granted });
|
|
60014
|
+
}
|
|
60015
|
+
|
|
60016
|
+
// Surface any grants whose widget is no longer installed (rare, but
|
|
60017
|
+
// possible if uninstall didn't revoke). The user can still revoke them.
|
|
60018
|
+
for (const [widgetId, granted] of grantsByWidget) {
|
|
60019
|
+
if (seen.has(widgetId)) continue;
|
|
60020
|
+
rows.push({ widgetId, declared: null, granted });
|
|
60021
|
+
}
|
|
60022
|
+
|
|
60023
|
+
return rows;
|
|
60024
|
+
});
|
|
60025
|
+
}
|
|
60026
|
+
|
|
60027
|
+
var widgetMcpGrantsController$1 = { setupWidgetMcpGrantsHandlers };
|
|
60028
|
+
|
|
59567
60029
|
/**
|
|
59568
60030
|
* clientFactories.js
|
|
59569
60031
|
*
|
|
@@ -61789,13 +62251,16 @@ const mcpApi$2 = {
|
|
|
61789
62251
|
* @param {string} serverName unique name for this server instance
|
|
61790
62252
|
* @param {object} mcpConfig { transport, command, args, envMapping }
|
|
61791
62253
|
* @param {object} credentials decrypted credentials object
|
|
62254
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a) —
|
|
62255
|
+
* server processes are keyed per workspace.
|
|
61792
62256
|
* @returns {Promise<{ success, serverName, tools, status } | { error, message }>}
|
|
61793
62257
|
*/
|
|
61794
|
-
startServer: (serverName, mcpConfig, credentials) =>
|
|
62258
|
+
startServer: (serverName, mcpConfig, credentials, workspaceId = null) =>
|
|
61795
62259
|
ipcRenderer$i.invoke(MCP_START_SERVER, {
|
|
61796
62260
|
serverName,
|
|
61797
62261
|
mcpConfig,
|
|
61798
62262
|
credentials,
|
|
62263
|
+
workspaceId,
|
|
61799
62264
|
}),
|
|
61800
62265
|
|
|
61801
62266
|
/**
|
|
@@ -61803,19 +62268,22 @@ const mcpApi$2 = {
|
|
|
61803
62268
|
* Stop a running MCP server
|
|
61804
62269
|
*
|
|
61805
62270
|
* @param {string} serverName the server to stop
|
|
62271
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
61806
62272
|
* @returns {Promise<{ success, serverName } | { error, message }>}
|
|
61807
62273
|
*/
|
|
61808
|
-
stopServer: (serverName) =>
|
|
61809
|
-
ipcRenderer$i.invoke(MCP_STOP_SERVER, { serverName }),
|
|
62274
|
+
stopServer: (serverName, workspaceId = null) =>
|
|
62275
|
+
ipcRenderer$i.invoke(MCP_STOP_SERVER, { serverName, workspaceId }),
|
|
61810
62276
|
|
|
61811
62277
|
/**
|
|
61812
62278
|
* listTools
|
|
61813
62279
|
* List available tools for a running MCP server
|
|
61814
62280
|
*
|
|
61815
62281
|
* @param {string} serverName the server name
|
|
62282
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
61816
62283
|
* @returns {Promise<{ tools } | { error, message }>}
|
|
61817
62284
|
*/
|
|
61818
|
-
listTools: (serverName
|
|
62285
|
+
listTools: (serverName, workspaceId = null) =>
|
|
62286
|
+
ipcRenderer$i.invoke(MCP_LIST_TOOLS, { serverName, workspaceId }),
|
|
61819
62287
|
|
|
61820
62288
|
/**
|
|
61821
62289
|
* callTool
|
|
@@ -61830,6 +62298,9 @@ const mcpApi$2 = {
|
|
|
61830
62298
|
* used to look up the widget's MCP permission manifest and gate
|
|
61831
62299
|
* the call accordingly. Should be the npm package name of the
|
|
61832
62300
|
* calling widget (e.g. "@trops/notes-summarizer").
|
|
62301
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a) —
|
|
62302
|
+
* the server process is scoped per (workspace, server). Slice 3b
|
|
62303
|
+
* will tie path scope to this id.
|
|
61833
62304
|
* @returns {Promise<{ result } | { error, message }>}
|
|
61834
62305
|
*/
|
|
61835
62306
|
callTool: (
|
|
@@ -61838,6 +62309,7 @@ const mcpApi$2 = {
|
|
|
61838
62309
|
args,
|
|
61839
62310
|
allowedTools = null,
|
|
61840
62311
|
widgetId = null,
|
|
62312
|
+
workspaceId = null,
|
|
61841
62313
|
) =>
|
|
61842
62314
|
ipcRenderer$i.invoke(MCP_CALL_TOOL, {
|
|
61843
62315
|
serverName,
|
|
@@ -61845,6 +62317,7 @@ const mcpApi$2 = {
|
|
|
61845
62317
|
args,
|
|
61846
62318
|
allowedTools,
|
|
61847
62319
|
widgetId,
|
|
62320
|
+
workspaceId,
|
|
61848
62321
|
}),
|
|
61849
62322
|
|
|
61850
62323
|
/**
|
|
@@ -61852,10 +62325,11 @@ const mcpApi$2 = {
|
|
|
61852
62325
|
* List available resources for a running MCP server
|
|
61853
62326
|
*
|
|
61854
62327
|
* @param {string} serverName the server name
|
|
62328
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
61855
62329
|
* @returns {Promise<{ resources } | { error, message }>}
|
|
61856
62330
|
*/
|
|
61857
|
-
listResources: (serverName) =>
|
|
61858
|
-
ipcRenderer$i.invoke(MCP_LIST_RESOURCES, { serverName }),
|
|
62331
|
+
listResources: (serverName, workspaceId = null) =>
|
|
62332
|
+
ipcRenderer$i.invoke(MCP_LIST_RESOURCES, { serverName, workspaceId }),
|
|
61859
62333
|
|
|
61860
62334
|
/**
|
|
61861
62335
|
* readResource
|
|
@@ -61863,20 +62337,22 @@ const mcpApi$2 = {
|
|
|
61863
62337
|
*
|
|
61864
62338
|
* @param {string} serverName the server name
|
|
61865
62339
|
* @param {string} uri the resource URI
|
|
62340
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
61866
62341
|
* @returns {Promise<{ resource } | { error, message }>}
|
|
61867
62342
|
*/
|
|
61868
|
-
readResource: (serverName, uri) =>
|
|
61869
|
-
ipcRenderer$i.invoke(MCP_READ_RESOURCE, { serverName, uri }),
|
|
62343
|
+
readResource: (serverName, uri, workspaceId = null) =>
|
|
62344
|
+
ipcRenderer$i.invoke(MCP_READ_RESOURCE, { serverName, uri, workspaceId }),
|
|
61870
62345
|
|
|
61871
62346
|
/**
|
|
61872
62347
|
* getServerStatus
|
|
61873
62348
|
* Get the connection status of a server
|
|
61874
62349
|
*
|
|
61875
62350
|
* @param {string} serverName the server name
|
|
62351
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
61876
62352
|
* @returns {Promise<{ status, tools, error }>}
|
|
61877
62353
|
*/
|
|
61878
|
-
getServerStatus: (serverName) =>
|
|
61879
|
-
ipcRenderer$i.invoke(MCP_SERVER_STATUS, { serverName }),
|
|
62354
|
+
getServerStatus: (serverName, workspaceId = null) =>
|
|
62355
|
+
ipcRenderer$i.invoke(MCP_SERVER_STATUS, { serverName, workspaceId }),
|
|
61880
62356
|
|
|
61881
62357
|
/**
|
|
61882
62358
|
* getCatalog
|
|
@@ -64613,6 +65089,7 @@ const paletteToThemeMapper = paletteToThemeMapper_1;
|
|
|
64613
65089
|
const webSocketController = webSocketController_1;
|
|
64614
65090
|
const extractionCacheController = extractionCacheController_1;
|
|
64615
65091
|
const mcpDashServerController = mcpDashServerController_1;
|
|
65092
|
+
const widgetMcpGrantsController = widgetMcpGrantsController$1;
|
|
64616
65093
|
|
|
64617
65094
|
// --- Errors ---
|
|
64618
65095
|
const themeFromUrlErrors = themeFromUrlErrors$1;
|
|
@@ -64717,6 +65194,7 @@ var electron = {
|
|
|
64717
65194
|
webSocketController,
|
|
64718
65195
|
extractionCacheController,
|
|
64719
65196
|
mcpDashServerController,
|
|
65197
|
+
widgetMcpGrantsController,
|
|
64720
65198
|
|
|
64721
65199
|
// Controller functions (flat) — spread for convenient destructuring
|
|
64722
65200
|
...controllers,
|