diff --git a/src/config/types.tools.ts b/src/config/types.tools.ts index 2662c5000..51fa53391 100644 --- a/src/config/types.tools.ts +++ b/src/config/types.tools.ts @@ -2,20 +2,19 @@ import type { AgentElevatedAllowFromConfig } from "./types.base.js"; export type ToolProfileId = "minimal" | "coding" | "messaging" | "full"; +export type ToolPolicyConfig = { + allow?: string[]; + deny?: string[]; + profile?: ToolProfileId; +}; + export type AgentToolsConfig = { /** Base tool profile applied before allow/deny lists. */ profile?: ToolProfileId; allow?: string[]; deny?: string[]; /** Optional tool policy overrides keyed by provider id or "provider/model". */ - byProvider?: Record< - string, - { - profile?: ToolProfileId; - allow?: string[]; - deny?: string[]; - } - >; + byProvider?: Record; /** Per-agent elevated exec gate (can only further restrict global tools.elevated). */ elevated?: { /** Enable or disable elevated mode for this agent (default: true). */ @@ -83,14 +82,7 @@ export type ToolsConfig = { allow?: string[]; deny?: string[]; /** Optional tool policy overrides keyed by provider id or "provider/model". */ - byProvider?: Record< - string, - { - profile?: ToolProfileId; - allow?: string[]; - deny?: string[]; - } - >; + byProvider?: Record; web?: { search?: { /** Enable web search tool (default: true when API key is present). */ diff --git a/src/config/zod-schema.agent-runtime.ts b/src/config/zod-schema.agent-runtime.ts index 341fadbb3..a7b06a8c4 100644 --- a/src/config/zod-schema.agent-runtime.ts +++ b/src/config/zod-schema.agent-runtime.ts @@ -146,10 +146,10 @@ export const ToolProfileSchema = z .union([z.literal("minimal"), z.literal("coding"), z.literal("messaging"), z.literal("full")]) .optional(); -const ByProviderPolicySchema = z.object({ - profile: ToolProfileSchema, +export const ToolPolicyWithProfileSchema = z.object({ allow: z.array(z.string()).optional(), deny: z.array(z.string()).optional(), + profile: ToolProfileSchema, }); // Provider docking: allowlists keyed by provider id (no schema updates when adding providers). @@ -176,7 +176,7 @@ export const AgentToolsSchema = z profile: ToolProfileSchema, allow: z.array(z.string()).optional(), deny: z.array(z.string()).optional(), - byProvider: z.record(z.string(), ByProviderPolicySchema).optional(), + byProvider: z.record(z.string(), ToolPolicyWithProfileSchema).optional(), elevated: z .object({ enabled: z.boolean().optional(), @@ -280,7 +280,7 @@ export const ToolsSchema = z profile: ToolProfileSchema, allow: z.array(z.string()).optional(), deny: z.array(z.string()).optional(), - byProvider: z.record(z.string(), ByProviderPolicySchema).optional(), + byProvider: z.record(z.string(), ToolPolicyWithProfileSchema).optional(), web: ToolsWebSchema, audio: z .object({ diff --git a/src/gateway/server-methods/agent.ts b/src/gateway/server-methods/agent.ts index 8613b5bdf..625d17d4c 100644 --- a/src/gateway/server-methods/agent.ts +++ b/src/gateway/server-methods/agent.ts @@ -119,7 +119,7 @@ export const agentHandlers: GatewayRequestHandlers = { undefined, errorShape( ErrorCodes.INVALID_REQUEST, - `invalid agent params: unknown channel: ${normalized}`, + `invalid agent params: unknown channel: ${String(normalized)}`, ), ); return; diff --git a/src/gateway/server.config-patch.test.ts b/src/gateway/server.config-patch.test.ts index 37fdf5b8c..9b5e7be43 100644 --- a/src/gateway/server.config-patch.test.ts +++ b/src/gateway/server.config-patch.test.ts @@ -1,6 +1,11 @@ import { describe, expect, it } from "vitest"; -import { connectOk, installGatewayTestHooks, onceMessage, startServerWithClient } from "./test-helpers.js"; +import { + connectOk, + installGatewayTestHooks, + onceMessage, + startServerWithClient, +} from "./test-helpers.js"; installGatewayTestHooks(); diff --git a/src/gateway/test-helpers.mocks.ts b/src/gateway/test-helpers.mocks.ts index 6d5bee241..2e6db412b 100644 --- a/src/gateway/test-helpers.mocks.ts +++ b/src/gateway/test-helpers.mocks.ts @@ -68,6 +68,17 @@ const hoisted = vi.hoisted(() => ({ }, })); +const testConfigRoot = { + value: path.join( + os.tmpdir(), + `clawdbot-gateway-test-${process.pid}-${crypto.randomUUID()}`, + ), +}; + +export const setTestConfigRoot = (root: string) => { + testConfigRoot.value = root; +}; + export const bridgeStartCalls = hoisted.bridgeStartCalls; export const bridgeInvoke = hoisted.bridgeInvoke; export const bridgeListConnected = hoisted.bridgeListConnected; @@ -157,7 +168,7 @@ vi.mock("../config/sessions.js", async () => { vi.mock("../config/config.js", async () => { const actual = await vi.importActual("../config/config.js"); - const resolveConfigPath = () => path.join(os.homedir(), ".clawdbot", "clawdbot.json"); + const resolveConfigPath = () => path.join(testConfigRoot.value, "clawdbot.json"); const hashConfigRaw = (raw: string | null) => crypto.createHash("sha256").update(raw ?? "").digest("hex"); @@ -233,8 +244,12 @@ vi.mock("../config/config.js", async () => { return { ...actual, - CONFIG_PATH_CLAWDBOT: resolveConfigPath(), - STATE_DIR_CLAWDBOT: path.dirname(resolveConfigPath()), + get CONFIG_PATH_CLAWDBOT() { + return resolveConfigPath(); + }, + get STATE_DIR_CLAWDBOT() { + return path.dirname(resolveConfigPath()); + }, get isNixMode() { return testIsNixMode.value; }, diff --git a/src/gateway/test-helpers.server.ts b/src/gateway/test-helpers.server.ts index 58031d1f1..e274bb4ff 100644 --- a/src/gateway/test-helpers.server.ts +++ b/src/gateway/test-helpers.server.ts @@ -21,6 +21,7 @@ import { embeddedRunMock, piSdkMock, sessionStoreSaveDelayMs, + setTestConfigRoot, testIsNixMode, testState, testTailnetIPv4, @@ -28,6 +29,7 @@ import { let previousHome: string | undefined; let tempHome: string | undefined; +let tempConfigRoot: string | undefined; export function installGatewayTestHooks() { beforeEach(async () => { @@ -35,6 +37,8 @@ export function installGatewayTestHooks() { previousHome = process.env.HOME; tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-gateway-home-")); process.env.HOME = tempHome; + tempConfigRoot = path.join(tempHome, ".clawdbot-test"); + setTestConfigRoot(tempConfigRoot); sessionStoreSaveDelayMs.value = 0; testTailnetIPv4.value = undefined; testState.gatewayBind = undefined; @@ -81,6 +85,7 @@ export function installGatewayTestHooks() { }); tempHome = undefined; } + tempConfigRoot = undefined; }); } diff --git a/src/infra/outbound/channel-selection.ts b/src/infra/outbound/channel-selection.ts index 47c5e383f..530e4a838 100644 --- a/src/infra/outbound/channel-selection.ts +++ b/src/infra/outbound/channel-selection.ts @@ -59,7 +59,7 @@ export async function resolveMessageChannelSelection(params: { const normalized = normalizeMessageChannel(params.channel); if (normalized) { if (!isKnownChannel(normalized)) { - throw new Error(`Unknown channel: ${normalized}`); + throw new Error(`Unknown channel: ${String(normalized)}`); } return { channel: normalized as MessageChannelId,