mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-26 08:31:55 +00:00
test: verify claude cli mcp cron e2e
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import {
|
||||
createBundleMcpTempHarness,
|
||||
createBundleProbePlugin,
|
||||
writeClaudeBundleManifest,
|
||||
} from "../../plugins/bundle-mcp.test-support.js";
|
||||
import { captureEnv } from "../../test-utils/env.js";
|
||||
import { prepareCliBundleMcpConfig } from "./bundle-mcp.js";
|
||||
@@ -85,6 +87,60 @@ describe("prepareCliBundleMcpConfig", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("loads workspace bundle MCP plugins from the configured workspace root", async () => {
|
||||
const workspaceDir = await tempHarness.createTempDir("openclaw-cli-bundle-mcp-workspace-root-");
|
||||
const pluginRoot = path.join(workspaceDir, ".openclaw", "extensions", "workspace-probe");
|
||||
const serverPath = path.join(pluginRoot, "servers", "probe.mjs");
|
||||
await fs.mkdir(path.dirname(serverPath), { recursive: true });
|
||||
await fs.writeFile(serverPath, "export {};\n", "utf-8");
|
||||
await writeClaudeBundleManifest({
|
||||
homeDir: workspaceDir,
|
||||
pluginId: "workspace-probe",
|
||||
manifest: { name: "workspace-probe" },
|
||||
});
|
||||
await fs.writeFile(
|
||||
path.join(pluginRoot, ".mcp.json"),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
mcpServers: {
|
||||
workspaceProbe: {
|
||||
command: "node",
|
||||
args: ["./servers/probe.mjs"],
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const prepared = await prepareCliBundleMcpConfig({
|
||||
enabled: true,
|
||||
backend: {
|
||||
command: "node",
|
||||
args: ["./fake-claude.mjs"],
|
||||
},
|
||||
workspaceDir,
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
"workspace-probe": { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const configFlagIndex = prepared.backend.args?.indexOf("--mcp-config") ?? -1;
|
||||
const generatedConfigPath = prepared.backend.args?.[configFlagIndex + 1];
|
||||
const raw = JSON.parse(await fs.readFile(generatedConfigPath as string, "utf-8")) as {
|
||||
mcpServers?: Record<string, { args?: string[] }>;
|
||||
};
|
||||
expect(raw.mcpServers?.workspaceProbe?.args).toEqual([await fs.realpath(serverPath)]);
|
||||
|
||||
await prepared.cleanup?.();
|
||||
});
|
||||
|
||||
it("merges loopback overlay config with bundle MCP servers", async () => {
|
||||
const env = captureEnv(["HOME"]);
|
||||
try {
|
||||
|
||||
@@ -56,6 +56,10 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [
|
||||
policy: { routeConfigGuard: "always" },
|
||||
route: { id: "gateway-status" },
|
||||
},
|
||||
{
|
||||
commandPath: ["cron"],
|
||||
policy: { bypassConfigGuard: true },
|
||||
},
|
||||
{
|
||||
commandPath: ["sessions"],
|
||||
exact: true,
|
||||
|
||||
@@ -51,5 +51,12 @@ describe("command-path-policy", () => {
|
||||
hideBanner: true,
|
||||
ensureCliPath: true,
|
||||
});
|
||||
expect(resolveCliCommandPathPolicy(["cron", "list"])).toEqual({
|
||||
bypassConfigGuard: true,
|
||||
routeConfigGuard: "never",
|
||||
loadPlugins: "never",
|
||||
hideBanner: false,
|
||||
ensureCliPath: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -274,6 +274,22 @@ describe("callGateway url resolution", () => {
|
||||
expect(lastClientOptions?.token).toBe("explicit-token");
|
||||
});
|
||||
|
||||
it("skips config loading when explicit url and token are provided", async () => {
|
||||
loadConfig.mockImplementation(() => {
|
||||
throw new Error("loadConfig should not run");
|
||||
});
|
||||
|
||||
await callGatewayCli({
|
||||
method: "health",
|
||||
url: "ws://127.0.0.1:18800",
|
||||
token: "test-token",
|
||||
});
|
||||
|
||||
expect(loadConfig).not.toHaveBeenCalled();
|
||||
expect(lastClientOptions?.url).toBe("ws://127.0.0.1:18800");
|
||||
expect(lastClientOptions?.token).toBe("test-token");
|
||||
});
|
||||
|
||||
it("keeps device identity enabled for local loopback shared-token auth", async () => {
|
||||
setLocalLoopbackGatewayConfig();
|
||||
|
||||
|
||||
@@ -289,20 +289,22 @@ function resolveGatewayCallTimeout(timeoutValue: unknown): {
|
||||
}
|
||||
|
||||
function resolveGatewayCallContext(opts: CallGatewayBaseOptions): ResolvedGatewayCallContext {
|
||||
const config = opts.config ?? loadGatewayConfig();
|
||||
const configPath = opts.configPath ?? resolveGatewayConfigPath(process.env);
|
||||
const isRemoteMode = config.gateway?.mode === "remote";
|
||||
const remote = isRemoteMode
|
||||
? (config.gateway?.remote as GatewayRemoteSettings | undefined)
|
||||
: undefined;
|
||||
const cliUrlOverride = trimToUndefined(opts.url);
|
||||
const explicitAuth = resolveExplicitGatewayAuth({ token: opts.token, password: opts.password });
|
||||
const envUrlOverride = cliUrlOverride
|
||||
? undefined
|
||||
: trimToUndefined(process.env.OPENCLAW_GATEWAY_URL);
|
||||
const urlOverride = cliUrlOverride ?? envUrlOverride;
|
||||
const urlOverrideSource = cliUrlOverride ? "cli" : envUrlOverride ? "env" : undefined;
|
||||
const canSkipConfigLoad =
|
||||
!opts.config && urlOverride && (explicitAuth.token || explicitAuth.password);
|
||||
const config = opts.config ?? (canSkipConfigLoad ? ({} as OpenClawConfig) : loadGatewayConfig());
|
||||
const configPath = opts.configPath ?? resolveGatewayConfigPath(process.env);
|
||||
const isRemoteMode = config.gateway?.mode === "remote";
|
||||
const remote = isRemoteMode
|
||||
? (config.gateway?.remote as GatewayRemoteSettings | undefined)
|
||||
: undefined;
|
||||
const remoteUrl = trimToUndefined(remote?.url);
|
||||
const explicitAuth = resolveExplicitGatewayAuth({ token: opts.token, password: opts.password });
|
||||
return {
|
||||
config,
|
||||
configPath,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { randomBytes, randomUUID } from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { isLiveTestEnabled } from "../agents/live-test-helpers.js";
|
||||
import { parseModelRef } from "../agents/model-selection.js";
|
||||
@@ -14,13 +16,14 @@ import { renderCatNoncePngBase64 } from "./live-image-probe.js";
|
||||
import { startGatewayServer } from "./server.js";
|
||||
import { extractPayloadText } from "./test-helpers.agent-results.js";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const LIVE = isLiveTestEnabled();
|
||||
const CLI_LIVE = isTruthyEnvValue(process.env.OPENCLAW_LIVE_CLI_BACKEND);
|
||||
const CLI_RESUME = isTruthyEnvValue(process.env.OPENCLAW_LIVE_CLI_BACKEND_RESUME_PROBE);
|
||||
const describeLive = LIVE && CLI_LIVE ? describe : describe.skip;
|
||||
|
||||
const DEFAULT_MODEL = "claude-cli/claude-sonnet-4-6";
|
||||
const CLI_BACKEND_LIVE_TIMEOUT_MS = 180_000;
|
||||
const CLI_BACKEND_LIVE_TIMEOUT_MS = 420_000;
|
||||
const CLI_GATEWAY_CONNECT_TIMEOUT_MS = 30_000;
|
||||
const DEFAULT_CLAUDE_ARGS = [
|
||||
"-p",
|
||||
@@ -77,39 +80,6 @@ function randomImageProbeCode(len = 6): string {
|
||||
return out;
|
||||
}
|
||||
|
||||
function editDistance(a: string, b: string): number {
|
||||
if (a === b) {
|
||||
return 0;
|
||||
}
|
||||
const aLen = a.length;
|
||||
const bLen = b.length;
|
||||
if (aLen === 0) {
|
||||
return bLen;
|
||||
}
|
||||
if (bLen === 0) {
|
||||
return aLen;
|
||||
}
|
||||
|
||||
let prev = Array.from({ length: bLen + 1 }, (_v, idx) => idx);
|
||||
let curr = Array.from({ length: bLen + 1 }, () => 0);
|
||||
|
||||
for (let i = 1; i <= aLen; i += 1) {
|
||||
curr[0] = i;
|
||||
const aCh = a.charCodeAt(i - 1);
|
||||
for (let j = 1; j <= bLen; j += 1) {
|
||||
const cost = aCh === b.charCodeAt(j - 1) ? 0 : 1;
|
||||
curr[j] = Math.min(
|
||||
prev[j] + 1, // delete
|
||||
curr[j - 1] + 1, // insert
|
||||
prev[j - 1] + cost, // substitute
|
||||
);
|
||||
}
|
||||
[prev, curr] = [curr, prev];
|
||||
}
|
||||
|
||||
return prev[bLen] ?? Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
function parseJsonStringArray(name: string, raw?: string): string[] | undefined {
|
||||
const trimmed = raw?.trim();
|
||||
if (!trimmed) {
|
||||
@@ -167,6 +137,7 @@ async function getFreeGatewayPort(): Promise<number> {
|
||||
|
||||
type BootstrapWorkspaceContext = {
|
||||
expectedInjectedFiles: string[];
|
||||
workspaceDir: string;
|
||||
workspaceRootDir: string;
|
||||
};
|
||||
|
||||
@@ -174,6 +145,17 @@ type SystemPromptReport = {
|
||||
injectedWorkspaceFiles?: Array<{ name?: string }>;
|
||||
};
|
||||
|
||||
type CronListCliResult = {
|
||||
jobs?: Array<{
|
||||
id?: string;
|
||||
name?: string;
|
||||
sessionTarget?: string;
|
||||
agentId?: string | null;
|
||||
sessionKey?: string | null;
|
||||
payload?: { kind?: string; text?: string; message?: string };
|
||||
}>;
|
||||
};
|
||||
|
||||
async function createBootstrapWorkspace(tempDir: string): Promise<BootstrapWorkspaceContext> {
|
||||
const workspaceRootDir = path.join(tempDir, "workspace");
|
||||
const workspaceDir = path.join(workspaceRootDir, "dev");
|
||||
@@ -191,7 +173,47 @@ async function createBootstrapWorkspace(tempDir: string): Promise<BootstrapWorks
|
||||
await fs.writeFile(path.join(workspaceDir, "SOUL.md"), `SOUL-${randomUUID()}\n`);
|
||||
await fs.writeFile(path.join(workspaceDir, "IDENTITY.md"), `IDENTITY-${randomUUID()}\n`);
|
||||
await fs.writeFile(path.join(workspaceDir, "USER.md"), `USER-${randomUUID()}\n`);
|
||||
return { expectedInjectedFiles, workspaceRootDir };
|
||||
return { expectedInjectedFiles, workspaceDir, workspaceRootDir };
|
||||
}
|
||||
|
||||
async function runOpenClawCliJson<T>(args: string[], env: NodeJS.ProcessEnv): Promise<T> {
|
||||
const childEnv = { ...env };
|
||||
delete childEnv.VITEST;
|
||||
delete childEnv.VITEST_MODE;
|
||||
delete childEnv.VITEST_POOL_ID;
|
||||
delete childEnv.VITEST_WORKER_ID;
|
||||
const { stdout, stderr } = await execFileAsync(process.execPath, ["openclaw.mjs", ...args], {
|
||||
cwd: process.cwd(),
|
||||
env: childEnv,
|
||||
timeout: 30_000,
|
||||
maxBuffer: 1024 * 1024,
|
||||
});
|
||||
const trimmed = stdout.trim();
|
||||
if (!trimmed) {
|
||||
throw new Error(
|
||||
[
|
||||
`openclaw ${args.join(" ")} produced no JSON stdout`,
|
||||
stderr.trim() ? `stderr: ${stderr.trim()}` : undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n"),
|
||||
);
|
||||
}
|
||||
try {
|
||||
return JSON.parse(trimmed) as T;
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
[
|
||||
`openclaw ${args.join(" ")} returned invalid JSON`,
|
||||
`stdout: ${trimmed}`,
|
||||
stderr.trim() ? `stderr: ${stderr.trim()}` : undefined,
|
||||
error instanceof Error ? `cause: ${error.message}` : undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n"),
|
||||
{ cause: error },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
@@ -318,6 +340,7 @@ describeLive("gateway live (cli backend)", () => {
|
||||
|
||||
const token = `test-${randomUUID()}`;
|
||||
process.env.OPENCLAW_GATEWAY_TOKEN = token;
|
||||
const port = await getFreeGatewayPort();
|
||||
|
||||
const rawModel = process.env.OPENCLAW_LIVE_CLI_BACKEND_MODEL ?? DEFAULT_MODEL;
|
||||
const parsed = parseModelRef(rawModel, "claude-cli");
|
||||
@@ -404,6 +427,11 @@ describeLive("gateway live (cli backend)", () => {
|
||||
const existingBackends = cfgWithCliBackends.agents?.defaults?.cliBackends ?? {};
|
||||
const nextCfg = {
|
||||
...cfg,
|
||||
gateway: {
|
||||
...cfg.gateway,
|
||||
port,
|
||||
auth: { mode: "token", token },
|
||||
},
|
||||
agents: {
|
||||
...cfg.agents,
|
||||
defaults: {
|
||||
@@ -432,7 +460,6 @@ describeLive("gateway live (cli backend)", () => {
|
||||
await fs.writeFile(tempConfigPath, `${JSON.stringify(nextCfg, null, 2)}\n`);
|
||||
process.env.OPENCLAW_CONFIG_PATH = tempConfigPath;
|
||||
|
||||
const port = await getFreeGatewayPort();
|
||||
const server = await startGatewayServer(port, {
|
||||
bind: "loopback",
|
||||
auth: { mode: "token", token },
|
||||
@@ -511,47 +538,140 @@ describeLive("gateway live (cli backend)", () => {
|
||||
}
|
||||
|
||||
if (enableCliImageProbe) {
|
||||
// Shorter code => less OCR flake across providers, still tests image attachments end-to-end.
|
||||
const imageCode = randomImageProbeCode();
|
||||
const imageBase64 = renderCatNoncePngBase64(imageCode);
|
||||
const runIdImage = randomUUID();
|
||||
const imageFilePath = path.join(
|
||||
bootstrapWorkspace?.workspaceDir ?? tempDir,
|
||||
`probe-${runIdImage}.png`,
|
||||
);
|
||||
await fs.writeFile(imageFilePath, Buffer.from(imageBase64, "base64"));
|
||||
|
||||
const imageProbe = await client.request(
|
||||
"agent",
|
||||
{
|
||||
sessionKey,
|
||||
idempotencyKey: `idem-${runIdImage}-image`,
|
||||
message:
|
||||
"Look at the attached image. Reply with exactly two tokens separated by a single space: " +
|
||||
"(1) the animal shown or written in the image, lowercase; " +
|
||||
"(2) the code printed in the image, uppercase. No extra text.",
|
||||
attachments: [
|
||||
{
|
||||
mimeType: "image/png",
|
||||
fileName: `probe-${runIdImage}.png`,
|
||||
content: imageBase64,
|
||||
providerId === "claude-cli"
|
||||
? {
|
||||
sessionKey,
|
||||
idempotencyKey: `idem-${runIdImage}-image`,
|
||||
message:
|
||||
`Image path: ${imageFilePath}\n` +
|
||||
"Best match: lobster, mouse, cat, horse. " +
|
||||
"Reply with one lowercase word only.",
|
||||
deliver: false,
|
||||
}
|
||||
: {
|
||||
sessionKey,
|
||||
idempotencyKey: `idem-${runIdImage}-image`,
|
||||
message:
|
||||
"Best match for the attached image: lobster, mouse, cat, horse. " +
|
||||
"Reply with one lowercase word only.",
|
||||
attachments: [
|
||||
{
|
||||
mimeType: "image/png",
|
||||
fileName: `probe-${runIdImage}.png`,
|
||||
content: imageBase64,
|
||||
},
|
||||
],
|
||||
deliver: false,
|
||||
},
|
||||
],
|
||||
deliver: false,
|
||||
},
|
||||
{ expectFinal: true },
|
||||
);
|
||||
if (imageProbe?.status !== "ok") {
|
||||
throw new Error(`image probe failed: status=${String(imageProbe?.status)}`);
|
||||
}
|
||||
const imageText = extractPayloadText(imageProbe?.result);
|
||||
if (!/\bcat\b/i.test(imageText)) {
|
||||
throw new Error(`image probe missing 'cat': ${imageText}`);
|
||||
const imageText = extractPayloadText(imageProbe?.result).trim().toLowerCase();
|
||||
if (imageText !== "cat") {
|
||||
throw new Error(`image probe expected 'cat', got: ${imageText}`);
|
||||
}
|
||||
const candidates = imageText.toUpperCase().match(/[A-Z0-9]{6,20}/g) ?? [];
|
||||
const bestDistance = candidates.reduce((best, cand) => {
|
||||
if (Math.abs(cand.length - imageCode.length) > 2) {
|
||||
return best;
|
||||
}
|
||||
|
||||
if (providerId === "claude-cli") {
|
||||
const cronProbeNonce = randomBytes(3).toString("hex").toUpperCase();
|
||||
const cronProbeName = `live-mcp-${cronProbeNonce.toLowerCase()}`;
|
||||
const cronProbeMessage = `probe-${cronProbeNonce.toLowerCase()}`;
|
||||
const cronProbeAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const cronArgsJson = JSON.stringify({
|
||||
action: "add",
|
||||
job: {
|
||||
name: cronProbeName,
|
||||
schedule: { kind: "at", at: cronProbeAt },
|
||||
payload: { kind: "agentTurn", message: cronProbeMessage },
|
||||
sessionTarget: "current",
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
let createdJob: CronListCliResult["jobs"] extends Array<infer T> ? T | undefined : never;
|
||||
let lastCronText = "";
|
||||
|
||||
for (let attempt = 0; attempt < 2 && !createdJob; attempt += 1) {
|
||||
const runIdMcp = randomUUID();
|
||||
const cronProbe = await client.request(
|
||||
"agent",
|
||||
{
|
||||
sessionKey,
|
||||
idempotencyKey: `idem-${runIdMcp}-mcp-${attempt}`,
|
||||
message:
|
||||
attempt === 0
|
||||
? "Use the OpenClaw MCP tool named cron. " +
|
||||
`Call it with JSON arguments ${cronArgsJson}. ` +
|
||||
"Do the actual tool call; I will verify externally with the OpenClaw cron CLI. " +
|
||||
`After the cron job is created, reply exactly: ${cronProbeName}`
|
||||
: "Return only a tool call for the OpenClaw MCP tool `cron`. " +
|
||||
`Use these exact JSON arguments: ${cronArgsJson}. ` +
|
||||
"No prose. I will verify externally with the OpenClaw cron CLI.",
|
||||
deliver: false,
|
||||
},
|
||||
{ expectFinal: true },
|
||||
);
|
||||
if (cronProbe?.status !== "ok") {
|
||||
throw new Error(`cron mcp probe failed: status=${String(cronProbe?.status)}`);
|
||||
}
|
||||
return Math.min(best, editDistance(cand, imageCode));
|
||||
}, Number.POSITIVE_INFINITY);
|
||||
if (!(bestDistance <= 5)) {
|
||||
throw new Error(`image probe missing code (${imageCode}): ${imageText}`);
|
||||
lastCronText = extractPayloadText(cronProbe?.result).trim();
|
||||
const cronList = await runOpenClawCliJson<CronListCliResult>(
|
||||
[
|
||||
"cron",
|
||||
"list",
|
||||
"--all",
|
||||
"--json",
|
||||
"--url",
|
||||
`ws://127.0.0.1:${port}`,
|
||||
"--token",
|
||||
token,
|
||||
],
|
||||
process.env,
|
||||
);
|
||||
createdJob =
|
||||
cronList.jobs?.find((job) => job.name === cronProbeName) ??
|
||||
cronList.jobs?.find((job) => job.payload?.message === cronProbeMessage);
|
||||
if (!createdJob && attempt === 1) {
|
||||
throw new Error(
|
||||
`cron cli verify could not find job ${cronProbeName}: reply=${JSON.stringify(lastCronText)} list=${JSON.stringify(cronList)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!createdJob) {
|
||||
throw new Error(`cron cli verify did not create job ${cronProbeName}`);
|
||||
}
|
||||
expect(createdJob.name).toBe(cronProbeName);
|
||||
expect(createdJob?.payload?.kind).toBe("agentTurn");
|
||||
expect(createdJob?.payload?.message).toBe(cronProbeMessage);
|
||||
expect(createdJob?.agentId).toBe("dev");
|
||||
expect(createdJob?.sessionKey).toBe(sessionKey);
|
||||
expect(createdJob?.sessionTarget).toBe(`session:${sessionKey}`);
|
||||
if (createdJob?.id) {
|
||||
await runOpenClawCliJson(
|
||||
[
|
||||
"cron",
|
||||
"rm",
|
||||
createdJob.id,
|
||||
"--json",
|
||||
"--url",
|
||||
`ws://127.0.0.1:${port}`,
|
||||
"--token",
|
||||
token,
|
||||
],
|
||||
process.env,
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -45,6 +45,7 @@ export class McpLoopbackToolCache {
|
||||
sessionKey: params.sessionKey,
|
||||
messageProvider: params.messageProvider,
|
||||
accountId: params.accountId,
|
||||
applyDefaultGatewayHttpDeny: false,
|
||||
excludeToolNames: NATIVE_TOOL_EXCLUDE,
|
||||
});
|
||||
const nextEntry: CachedScopedTools = {
|
||||
|
||||
@@ -103,6 +103,7 @@ describe("mcp loopback server", () => {
|
||||
sessionKey: "agent:main:telegram:group:chat123",
|
||||
accountId: "work",
|
||||
messageProvider: "telegram",
|
||||
applyDefaultGatewayHttpDeny: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@ export function resolveGatewayScopedTools(params: {
|
||||
agentThreadId?: string;
|
||||
allowGatewaySubagentBinding?: boolean;
|
||||
allowMediaInvokeCommands?: boolean;
|
||||
applyDefaultGatewayHttpDeny?: boolean;
|
||||
excludeToolNames?: Iterable<string>;
|
||||
disablePluginTools?: boolean;
|
||||
}) {
|
||||
@@ -112,9 +113,9 @@ export function resolveGatewayScopedTools(params: {
|
||||
});
|
||||
|
||||
const gatewayToolsCfg = params.cfg.gateway?.tools;
|
||||
const defaultGatewayDeny = DEFAULT_GATEWAY_HTTP_TOOL_DENY.filter(
|
||||
(name) => !gatewayToolsCfg?.allow?.includes(name),
|
||||
);
|
||||
const defaultGatewayDeny = params.applyDefaultGatewayHttpDeny
|
||||
? DEFAULT_GATEWAY_HTTP_TOOL_DENY.filter((name) => !gatewayToolsCfg?.allow?.includes(name))
|
||||
: [];
|
||||
const gatewayDenySet = new Set([
|
||||
...defaultGatewayDeny,
|
||||
...(Array.isArray(gatewayToolsCfg?.deny) ? gatewayToolsCfg.deny : []),
|
||||
|
||||
@@ -237,6 +237,7 @@ export async function handleToolsInvokeHttpRequest(
|
||||
agentThreadId,
|
||||
allowGatewaySubagentBinding: true,
|
||||
allowMediaInvokeCommands: true,
|
||||
applyDefaultGatewayHttpDeny: true,
|
||||
disablePluginTools: isKnownCoreToolId(toolName),
|
||||
});
|
||||
// Owner semantics intentionally follow the same shared-secret HTTP contract
|
||||
|
||||
Reference in New Issue
Block a user