openmausbot 0.1.93 → 0.1.94
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/assets/{DesktopViewer-CKNR3SEW.js → DesktopViewer-bxM7NV3N.js} +1 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-Ckrm1I1X.js → abnfDiagram-O67JEVCF-BhMdJhRJ.js} +1 -1
- package/dist/assets/{arc-BBdBuCgB.js → arc-B5itKDcx.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-CDdA3Hjs.js → architectureDiagram-NJMV4G6O-C5d7UY6U.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-AUrsx1YR.js → blockDiagram-BEXU5L5S-BrZIhMmu.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-BEqQ0Unh.js → c4Diagram-YGBWAQC7-7I0ArfbD.js} +1 -1
- package/dist/assets/channel-CdJX6i8k.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-CEEuvd4C.js → chunk-2Q5K7J3B-BnEsyN36.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-DyANCiHl.js → chunk-3FUC2YCW-BXATQ52s.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-Dx_Slpw9.js → chunk-5VM5RSS4-B0BpTkEx.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-Dl7h7iiD.js → chunk-GTNCS2PH-BtLTz6FR.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-BLx07SPb.js → chunk-GWA4HPMP-D6gcZOYt.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-BMxDj3n7.js → chunk-JWPE2WC7-DCLrosvf.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-CN66PT4H.js → chunk-NETBCI7D-B9JsknV9.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-6HwhsDKw.js → chunk-WEXAMYUT-CsoTxw7z.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-gWnLt7SN.js → chunk-XXDRQBXY-0F_frB0c.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-CMS5l2ea.js → classDiagram-v2-NBCMYWYE-DkrpnflX.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-CV7sQwIG.js → cose-bilkent-JH36ORCC-BNDwYBt9.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-CSyl-7vl.js → cynefin-OW5HDTMX-JUkNr1Mk.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-BCLKL6az.js → cynefinDiagram-VND7K2PF-DKeh9N4M.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DiaZs6S1.js → dagre-6A5THRUB-ve-6T_wX.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-BeTtmt3I.js → diagram-22UHCM2B-BuJ2d8rT.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-CsdBG-QQ.js → diagram-3UASUU5V-BgOs_Ni-.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-BczvdbR1.js → diagram-ATOU4E4O-DMK6VmcP.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DHTTTMhi.js → diagram-CDSNMT55-DksASlcY.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-RTjHyd8z.js → diagram-MLGK6HIB-Bgn8I5k6.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-BDenHEH_.js → diagram-MPIPVDR6-zK0jqO8N.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-BSiRSbtL.js → ebnfDiagram-ZINNZB2B-vKrbzTwr.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-BCAElss-.js → elk-276RUBZZ-Bzv2mPNf.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-C4_1ZPJe.js → erDiagram-OPXOYQCR-CvOyYuxu.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-Bcb4Z75S.js → flowDiagram-KWPJA3E3-CtbnDbc0.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-BiPOzxzb.js → ganttDiagram-FUAMR5RP-c4dMRYyG.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-B1wtF3ZS.js → gitGraphDiagram-X574FWY7-B34uaQho.js} +1 -1
- package/dist/assets/index-CPYrGsv1.js +610 -0
- package/dist/assets/{index-BkYwilkG.js → index-CPiqc_DN.js} +1 -1
- package/dist/assets/index-CzT42GR8.css +1 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-A19No71U.js → infoDiagram-VRGFBTTK-o64ma4CI.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-BQsqJ_cV.js → ishikawaDiagram-OU5B5YK6-DV4C9BAm.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-DAO0pVan.js → journeyDiagram-ZHPQQLJL-Bf0AHk1H.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-NepmKEe2.js → kanban-definition-PNTS6WVX-B1UMSKmE.js} +1 -1
- package/dist/assets/{linear-bq1PTDTW.js → linear-BpKhuyLm.js} +1 -1
- package/dist/assets/{mermaid.core-s4LTbQle.js → mermaid.core-B_BKNCw5.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-LtdbgHFH.js → mindmap-definition-NLK3R4M7-DeARruT1.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-tdbaCxiK.js → pegDiagram-GJSIUBJH-BOQEk_1D.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DvkbpW8l.js → pieDiagram-5QR66LMP-EIfoiTZo.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-Do4KaH-E.js → quadrantDiagram-O4NWA36T-GgZwT-fi.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-BQWULOAs.js → railroadDiagram-XR7U4H2S-D9rDQomj.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-w9kei1FU.js → requirementDiagram-PLB6GJNP-DAh1MIeo.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-D2gRy0e8.js → sankeyDiagram-IPEJSGJF-CPr6Uz64.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-a2sO5_Eo.js → sequenceDiagram-PO4LG4MO-BU6VIdcm.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-CiPJmFEB.js → sizeCapture-INFHLROL-qF2HO41r.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-DAkUUlL7.js → stateDiagram-v2-GCMORJYK-DlJ8G4MD.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-DANf2R8Y.js → swimlanes-2SLR337P-DzYE8I54.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-D-U4GtLQ.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-BKpQmzrT.js → timeline-definition-EJHVYXUP-B7wzEQ0A.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-BtI7qLN-.js → usecaseDiagram-POWQR4AR-BvGgrxIP.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-gXJXrAG_.js → vennDiagram-UO4OBE2U-CEECdgQg.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-GU8Po5-n.js → wardleyDiagram-VNRHLVJA-XQHA0P_P.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-BF4J30g6.js → xychartDiagram-PMCCYNJV-B5mLcZI2.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +1 -0
- package/dist-server/companion/src/routes.js +21 -0
- package/dist-server/container-mcp.js +93 -76
- package/dist-server/drivers/agents-proxy.js +38 -1
- package/dist-server/drivers/pi-mcp-extension.ts +245 -299
- package/dist-server/enterprise/server/index.js +1 -0
- package/dist-server/index.js +16942 -13191
- package/dist-server/local-computer.js +1 -0
- package/dist-server/mcp-gate.js +147 -19
- package/dist-server/mcp-remote-proxy.js +437 -0
- package/dist-server/openmausbot.js +8233 -7651
- package/dist-server/pair-cli.js +8233 -7651
- package/dist-server/permission-proxy.js +65 -1
- package/dist-server/proxy-paths.js +1 -0
- package/dist-server/server/auto-approve.js +33 -11
- package/dist-server/server/browser-live.js +14 -5
- package/dist-server/server/browser-runtime.js +6 -0
- package/dist-server/server/channel-queue.js +4 -1
- package/dist-server/server/cli.js +32 -12
- package/dist-server/server/cloud-move-http.js +9 -3
- package/dist-server/server/cloud-move.js +18 -0
- package/dist-server/server/computer-wait.js +33 -13
- package/dist-server/server/config.js +44 -10
- package/dist-server/server/decision-log.js +9 -6
- package/dist-server/server/delegations.js +47 -22
- package/dist-server/server/drivers/acp/core.js +57 -8
- package/dist-server/server/drivers/acp/grok.js +208 -3
- package/dist-server/server/drivers/agents-call.js +28 -0
- package/dist-server/server/drivers/agents-catalog.js +10 -1
- package/dist-server/server/drivers/boatagent.js +2 -0
- package/dist-server/server/drivers/chat-mcp-tools.js +27 -14
- package/dist-server/server/drivers/claude.js +18 -6
- package/dist-server/server/drivers/codex.js +84 -10
- package/dist-server/server/drivers/openai-chat.js +11 -3
- package/dist-server/server/drivers/pi.js +48 -4
- package/dist-server/server/environment.js +3 -0
- package/dist-server/server/guarded-requests.js +2 -1
- package/dist-server/server/index.js +1143 -254
- package/dist-server/server/live-call-controller.js +868 -0
- package/dist-server/server/live-call.js +216 -0
- package/dist-server/server/mcp-gate-config.js +43 -12
- package/dist-server/server/mcp-gate.js +127 -25
- package/dist-server/server/mcp-http.js +46 -8
- package/dist-server/server/mcp-remote-proxy.js +108 -0
- package/dist-server/server/new-bot-defaults.js +3 -0
- package/dist-server/server/peer-provenance.js +4 -2
- package/dist-server/server/permission-proxy.js +15 -1
- package/dist-server/server/plan-usage.js +797 -0
- package/dist-server/server/proxy-paths.js +1 -0
- package/dist-server/server/redact.js +2 -0
- package/dist-server/server/request-auth.js +3 -2
- package/dist-server/server/routes/live.js +98 -0
- package/dist-server/server/skill-library.js +207 -0
- package/dist-server/server/skills-library-migration.js +235 -0
- package/dist-server/server/skills.js +66 -3
- package/dist-server/server/steer-queue.js +13 -1
- package/dist-server/server/store.js +32 -4
- package/dist-server/server/surface.js +9 -3
- package/dist-server/server/team-backup.js +2 -1
- package/dist-server/server/thread-events.js +1 -1
- package/dist-server/server/tunnel.js +20 -1
- package/dist-server/server/turn-log.js +10 -0
- package/dist-server/server/turn-resources.js +124 -0
- package/dist-server/shared/ask-question.js +13 -0
- package/dist-server/shared/call-consent.js +28 -0
- package/dist-server/shared/live-approval.js +124 -0
- package/dist-server/shared/live-call.js +176 -0
- package/dist-server/shared/skill-md.js +10 -0
- package/dist-server/shared/team-backup.js +3 -0
- package/dist-server/shared/tool-scope-support.js +25 -0
- package/dist-server/shared/tool-scope.js +136 -0
- package/dist-server/src/lib/thread-refs.js +406 -0
- package/dist-server/vps-container-mcp.js +93 -76
- package/dist-server/workspace-backup.worker.js +89 -100
- package/enterprise/server/index.js +1 -0
- package/package.json +1 -1
- package/dist/assets/channel-BU94VpxE.js +0 -1
- package/dist/assets/index-Biji-bfL.css +0 -1
- package/dist/assets/index-DZlhoES0.js +0 -597
- package/dist/assets/swimlanesDiagram-TC7HE7FX-CdHxG8aM.js +0 -8
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
// The generic protocol runtime lives in acp/core.ts; this file is only the
|
|
5
5
|
// per-harness quirks. Verified against grok 1.0.0.
|
|
6
6
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { join } from "node:path";
|
|
8
|
-
import {
|
|
7
|
+
import { dirname, join, resolve } from "node:path";
|
|
8
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
9
|
+
import { parse as parseYaml } from "yaml";
|
|
10
|
+
import { harnessHome, splitCliString } from "../../env-path.js";
|
|
9
11
|
import { decodeInjectId, hostApiKey, localHost, mergeLocalInject } from "../local-inject.js";
|
|
10
12
|
import { createAcpDriver } from "./core.js";
|
|
13
|
+
import { allowsTool, canUseMcpServer, narrowsNativeTools, parseToolScope } from "../../../shared/tool-scope.js";
|
|
11
14
|
export const STATIC_GROK_MODELS = {
|
|
12
15
|
default: "grok-4.7",
|
|
13
16
|
options: [
|
|
@@ -181,6 +184,140 @@ export function grokAcceptsUnadvertisedImages(init) {
|
|
|
181
184
|
const version = /^1\.0\.(\d+)$/.exec(meta.agentVersion);
|
|
182
185
|
return Boolean(version && Number(version[1]) >= 25);
|
|
183
186
|
}
|
|
187
|
+
// Original names in the official 1.0.41 default registry, not arbitrary
|
|
188
|
+
// owner strings forwarded into Grok's inherit-all profile allowlist.
|
|
189
|
+
const GROK_NATIVE_TOOLS = [
|
|
190
|
+
["read_file", "GrokBuild:read_file"], ["search_replace", "GrokBuild:search_replace"], ["write", "OpenCode:write"],
|
|
191
|
+
["run_terminal_command", "GrokBuild:run_terminal_cmd"], ["list_dir", "GrokBuild:list_dir"], ["grep", "GrokBuild:grep"],
|
|
192
|
+
["kill_command_or_subagent", "GrokBuild:kill_task"], ["todo_write", "GrokBuild:todo_write"],
|
|
193
|
+
["get_command_or_subagent_output", "GrokBuild:get_task_output"], ["spawn_subagent", "GrokBuild:task"],
|
|
194
|
+
["scheduler_create", "GrokBuild:scheduler_create"], ["scheduler_delete", "GrokBuild:scheduler_delete"], ["scheduler_list", "GrokBuild:scheduler_list"],
|
|
195
|
+
["monitor", "GrokBuild:monitor"], ["search_tool", "GrokBuild:search_tool"], ["use_tool", "GrokBuild:use_tool"],
|
|
196
|
+
["workflow", "GrokBuild:workflow"], ["enter_plan_mode", "GrokBuild:enter_plan_mode"], ["exit_plan_mode", "GrokBuild:exit_plan_mode"],
|
|
197
|
+
["ask_user_question", "GrokBuild:ask_user_question"], ["send_feedback", "GrokBuild:send_feedback"],
|
|
198
|
+
["image_gen", "GrokBuild:image_gen"], ["image_edit", "GrokBuild:image_edit"],
|
|
199
|
+
["image_to_video", "GrokBuild:image_to_video"], ["reference_to_video", "GrokBuild:reference_to_video"],
|
|
200
|
+
];
|
|
201
|
+
/** Read a supported on-disk profile rather than overriding its restriction. */
|
|
202
|
+
export function grokInheritedProfile(cli, env, cwd) {
|
|
203
|
+
const unsupported = () => { throw new Error("Grok's existing agent profile cannot be safely intersected with tool selection. Use a profile file with explicit tools or a separate default Grok account."); };
|
|
204
|
+
if ((env.GROK_AGENT && env.GROK_AGENT !== "grok-build") || env.GROK_CONFIG || env.GROK_CONFIG_PATH)
|
|
205
|
+
return unsupported();
|
|
206
|
+
const args = splitCliString(cli);
|
|
207
|
+
// Operator filters are applied after ACP's profile by the CLI. Refuse
|
|
208
|
+
// unverified overrides rather than replacing an inherited restriction.
|
|
209
|
+
if (args.some(arg => /^(--tools|--disallowed-tools|--disallowedTools|--agent)(=|$)/.test(arg)))
|
|
210
|
+
return unsupported();
|
|
211
|
+
const flags = args.flatMap((arg, index) => arg === "--agent-profile" || arg.startsWith("--agent-profile=") ? [index] : []);
|
|
212
|
+
if (flags.length > 1)
|
|
213
|
+
return unsupported();
|
|
214
|
+
const flag = flags[0] ?? -1;
|
|
215
|
+
let path = flag >= 0 ? (args[flag].includes("=") ? args[flag].slice(args[flag].indexOf("=") + 1) : args[flag + 1]) : undefined;
|
|
216
|
+
if (flag >= 0 && (!path || path.startsWith("-")))
|
|
217
|
+
return unsupported();
|
|
218
|
+
const files = [join(env.GROK_HOME || harnessHome("grok", env), "config.toml")];
|
|
219
|
+
for (let directory = resolve(cwd);;) {
|
|
220
|
+
const file = join(directory, ".grok", "config.toml");
|
|
221
|
+
if (!files.includes(file))
|
|
222
|
+
files.push(file);
|
|
223
|
+
const parent = dirname(directory);
|
|
224
|
+
if (parent === directory)
|
|
225
|
+
break;
|
|
226
|
+
directory = parent;
|
|
227
|
+
}
|
|
228
|
+
for (const file of files) {
|
|
229
|
+
if (!existsSync(file))
|
|
230
|
+
continue;
|
|
231
|
+
const text = readFileSync(file, "utf8");
|
|
232
|
+
if (text.length > 262_144)
|
|
233
|
+
return unsupported();
|
|
234
|
+
// Support a deliberately small, unambiguous TOML subset. Scan the whole
|
|
235
|
+
// file: quoted/dotted keys, inline agent tables and multiline constructs
|
|
236
|
+
// must never be silently missed by a section regex. Unsupported syntax
|
|
237
|
+
// refuses this scoped turn; ordinary unrestricted turns are unaffected.
|
|
238
|
+
let section = "";
|
|
239
|
+
for (const line of text.split(/\r?\n/).map(line => line.trim()).filter(line => line && !line.startsWith("#"))) {
|
|
240
|
+
const header = /^\[([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)\]\s*(?:#.*)?$/.exec(line);
|
|
241
|
+
if (header) {
|
|
242
|
+
section = header[1];
|
|
243
|
+
if (section.startsWith("agent."))
|
|
244
|
+
return unsupported();
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const assignment = /^([A-Za-z0-9_-]+)\s*=\s*(.+)$/.exec(line);
|
|
248
|
+
if (!assignment || /'''|"""/.test(assignment[2]) || (!section && assignment[1] === "agent"))
|
|
249
|
+
return unsupported();
|
|
250
|
+
if (section !== "agent")
|
|
251
|
+
continue;
|
|
252
|
+
const match = /^(name|definition)\s*=\s*("(?:[^"\\]|\\.)*"|'[^']*')\s*(?:#.*)?$/.exec(line);
|
|
253
|
+
if (!match)
|
|
254
|
+
return unsupported();
|
|
255
|
+
let value;
|
|
256
|
+
try {
|
|
257
|
+
value = match[2].startsWith('"') ? JSON.parse(match[2]) : match[2].slice(1, -1);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return unsupported();
|
|
261
|
+
}
|
|
262
|
+
// Grok expands environment variables in paths. Do not resolve a
|
|
263
|
+
// different literal file or fall back when that contract is unknown.
|
|
264
|
+
if (value.includes("$") || !value)
|
|
265
|
+
return unsupported();
|
|
266
|
+
if (match[1] === "name" && value !== "grok-build")
|
|
267
|
+
return unsupported();
|
|
268
|
+
if (match[1] === "definition" && flag < 0) {
|
|
269
|
+
if (path !== undefined && path !== value)
|
|
270
|
+
return unsupported();
|
|
271
|
+
path = value;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (!path)
|
|
276
|
+
return;
|
|
277
|
+
const text = readFileSync(resolve(cwd, path), "utf8");
|
|
278
|
+
if (text.length > 262_144)
|
|
279
|
+
return unsupported();
|
|
280
|
+
const match = /^\s*---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?$/.exec(text);
|
|
281
|
+
if (!match)
|
|
282
|
+
return unsupported();
|
|
283
|
+
const profile = parseYaml(match[1]);
|
|
284
|
+
if (!profile || typeof profile.name !== "string" || typeof profile.description !== "string"
|
|
285
|
+
|| (profile.tools !== undefined && (!Array.isArray(profile.tools) || !profile.tools.every((tool) => typeof tool === "string")))
|
|
286
|
+
|| (profile.disallowedTools !== undefined && (!Array.isArray(profile.disallowedTools) || !profile.disallowedTools.every((tool) => typeof tool === "string"))))
|
|
287
|
+
return unsupported();
|
|
288
|
+
const toolConfig = profile.toolConfig ?? { tools: GROK_NATIVE_TOOLS.map(([name, id]) => ({ id, name_override: name })) };
|
|
289
|
+
if (!Array.isArray(toolConfig.tools) || !toolConfig.tools.every((tool) => typeof tool?.id === "string" && (tool.name_override === undefined || typeof tool.name_override === "string")))
|
|
290
|
+
return unsupported();
|
|
291
|
+
return { ...profile, name: profile.name, description: profile.description, toolConfig, ...(match[2]?.trim() ? { promptBody: match[2].trim() } : {}) };
|
|
292
|
+
}
|
|
293
|
+
/** A curated registry: empty/unknown selections never inherit Grok's defaults. */
|
|
294
|
+
export function grokToolScopeProfile(scope, init, inherited, hasMcp = false) {
|
|
295
|
+
const parsed = parseToolScope(scope);
|
|
296
|
+
if (!parsed.ok)
|
|
297
|
+
throw new Error(parsed.error);
|
|
298
|
+
const meta = init?._meta;
|
|
299
|
+
if (meta?.grokShell !== true || meta.agentVersion !== "1.0.41") {
|
|
300
|
+
throw new Error("This Grok runtime does not have verified native tool selection. Use Grok 1.0.41 or Pi; update Grok support before using another version.");
|
|
301
|
+
}
|
|
302
|
+
if (hasMcp && (!allowsTool(scope, { kind: "native", name: "search_tool" }) || !allowsTool(scope, { kind: "native", name: "use_tool" }))) {
|
|
303
|
+
throw new Error("Grok requires native:search_tool and native:use_tool to find and call selected MCP tools. Allow both explicitly or use Pi.");
|
|
304
|
+
}
|
|
305
|
+
const candidates = inherited?.toolConfig.tools ?? GROK_NATIVE_TOOLS.map(([name, id]) => ({ id, name_override: name }));
|
|
306
|
+
const selected = candidates.filter((tool) => {
|
|
307
|
+
const name = tool.name_override ?? GROK_NATIVE_TOOLS.find(([, id]) => id === tool.id)?.[0];
|
|
308
|
+
return name !== undefined && allowsTool(scope, { kind: "native", name })
|
|
309
|
+
&& (!inherited?.tools?.length || inherited.tools.includes(name) || inherited.tools.includes(tool.id))
|
|
310
|
+
&& !inherited?.disallowedTools?.some((denied) => denied === name || denied === tool.id);
|
|
311
|
+
});
|
|
312
|
+
// Grok rejects an empty curated construction. A real read entry explicitly
|
|
313
|
+
// disabled by its exact native name constructs an empty final registry.
|
|
314
|
+
return {
|
|
315
|
+
...inherited, name: inherited?.name ?? "openmausbot-tools", description: inherited?.description ?? "Owner-selected tools",
|
|
316
|
+
injectDefaultTools: false, discoverSkills: false,
|
|
317
|
+
toolConfig: { ...inherited?.toolConfig, tools: selected.length ? selected : [{ id: "GrokBuild:read_file", name_override: "read_file" }] },
|
|
318
|
+
tools: [], disallowedTools: [...new Set([...(inherited?.disallowedTools ?? []), ...(selected.length ? [] : ["read_file"])])],
|
|
319
|
+
};
|
|
320
|
+
}
|
|
184
321
|
const support = {
|
|
185
322
|
driverKind: "grokAgent",
|
|
186
323
|
displayName: "Grok",
|
|
@@ -225,17 +362,69 @@ const support = {
|
|
|
225
362
|
? "bypassPermissions"
|
|
226
363
|
: turn.approvalMode === "auto" ? "auto" : turn.approvalMode === "edits" ? "acceptEdits" : "default",
|
|
227
364
|
"agent",
|
|
365
|
+
...(turn.toolScope !== undefined ? ["--no-leader"] : []),
|
|
228
366
|
...(turn.model ? ["-m", turn.model] : []),
|
|
229
367
|
// long form on purpose: `--effort` is documented as an alias, and an
|
|
230
368
|
// alias is the part a CLI is free to rename
|
|
231
369
|
...(turn.effort ? ["--reasoning-effort", turn.effort] : []),
|
|
232
370
|
"stdio",
|
|
233
371
|
],
|
|
372
|
+
toolScopeSessionParams: (turn, init, hasMcp, { config, env, cwd }) => {
|
|
373
|
+
if (!narrowsNativeTools(turn.toolScope))
|
|
374
|
+
return {};
|
|
375
|
+
const profile = grokToolScopeProfile(turn.toolScope, init, grokInheritedProfile(config.cli, env, cwd), hasMcp);
|
|
376
|
+
// Establish on the selected model. Changing harnesses after session/new
|
|
377
|
+
// can discard an ACP profile and restore the model's default tools.
|
|
378
|
+
if (turn.model)
|
|
379
|
+
profile.model = ensureGrokInjectSlug(turn.model, env);
|
|
380
|
+
return { _meta: { agentProfile: profile } };
|
|
381
|
+
},
|
|
382
|
+
toolScopeCacheKey: ({ config, env, cwd }) => JSON.stringify(grokInheritedProfile(config.cli, env, cwd) ?? null),
|
|
234
383
|
// -m on argv is necessary but not sufficient: session/new still starts on
|
|
235
384
|
// [models].default. Pin the slug over the wire, same as Hermes/Droid.
|
|
236
|
-
async configureSession({ request, sessionId, turn }) {
|
|
385
|
+
async configureSession({ request, sessionId, turn, currentModelId }) {
|
|
386
|
+
if (turn.toolScope !== undefined) {
|
|
387
|
+
const available = new Set([
|
|
388
|
+
...(turn.integrations?.agents ? ["agents"] : []), ...(turn.integrations?.composio ? ["composio"] : []),
|
|
389
|
+
...(turn.integrations?.browser ? ["browser"] : []), ...(turn.integrations?.localComputer ? ["computer"] : []),
|
|
390
|
+
...Object.keys(turn.integrations?.custom ?? {}),
|
|
391
|
+
].filter((name) => canUseMcpServer(turn.toolScope, name)));
|
|
392
|
+
const deadline = Date.now() + 10_000;
|
|
393
|
+
for (;;) {
|
|
394
|
+
const reply = await request("_x.ai/mcp/list", { sessionId, cache: true }, 10_000);
|
|
395
|
+
const catalog = reply?.result;
|
|
396
|
+
if (!Array.isArray(catalog?.servers))
|
|
397
|
+
throw new Error("Grok could not confirm its MCP catalog. No prompt was sent.");
|
|
398
|
+
for (const server of catalog.servers) {
|
|
399
|
+
if (server?.session?.enabled === false)
|
|
400
|
+
continue;
|
|
401
|
+
if (!available.has(server?.name))
|
|
402
|
+
throw new Error("Grok has an MCP connection outside the bot's selection. Disable native MCP connections before using tool selection. No prompt was sent.");
|
|
403
|
+
if (Array.isArray(server?.session?.tools) && server.session.tools.some((tool) => typeof tool.name !== "string" || !allowsTool(turn.toolScope, { kind: "mcp", server: server.name, name: tool.name }))) {
|
|
404
|
+
throw new Error("Grok could not enforce the selected MCP catalog. No prompt was sent.");
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (catalog.sessionMcpResolved === true) {
|
|
408
|
+
if ([...available].some((name) => !catalog.servers.some((server) => server.name === name && server.session?.enabled === true && server.session.status === "ready"))) {
|
|
409
|
+
throw new Error("Grok could not connect a selected MCP server. No prompt was sent.");
|
|
410
|
+
}
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
if (Date.now() >= deadline)
|
|
414
|
+
throw new Error("Grok did not confirm its selected MCP catalog in time. No prompt was sent.");
|
|
415
|
+
await delay(50);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
237
418
|
if (!turn.model)
|
|
238
419
|
return;
|
|
420
|
+
if (narrowsNativeTools(turn.toolScope)) {
|
|
421
|
+
if (currentModelId !== turn.model) {
|
|
422
|
+
throw new Error("Grok could not confirm the selected model without replacing its tool profile. Start a new conversation or choose a model with a supported Grok profile. No prompt was sent.");
|
|
423
|
+
}
|
|
424
|
+
// The inline profile already pins this model. Repeating set_model can
|
|
425
|
+
// rebuild the harness, or reject an otherwise valid resumed profile.
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
239
428
|
try {
|
|
240
429
|
await request("session/set_model", { sessionId, modelId: turn.model });
|
|
241
430
|
}
|
|
@@ -249,6 +438,22 @@ const support = {
|
|
|
249
438
|
transformEnv: (env) => {
|
|
250
439
|
delete env.XAI_API_KEY;
|
|
251
440
|
},
|
|
441
|
+
applyTurnEnv: (env, { toolScope }) => {
|
|
442
|
+
if (toolScope !== undefined) {
|
|
443
|
+
// The verified runtime gives these local env switches priority over
|
|
444
|
+
// account defaults, preventing an unfiltered managed gateway fallback.
|
|
445
|
+
env.GROK_MANAGED_MCPS_ENABLED = "false";
|
|
446
|
+
env.GROK_MANAGED_MCP_GATEWAY_TOOLS_ENABLED = "false";
|
|
447
|
+
if (narrowsNativeTools(toolScope)) {
|
|
448
|
+
if (env.GROK_AGENT && env.GROK_AGENT !== "grok-build") {
|
|
449
|
+
throw new Error("Grok's existing agent profile cannot be safely intersected with tool selection. Use a separate default Grok account.");
|
|
450
|
+
}
|
|
451
|
+
// A model's agent_type otherwise takes priority over ACP profiles.
|
|
452
|
+
// Preserve profile-file restrictions when intersecting them below.
|
|
453
|
+
env.GROK_AGENT = "grok-build";
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
},
|
|
252
457
|
// Bind the grok.com subscription login. No API-key fallback by design —
|
|
253
458
|
// an unauthenticated CLI is a user action, not something to paper over.
|
|
254
459
|
pickAuthMethod: (methods) => (methods.some((m) => m.id === "cached_token") ? "cached_token" : null),
|
|
@@ -507,6 +507,34 @@ export async function callTool(name, args, context) {
|
|
|
507
507
|
}) });
|
|
508
508
|
return { text: JSON.stringify(r), ...(r.error ? { isError: true } : {}) };
|
|
509
509
|
}
|
|
510
|
+
if (name === "send_to_bot") {
|
|
511
|
+
const botId = String(args.bot_id ?? "").trim();
|
|
512
|
+
const title = String(args.title ?? "").trim();
|
|
513
|
+
const message = String(args.message ?? "").trim();
|
|
514
|
+
if (!botId || !title || !message) {
|
|
515
|
+
return { text: "send_to_bot needs bot_id, title and message.", isError: true };
|
|
516
|
+
}
|
|
517
|
+
if (botId === context.botId) {
|
|
518
|
+
return { text: "send_to_bot is cross-bot only. Use start_thread to send independent work to yourself.", isError: true };
|
|
519
|
+
}
|
|
520
|
+
if (turn.threadsOpenedThisTurn >= MAX_THREADS_PER_TURN) {
|
|
521
|
+
return { text: `You have already opened ${MAX_THREADS_PER_TURN} threads this turn, which is the limit.`, isError: true };
|
|
522
|
+
}
|
|
523
|
+
const r = await api("/api/internal/threads", { method: "POST", body: JSON.stringify({
|
|
524
|
+
fromBotId: context.botId, fromThreadId: context.threadId, toBotId: botId,
|
|
525
|
+
title, message, depth: context.depth, oneWay: true,
|
|
526
|
+
}) });
|
|
527
|
+
if (r.error)
|
|
528
|
+
return { text: `Couldn't send to that bot: ${String(r.error)}`, isError: true };
|
|
529
|
+
turn.threadsOpenedThisTurn += 1;
|
|
530
|
+
const destination = `@${String(r.botName ?? "that bot")} in #${String(r.title ?? title)} [thread id: ${String(r.threadId ?? "")}]`;
|
|
531
|
+
const state = r.approvalRequired === true
|
|
532
|
+
? `Send to ${destination} is pending approval. The person's approval card appears after this turn ends; dispatch starts once approved.`
|
|
533
|
+
: r.state === "queued"
|
|
534
|
+
? `Send to ${destination} is queued ${ordinal(Number(r.position) || 1)} for a free slot and can dispatch after this turn ends.`
|
|
535
|
+
: `Send to ${destination} is pending until this turn ends, then dispatch starts.`;
|
|
536
|
+
return { text: `${state} Its result stays in that thread; nothing there will resume you.` };
|
|
537
|
+
}
|
|
510
538
|
if (name === "list_bots") {
|
|
511
539
|
const r = await api(`/api/internal/agents?self=${encodeURIComponent(BOT_ID)}`);
|
|
512
540
|
const bots = r.bots ?? [];
|
|
@@ -221,6 +221,15 @@ const toolDefinitions = (externalRuntime) => [
|
|
|
221
221
|
label: { type: "string", description: "Optional short name (one line, at most 60 characters) for this job. Names the new direct assignment thread." },
|
|
222
222
|
}, required: ["bot_ids", "message", "request_key"] },
|
|
223
223
|
},
|
|
224
|
+
{
|
|
225
|
+
name: "send_to_bot",
|
|
226
|
+
description: "Send work one way to another bot in a fresh thread. This is cross-bot only: use start_thread to send independent work to yourself. The recipient owns the new thread; its results, failures and questions stay there and never resume you. Use coordinate_bots when you need the result returned here.",
|
|
227
|
+
inputSchema: { type: "object", additionalProperties: false, properties: {
|
|
228
|
+
bot_id: { type: "string", description: "One reachable teammate id from list_bots or list_room_targets." },
|
|
229
|
+
title: { type: "string", description: "A short one-line title, at most 80 characters." },
|
|
230
|
+
message: { type: "string", description: "Complete instructions; the recipient does not inherit this conversation." },
|
|
231
|
+
}, required: ["bot_id", "title", "message"] },
|
|
232
|
+
},
|
|
224
233
|
{
|
|
225
234
|
name: "list_bots",
|
|
226
235
|
description: "List the other bots (agents) you may contact in your own team and any additional teams the owner has explicitly allowed you to coordinate, with their team, model and current status. Call this to discover exact teammate IDs before assigning work or requesting advice through your available coordination tools.",
|
|
@@ -757,7 +766,7 @@ const VOICE_TOOL_NAMES = new Set(["send_voice_note"]);
|
|
|
757
766
|
// One teamwork path in room turns; keep all unrelated integrations available.
|
|
758
767
|
// Ordinary direct chats use this same bounded coordinator. Goal-owned turns
|
|
759
768
|
// retain their independent loop and cannot start a second coordinator.
|
|
760
|
-
const ROOM_ONLY_TOOLS = new Set(["list_room_targets", "coordinate_bots"]);
|
|
769
|
+
const ROOM_ONLY_TOOLS = new Set(["list_room_targets", "coordinate_bots", "send_to_bot"]);
|
|
761
770
|
const ROOM_REPLACED_TOOLS = new Set(["ask_bot", "delegate_bot", "check_delegation", "wait_delegation", "start_thread", "send_to_thread", "wait_thread"]);
|
|
762
771
|
const EXTERNAL_TOOL_NAMES = new Set(["list_bots", "ask_bot", "delegate_bot", "check_delegation", "wait_delegation"]);
|
|
763
772
|
const WATCHER_TOOL_NAMES = new Set(["create_options_card"]);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { newEventId, newId } from "../contracts.js";
|
|
2
2
|
import { boatCredential, boatProviderApi } from "../included-services.js";
|
|
3
3
|
import { appendNative } from "./native.js";
|
|
4
|
+
import { assertToolScopeSupported } from "../../shared/tool-scope-support.js";
|
|
4
5
|
import { OMB_ASK_TOOL, answerWithoutPreamble, askQuestionSummary, capAnswerEcho, ombAskProtocolPrompt, parseOmbAskQuestions, questionChoices, stripOmbAskBlock, } from "../../shared/ask-question.js";
|
|
5
6
|
// "boxAgent" is Boat's historical driver kind (catalog and wire); keep it.
|
|
6
7
|
const DRIVER_KIND = "boxAgent";
|
|
@@ -96,6 +97,7 @@ export const BoatAgentDriver = {
|
|
|
96
97
|
return body;
|
|
97
98
|
};
|
|
98
99
|
const sendTurn = async (turn) => {
|
|
100
|
+
turn = { ...turn, toolScope: assertToolScopeSupported(DRIVER_KIND, turn.toolScope) };
|
|
99
101
|
const { threadId } = turn;
|
|
100
102
|
const computer = turn.integrations?.computer;
|
|
101
103
|
const boxId = computer && (!computer.kind || computer.kind === "box") ? computer.boxId : undefined;
|
|
@@ -8,6 +8,8 @@ import { augmentedPath } from "../env-path.js";
|
|
|
8
8
|
import { killCliTree, spawnCli } from "../procs.js";
|
|
9
9
|
import { chatImage } from "./chat-images.js";
|
|
10
10
|
import { ChatBoatClient } from "./chat-boat-tools.js";
|
|
11
|
+
import { mcpStdioServer } from "../mcp-gate-config.js";
|
|
12
|
+
import { allowsTool, canUseMcpServer, parseToolScope } from "../../shared/tool-scope.js";
|
|
11
13
|
/** The transport cannot safely continue this turn. A dispatched operation may
|
|
12
14
|
* already have taken effect, so callers must not retry it through a new round. */
|
|
13
15
|
export class ChatToolSessionError extends Error {
|
|
@@ -190,7 +192,7 @@ class ChatMcpClient {
|
|
|
190
192
|
throw error;
|
|
191
193
|
}
|
|
192
194
|
}
|
|
193
|
-
async tools(signal) {
|
|
195
|
+
async tools(signal, include = () => true) {
|
|
194
196
|
const deadline = Date.now() + STARTUP_MS;
|
|
195
197
|
const remaining = () => {
|
|
196
198
|
if (Date.now() >= deadline)
|
|
@@ -212,7 +214,7 @@ class ChatMcpClient {
|
|
|
212
214
|
const result = await this.call("tools/list", cursor ? { cursor } : {}, signal, remaining());
|
|
213
215
|
if (!object(result) || !Array.isArray(result.tools))
|
|
214
216
|
throw new Error("MCP tools/list returned an invalid result");
|
|
215
|
-
tools.push(...result.tools);
|
|
217
|
+
tools.push(...result.tools.filter(include));
|
|
216
218
|
if (tools.length > TOOL_COUNT)
|
|
217
219
|
throw new Error("MCP tool count exceeds the 128-tool limit");
|
|
218
220
|
if (result.nextCursor === undefined)
|
|
@@ -282,23 +284,30 @@ function omitBlankDefaults(builtInBrowser, tool, args) {
|
|
|
282
284
|
delete args[field];
|
|
283
285
|
}
|
|
284
286
|
}
|
|
285
|
-
export async function mountChatTools(integrations, signal, computerUse = false) {
|
|
287
|
+
export async function mountChatTools(integrations, signal, computerUse = false, toolScope) {
|
|
288
|
+
const parsed = parseToolScope(toolScope);
|
|
289
|
+
if (!parsed.ok)
|
|
290
|
+
throw new Error(parsed.error);
|
|
291
|
+
const scope = parsed.scope;
|
|
286
292
|
const servers = [];
|
|
287
|
-
|
|
293
|
+
const eligible = (server) => scope === undefined || canUseMcpServer(scope, server);
|
|
294
|
+
if (computerUse && integrations?.computer && eligible("computer"))
|
|
288
295
|
servers.push(["computer", integrations.computer]);
|
|
289
|
-
if (computerUse && integrations?.localComputer)
|
|
296
|
+
if (computerUse && integrations?.localComputer && eligible("computer"))
|
|
290
297
|
servers.push(["computer", integrations.localComputer]);
|
|
291
|
-
if (computerUse && integrations?.browser)
|
|
298
|
+
if (computerUse && integrations?.browser && eligible("browser"))
|
|
292
299
|
servers.push(["browser", integrations.browser]);
|
|
293
|
-
if (integrations?.agents)
|
|
300
|
+
if (integrations?.agents && eligible("agents"))
|
|
294
301
|
servers.push(["agents", integrations.agents]);
|
|
295
|
-
if (integrations?.composio)
|
|
302
|
+
if (integrations?.composio && eligible("composio"))
|
|
296
303
|
servers.push(["composio", integrations.composio]);
|
|
297
|
-
// this client starts its servers and talks over stdio; a remote (url)
|
|
298
|
-
// entry is skipped here and reaches Claude and Codex bots
|
|
299
304
|
for (const [name, server] of Object.entries(integrations?.custom ?? {})) {
|
|
300
|
-
if (
|
|
301
|
-
|
|
305
|
+
if (!eligible(name))
|
|
306
|
+
continue;
|
|
307
|
+
const stdio = mcpStdioServer(server, { nodeEnv: { ELECTRON_RUN_AS_NODE: "1" } });
|
|
308
|
+
if (!stdio)
|
|
309
|
+
throw new Error("MCP server configuration is invalid");
|
|
310
|
+
servers.push([name, { command: stdio.command, args: stdio.args ?? [], env: stdio.env ?? {} }]);
|
|
302
311
|
}
|
|
303
312
|
if (servers.length > 32)
|
|
304
313
|
throw new Error("MCP server count exceeds the 32-server limit");
|
|
@@ -332,7 +341,9 @@ export async function mountChatTools(integrations, signal, computerUse = false)
|
|
|
332
341
|
// image delivery, including custom servers. Text stays bounded below.
|
|
333
342
|
const client = "boxId" in descriptor ? new ChatBoatClient(descriptor) : new ChatMcpClient(descriptor, computerUse);
|
|
334
343
|
clients.push(client);
|
|
335
|
-
|
|
344
|
+
const include = (tool) => scope === undefined || (object(tool) && typeof tool.name === "string" && allowsTool(scope, { kind: "mcp", server: name, name: tool.name }));
|
|
345
|
+
const tools = client instanceof ChatMcpClient ? await client.tools(signal, include) : (await client.tools()).filter(include);
|
|
346
|
+
return { name, client, builtInBrowser: descriptor === integrations?.browser, tools };
|
|
336
347
|
}));
|
|
337
348
|
for (const mount of mounts) {
|
|
338
349
|
if (mount.status === "rejected")
|
|
@@ -368,7 +379,7 @@ export async function mountChatTools(integrations, signal, computerUse = false)
|
|
|
368
379
|
const suffix = `_${index}`;
|
|
369
380
|
name = base.slice(0, 64 - suffix.length) + suffix;
|
|
370
381
|
}
|
|
371
|
-
registered.set(name, { client, builtInBrowser, name: tool.name, schema });
|
|
382
|
+
registered.set(name, { client, server, builtInBrowser, name: tool.name, schema });
|
|
372
383
|
definitions.push({ type: "function", function: { name, description, parameters } });
|
|
373
384
|
if (Buffer.byteLength(JSON.stringify(definitions)) > CATALOG_BYTES)
|
|
374
385
|
throw new Error("MCP tool catalog exceeds the 1MB limit");
|
|
@@ -387,6 +398,8 @@ export async function mountChatTools(integrations, signal, computerUse = false)
|
|
|
387
398
|
const tool = registered.get(name);
|
|
388
399
|
if (!tool)
|
|
389
400
|
throw new Error("The requested tool was not advertised for this turn");
|
|
401
|
+
if (scope !== undefined && !allowsTool(scope, { kind: "mcp", server: tool.server, name: tool.name }))
|
|
402
|
+
throw new Error("Tool selection excludes this tool");
|
|
390
403
|
omitBlankDefaults(tool.builtInBrowser, tool.name, args);
|
|
391
404
|
if (!object(args) || !tool.schema(args))
|
|
392
405
|
throw new Error("Tool arguments do not match the advertised input schema; use its required fields and types");
|
|
@@ -20,6 +20,8 @@ import { brokerSocketPath, describeSpawnFailure, execCli, killCliTree, spawnCli
|
|
|
20
20
|
import { classifyResumeFailure, mayReplay, recoveryPromptFor } from "../resume-recovery.js";
|
|
21
21
|
import { ClaudeLoginController } from "./claude-login-auth.js";
|
|
22
22
|
import { gateServer, resultBudget } from "../mcp-gate-config.js";
|
|
23
|
+
import { canUseMcpServer } from "../../shared/tool-scope.js";
|
|
24
|
+
import { assertToolScopeSupported } from "../../shared/tool-scope-support.js";
|
|
23
25
|
import { newEventId, newId } from "../contracts.js";
|
|
24
26
|
import { askInputSummary, commandSummary, toolDetailPreview } from "../tool-summary.js";
|
|
25
27
|
import { classifyError, computeBackoff, interruptibleDelay, RETRY_MAX_ATTEMPTS } from "./retry.js";
|
|
@@ -1127,6 +1129,7 @@ export const ClaudeDriver = {
|
|
|
1127
1129
|
// is a fresh sendTurn, and the attempt cap must survive across launches
|
|
1128
1130
|
const retryState = new Map();
|
|
1129
1131
|
const sendTurn = async (turn, logicalTurnId) => {
|
|
1132
|
+
turn = { ...turn, toolScope: assertToolScopeSupported(DRIVER_KIND, turn.toolScope) };
|
|
1130
1133
|
if (config.managedModels && (!turn.model || !config.managedModels.includes(turn.model)))
|
|
1131
1134
|
throw new Error("This model is not assigned to this workspace.");
|
|
1132
1135
|
if (config.requireApiKey && !input.environment.ANTHROPIC_API_KEY)
|
|
@@ -1200,7 +1203,7 @@ export const ClaudeDriver = {
|
|
|
1200
1203
|
args.push("--disallowedTools", config.disallowedTools.join(","));
|
|
1201
1204
|
}
|
|
1202
1205
|
const turnEnvironment = environment();
|
|
1203
|
-
if ((turn.refreshSystemPrompt || turn.guestConfined) && !cliVersionChecked) {
|
|
1206
|
+
if ((turn.refreshSystemPrompt || turn.guestConfined || turn.toolScope !== undefined) && !cliVersionChecked) {
|
|
1204
1207
|
const version = await readCliVersion(turnEnvironment);
|
|
1205
1208
|
if (version) {
|
|
1206
1209
|
cliVersion = parseClaudeCliVersion(version);
|
|
@@ -1216,6 +1219,9 @@ export const ClaudeDriver = {
|
|
|
1216
1219
|
if (replaysUserMessages)
|
|
1217
1220
|
args.push("--replay-user-messages");
|
|
1218
1221
|
const isolated = !inheritsUserConfig(turnEnvironment);
|
|
1222
|
+
if (turn.toolScope !== undefined && (!isolated || turn.mcpFromUserConfig || cliVersion === null || !claudeCliSupports(cliVersion, "--strict-mcp-config"))) {
|
|
1223
|
+
throw new Error("Claude cannot confirm a restricted MCP configuration for this account. Disable inherited Claude MCP servers and use a current, identifiable Claude CLI before using tool selection.");
|
|
1224
|
+
}
|
|
1219
1225
|
if (isolated) {
|
|
1220
1226
|
// A bot gets the tools and instructions its owner gave it, not
|
|
1221
1227
|
// whatever this machine's Claude Code happens to be set up with.
|
|
@@ -1350,11 +1356,16 @@ export const ClaudeDriver = {
|
|
|
1350
1356
|
// permission broker, computer, browser, agents, dweb) are already
|
|
1351
1357
|
// bounded and are deliberately left alone.
|
|
1352
1358
|
const budget = resultBudget(turnEnvironment);
|
|
1353
|
-
for (const name of botOwned) {
|
|
1354
|
-
|
|
1359
|
+
for (const name of turn.toolScope === undefined ? botOwned : Object.keys(mcpServers)) {
|
|
1360
|
+
if (!canUseMcpServer(turn.toolScope, name)) {
|
|
1361
|
+
delete mcpServers[name];
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1364
|
+
const gated = gateServer({ name, server: mcpServers[name], threadId, budget: botOwned.has(name) ? budget : 0, nodeEnv: NODE_ENV_FLAG, toolScope: turn.toolScope });
|
|
1355
1365
|
if (gated)
|
|
1356
|
-
mcpServers[name] = gated;
|
|
1366
|
+
mcpServers[name] = { ...gated, ...(mcpServers[name]?.alwaysLoad === true ? { alwaysLoad: true } : {}) };
|
|
1357
1367
|
}
|
|
1368
|
+
allowed.splice(0, allowed.length, ...allowed.filter((name) => Object.hasOwn(mcpServers, name.slice("mcp__".length))));
|
|
1358
1369
|
// Keep ask_user available even in Full access. Native bypass skips
|
|
1359
1370
|
// permission prompts, not questions requiring a person's answer.
|
|
1360
1371
|
let broker;
|
|
@@ -1362,7 +1373,8 @@ export const ClaudeDriver = {
|
|
|
1362
1373
|
if (permissionMode !== "bypassPermissions") {
|
|
1363
1374
|
args.push("--permission-prompt-tool", "mcp__ogb__approve");
|
|
1364
1375
|
}
|
|
1365
|
-
|
|
1376
|
+
const permissionEnv = { ...NODE_ENV_FLAG, ...(turn.toolScope !== undefined ? { OMB_PERMISSION_TOOL_SCOPE: JSON.stringify(turn.toolScope) } : {}) };
|
|
1377
|
+
mcpServers.ogb = { command: process.execPath, args: [PERM_PROXY_PATH, socketPath], env: permissionEnv, alwaysLoad: true };
|
|
1366
1378
|
allowed.push("mcp__ogb");
|
|
1367
1379
|
// A guest's turn pre-allows only the harness's own tools: anything
|
|
1368
1380
|
// else (the browser can open a file: address) asks the owner first.
|
|
@@ -1575,7 +1587,7 @@ export const ClaudeDriver = {
|
|
|
1575
1587
|
// the base path: the nonce is not part of the spawn contract, and a
|
|
1576
1588
|
// retained session keeps its own broker object anyway.
|
|
1577
1589
|
if (broker.socketPath !== socketPath && mcpConfigPath) {
|
|
1578
|
-
mcpServers.ogb = { command: process.execPath, args: [PERM_PROXY_PATH, broker.socketPath], env:
|
|
1590
|
+
mcpServers.ogb = { command: process.execPath, args: [PERM_PROXY_PATH, broker.socketPath], env: permissionEnv, alwaysLoad: true };
|
|
1579
1591
|
}
|
|
1580
1592
|
}
|
|
1581
1593
|
// Write once, only after the broker has selected its real endpoint.
|