mirror of
https://github.com/cline/cline.git
synced 2026-09-16 21:01:52 +08:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab69180abc | ||
|
|
6e0b63ba2c | ||
|
|
83e0b0653a | ||
|
|
3914977b10 | ||
|
|
28e0b5b14c | ||
|
|
5bfbc9e214 | ||
|
|
93a280ae68 | ||
|
|
7610471daa | ||
|
|
a8ec1a598a | ||
|
|
20afeff786 | ||
|
|
94b46f7615 | ||
|
|
31007e4c83 | ||
|
|
1e9dbde73e |
@@ -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))),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user