@trops/dash-core 0.1.638 → 0.1.639

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -983,6 +983,8 @@ const BOTS_PAUSE_BOT$1 = "bots-pause-bot";
983
983
  const BOTS_RESUME_BOT$1 = "bots-resume-bot";
984
984
  const BOTS_GET_PAUSE_STATE$1 = "bots-get-pause-state";
985
985
  const BOTS_LIST_TOOL_SOURCES$1 = "bots-list-tool-sources";
986
+ const BOTS_GET_GRANTS$1 = "bots-get-grants";
987
+ const BOTS_REVOKE_GRANT$1 = "bots-revoke-grant";
986
988
 
987
989
  // --- Main → Renderer (send) ---
988
990
  const BOT_STREAM$2 = "bot-stream"; // { botId, event: BotEvent }
@@ -1010,6 +1012,8 @@ var botEvents$1 = {
1010
1012
  BOTS_RESUME_BOT: BOTS_RESUME_BOT$1,
1011
1013
  BOTS_GET_PAUSE_STATE: BOTS_GET_PAUSE_STATE$1,
1012
1014
  BOTS_LIST_TOOL_SOURCES: BOTS_LIST_TOOL_SOURCES$1,
1015
+ BOTS_GET_GRANTS: BOTS_GET_GRANTS$1,
1016
+ BOTS_REVOKE_GRANT: BOTS_REVOKE_GRANT$1,
1013
1017
  BOT_STREAM: BOT_STREAM$2,
1014
1018
  BOT_APPROVAL_PENDING: BOT_APPROVAL_PENDING$2,
1015
1019
  BOT_BUDGET_ALERT: BOT_BUDGET_ALERT$2,
@@ -19492,7 +19496,7 @@ util$E.isObject = isObject;
19492
19496
  util$E.isPlainObject = isPlainObject;
19493
19497
  util$E.numKeys = numKeys;
19494
19498
  util$E.escapeRegex = escapeRegex;
19495
- util$E.clone = clone;
19499
+ util$E.clone = clone$1;
19496
19500
  util$E.normalizeParams = normalizeParams;
19497
19501
  util$E.createTransparentProxy = createTransparentProxy;
19498
19502
  util$E.stringifyPrimitive = stringifyPrimitive;
@@ -19715,7 +19719,7 @@ function escapeRegex(str) {
19715
19719
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
19716
19720
  }
19717
19721
  // zod-specific utils
19718
- function clone(inst, def, params) {
19722
+ function clone$1(inst, def, params) {
19719
19723
  const cl = new inst._zod.constr(def ?? inst._zod.def);
19720
19724
  if (!def || params?.parent)
19721
19725
  cl._zod.parent = inst;
@@ -19805,7 +19809,7 @@ function pick$1(schema, mask) {
19805
19809
  // pick key
19806
19810
  newShape[key] = currDef.shape[key];
19807
19811
  }
19808
- return clone(schema, {
19812
+ return clone$1(schema, {
19809
19813
  ...schema._zod.def,
19810
19814
  shape: newShape,
19811
19815
  checks: [],
@@ -19822,7 +19826,7 @@ function omit(schema, mask) {
19822
19826
  continue;
19823
19827
  delete newShape[key];
19824
19828
  }
19825
- return clone(schema, {
19829
+ return clone$1(schema, {
19826
19830
  ...schema._zod.def,
19827
19831
  shape: newShape,
19828
19832
  checks: [],
@@ -19841,10 +19845,10 @@ function extend(schema, shape) {
19841
19845
  },
19842
19846
  checks: [], // delete existing checks
19843
19847
  };
19844
- return clone(schema, def);
19848
+ return clone$1(schema, def);
19845
19849
  }
19846
19850
  function merge(a, b) {
19847
- return clone(a, {
19851
+ return clone$1(a, {
19848
19852
  ...a._zod.def,
19849
19853
  get shape() {
19850
19854
  const _shape = { ...a._zod.def.shape, ...b._zod.def.shape };
@@ -19885,7 +19889,7 @@ function partial(Class, schema, mask) {
19885
19889
  : oldShape[key];
19886
19890
  }
19887
19891
  }
19888
- return clone(schema, {
19892
+ return clone$1(schema, {
19889
19893
  ...schema._zod.def,
19890
19894
  shape,
19891
19895
  checks: [],
@@ -19917,7 +19921,7 @@ function required$3(Class, schema, mask) {
19917
19921
  });
19918
19922
  }
19919
19923
  }
19920
- return clone(schema, {
19924
+ return clone$1(schema, {
19921
19925
  ...schema._zod.def,
19922
19926
  shape,
19923
19927
  // optional: [],
@@ -36674,7 +36678,7 @@ function _resolveIdentity({ token, widgetId }) {
36674
36678
  // Argument keys that look like paths. Different MCP servers use
36675
36679
  // different conventions; this list covers the common filesystem-style
36676
36680
  // servers. Extensible — add as new patterns surface.
36677
- const PATH_ARG_KEYS = ["path", "uri", "filepath", "file", "directory"];
36681
+ const PATH_ARG_KEYS$1 = ["path", "uri", "filepath", "file", "directory"];
36678
36682
 
36679
36683
  // Heuristic: tool names matching this regex are treated as writes for
36680
36684
  // purposes of choosing readPaths vs writePaths. The match is intentionally
@@ -36683,7 +36687,7 @@ const PATH_ARG_KEYS = ["path", "uri", "filepath", "file", "directory"];
36683
36687
  const WRITE_TOOL_PATTERN =
36684
36688
  /(^|_)(write|create|edit|delete|remove|append|move|rename|chmod|chown|mkdir)/i;
36685
36689
 
36686
- function isWriteTool(toolName) {
36690
+ function isWriteTool$1(toolName) {
36687
36691
  if (typeof toolName !== "string") return false;
36688
36692
  return WRITE_TOOL_PATTERN.test(toolName);
36689
36693
  }
@@ -36757,7 +36761,7 @@ function _evaluateGrant({
36757
36761
 
36758
36762
  // Path-argument containment. Only checked when the tool's args
36759
36763
  // include a path-shaped key.
36760
- const isWrite = isWriteTool(toolName);
36764
+ const isWrite = isWriteTool$1(toolName);
36761
36765
  // Write tools must use writePaths; read tools may use either
36762
36766
  // readPaths or writePaths (write access implies read access).
36763
36767
  const allowedPaths = isWrite
@@ -36765,7 +36769,7 @@ function _evaluateGrant({
36765
36769
  : [...serverPerms.readPaths, ...serverPerms.writePaths];
36766
36770
 
36767
36771
  if (args && typeof args === "object") {
36768
- for (const key of PATH_ARG_KEYS) {
36772
+ for (const key of PATH_ARG_KEYS$1) {
36769
36773
  const v = args[key];
36770
36774
  if (typeof v !== "string" || !v) continue;
36771
36775
  if (allowedPaths.length === 0) {
@@ -37134,8 +37138,8 @@ var permissionGate = {
37134
37138
  gateToolCall: gateToolCall$1,
37135
37139
  gateBotToolCall,
37136
37140
  gateToolCallWithJit: gateToolCallWithJit$1,
37137
- isWriteTool,
37138
- PATH_ARG_KEYS,
37141
+ isWriteTool: isWriteTool$1,
37142
+ PATH_ARG_KEYS: PATH_ARG_KEYS$1,
37139
37143
  };
37140
37144
 
37141
37145
  /**
@@ -75184,6 +75188,9 @@ var agentToolBridge = {
75184
75188
  const { createEventStream } = eventStream;
75185
75189
  const { BOT_MCP_SERVER, buildBotMcpServer } = agentToolBridge;
75186
75190
 
75191
+ // The SDK names bridged tools "mcp__<server>__<tool>".
75192
+ const BRIDGED_PREFIX = `mcp__${BOT_MCP_SERVER}__`;
75193
+
75187
75194
  // Test seam: injected fake `query` implementation (see __setQueryForTest).
75188
75195
  let _queryImpl = null;
75189
75196
 
@@ -75243,7 +75250,27 @@ function _makeCanUseTool(ctx) {
75243
75250
  if (ctx.approvalPolicy === "allow") {
75244
75251
  return { behavior: "allow", updatedInput: input };
75245
75252
  }
75246
- // Otherwise gate through the approval queue (shown in the Activity panel).
75253
+ // Bridged Dash provider tools go through the SAME Dash permission gate as
75254
+ // the tool-loop engine — grants, remembered approvals ("Always allow"),
75255
+ // and the approval queue all apply, keyed by the real provider.
75256
+ if (
75257
+ typeof toolName === "string" &&
75258
+ toolName.startsWith(BRIDGED_PREFIX) &&
75259
+ typeof ctx.requestPermission === "function"
75260
+ ) {
75261
+ const decision = await ctx.requestPermission(
75262
+ toolName.slice(BRIDGED_PREFIX.length),
75263
+ input,
75264
+ );
75265
+ return decision && decision.allow
75266
+ ? { behavior: "allow", updatedInput: input }
75267
+ : {
75268
+ behavior: "deny",
75269
+ message: (decision && decision.reason) || "not permitted",
75270
+ };
75271
+ }
75272
+ // Built-in tools (Bash, Write, …) ask every time via the approval queue
75273
+ // (shown in the Activity panel) — they're never remembered.
75247
75274
  if (typeof ctx.createApproval !== "function") {
75248
75275
  return { behavior: "deny", message: "no approval channel available" };
75249
75276
  }
@@ -76538,6 +76565,111 @@ function normalizeMcpResult$1(mcpResult) {
76538
76565
 
76539
76566
  var mcpResult = { normalizeMcpResult: normalizeMcpResult$1 };
76540
76567
 
76568
+ /**
76569
+ * rememberGrant.js — turn an "Always allow" bot approval into a durable grant,
76570
+ * and revoke it.
76571
+ *
76572
+ * Bot grants live in the shared grant store (electron/mcp/grantedPermissions)
76573
+ * keyed by botId, shape `{ servers: { [provider]: { tools, readPaths,
76574
+ * writePaths } } }`, and are enforced by the bot permission gate
76575
+ * (gateBotToolCall). A remembered approval is scoped to this bot + this
76576
+ * provider + this tool, plus — when the tool call carries a path argument —
76577
+ * the folder of that path (write folder for write tools, read folder
76578
+ * otherwise). Nothing broader.
76579
+ *
76580
+ * Pure (NFR-006): the gate's path rules (isWriteTool, path arg keys) and
76581
+ * `dirname` are injected so this matches the real gate exactly.
76582
+ */
76583
+
76584
+ const LISTING_TOOL = /(list|director|tree|search)/i;
76585
+
76586
+ /**
76587
+ * The folder to remember for a path argument: a `directory` arg, or a
76588
+ * directory-listing tool, names the folder itself; any other path is a file,
76589
+ * so its parent folder is remembered.
76590
+ */
76591
+ function folderForPathArg(key, value, toolName, dirname) {
76592
+ if (key === "directory" || LISTING_TOOL.test(String(toolName || ""))) {
76593
+ return value;
76594
+ }
76595
+ return dirname(value);
76596
+ }
76597
+
76598
+ const clone = (perms) =>
76599
+ perms && typeof perms === "object" ? JSON.parse(JSON.stringify(perms)) : {};
76600
+
76601
+ const addUnique = (list, value) =>
76602
+ list.includes(value) ? list : [...list, value];
76603
+
76604
+ /**
76605
+ * @param {object|null} perms the bot's current grant (or null)
76606
+ * @param {{ serverName: string, toolName: string, args?: object }} call
76607
+ * @param {{ isWriteTool: Function, pathArgKeys: string[], dirname: Function }} helpers
76608
+ * @returns {object} the new grant (input is not mutated)
76609
+ */
76610
+ function rememberToolGrant$1(perms, call, helpers) {
76611
+ const { serverName, toolName, args } = call;
76612
+ const next = clone(perms);
76613
+ next.grantOrigin = next.grantOrigin || "manual";
76614
+ next.servers = next.servers || {};
76615
+ const server = next.servers[serverName] || {
76616
+ tools: [],
76617
+ readPaths: [],
76618
+ writePaths: [],
76619
+ };
76620
+ server.tools = addUnique(server.tools || [], toolName);
76621
+ server.readPaths = server.readPaths || [];
76622
+ server.writePaths = server.writePaths || [];
76623
+
76624
+ if (args && typeof args === "object") {
76625
+ const isWrite = helpers.isWriteTool(toolName);
76626
+ for (const key of helpers.pathArgKeys) {
76627
+ const v = args[key];
76628
+ if (typeof v !== "string" || !v) continue;
76629
+ const folder = folderForPathArg(key, v, toolName, helpers.dirname);
76630
+ if (isWrite) server.writePaths = addUnique(server.writePaths, folder);
76631
+ else server.readPaths = addUnique(server.readPaths, folder);
76632
+ }
76633
+ }
76634
+
76635
+ next.servers[serverName] = server;
76636
+ return next;
76637
+ }
76638
+
76639
+ /**
76640
+ * Revoke one remembered tool. When it was the provider's last remembered
76641
+ * tool, the provider entry (and its folders) is removed entirely.
76642
+ */
76643
+ function forgetToolGrant$1(perms, serverName, toolName) {
76644
+ const server = perms && perms.servers && perms.servers[serverName];
76645
+ if (!server || !(server.tools || []).includes(toolName)) return perms;
76646
+ const next = clone(perms);
76647
+ const tools = next.servers[serverName].tools.filter((t) => t !== toolName);
76648
+ if (tools.length) next.servers[serverName].tools = tools;
76649
+ else delete next.servers[serverName];
76650
+ return next;
76651
+ }
76652
+
76653
+ /** Remembered tools + folders per provider, for display in the bot form. */
76654
+ function summarizeGrants$1(perms) {
76655
+ const out = {};
76656
+ const servers = (perms && perms.servers) || {};
76657
+ for (const [name, s] of Object.entries(servers)) {
76658
+ out[name] = {
76659
+ tools: [...(s.tools || [])],
76660
+ folders: [...(s.readPaths || []), ...(s.writePaths || [])],
76661
+ };
76662
+ }
76663
+ return out;
76664
+ }
76665
+
76666
+ var rememberGrant = {
76667
+ rememberToolGrant: rememberToolGrant$1,
76668
+ forgetToolGrant: forgetToolGrant$1,
76669
+ summarizeGrants: summarizeGrants$1,
76670
+ folderForPathArg,
76671
+ };
76672
+
76541
76673
  /**
76542
76674
  * botController.js
76543
76675
  *
@@ -76575,6 +76707,13 @@ const {
76575
76707
  resolveBotTools,
76576
76708
  checkToolCall,
76577
76709
  } = toolSources;
76710
+ const {
76711
+ rememberToolGrant,
76712
+ forgetToolGrant,
76713
+ summarizeGrants,
76714
+ } = rememberGrant;
76715
+ const grantStore = grantedPermissions;
76716
+ const { isWriteTool, PATH_ARG_KEYS } = permissionGate;
76578
76717
  const {
76579
76718
  getProvider: getProvider$1,
76580
76719
  getDefaultModel,
@@ -76802,6 +76941,8 @@ const botController$1 = {
76802
76941
 
76803
76942
  delete(botId) {
76804
76943
  this._scheduler.unregister(botId);
76944
+ // A deleted bot's remembered approvals go with it.
76945
+ grantStore.revokeGrant(botId);
76805
76946
  return this._store.delete(botId);
76806
76947
  },
76807
76948
 
@@ -76813,10 +76954,50 @@ const botController$1 = {
76813
76954
  return { stopped: this._runner.abort(botId) };
76814
76955
  },
76815
76956
 
76957
+ /**
76958
+ * Resolve a pending approval. `decision.remember` ("Always allow") also
76959
+ * saves a durable grant for this bot + provider + tool (+ the folder of a
76960
+ * path argument), so later runs use that tool without asking. Only provider
76961
+ * tool approvals can be remembered — built-in agent tools have no provider
76962
+ * and always ask.
76963
+ */
76816
76964
  approve(approvalId, decision = {}) {
76817
- return decision.allow
76818
- ? { ok: this._approvals.resolve(approvalId, decision) }
76819
- : { ok: this._approvals.deny(approvalId, decision.reason) };
76965
+ if (!decision.allow) {
76966
+ return { ok: this._approvals.deny(approvalId, decision.reason) };
76967
+ }
76968
+ let remembered = false;
76969
+ if (decision.remember) {
76970
+ const pending = this._approvals.get(approvalId);
76971
+ const req = pending && pending.request;
76972
+ if (req && req.botId && req.serverName && req.toolName) {
76973
+ const next = rememberToolGrant(
76974
+ grantStore.getGrant(req.botId),
76975
+ {
76976
+ serverName: req.serverName,
76977
+ toolName: req.toolName,
76978
+ args: req.input,
76979
+ },
76980
+ { isWriteTool, pathArgKeys: PATH_ARG_KEYS, dirname: path$2.dirname },
76981
+ );
76982
+ remembered = grantStore.setGrant(req.botId, next) === true;
76983
+ }
76984
+ }
76985
+ return { ok: this._approvals.resolve(approvalId, decision), remembered };
76986
+ },
76987
+
76988
+ /** Remembered approvals for a bot: { [provider]: { tools, folders } }. */
76989
+ getGrants(botId) {
76990
+ return summarizeGrants(grantStore.getGrant(botId));
76991
+ },
76992
+
76993
+ /** Revoke one remembered tool approval; returns the updated summary. */
76994
+ revokeGrant(botId, serverName, toolName) {
76995
+ const current = grantStore.getGrant(botId);
76996
+ if (current) {
76997
+ const next = forgetToolGrant(current, serverName, toolName);
76998
+ if (next !== current) grantStore.setGrant(botId, next);
76999
+ }
77000
+ return this.getGrants(botId);
76820
77001
  },
76821
77002
 
76822
77003
  listApprovals() {
@@ -80985,6 +81166,8 @@ const {
80985
81166
  BOTS_RESUME_BOT,
80986
81167
  BOTS_GET_PAUSE_STATE,
80987
81168
  BOTS_LIST_TOOL_SOURCES,
81169
+ BOTS_GET_GRANTS,
81170
+ BOTS_REVOKE_GRANT,
80988
81171
  BOT_STREAM,
80989
81172
  BOT_APPROVAL_PENDING,
80990
81173
  BOT_BUDGET_ALERT,
@@ -81037,6 +81220,12 @@ const botApi$1 = {
81037
81220
  listToolSources: (workspaceId = null) =>
81038
81221
  ipcRenderer$3.invoke(BOTS_LIST_TOOL_SOURCES, { workspaceId }),
81039
81222
 
81223
+ /** Remembered approvals ("Always allow"): { [provider]: { tools, folders } }. */
81224
+ getGrants: (botId) => ipcRenderer$3.invoke(BOTS_GET_GRANTS, { botId }),
81225
+ /** Revoke one remembered tool approval; resolves to the updated summary. */
81226
+ revokeGrant: (botId, serverName, toolName) =>
81227
+ ipcRenderer$3.invoke(BOTS_REVOKE_GRANT, { botId, serverName, toolName }),
81228
+
81040
81229
  /** Subscribe to streamed run events: { botId, event }. */
81041
81230
  onStream: (callback) => _addListener(BOT_STREAM, callback),
81042
81231
  /** Subscribe to new pending approvals: { id, request }. */