Compare commits

...
17 changed files with 284 additions and 13 deletions
@@ -353,6 +353,32 @@ Use this skill.`,
).toBe(true);
});
it("loads runCommandsTimeoutMs from global settings and defaults invalid values", async () => {
const tempRoot = await mkdtemp(join(tmpdir(), "cli-config-data-"));
tempRoots.push(tempRoot);
process.env.CLINE_GLOBAL_SETTINGS_PATH = join(
tempRoot,
"global-settings.json",
);
await writeFile(
process.env.CLINE_GLOBAL_SETTINGS_PATH,
JSON.stringify({ runCommandsTimeoutMs: "invalid" }, null, 2),
);
const loader = createInteractiveConfigDataLoader({
config: createConfig(tempRoot),
});
const invalidData = await loader.loadConfigData();
expect(invalidData.general.runCommandsTimeoutMs).toBe(30000);
await writeFile(
process.env.CLINE_GLOBAL_SETTINGS_PATH,
JSON.stringify({ runCommandsTimeoutMs: 120000 }, null, 2),
);
const configuredData = await loader.loadConfigData();
expect(configuredData.general.runCommandsTimeoutMs).toBe(120000);
});
it("uses the package name for package-backed plugin entries", async () => {
const tempRoot = await mkdtemp(join(tmpdir(), "cli-config-data-"));
tempRoots.push(tempRoot);
@@ -2,7 +2,9 @@ import {
getCurrentContextSize,
type ProviderSettings,
ProviderSettingsManager,
readGlobalSettings,
type UserInstructionConfigService,
writeGlobalSettings,
} from "@cline/core";
import type { CliMigrationNotice } from "../kanban-migration/notice";
import { logCliError } from "../logging/errors";
@@ -576,6 +578,14 @@ export async function runInteractive(
applyCliCompactionMode(config, mode);
await sessionRuntime.restartWithCurrentMessages();
},
onRunCommandsTimeoutChange: async (timeoutMs) => {
writeGlobalSettings({
...readGlobalSettings(),
runCommandsTimeoutMs: timeoutMs,
});
await sessionRuntime.ensureReady();
await sessionRuntime.restartWithCurrentMessages();
},
onModeChange: async (mode) => {
if (!isInteractiveMode(mode)) return;
if (isRunning) {
@@ -28,7 +28,7 @@ test.describe("cline --model (interactive mode, flag ignored)", () => {
test("starts interactive mode", async ({ terminal }) => {
await waitForChatReady(terminal);
await expectVisible(terminal, "GPT-5.3-Codex");
await expectVisible(terminal, "GPT-5.3 Codex");
});
});
@@ -48,6 +48,7 @@ function makeConfig(overrides: Partial<Config> = {}): Config {
mode: "act",
defaultToolAutoApprove: false,
toolPolicies: {},
runCommandsTimeoutMs: 30000,
enableTools: true,
cwd: "/tmp/workspace",
logger: {
@@ -1,4 +1,4 @@
import { Llms } from "@cline/core";
import { Llms, readGlobalSettings } from "@cline/core";
import type { ChoiceContext } from "@opentui-ui/dialog";
import type { DialogActions } from "@opentui-ui/dialog/react";
import { useCallback, useMemo } from "react";
@@ -21,6 +21,7 @@ export function useConfigPanel(opts: {
toggleMode: () => void;
toggleAutoApprove: () => void;
setCompactionMode: (mode: CliCompactionMode) => void;
setRunCommandsTimeoutMs: (value: number) => void;
termHeight: number;
loadConfigData: (
options?: LoadInteractiveConfigDataOptions,
@@ -35,6 +36,9 @@ export function useConfigPanel(opts: {
}) {
const emptyConfigData = useMemo(
() => ({
general: {
runCommandsTimeoutMs: readGlobalSettings().runCommandsTimeoutMs,
},
workflows: [] as InteractiveConfigItem[],
rules: [] as InteractiveConfigItem[],
skills: [] as InteractiveConfigItem[],
@@ -74,6 +78,7 @@ export function useConfigPanel(opts: {
onToggleMode={opts.toggleMode}
onToggleAutoApprove={opts.toggleAutoApprove}
onSetCompactionMode={opts.setCompactionMode}
onSetRunCommandsTimeoutMs={opts.setRunCommandsTimeoutMs}
/>
),
});
@@ -19,6 +19,10 @@ import {
} from "@cline/core";
import { getToolCatalog } from "../runtime/tools";
export interface InteractiveGeneralConfig {
runCommandsTimeoutMs: number;
}
export type InteractiveConfigTab =
| "general"
| "tools"
@@ -62,6 +66,7 @@ export interface InteractiveConfigItem {
}
export interface InteractiveConfigData {
general: InteractiveGeneralConfig;
workflows: InteractiveConfigItem[];
rules: InteractiveConfigItem[];
skills: InteractiveConfigItem[];
@@ -262,6 +267,9 @@ export async function loadInteractiveConfigData(input: {
const hooks: InteractiveConfigItem[] = [];
const agents: InteractiveConfigItem[] = [];
const plugins: InteractiveConfigItem[] = [];
const general = {
runCommandsTimeoutMs: readGlobalSettings().runCommandsTimeoutMs,
};
const mcp: InteractiveConfigItem[] = [];
const tools: InteractiveConfigItem[] = [];
@@ -417,6 +425,7 @@ export async function loadInteractiveConfigData(input: {
}
return {
general,
workflows: toSorted(workflows.filter((item) => existsSync(item.path))),
rules: toSorted(rules.filter((item) => existsSync(item.path))),
skills: toSorted(skills.filter((item) => existsSync(item.path))),
+3
View File
@@ -196,6 +196,9 @@ function App(props: TuiProps) {
toggleMode,
toggleAutoApprove: () => session.toggleAutoApprove(),
setCompactionMode: session.setCompactionMode,
setRunCommandsTimeoutMs: (value) => {
void props.onRunCommandsTimeoutChange(value);
},
termHeight,
loadConfigData: props.loadConfigData,
onToggleConfigItem: props.onToggleConfigItem,
+1
View File
@@ -147,6 +147,7 @@ export interface TuiProps {
onTurnErrorReported: (reported: boolean) => void;
onAutoApproveChange: (enabled: boolean) => void;
onCompactionModeChange: (mode: CliCompactionMode) => Promise<void>;
onRunCommandsTimeoutChange: (timeoutMs: number) => Promise<void>;
onModelChange: () => Promise<void>;
onModeChange: (mode: AgentMode) => Promise<void>;
onSessionRestart: () => Promise<void>;
+37 -1
View File
@@ -1,6 +1,7 @@
import { useTerminalDimensions } from "@opentui/react";
import type { ChoiceContext } from "@opentui-ui/dialog";
import { useDialogKeyboard } from "@opentui-ui/dialog/react";
import { DEFAULT_RUN_COMMANDS_TIMEOUT_MS } from "@cline/core";
import { useEffect, useMemo, useState } from "react";
import type {
InteractiveConfigData,
@@ -131,6 +132,7 @@ export interface ConfigPanelProps extends ChoiceContext<ConfigAction> {
onToggleMode: () => void;
onToggleAutoApprove: () => void;
onSetCompactionMode: (mode: CliCompactionMode) => void;
onSetRunCommandsTimeoutMs: (value: number) => void;
}
function groupToolItems(
@@ -315,6 +317,9 @@ export function ConfigPanelContent(props: ConfigPanelProps) {
const [compactionMode, setCompactionMode] = useState(
props.currentCompactionMode,
);
const [runCommandsTimeoutMs, setRunCommandsTimeoutMs] = useState(
props.configData.general.runCommandsTimeoutMs,
);
const [activeTab, setActiveTab] = useState<InteractiveConfigTab>("general");
const [configData, setConfigData] = useState(props.configData);
const [pluginToolsLoaded, setPluginToolsLoaded] = useState(
@@ -329,6 +334,12 @@ export function ConfigPanelContent(props: ConfigPanelProps) {
const [navPos, setNavPos] = useState(0);
const displayName = resolveModelDisplayName(config);
const runCommandsTimeoutPresets = [
DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
60_000,
120_000,
300_000,
];
useEffect(() => {
if (
@@ -383,6 +394,11 @@ export function ConfigPanelContent(props: ConfigPanelProps) {
label: "Auto-approve all",
});
r.push({ kind: "toggle", id: "verbose", label: "Verbose" });
r.push({
kind: "toggle",
id: "run-commands-timeout",
label: "Run Command timeout",
});
} else {
const activeItems = resolveActiveConfigItems(configData, activeTab);
r.push({
@@ -442,7 +458,12 @@ export function ConfigPanelContent(props: ConfigPanelProps) {
}
return r;
}, [activeTab, configData, pluginToolsError, pluginToolsLoading]);
}, [
activeTab,
configData,
pluginToolsError,
pluginToolsLoading,
]);
const navIndices = useMemo(
() => rows.map((r, i) => (isNavigable(r) ? i : -1)).filter((i) => i >= 0),
@@ -518,6 +539,18 @@ export function ConfigPanelContent(props: ConfigPanelProps) {
config.verbose = !verbose;
setVerbose(!verbose);
break;
case "run-commands-timeout": {
const currentIndex = runCommandsTimeoutPresets.indexOf(
runCommandsTimeoutMs,
);
const nextTimeoutMs =
runCommandsTimeoutPresets[
(currentIndex + 1) % runCommandsTimeoutPresets.length
] ?? DEFAULT_RUN_COMMANDS_TIMEOUT_MS;
setRunCommandsTimeoutMs(nextTimeoutMs);
props.onSetRunCommandsTimeoutMs(nextTimeoutMs);
break;
}
}
break;
case "ext": {
@@ -692,6 +725,9 @@ export function ConfigPanelContent(props: ConfigPanelProps) {
} else if (row.id === "compaction") {
value = formatCliCompactionMode(compactionMode);
valueColor = COMPACTION_MODE_COLORS[compactionMode];
} else if (row.id === "run-commands-timeout") {
value = `${runCommandsTimeoutMs / 1000}s`;
valueColor = "white";
} else {
value = verbose ? "● on" : "○ off";
valueColor = verbose ? palette.success : "gray";
@@ -6,6 +6,10 @@
import type { DefaultToolName } from "./types";
export const DEFAULT_RUN_COMMANDS_TIMEOUT_MS = 30_000;
export const MIN_RUN_COMMANDS_TIMEOUT_MS = 1_000;
export const MAX_RUN_COMMANDS_TIMEOUT_MS = 3_600_000;
/**
* Constants for default tool names
*/
@@ -7,7 +7,13 @@
// Zod Utilities
export { validateWithZod, zodToJsonSchema } from "@cline/shared";
// Constants
export { ALL_DEFAULT_TOOL_NAMES, DefaultToolNames } from "./constants";
export {
ALL_DEFAULT_TOOL_NAMES,
DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
DefaultToolNames,
MAX_RUN_COMMANDS_TIMEOUT_MS,
MIN_RUN_COMMANDS_TIMEOUT_MS,
} from "./constants";
// AgentTool Definitions
export {
createApplyPatchTool,
@@ -178,6 +184,8 @@ export function createBuiltinTools(
return createDefaultTools({
...toolsConfig,
bashTimeoutMs:
toolsConfig.bashTimeoutMs ?? executorOptions.bash?.timeoutMs,
executors,
});
}
+3
View File
@@ -629,6 +629,7 @@ export {
createDefaultTools,
createDefaultToolsWithPreset,
createToolPoliciesWithPreset,
DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
type DefaultExecutorsOptions,
type DefaultToolName,
DefaultToolNames,
@@ -637,6 +638,8 @@ export {
getCoreBuiltinToolCatalog,
getCoreDefaultEnabledToolIds,
getCoreHeadlessToolNames,
MAX_RUN_COMMANDS_TIMEOUT_MS,
MIN_RUN_COMMANDS_TIMEOUT_MS,
resolveCoreSelectedToolIds,
TEAM_TOOL_NAMES,
type ToolCatalogEntry,
@@ -7,7 +7,8 @@ import {
createContributionRegistry,
type Message,
} from "@cline/shared";
import { afterEach, describe, expect, it } from "vitest";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { DEFAULT_RUN_COMMANDS_TIMEOUT_MS } from "../../extensions/tools/constants";
import { TelemetryService } from "../../services/telemetry/TelemetryService";
import type { CoreSessionConfig } from "../../types/config";
import { DefaultRuntimeBuilder } from "./runtime-builder";
@@ -53,9 +54,23 @@ async function collectExtensionTools(
describe("DefaultRuntimeBuilder", () => {
const previousGlobalSettingsPath = process.env.CLINE_GLOBAL_SETTINGS_PATH;
const previousMcpSettingsPath = process.env.CLINE_MCP_SETTINGS_PATH;
beforeEach(() => {
const tempRoot = mkdtempSync(join(tmpdir(), "runtime-builder-settings-"));
process.env.CLINE_GLOBAL_SETTINGS_PATH = join(
tempRoot,
"global-settings.json",
);
process.env.CLINE_MCP_SETTINGS_PATH = join(
tempRoot,
"cline_mcp_settings.json",
);
});
afterEach(() => {
process.env.CLINE_GLOBAL_SETTINGS_PATH = previousGlobalSettingsPath;
process.env.CLINE_MCP_SETTINGS_PATH = previousMcpSettingsPath;
});
it("includes builtin tools when enabled", async () => {
@@ -280,6 +295,43 @@ describe("DefaultRuntimeBuilder", () => {
expect(names).toContain("read_files");
});
it("uses runCommandsTimeoutMs from global settings for run_commands tool and executor", async () => {
const tempRoot = mkdtempSync(join(tmpdir(), "runtime-builder-timeout-"));
const settingsPath = join(tempRoot, "global-settings.json");
process.env.CLINE_GLOBAL_SETTINGS_PATH = settingsPath;
writeFileSync(
settingsPath,
JSON.stringify({ runCommandsTimeoutMs: 120000 }, null, 2),
"utf8",
);
const runtime = await new DefaultRuntimeBuilder().build({
config: makeBaseConfig(),
});
const runCommandsTool = runtime.tools.find((tool) => tool.name === "run_commands");
expect(runCommandsTool).toBeDefined();
expect(runCommandsTool?.timeoutMs).toBe(240000);
});
it("defaults invalid runCommandsTimeoutMs in global settings", async () => {
const tempRoot = mkdtempSync(join(tmpdir(), "runtime-builder-timeout-invalid-"));
const settingsPath = join(tempRoot, "global-settings.json");
process.env.CLINE_GLOBAL_SETTINGS_PATH = settingsPath;
writeFileSync(
settingsPath,
JSON.stringify({ runCommandsTimeoutMs: "oops" }, null, 2),
"utf8",
);
const runtime = await new DefaultRuntimeBuilder().build({
config: makeBaseConfig(),
});
const runCommandsTool = runtime.tools.find((tool) => tool.name === "run_commands");
expect(runCommandsTool?.timeoutMs).toBe(
DEFAULT_RUN_COMMANDS_TIMEOUT_MS * 2,
);
});
it("adds spawn tool when enabled", async () => {
const runtime = await new DefaultRuntimeBuilder().build({
config: makeBaseConfig({
@@ -299,7 +351,7 @@ describe("DefaultRuntimeBuilder", () => {
}),
});
await expect(runtime.shutdown("test")).resolves.toBeUndefined();
await runtime.shutdown("test");
});
it("includes MCP tools from configured servers", async () => {
@@ -34,6 +34,7 @@ import {
} from "../../extensions/tools/team";
import {
filterDisabledTools,
readGlobalSettings,
resolveDisabledToolNames,
} from "../../services/global-settings";
import { createLocalTeamStore } from "../../services/storage/team-store";
@@ -96,6 +97,7 @@ function createBuiltinToolsList(
executorOverrides?: Partial<ToolExecutors>,
): AgentTool[] {
const preset = ToolPresets[resolveToolPresetName({ mode })];
const { runCommandsTimeoutMs } = readGlobalSettings();
const toolRoutingConfig = resolveToolRoutingConfig(
providerId,
modelId,
@@ -109,6 +111,9 @@ function createBuiltinToolsList(
...preset,
enableSkills: !!skillsExecutor,
...toolRoutingConfig,
executorOptions: {
bash: { timeoutMs: runCommandsTimeoutMs },
},
executors: {
...(skillsExecutor
? {
@@ -3,6 +3,11 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import type { ITelemetryService } from "@cline/shared";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
MAX_RUN_COMMANDS_TIMEOUT_MS,
MIN_RUN_COMMANDS_TIMEOUT_MS,
} from "../extensions/tools/constants";
import {
GlobalSettingsSchema,
readGlobalSettings,
@@ -28,6 +33,7 @@ describe("global-settings", () => {
).toEqual({
disabledPlugins: ["/plugins/example.js"],
disabledTools: ["editor", "read_files"],
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: false,
});
expect(
@@ -35,8 +41,12 @@ describe("global-settings", () => {
disabledTools: [],
telemetryOptOut: true,
}),
).toEqual({ telemetryOptOut: true });
).toEqual({
telemetryOptOut: true,
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
});
expect(GlobalSettingsSchema.parse({ disabledTools: [] })).toEqual({
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: false,
});
expect(
@@ -46,6 +56,7 @@ describe("global-settings", () => {
}),
).toEqual({
disabledTools: ["read_files"],
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: false,
});
expect(
@@ -55,6 +66,7 @@ describe("global-settings", () => {
telemetryOptOut: true,
}),
).toEqual({
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: true,
});
});
@@ -68,14 +80,17 @@ describe("global-settings", () => {
writeGlobalSettings({
disabledTools: [" editor ", "read_files", "editor"],
disabledPlugins: [],
runCommandsTimeoutMs: 120000,
});
expect(readGlobalSettings()).toEqual({
disabledTools: ["editor", "read_files"],
runCommandsTimeoutMs: 120000,
telemetryOptOut: false,
});
expect(JSON.parse(await readFile(settingsPath, "utf8"))).toEqual({
disabledTools: ["editor", "read_files"],
runCommandsTimeoutMs: 120000,
telemetryOptOut: false,
});
@@ -89,6 +104,7 @@ describe("global-settings", () => {
);
expect(readGlobalSettings()).toEqual({
disabledTools: ["read_files"],
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: true,
});
@@ -100,7 +116,10 @@ describe("global-settings", () => {
telemetryOptOut: true,
}),
);
expect(readGlobalSettings()).toEqual({ telemetryOptOut: true });
expect(readGlobalSettings()).toEqual({
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: true,
});
} finally {
await rm(root, { recursive: true, force: true });
}
@@ -119,11 +138,13 @@ describe("global-settings", () => {
expect(readGlobalSettings()).toEqual({
disabledPlugins: ["/plugins/example.js"],
disabledTools: ["read_files"],
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: false,
});
expect(JSON.parse(await readFile(settingsPath, "utf8"))).toEqual({
disabledPlugins: ["/plugins/example.js"],
disabledTools: ["read_files"],
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: false,
});
} finally {
@@ -147,7 +168,64 @@ describe("global-settings", () => {
expect(captureRequired).toHaveBeenCalledTimes(1);
expect(captureRequired).toHaveBeenCalledWith("user.opt_out", undefined);
expect(readGlobalSettings()).toEqual({ telemetryOptOut: false });
expect(readGlobalSettings()).toEqual({
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: false,
});
} finally {
await rm(root, { recursive: true, force: true });
}
});
it("defaults invalid runCommandsTimeoutMs and preserves configured values", async () => {
const root = await mkdtemp(join(tmpdir(), "core-global-settings-"));
try {
const settingsPath = join(root, "global-settings.json");
process.env.CLINE_GLOBAL_SETTINGS_PATH = settingsPath;
await writeFile(
settingsPath,
JSON.stringify({ runCommandsTimeoutMs: "bad" }),
);
expect(readGlobalSettings().runCommandsTimeoutMs).toBe(
DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
);
await writeFile(
settingsPath,
JSON.stringify({
runCommandsTimeoutMs: MIN_RUN_COMMANDS_TIMEOUT_MS - 1,
}),
);
expect(readGlobalSettings().runCommandsTimeoutMs).toBe(
DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
);
await writeFile(
settingsPath,
JSON.stringify({
runCommandsTimeoutMs: MAX_RUN_COMMANDS_TIMEOUT_MS + 1,
}),
);
expect(readGlobalSettings().runCommandsTimeoutMs).toBe(
DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
);
await writeFile(
settingsPath,
JSON.stringify({ runCommandsTimeoutMs: MIN_RUN_COMMANDS_TIMEOUT_MS }),
);
expect(readGlobalSettings().runCommandsTimeoutMs).toBe(
MIN_RUN_COMMANDS_TIMEOUT_MS,
);
await writeFile(
settingsPath,
JSON.stringify({ runCommandsTimeoutMs: MAX_RUN_COMMANDS_TIMEOUT_MS }),
);
expect(readGlobalSettings().runCommandsTimeoutMs).toBe(
MAX_RUN_COMMANDS_TIMEOUT_MS,
);
} finally {
await rm(root, { recursive: true, force: true });
}
@@ -166,6 +244,7 @@ describe("global-settings", () => {
expect(readGlobalSettings()).toEqual({
disabledTools: ["read_files"],
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: false,
});
} finally {
@@ -188,6 +267,7 @@ describe("global-settings", () => {
expect(readGlobalSettings()).toEqual({
disabledTools: ["read_files"],
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: false,
});
} finally {
@@ -206,6 +286,7 @@ describe("global-settings", () => {
writeGlobalSettings({ disabledTools: ["editor"] });
expect(readGlobalSettings()).toEqual({
disabledTools: ["editor"],
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: false,
});
@@ -213,12 +294,14 @@ describe("global-settings", () => {
writeGlobalSettings({ disabledTools: ["read_files"] });
expect(readGlobalSettings()).toEqual({
disabledTools: ["read_files"],
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: false,
});
process.env.CLINE_GLOBAL_SETTINGS_PATH = pathA;
expect(readGlobalSettings()).toEqual({
disabledTools: ["editor"],
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: false,
});
} finally {
@@ -233,8 +316,14 @@ describe("global-settings", () => {
const settingsPath = join(root, "missing-global-settings.json");
process.env.CLINE_GLOBAL_SETTINGS_PATH = settingsPath;
expect(readGlobalSettings()).toEqual({ telemetryOptOut: false });
expect(readGlobalSettings()).toEqual({ telemetryOptOut: false });
expect(readGlobalSettings()).toEqual({
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: false,
});
expect(readGlobalSettings()).toEqual({
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: false,
});
} finally {
await rm(root, { recursive: true, force: true });
}
@@ -272,7 +361,10 @@ describe("global-settings", () => {
const settingsPath = join(root, "global-settings.json");
process.env.CLINE_GLOBAL_SETTINGS_PATH = settingsPath;
expect(readGlobalSettings()).toEqual({ telemetryOptOut: false });
expect(readGlobalSettings()).toEqual({
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: false,
});
await writeFile(
settingsPath,
@@ -281,6 +373,7 @@ describe("global-settings", () => {
expect(readGlobalSettings()).toEqual({
disabledTools: ["editor"],
runCommandsTimeoutMs: DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
telemetryOptOut: false,
});
} finally {
@@ -3,6 +3,11 @@ import { dirname } from "node:path";
import type { AgentConfig, AgentTool, ITelemetryService } from "@cline/shared";
import { resolveGlobalSettingsPath } from "@cline/shared/storage";
import { z } from "zod";
import {
DEFAULT_RUN_COMMANDS_TIMEOUT_MS,
MAX_RUN_COMMANDS_TIMEOUT_MS,
MIN_RUN_COMMANDS_TIMEOUT_MS,
} from "../extensions/tools/constants";
import { captureTelemetryOptOut } from "./telemetry/core-events";
type AgentExtension = NonNullable<AgentConfig["extensions"]>[number];
@@ -34,6 +39,13 @@ export const GlobalSettingsSchema = z
telemetryOptOut: z.boolean().default(false).catch(false),
disabledTools: GlobalSettingsStringListSchema.optional(),
disabledPlugins: GlobalSettingsStringListSchema.optional(),
runCommandsTimeoutMs: z
.number()
.int()
.min(MIN_RUN_COMMANDS_TIMEOUT_MS)
.max(MAX_RUN_COMMANDS_TIMEOUT_MS)
.catch(DEFAULT_RUN_COMMANDS_TIMEOUT_MS)
.default(DEFAULT_RUN_COMMANDS_TIMEOUT_MS),
})
.strip()
.transform((settings) => {
@@ -41,8 +53,10 @@ export const GlobalSettingsSchema = z
telemetryOptOut: boolean;
disabledTools?: string[];
disabledPlugins?: string[];
runCommandsTimeoutMs: number;
} = {
telemetryOptOut: settings.telemetryOptOut,
runCommandsTimeoutMs: settings.runCommandsTimeoutMs,
};
if (settings.disabledTools?.length) {
normalized.disabledTools = settings.disabledTools;
@@ -288,6 +288,7 @@ Use this skill.`,
),
).toEqual({
disabledTools: ["plugin-tool"],
runCommandsTimeoutMs: 30000,
telemetryOptOut: false,
});
@@ -297,6 +298,6 @@ Use this skill.`,
JSON.parse(
await readFile(process.env.CLINE_GLOBAL_SETTINGS_PATH, "utf8"),
),
).toEqual({ telemetryOptOut: false });
).toEqual({ runCommandsTimeoutMs: 30000, telemetryOptOut: false });
});
});