Compare commits

..
Author SHA1 Message Date
Saoud Rizwan fe6a8f4432 fix(cli): pass persisted provider endpoint config 2026-06-11 15:53:56 -07:00
Saoud Rizwan efa14b6cab chore(cli): release v3.0.24 2026-06-11 14:27:27 -07:00
Saoud Rizwan c10b417b78 chore(sdk): release v0.0.47 2026-06-11 14:02:45 -07:00
Saoud Rizwan 9958e3f354 feat(cli): allow plugin commands to submit prompts (#11479)
* feat(cli): allow plugin commands to submit prompts

* fix(cli): preserve plugin command output on abort

* revert(cli): drop ineffective clear view tweak
2026-06-11 13:49:50 -07:00
Tomás Barreiro ec75291d5b Allow overriding the API base url (#11440)
* Allow overriding the base url

* Override the mcpbaseurl

* update the api base url

* Fix tests
2026-06-11 22:29:17 +02:00
Tomás BarreiroandSaoud Rizwan a3a31da37d Add the FeatureFlagService to the SDK [NOOP] (#11444)
* Add the FeatureFlagService to the SDK

* Fix comments

* Dispose of the telemetry service

* Dispose of the feature flag service

* Address PR feedback

* Stop the polling early if a new one is triggered with another user id

* Address PR feedback

* Dispose of the feature flag service

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-06-11 22:11:35 +02:00
Tomás BarreiroandSaoud Rizwan a69d650838 Open URLs when starting device auth (#11393)
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-06-11 21:27:44 +02:00
AraandClaude Fable 5 7934d367a9 fix(sdk): truncate structured ToolOperationResult strings in MessageBuilder (#11465)
* test(sdk): add regression tests for structured ToolOperationResult truncation

MessageBuilder tests only covered string and {type:

🔮 View transcript: https://agentlogs.beatrixwoo.workers.dev/s/mrbt2b39jfn370scr4g0ivzo"text"} tool-result
content, not the structured ToolOperationResult[] shape the default tools
(run_commands, read_files, search_codebase) actually emit. Those entries
are plain {query, result, success} objects with no type discriminator, so
the token-bloat path they create was unprotected by tests.

Adds regression tests using the real structured shape: huge result, huge
query, huge read_files payload, aggregate budget across multiple results,
mutation safety, and provider-formatted AI SDK payload size. Assertions
are on actual serialized payload sizes, not transcript shape.

The new tests fail at this commit by design; the following commit makes
them pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(sdk): truncate structured ToolOperationResult strings in MessageBuilder

The runtime stores structured tool outputs (ToolOperationResult[] from
run_commands/read_files/search_codebase) directly as the tool_result
content array (agentPartToContentBlock casts the array straight through).
Those entries have no type discriminator, so MessageBuilder's per-result
truncation, aggregate byte counting, and budget truncation all skipped
them — multi-megabyte command outputs and file reads were JSON-serialized
in full into every subsequent provider request.

MessageBuilder now deep-truncates nested strings inside structured
entries (middle truncation, preserving head and tail), counts them
against the aggregate text budget, collects them as budget-truncation
candidates, and deep-clones them before mutation so the original
conversation history stays untouched. Image blocks are skipped so base64
payloads survive intact.

Real-inference A/B on openrouter:minimax/minimax-m2.7 with realistic
structured payloads: 58.7% overall input-token reduction (82.6% on a
single huge command output) with identical answer correctness.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

🔮 View transcript: https://agentlogs.beatrixwoo.workers.dev/s/mrbt2b39jfn370scr4g0ivzo

* fix(sdk): include fetch_web_content in MessageBuilder truncation targets

Review feedback: fetch_web_content also returns ToolOperationResult[] and
its executor allows responses up to 5MB, but the tool was missing from
TARGET_TOOL_NAMES, so a single web fetch could still bloat every
subsequent provider request. Adds the tool to the truncation target set
with a regression test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

🔮 View transcript: https://agentlogs.beatrixwoo.workers.dev/s/mrbt2b39jfn370scr4g0ivzo

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 11:34:43 -07:00
AraandClaude Fable 5 7f9d5461f1 fix(sdk): stop echoing full command text in run_commands tool results (#11463)
The run_commands tool result's query field repeated the entire executed
command, which already exists verbatim in the assistant tool-call input.
For large generated-file commands (e.g. cat <<EOF heredocs) this
duplicated thousands of chars of source text into every subsequent
provider request.

Bound the provider-facing echo to a 200-char preview plus a truncation
note pointing at the tool call input. Short commands pass through
unchanged. Applies to both createBashTool and createWindowsShellTool,
on success and error paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

🔮 View transcript: https://agentlogs.beatrixwoo.workers.dev/s/og840mbjqaog4zita8m0262m
2026-06-11 11:22:20 -07:00
Saoud Rizwan e1bdeeff68 docs(changelog): note Vertex SDK companion bump in 3.89.2 (#11459) 2026-06-11 04:10:30 -07:00
Saoud Rizwan 49897830bb fix(vscode): align Anthropic Vertex SDK with runtime SDK (#11458) 2026-06-11 04:07:51 -07:00
Saoud Rizwan 1f316a2734 fix(vscode): remove unused ClineStorageMessage import in openai-format (#11457)
The SDK 0.50.1 upgrade widened convertToOpenAiMessages to take
Anthropic.Messages.MessageParam[], which left the ClineStorageMessage
import referenced only in comments. tsc does not flag unused imports in
this config, but biome lint does, and it blocked the 3.89.2 publish.
2026-06-11 03:52:55 -07:00
Saoud Rizwan 9c1f9133c7 v3.89.2 Release Notes (#11455) 2026-06-11 03:47:24 -07:00
Saoud Rizwan 2faef2b40d fix(vscode): upgrade @anthropic-ai/sdk to 0.50.1 for Node 24 compatibility (#11454)
VS Code 1.123.0 bumped its bundled runtime from Node 22 to Node 24
(Electron 39 to 42). The Anthropic provider broke on the updated editor
because the old SDK (<=0.41.x) shipped a legacy runtime built on
node-fetch and an internal _shims layer that does not work under Node 24.

0.50.1 is the first SDK release rewritten on top of the platform's native
fetch: it has zero runtime dependencies (no node-fetch, no _shims), which
removes the incompatibility. This is the actual fix; the earlier 0.40.1
bump did not change the runtime architecture.

The 0.50.1 type changes are minimal:
- Usage gained a required server_tool_use field, so fabricated Usage
  objects in the gemini/o1/openai/vscode-lm transforms set it to null.
- ContentBlockParam widened, so Anthropic.MessageParam is no longer
  structurally assignable to ClineStorageMessage. Handled by narrowing
  the two transform helpers that only ever receive Cline history
  (sanitizeAnthropicMessages, convertAnthropicMessageToGemini), typing
  getSavedApiConversationHistory as the Cline history it reads, and
  narrowing ContextManager's loosely-typed truncated output back to
  ClineStorageMessage at the two provider/hook boundaries.
2026-06-11 03:43:33 -07:00
Saoud Rizwan 64829bca8c chore(vscode): release v3.89.1 (#11451) 2026-06-11 02:49:39 -07:00
Saoud Rizwan 4c9ba6b091 fix(vscode): restore Anthropic provider on Node 24 by bumping SDK (#11449)
VS Code 1.123.0 bumped its bundled runtime from Node 22 to Node 24
(Electron 39 to 42). The extension passes VS Code's globalThis.fetch to
every provider SDK, but @anthropic-ai/sdk was pinned at 0.37.0, which
predates the SDK's native-fetch rewrite and relies on legacy _shims
runtime detection that breaks under Node 24. The modern OpenAI and Gemini
SDKs are unaffected, which is why only the Anthropic provider broke after
users updated VS Code.

Bump to ^0.40.1, the first release with the native-fetch rewrite that
restores Node 24 compatibility, while staying short of the latest line's
larger breaking surface.

The only code change the bump requires is narrowing the image source type:
ImageBlockParam.source widened from a base64-only type to
Base64ImageSource | URLImageSource. Add a getBase64ImageSource/
getImageDataUrl helper in shared/messages/content.ts and route the
provider transforms through it. Cline only ever produces base64 image
sources, so behavior is unchanged; the helper emits the same data URL the
inline code did.
2026-06-11 02:44:25 -07:00
94 changed files with 1978 additions and 1638 deletions
+14
View File
@@ -1,5 +1,19 @@
# Changelog
## [3.89.2]
### Fixed
- Complete the fix for the Anthropic provider on VS Code 1.123 and later by upgrading the bundled Anthropic SDK to a release compatible with the Node 24 runtime.
- Update the Vertex AI provider to a compatible Anthropic Vertex SDK release so it works with the upgraded Anthropic SDK.
## [3.89.1]
### Fixed
- Restore the Anthropic provider on VS Code 1.123 and later, where the updated Node 24 runtime broke the bundled Anthropic SDK.
- Handle the DeepSeek V4 reasoning format.
## [3.89.0]
### Added
+10
View File
@@ -1,5 +1,15 @@
# Cline CLI Changelog
## 3.0.24
- Plugin commands can now submit prompts to the agent
- Added support for overriding the API base URL
- Open the verification URL automatically when starting device authentication
- Enforced a single shared Cline Hub, so a stale hub is respawned after an upgrade
- Suppressed flickering console windows on Windows
- Fixed truncation of structured tool operation result strings so oversized tool output stays within limits
- Stopped echoing the full command text in run_commands tool results
## 3.0.23
- Fixed Vertex AI GCP settings configuration
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@cline/cli",
"displayName": "cline",
"version": "3.0.23",
"version": "3.0.24",
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
"type": "module",
"publishConfig": {
-5
View File
@@ -51,10 +51,6 @@ export function addRootOptions(cmd: Command): Command {
"-s, --system <system-prompt>",
"Override the default system prompt",
)
.option(
"--agent <name>",
"Use an agent profile from .cline/agents for this session",
)
.option("-z, --zen", "Start a session that runs in the background hub")
.option(
"--retries [value]",
@@ -229,7 +225,6 @@ export function commanderToParsedArgs(program: Command): ParsedArgs {
if (opts.cwd !== undefined) result.cwd = opts.cwd;
if (opts.teamName !== undefined) result.teamName = opts.teamName;
if (opts.system !== undefined) result.systemPrompt = opts.system;
if (opts.agent !== undefined) result.agent = opts.agent;
if (opts.model !== undefined) result.model = opts.model;
if (opts.provider !== undefined) result.provider = opts.provider;
if (opts.key !== undefined) result.key = opts.key;
+44 -158
View File
@@ -2,23 +2,6 @@ import { fstatSync } from "node:fs";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { CliMigrationNotice } from "./kanban-migration/notice";
interface MockConfiguredAgentConfig {
name: string;
description: string;
systemPrompt: string;
path?: string;
}
interface MockConfiguredAgentReadError {
path: string;
error: Error;
}
interface MockConfiguredAgentLoadResult {
configs: MockConfiguredAgentConfig[];
errors: MockConfiguredAgentReadError[];
}
/** Real `fstatSync`: used when tests stub only stdin (fd 0); throwing for every fd breaks imports and session I/O. */
const fsActual = vi.hoisted(() => ({
realFstatSync: null as null | typeof import("node:fs").fstatSync,
@@ -64,14 +47,6 @@ const sessionMocks = vi.hoisted(() => ({
const llmMocks = vi.hoisted(() => ({
resolveProviderConfig: vi.fn(async (): Promise<unknown> => undefined),
}));
const agentConfigMocks = vi.hoisted(() => ({
loadConfiguredAgentConfigs: vi.fn<
(input: { workspaceRoot?: string }) => MockConfiguredAgentLoadResult
>(() => ({
configs: [],
errors: [],
})),
}));
const promptMocks = vi.hoisted(() => ({
resolveSystemPrompt: vi.fn(async () => "system prompt"),
}));
@@ -167,7 +142,6 @@ vi.mock("./session/session", () => sessionMocks);
vi.mock("@cline/core", () => {
return {
resolveProviderConfig: llmMocks.resolveProviderConfig,
loadConfiguredAgentConfigs: agentConfigMocks.loadConfiguredAgentConfigs,
createTeamName: vi.fn(() => "team-test"),
createUserInstructionConfigService: vi.fn(() => ({
start: vi.fn(async () => {}),
@@ -241,11 +215,6 @@ describe("runCli lightweight command dispatch", () => {
});
llmMocks.resolveProviderConfig.mockReset();
llmMocks.resolveProviderConfig.mockResolvedValue(undefined);
agentConfigMocks.loadConfiguredAgentConfigs.mockReset();
agentConfigMocks.loadConfiguredAgentConfigs.mockReturnValue({
configs: [],
errors: [],
});
authMocks.ensureOAuthProviderApiKey.mockReset();
authMocks.getPersistedProviderApiKey.mockReset();
authMocks.getPersistedProviderApiKey.mockReturnValue(undefined);
@@ -832,6 +801,50 @@ describe("runCli lightweight command dispatch", () => {
);
});
it("passes persisted provider endpoint config into prompt runtime", async () => {
const ollamaSettings = {
provider: "ollama",
baseUrl: "http://127.0.0.1:11434/v1",
model: "qwen3.6",
};
const ollamaProviderConfig = {
providerId: "ollama",
modelId: "qwen3.6",
baseUrl: "http://127.0.0.1:11434/v1",
};
providerSettingsMocks.getLastUsedProviderSettings.mockReturnValue(
ollamaSettings,
);
providerSettingsMocks.getProviderSettings.mockReturnValue(ollamaSettings);
providerSettingsMocks.getProviderConfig.mockReturnValue(
ollamaProviderConfig,
);
authMocks.normalizeProviderId.mockImplementation(
(providerId?: string) => providerId ?? "ollama",
);
forcePromptModeInput();
process.argv = ["bun", "src/index.ts", "hello"];
const { runCli } = await import("./main");
await expect(runCli()).resolves.toBeUndefined();
expect(llmMocks.resolveProviderConfig).toHaveBeenCalledWith(
"ollama",
undefined,
ollamaProviderConfig,
);
expect(runtimeMocks.runAgent).toHaveBeenCalledWith(
"hello",
expect.objectContaining({
providerId: "ollama",
modelId: "qwen3.6",
baseUrl: "http://127.0.0.1:11434/v1",
providerConfig: ollamaProviderConfig,
}),
expect.anything(),
);
});
it("runs kanban before loading runtime modules", async () => {
process.argv = ["bun", "src/index.ts", "kanban"];
@@ -903,133 +916,6 @@ describe("runCli lightweight command dispatch", () => {
expect(hubRuntimeMocks.ensureCliHubServer).not.toHaveBeenCalled();
});
it("applies an agent profile to prompt runs", async () => {
agentConfigMocks.loadConfiguredAgentConfigs.mockReturnValue({
configs: [
{
name: "Reviewer",
description: "Reviews code",
systemPrompt: "You are a reviewer.",
path: "/repo/.cline/agents/reviewer.yaml",
},
],
errors: [],
});
forcePromptModeInput();
process.argv = ["bun", "src/index.ts", "--agent", "reviewer", "hello"];
const { runCli } = await import("./main");
await expect(runCli()).resolves.toBeUndefined();
expect(agentConfigMocks.loadConfiguredAgentConfigs).toHaveBeenCalledWith({
workspaceRoot: "/workspace/cline",
});
expect(promptMocks.resolveSystemPrompt).toHaveBeenCalledWith(
expect.objectContaining({
agentPersona: "You are a reviewer.",
}),
);
expect(runtimeMocks.runAgent).toHaveBeenCalledWith(
"hello",
expect.objectContaining({
agentProfile: {
name: "Reviewer",
systemPrompt: "You are a reviewer.",
},
systemPrompt: "system prompt",
}),
expect.anything(),
);
});
it("lets --system override --agent without storing the profile", async () => {
agentConfigMocks.loadConfiguredAgentConfigs.mockReturnValue({
configs: [
{
name: "Reviewer",
description: "Reviews code",
systemPrompt: "You are a reviewer.",
path: "/repo/.cline/agents/reviewer.yaml",
},
],
errors: [],
});
forcePromptModeInput();
process.argv = [
"bun",
"src/index.ts",
"--agent",
"reviewer",
"--system",
"Custom system.",
"hello",
];
const { runCli } = await import("./main");
await expect(runCli()).resolves.toBeUndefined();
expect(agentConfigMocks.loadConfiguredAgentConfigs).not.toHaveBeenCalled();
expect(promptMocks.resolveSystemPrompt).toHaveBeenCalledWith(
expect.objectContaining({
explicitSystemPrompt: "Custom system.",
}),
);
expect(runtimeMocks.runAgent).toHaveBeenCalledWith(
"hello",
expect.objectContaining({
agentProfile: undefined,
}),
expect.anything(),
);
});
it("rejects missing agent profiles before starting the runtime", async () => {
agentConfigMocks.loadConfiguredAgentConfigs.mockReturnValue({
configs: [
{
name: "Planner",
description: "Plans work",
systemPrompt: "You are a planner.",
path: "/repo/.cline/agents/planner.yaml",
},
],
errors: [
{
path: "/repo/.cline/agents/broken.yaml",
error: new Error("Missing system prompt body"),
},
],
});
forcePromptModeInput();
process.argv = ["bun", "src/index.ts", "--agent", "reviewer", "hello"];
const { runCli } = await import("./main");
await expect(runCli()).resolves.toBeUndefined();
expect(process.exitCode).toBe(1);
expect(runtimeMocks.runAgent).not.toHaveBeenCalled();
expect(runtimeMocks.runInteractive).not.toHaveBeenCalled();
});
it("rejects --agent in yolo mode before loading profiles", async () => {
forcePromptModeInput();
process.argv = [
"bun",
"src/index.ts",
"--yolo",
"--agent",
"reviewer",
"hello",
];
const { runCli } = await import("./main");
await expect(runCli()).resolves.toBeUndefined();
expect(process.exitCode).toBe(1);
expect(agentConfigMocks.loadConfiguredAgentConfigs).not.toHaveBeenCalled();
expect(runtimeMocks.runAgent).not.toHaveBeenCalled();
});
it("rewrites /team prompts and enables teams in single-prompt mode", async () => {
runtimeMocks.runAgent.mockClear();
+8 -47
View File
@@ -42,7 +42,7 @@ import {
captureCliExtensionActivated,
getCliTelemetryService,
} from "./utils/telemetry";
import type { ActiveAgentProfile, Config } from "./utils/types";
import type { Config } from "./utils/types";
import { runConnectWizard } from "./wizards/connect";
import { runMcpWizard } from "./wizards/mcp";
import { runScheduleWizard } from "./wizards/schedule";
@@ -877,8 +877,9 @@ export async function runCli(): Promise<void> {
}
let knownModels: Config["knownModels"];
let persistedProviderConfig: Config["providerConfig"];
try {
const persistedProviderConfig = providerSettingsManager.getProviderConfig(
persistedProviderConfig = providerSettingsManager.getProviderConfig(
provider,
{
includeKnownModels: false,
@@ -928,49 +929,6 @@ export async function runCli(): Promise<void> {
cwd,
});
let activeAgentProfile: ActiveAgentProfile | undefined;
const requestedAgentName = args.agent?.trim();
if (requestedAgentName) {
if (isYoloMode) {
writeErr("--agent is not supported in yolo mode");
process.exitCode = 1;
return;
}
if (args.systemPrompt) {
// Don't store an unused profile: it would resurface on plan/act toggles.
writeln(
`${c.dim}[warn] --system overrides --agent; ignoring agent profile "${requestedAgentName}"${c.reset}`,
);
} else {
const { loadConfiguredAgentConfigs } = await import("@cline/core");
const { configs, errors } = loadConfiguredAgentConfigs({
workspaceRoot,
});
const profile = configs.find(
(candidate) =>
candidate.name.trim().toLowerCase() ===
requestedAgentName.toLowerCase(),
);
if (!profile) {
const availableNames = configs.map((candidate) => candidate.name);
writeErr(
availableNames.length > 0
? `agent profile "${requestedAgentName}" not found (available: ${availableNames.join(", ")})`
: `agent profile "${requestedAgentName}" not found (no agent profiles in .cline/agents)`,
);
for (const error of errors) {
writeErr(`failed to load ${error.path}: ${error.error.message}`);
}
process.exitCode = 1;
return;
}
activeAgentProfile = {
name: profile.name,
systemPrompt: profile.systemPrompt,
};
}
}
const config: Config = {
providerId: provider,
modelId:
@@ -979,13 +937,17 @@ export async function runCli(): Promise<void> {
knownModelIds[0] ??
"anthropic/claude-sonnet-4.6",
apiKey: apiKey ?? "",
baseUrl:
persistedProviderConfig?.baseUrl ?? selectedProviderSettings?.baseUrl,
headers:
persistedProviderConfig?.headers ?? selectedProviderSettings?.headers,
providerConfig: persistedProviderConfig,
knownModels,
systemPrompt: await resolveSystemPrompt({
cwd,
explicitSystemPrompt: args.systemPrompt,
providerId: provider,
mode: args.mode ?? "act",
agentPersona: activeAgentProfile?.systemPrompt,
}),
execution: {
maxConsecutiveMistakes: args.retries ?? 3,
@@ -1008,7 +970,6 @@ export async function runCli(): Promise<void> {
telemetry: getCliTelemetryService(loggerAdapter.core),
defaultToolAutoApprove,
toolPolicies,
agentProfile: activeAgentProfile,
enableSpawnAgent: !isYoloMode,
enableAgentTeams: !isYoloMode,
enableTools: true,
@@ -1,6 +1,7 @@
import { describe, expect, it, vi } from "vitest";
import {
type ChatCommandState,
createChatCommandHost,
chatCommandHost,
} from "../../utils/chat-commands";
import type { Config } from "../../utils/types";
@@ -162,4 +163,37 @@ describe("runInteractiveChatCommand", () => {
expect(state.autoApproveTools).toBe(true);
expect(setInteractiveAutoApprove).toHaveBeenCalledWith(true);
});
it("returns plugin command submit prompts as model input", async () => {
const config = makeConfig();
const runtime = makeRuntime();
const onCommandOutput = vi.fn();
const host = createChatCommandHost().register("command", {
names: ["/goal"],
run: async ({ args }, context) => {
await context.reply(`Goal guard set: ${args.join(" ")}`);
await context.submitPrompt?.(args.join(" "));
},
});
const result = await runInteractiveChatCommand({
prompt: "/goal fix tests",
enabled: true,
config,
host,
chatCommandState: makeState(config),
autoApproveAllRef: { current: false },
setInteractiveAutoApprove: () => {},
sessionRuntime: runtime,
stop: () => {},
onCommandOutput,
});
expect(result).toEqual({
handled: false,
input: "fix tests",
commandOutput: "Goal guard set: fix tests",
});
expect(onCommandOutput).toHaveBeenCalledWith("Goal guard set: fix tests");
});
});
@@ -26,7 +26,7 @@ export type InteractiveChatCommandRuntime = Pick<
export type InteractiveChatCommandResult =
| { handled: true; turnResult: InteractiveTurnResult }
| { handled: false; input: string };
| { handled: false; input: string; commandOutput?: string };
function commandTurnResult(commandOutput?: string): InteractiveTurnResult {
return {
@@ -46,6 +46,7 @@ export async function runInteractiveChatCommand(input: {
setInteractiveAutoApprove: (enabled: boolean) => void;
sessionRuntime: InteractiveChatCommandRuntime;
stop: () => void;
onCommandOutput?: (text: string) => void;
}): Promise<InteractiveChatCommandResult> {
let prompt = input.prompt;
const rewrittenTeamPrompt = rewriteTeamPrompt(prompt);
@@ -64,6 +65,7 @@ export async function runInteractiveChatCommand(input: {
}
let commandOutput: string | undefined;
let submitPrompt: string | undefined;
const handled = await maybeHandleChatCommand(prompt, {
enabled: input.enabled,
host: input.host,
@@ -80,6 +82,13 @@ export async function runInteractiveChatCommand(input: {
},
reply: async (text) => {
commandOutput = text;
input.onCommandOutput?.(text);
},
submitPrompt: async (text) => {
const trimmed = text.trim();
if (trimmed) {
submitPrompt = trimmed;
}
},
reset: async () => {
await input.sessionRuntime.resetForNewSession();
@@ -98,6 +107,13 @@ export async function runInteractiveChatCommand(input: {
fork: input.sessionRuntime.forkCurrentSession,
});
if (handled) {
if (submitPrompt) {
return {
handled: false,
input: submitPrompt,
...(commandOutput ? { commandOutput } : {}),
};
}
return {
handled: true,
turnResult: commandTurnResult(commandOutput),
@@ -78,36 +78,4 @@ describe("applyInteractiveModeConfig", () => {
expect(config.extraTools).toEqual([]);
expect(config.systemPrompt).toBe("system prompt for act");
});
it("threads the active agent profile persona across mode switches", async () => {
const config = makeConfig();
config.agentProfile = {
name: "reviewer",
systemPrompt: "You are a reviewer.",
};
await applyInteractiveModeConfig({
config,
mode: "plan",
switchToActModeTool,
});
expect(resolveSystemPrompt).toHaveBeenLastCalledWith({
cwd: config.cwd,
providerId: config.providerId,
mode: "plan",
agentPersona: "You are a reviewer.",
});
await applyInteractiveModeConfig({
config,
mode: "act",
switchToActModeTool,
});
expect(resolveSystemPrompt).toHaveBeenLastCalledWith({
cwd: config.cwd,
providerId: config.providerId,
mode: "act",
agentPersona: "You are a reviewer.",
});
});
});
-1
View File
@@ -43,6 +43,5 @@ export async function applyInteractiveModeConfig(input: {
cwd: input.config.cwd,
providerId: input.config.providerId,
mode: input.mode,
agentPersona: input.config.agentProfile?.systemPrompt,
});
}
@@ -21,7 +21,7 @@ import type {
import { createRuntimeHooks } from "../../utils/hooks";
import { setActiveCliSession } from "../../utils/output";
import { loadInteractiveResumeMessages } from "../../utils/resume";
import type { ActiveAgentProfile, Config } from "../../utils/types";
import type { Config } from "../../utils/types";
import { markAbortInProgress } from "../active-runtime";
import type {
PendingPromptSnapshot,
@@ -359,19 +359,6 @@ export function createInteractiveSessionRuntime(input: {
await restartWithCurrentMessages();
};
const applyAgentProfile = async (
profile: ActiveAgentProfile | undefined,
): Promise<void> => {
input.config.agentProfile = profile;
// Re-apply the current mode so the system prompt picks up the persona.
await applyInteractiveModeConfig({
config: input.config,
mode: input.config.mode === "plan" ? "plan" : "act",
switchToActModeTool: input.switchToActModeTool,
});
await restartWithCurrentMessages();
};
const sendCurrentTurn = async (
turnInput: CurrentTurnInput,
): Promise<CurrentTurnResult> => {
@@ -652,7 +639,6 @@ export function createInteractiveSessionRuntime(input: {
getCheckpointData,
restoreCheckpoint,
applyMode,
applyAgentProfile,
resetAbortRequest,
abortAll,
cleanup,
-3
View File
@@ -28,8 +28,6 @@ export async function resolveSystemPrompt(input: {
providerId?: string;
rules?: string;
mode?: AgentMode;
/** Agent profile body that replaces the persona slot of the base prompt */
agentPersona?: string;
}): Promise<string> {
const metadata = await buildWorkspaceMetadata(input.cwd);
let rules = mergeRulesForSystemPrompt(undefined, input.rules);
@@ -47,7 +45,6 @@ export async function resolveSystemPrompt(input: {
mode: input.mode,
providerId: input.providerId,
overridePrompt: input.explicitSystemPrompt,
personaPrompt: input.agentPersona,
platform:
(typeof process !== "undefined" && process?.platform) || "unknown",
});
+9 -5
View File
@@ -427,7 +427,8 @@ export async function runInteractive(
uiEvents.off("pending-prompt-submitted", onPendingPromptSubmitted);
};
},
onSubmit: async (input, mode, delivery, attachments) => {
onSubmit: async (input, mode, delivery, attachments, onCommandOutput) => {
let commandOutput: string | undefined;
try {
await sessionRuntime.ensureReady();
await waitForSubmittedMode(mode);
@@ -446,6 +447,7 @@ export async function runInteractive(
setInteractiveAutoApprove,
sessionRuntime,
stop: () => tuiApp?.destroy(),
onCommandOutput,
});
if (chatCommandResult.handled) {
return chatCommandResult.turnResult;
@@ -465,12 +467,14 @@ export async function runInteractive(
setInteractiveAutoApprove,
sessionRuntime,
stop: () => tuiApp?.destroy(),
onCommandOutput,
});
if (chatCommandResult.handled) {
return chatCommandResult.turnResult;
}
}
input = chatCommandResult.input;
commandOutput = chatCommandResult.commandOutput;
const {
prompt: userInput,
userImages,
@@ -507,6 +511,7 @@ export async function runInteractive(
iterations: 0,
finishReason: "queued",
queued: delivery === "queue" || delivery === "steer",
commandOutput,
};
}
if (result.finishReason !== "completed") {
@@ -519,6 +524,7 @@ export async function runInteractive(
currentContextSize: getCurrentContextSize(result.messages),
iterations: result.iterations,
finishReason: "aborted",
commandOutput,
};
}
const errorText = result.text.trim();
@@ -532,6 +538,7 @@ export async function runInteractive(
currentContextSize: getCurrentContextSize(result.messages),
iterations: result.iterations,
finishReason: result.finishReason,
commandOutput,
};
} catch (error) {
if (isAbortInProgress()) {
@@ -539,6 +546,7 @@ export async function runInteractive(
usage: { inputTokens: 0, outputTokens: 0 },
iterations: 0,
finishReason: "aborted",
commandOutput,
};
}
logCliError(config.logger, "Interactive turn failed", {
@@ -598,10 +606,6 @@ export async function runInteractive(
}
await applyModeChange(mode);
},
onAgentProfileChange: async (profile) => {
await sessionRuntime.ensureReady();
await sessionRuntime.applyAgentProfile(profile ?? undefined);
},
onNewSession: async () => {
await sessionRuntime.resetForNewSession();
},
@@ -271,35 +271,6 @@ describe("slash command registry", () => {
).toContain("settings");
});
it("exposes agents as a local command with a hidden agent alias", () => {
const registry = buildSlashCommandRegistry({});
const commandNames = getVisibleSystemSlashCommands(registry).map(
(command) => command.name,
);
expect(resolveSlashCommand(registry, "agents")).toMatchObject({
source: "tui",
execution: "local",
description: "Switch agent profile",
visible: true,
selectable: true,
});
expect(resolveSlashCommand(registry, "agent")).toMatchObject({
source: "tui",
execution: "local",
visible: false,
selectable: false,
});
expect(commandNames).toContain("agents");
expect(commandNames).not.toContain("agent");
expect(commandNames.indexOf("agents")).toBeGreaterThan(
commandNames.indexOf("model"),
);
expect(commandNames.indexOf("agents")).toBeLessThan(
commandNames.indexOf("account"),
);
});
it("always exposes the account command", () => {
const registry = buildSlashCommandRegistry({});
@@ -17,8 +17,6 @@ export type LocalSlashCommandName =
| "plugins"
| "account"
| "model"
| "agents"
| "agent"
| "compact"
| "skills"
| "fork"
@@ -64,15 +62,6 @@ const TUI_LOCAL_COMMANDS: Array<{
name: "model",
description: "Switch model or provider",
},
{
name: "agents",
description: "Switch agent profile",
},
{
name: "agent",
description: "Switch agent profile",
visible: false,
},
{
name: "account",
description: "View Cline account",
@@ -123,7 +112,6 @@ const TUI_LOCAL_COMMANDS: Array<{
const SYSTEM_COMMAND_ORDER = [
"settings",
"model",
"agents",
"account",
"mcp",
"plugins",
@@ -1,108 +0,0 @@
import { basename } from "node:path";
import type { ChoiceContext } from "@opentui-ui/dialog";
import { useDialogKeyboard } from "@opentui-ui/dialog/react";
import { useMemo } from "react";
import {
type SearchableItem,
SearchableList,
useSearchableList,
} from "../searchable-list";
/** Sentinel resolved when the user picks the default Cline agent. */
export const DEFAULT_AGENT_ACTION = "__default_agent__";
export interface AgentProfileOption {
name: string;
description?: string;
systemPrompt: string;
source: "workspace" | "global";
}
export interface AgentProfileLoadError {
path: string;
message: string;
}
export function AgentSelectorContent(
props: ChoiceContext<string> & {
currentAgentName: string | null;
agents: AgentProfileOption[];
loadErrors: AgentProfileLoadError[];
},
) {
const { resolve, dismiss, dialogId, currentAgentName, agents, loadErrors } =
props;
const items: SearchableItem[] = useMemo(() => {
const normalizedCurrent = currentAgentName?.trim().toLowerCase() ?? null;
const defaultItem: SearchableItem = {
key: DEFAULT_AGENT_ACTION,
label: "Cline (default)",
detail: "Standard Cline agent",
section: "Agents",
rightLabel: normalizedCurrent === null ? "(current)" : undefined,
};
const profileItems = agents.map((agent) => ({
key: agent.name.toLowerCase(),
label: agent.name,
detail: agent.description,
section:
agent.source === "workspace" ? "Workspace agents" : "Global agents",
rightLabel:
normalizedCurrent === agent.name.trim().toLowerCase()
? "(current)"
: undefined,
}));
return [defaultItem, ...profileItems];
}, [agents, currentAgentName]);
const list = useSearchableList(items);
useDialogKeyboard((key) => {
if (key.name === "escape") {
dismiss();
return;
}
if (key.name === "return") {
const item = list.selectedItem;
if (item) resolve(item.key);
return;
}
if (key.name === "up") {
list.moveUp();
return;
}
if (key.name === "down") {
list.moveDown();
}
}, dialogId);
return (
<box flexDirection="column" gap={1}>
<text>Select Agent</text>
<SearchableList
items={list.filtered}
selected={list.safeSelected}
placeholder="Search agents..."
onSearchChange={list.setSearch}
onItemSelect={(item) => resolve(item.key)}
emptyText="No agents match"
/>
{loadErrors.length > 0 && (
<box flexDirection="column">
{loadErrors.map((error) => (
<text key={error.path} fg="red">
{basename(error.path)}: {error.message}
</text>
))}
</box>
)}
<text fg="gray">
Type to search, / navigate, Enter to select, Esc to go back
</text>
</box>
);
}
@@ -2,7 +2,6 @@ export type CommandPaletteAction =
| "settings"
| "change-model"
| "change-provider"
| "agents"
| "account"
| "mcp"
| "plugins"
@@ -58,13 +57,6 @@ const ACTION_ITEMS: Array<{
description: "Switch provider and configure credentials",
keywords: ["provider", "api key", "account", "auth"],
},
{
action: "agents",
label: "Switch Agent",
shortcut: "Opt+T",
description: "Use an agent profile from .cline/agents",
keywords: ["agent", "agents", "profile", "persona", "subagent"],
},
{
action: "mcp",
label: "Manage MCP Servers",
@@ -120,12 +120,6 @@ const HELP_ROWS: HelpRow[] = [
key: "/model",
desc: "Switch model or provider",
},
{
kind: "entry",
id: "c-agents",
key: "/agents",
desc: "Switch agent profile",
},
{
kind: "entry",
id: "c-settings",
@@ -1,8 +1,6 @@
import { describe, expect, it, vi } from "vitest";
import {
createContextBar,
formatStatusBarAgentLabel,
formatStatusBarAgentName,
formatStatusBarUsageText,
resolveContextBarFilledForeground,
} from "./status-bar";
@@ -51,48 +49,6 @@ describe("createContextBar", () => {
});
});
describe("formatStatusBarAgentName", () => {
it("keeps short names intact", () => {
expect(formatStatusBarAgentName("reviewer")).toBe("reviewer");
expect(formatStatusBarAgentName(" reviewer ")).toBe("reviewer");
});
it("truncates long names with an ellipsis", () => {
expect(formatStatusBarAgentName("documentation-specialist")).toBe(
"documentation...",
);
expect(formatStatusBarAgentName("documentation-specialist").length).toBe(
16,
);
});
it("handles very narrow limits without negative slicing", () => {
expect(formatStatusBarAgentName("reviewer", 3)).toBe("...");
expect(formatStatusBarAgentName("reviewer", 0)).toBe("");
});
});
describe("formatStatusBarAgentLabel", () => {
it("returns the trimmed active agent name", () => {
expect(formatStatusBarAgentLabel("reviewer")).toBe("reviewer");
expect(formatStatusBarAgentLabel(" reviewer ")).toBe("reviewer");
});
it("truncates to fit the label width", () => {
expect(formatStatusBarAgentLabel("documentation-specialist", 18)).toBe(
"documentation-s...",
);
expect(
formatStatusBarAgentLabel("documentation-specialist", 18)?.length,
).toBe(18);
});
it("hides blank or too-narrow labels", () => {
expect(formatStatusBarAgentLabel(" ")).toBeUndefined();
expect(formatStatusBarAgentLabel("reviewer", 0)).toBeUndefined();
});
});
describe("formatStatusBarUsageText", () => {
it("includes cost when usage cost is visible", () => {
expect(
+6 -54
View File
@@ -104,23 +104,6 @@ export function resolveModelMaxInputTokens(config: {
return undefined;
}
export function formatStatusBarAgentName(name: string, maxLen = 16): string {
const normalized = name.trim();
if (maxLen <= 0) return "";
if (normalized.length <= maxLen) return normalized;
if (maxLen <= 3) return ".".repeat(maxLen);
return `${normalized.slice(0, maxLen - 3)}...`;
}
export function formatStatusBarAgentLabel(
name: string,
maxLen = 18,
): string | undefined {
const normalized = name.trim();
if (!normalized || maxLen <= 0) return undefined;
return formatStatusBarAgentName(normalized, maxLen);
}
export interface StatusBarProps {
providerId: string;
modelId: string;
@@ -137,9 +120,6 @@ export interface StatusBarProps {
deletions: number;
} | null;
onToggleMode?: () => void;
/** Active agent profile name; the indicator is hidden when unset */
agentName?: string | null;
onOpenAgent?: () => void;
variant?: "home" | "chat";
}
@@ -155,8 +135,6 @@ export function StatusBar(props: StatusBarProps) {
gitBranch,
gitDiffStats,
onToggleMode,
agentName,
onOpenAgent,
} = props;
const { width } = useTerminalDimensions();
@@ -184,26 +162,20 @@ export function StatusBar(props: StatusBarProps) {
? Math.min(width, HOME_VIEW_MAX_WIDTH) - 2
: width - 2;
// Row 1 layout: [model + context info] .... [agent label] [Plan/Act toggle]
// Row 1 layout: [model + context info] .... [Plan/Act toggle]
// When the full row doesn't fit, context info drops to its own row 2.
// Model ID truncates with "..." before wrapping; toggle stays right-aligned.
const toggleWidth = 20;
const fullAgentLabel = agentName
? formatStatusBarAgentLabel(agentName)
: undefined;
const agentLabelWidth = fullAgentLabel ? fullAgentLabel.length + 3 : 0;
const usageText = formatStatusBarUsageText({
totalTokens,
totalCost,
showCost: showUsageCost,
});
const contextInlineText = bar
? `${bar.filled}${bar.empty} ${usageText}`
: usageText;
const contextText = ` ${contextInlineText}`;
const contextText = bar
? ` ${bar.filled}${bar.empty} ${usageText}`
: ` ${usageText}`;
const firstRowFits =
modelId.length + contextText.length + toggleWidth + agentLabelWidth + 1 <=
avail;
modelId.length + contextText.length + toggleWidth + 1 <= avail;
const renderContextText = (withLeadingSpace: boolean) => (
<>
{withLeadingSpace && " "}
@@ -219,11 +191,7 @@ export function StatusBar(props: StatusBarProps) {
const modelMaxLen = Math.max(
10,
avail -
toggleWidth -
agentLabelWidth -
(firstRowFits ? contextText.length : 0) -
1,
avail - toggleWidth - (firstRowFits ? contextText.length : 0) - 1,
);
const truncatedModel =
modelId.length > modelMaxLen
@@ -242,14 +210,6 @@ export function StatusBar(props: StatusBarProps) {
pathPart.length > pathMax
? `${pathPart.slice(0, pathMax - 3)}...`
: pathPart;
const firstRowAgentMaxLen = Math.min(
18,
Math.max(0, avail - toggleWidth - 3),
);
const agentLabel = agentName
? formatStatusBarAgentLabel(agentName, firstRowAgentMaxLen)
: undefined;
return (
<box flexDirection="column" paddingX={1}>
<box flexDirection="row" justifyContent="space-between">
@@ -263,14 +223,6 @@ export function StatusBar(props: StatusBarProps) {
flexShrink={0}
onMouseDown={onToggleMode}
>
{agentLabel && (
<>
<box flexShrink={0} onMouseDown={onOpenAgent}>
<text fg={defaultFg}>{agentLabel}</text>
</box>
<text fg="gray">|</text>
</>
)}
<text fg={uiMode === "plan" ? planAccent : "gray"}>
{uiMode === "plan" ? "●" : "○"} Plan
</text>
@@ -21,7 +21,6 @@ interface SessionContextValue {
uiMode: AgentMode;
autoApproveAll: boolean;
compactionMode: CliCompactionMode;
activeAgentName: string | null;
lastTotalTokens: number;
lastTotalCost: number;
isExitRequested: boolean;
@@ -42,7 +41,6 @@ interface SessionContextValue {
setUiMode: (mode: AgentMode) => void;
toggleMode: () => void;
toggleAutoApprove: () => void;
setActiveAgentName: (name: string | null) => void;
setCompactionMode: (mode: CliCompactionMode) => void;
requestExit: () => void;
clearEntries: () => void;
@@ -114,9 +112,6 @@ export function SessionProvider(props: {
const [compactionMode, _setCompactionMode] = useState<CliCompactionMode>(() =>
getCliCompactionMode(config),
);
const [activeAgentName, setActiveAgentName] = useState<string | null>(
config.agentProfile?.name ?? null,
);
const [lastTotalTokens, setLastTotalTokens] = useState(
() => initialUsage?.totalTokens ?? 0,
);
@@ -255,7 +250,6 @@ export function SessionProvider(props: {
uiMode,
autoApproveAll,
compactionMode,
activeAgentName,
lastTotalTokens,
lastTotalCost,
isExitRequested,
@@ -274,7 +268,6 @@ export function SessionProvider(props: {
setUiMode,
toggleMode,
toggleAutoApprove,
setActiveAgentName,
setCompactionMode,
requestExit,
clearEntries,
@@ -7,7 +7,6 @@ export interface LocalSlashCommandActionInput {
openConfig: (options?: OpenConfigOptions) => void;
openMcpManager: () => Promise<boolean>;
openModelSelector: () => void;
openAgentSelector: () => void;
openSkills: (invocation?: LocalSlashCommandInvocation) => void;
invocation?: LocalSlashCommandInvocation;
runCompact: () => void;
@@ -46,10 +45,6 @@ export function runLocalSlashCommandAction(
input.openModelSelector();
return true;
}
if (normalized === "agents" || normalized === "agent") {
input.openAgentSelector();
return true;
}
if (normalized === "compact") {
input.runCompact();
return true;
@@ -1,119 +0,0 @@
import { sep } from "node:path";
import { loadConfiguredAgentConfigs } from "@cline/core";
import type { ChoiceContext } from "@opentui-ui/dialog";
import type { DialogActions } from "@opentui-ui/dialog/react";
import { useCallback } from "react";
import type { Config } from "../../utils/types";
import {
type AgentProfileLoadError,
type AgentProfileOption,
AgentSelectorContent,
DEFAULT_AGENT_ACTION,
} from "../components/dialogs/agent-selector";
import { withLoadingDialog } from "../components/dialogs/loading-dialog";
import { useSession } from "../contexts/session-context";
import type { TuiProps } from "../types";
function loadAgentProfileEntries(config: Config): {
agents: AgentProfileOption[];
loadErrors: AgentProfileLoadError[];
} {
const workspaceRoot = config.workspaceRoot?.trim() || config.cwd;
const { configs, errors } = loadConfiguredAgentConfigs({ workspaceRoot });
return {
agents: configs.map((profile) => ({
name: profile.name,
description: profile.description,
systemPrompt: profile.systemPrompt,
source:
workspaceRoot && profile.path?.startsWith(`${workspaceRoot}${sep}`)
? ("workspace" as const)
: ("global" as const),
})),
loadErrors: errors.map((error) => ({
path: error.path,
message: error.error.message,
})),
};
}
export function useAgentSelector(opts: {
dialog: DialogActions;
config: Config;
termHeight: number;
onAgentProfileChange: TuiProps["onAgentProfileChange"];
refocusTextarea: () => void;
}): () => Promise<void> {
const { dialog, config, termHeight, onAgentProfileChange, refocusTextarea } =
opts;
const session = useSession();
const openAgentSelector = useCallback(async () => {
// Applying a profile restarts the session in place, so refuse while a
// turn is running instead of yanking the live stream out from under it.
if (session.isRunning) {
session.appendEntry({
kind: "status",
text: "Finish or abort the current task before switching agents.",
});
refocusTextarea();
return;
}
const { agents, loadErrors } = loadAgentProfileEntries(config);
const currentAgentName = config.agentProfile?.name ?? null;
const selectedKey = await dialog.choice<string>({
style: { maxHeight: termHeight - 2 },
content: (ctx: ChoiceContext<string>) => (
<AgentSelectorContent
{...ctx}
currentAgentName={currentAgentName}
agents={agents}
loadErrors={loadErrors}
/>
),
});
if (!selectedKey) {
refocusTextarea();
return;
}
if (selectedKey === DEFAULT_AGENT_ACTION) {
if (config.agentProfile) {
await withLoadingDialog(dialog, "Applying agent...", async () => {
await onAgentProfileChange(null);
});
session.setActiveAgentName(null);
}
refocusTextarea();
return;
}
const profile = agents.find(
(agent) => agent.name.toLowerCase() === selectedKey,
);
if (!profile) {
refocusTextarea();
return;
}
if (profile.name !== currentAgentName) {
await withLoadingDialog(dialog, "Applying agent...", async () => {
await onAgentProfileChange({
name: profile.name,
systemPrompt: profile.systemPrompt,
});
});
session.setActiveAgentName(profile.name);
}
refocusTextarea();
}, [
dialog,
config,
termHeight,
onAgentProfileChange,
refocusTextarea,
session,
]);
return openAgentSelector;
}
@@ -13,7 +13,6 @@ function makeActions(
openConfig: vi.fn(),
openMcpManager: vi.fn(async () => false),
openModelSelector: vi.fn(),
openAgentSelector: vi.fn(),
openSkills: vi.fn(),
runCompact: vi.fn(),
runFork: vi.fn(),
@@ -46,21 +45,6 @@ describe("runLocalSlashCommandAction", () => {
expect(openSkills).toHaveBeenCalledWith(invocation);
});
it("opens the agent selector with agents and the agent alias", () => {
for (const name of ["agents", "agent"]) {
const openAgentSelector = vi.fn();
const actions = makeActions({ openAgentSelector });
const handled = runLocalSlashCommandAction({
name,
...actions,
});
expect(handled).toBe(true);
expect(openAgentSelector).toHaveBeenCalledOnce();
}
});
it("opens settings to the plugins tab with plugins", () => {
const openConfig = vi.fn();
const actions = makeActions({ openConfig });
@@ -23,7 +23,6 @@ export function useLocalCommandActions(input: {
openConfig: (options?: OpenConfigOptions) => void;
openMcpManager: () => Promise<boolean>;
openModelSelector: () => void;
openAgentSelector: () => void;
openSkills: (invocation?: LocalSlashCommandInvocation) => void;
refocusTextarea: () => void;
setAppView: (view: AppView) => void;
@@ -44,7 +43,6 @@ export function useLocalCommandActions(input: {
openConfig,
openMcpManager,
openModelSelector,
openAgentSelector,
openSkills,
refocusTextarea,
setAppView,
@@ -187,7 +185,6 @@ export function useLocalCommandActions(input: {
openConfig,
openMcpManager,
openModelSelector,
openAgentSelector,
openSkills,
runCompact,
runFork,
@@ -208,7 +205,6 @@ export function useLocalCommandActions(input: {
openHelp,
openHistory,
openModelSelector,
openAgentSelector,
openSkills,
runCompact,
runFork,
@@ -327,6 +327,14 @@ export function usePromptInputController(input: {
}
const startedAt = performance.now();
let commandOutputAppended = false;
const appendCommandOutput = (text: string) => {
commandOutputAppended = true;
session.appendEntry({
kind: "status",
text,
});
};
try {
const result = await onSubmit(
promptForSubmit,
@@ -335,8 +343,9 @@ export function usePromptInputController(input: {
activeUserImages.length > 0
? { userImages: activeUserImages }
: undefined,
appendCommandOutput,
);
if (result.commandOutput) {
if (result.commandOutput && !commandOutputAppended) {
session.appendEntry({
kind: "status",
text: result.commandOutput,
+53 -25
View File
@@ -1,4 +1,4 @@
import { existsSync, readFileSync } from "node:fs";
import { existsSync, readdirSync, readFileSync } from "node:fs";
import {
basename,
dirname,
@@ -14,11 +14,11 @@ import {
hasMcpSettingsFile,
listHookConfigFiles,
listPluginToolsWithDiagnostics,
loadConfiguredAgentConfigs,
type McpServerRegistration,
type PluginInitializationFailure,
type RuleConfig,
readGlobalSettings,
resolveAgentConfigSearchPaths,
resolveDefaultMcpSettingsPath,
resolveMcpServerRegistrations,
resolvePluginConfigSearchPaths,
@@ -175,30 +175,58 @@ function getMcpDescription(registration: McpServerRegistration): string {
}
function loadAgentConfigItems(workspaceRoot: string): InteractiveConfigItem[] {
const { configs, errors } = loadConfiguredAgentConfigs({ workspaceRoot });
const items: InteractiveConfigItem[] = configs.map((config) => ({
id: config.name.toLowerCase(),
name: config.name,
path: config.path ?? "",
enabled: true,
kind: "agent" as const,
source: detectSource(config.path ?? "", workspaceRoot),
description: config.description,
}));
// Keep broken profile files visible so users can spot and fix them.
for (const error of errors) {
items.push({
id: error.path,
name: basename(error.path, extname(error.path)),
path: error.path,
enabled: false,
kind: "agent" as const,
source: detectSource(error.path, workspaceRoot),
description: error.error.message,
loadError: error.error.message,
});
const agentsById = new Map<string, InteractiveConfigItem>();
const directories = resolveAgentConfigSearchPaths(workspaceRoot).filter(
(directory) => existsSync(directory),
);
for (const directory of directories) {
try {
const entries = readdirSync(directory, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isFile()) {
continue;
}
const extension = extname(entry.name).toLowerCase();
if (extension !== ".yml" && extension !== ".yaml") {
continue;
}
const filePath = join(directory, entry.name);
const raw = readFileSync(filePath, "utf8");
const frontmatterMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
const frontmatter = frontmatterMatch?.[1] ?? "";
const nameMatch = frontmatter.match(/^\s*name:\s*(.+?)\s*$/m);
const descriptionMatch = frontmatter.match(
/^\s*description:\s*(.+?)\s*$/m,
);
const parsedName = nameMatch?.[1]?.replace(/^["']|["']$/g, "").trim();
const parsedDescription = descriptionMatch?.[1]
?.replace(/^["']|["']$/g, "")
.trim();
const name =
parsedName && parsedName.length > 0
? parsedName
: basename(entry.name, extension);
const id = name.toLowerCase();
if (agentsById.has(id)) {
continue;
}
agentsById.set(id, {
id,
name,
path: filePath,
enabled: true,
kind: "agent",
source: detectSource(filePath, workspaceRoot),
description: parsedDescription,
});
}
} catch {
// Best effort: keep listing other agent config roots.
}
}
return items;
return [...agentsById.values()];
}
function readPackageName(packageJsonPath: string): string | undefined {
-13
View File
@@ -41,7 +41,6 @@ import { EventBridgeProvider } from "./contexts/event-bridge-context";
import { SessionProvider, useSession } from "./contexts/session-context";
import { useAccountDialog } from "./hooks/use-account-dialog";
import { useAgentEventHandlers } from "./hooks/use-agent-events";
import { useAgentSelector } from "./hooks/use-agent-selector";
import { useAutocomplete } from "./hooks/use-autocomplete";
import { useConfigPanel } from "./hooks/use-config-panel";
import { useLocalCommandActions } from "./hooks/use-local-command-actions";
@@ -188,14 +187,6 @@ function App(props: TuiProps) {
refocusTextarea: () => refocusTextareaRef.current(),
});
const openAgentSelector = useAgentSelector({
dialog,
config: props.config,
termHeight,
onAgentProfileChange: props.onAgentProfileChange,
refocusTextarea: () => refocusTextareaRef.current(),
});
const openMcpManager = useMcpManager({
dialog,
termHeight,
@@ -648,7 +639,6 @@ function App(props: TuiProps) {
openConfig,
openMcpManager,
openModelSelector,
openAgentSelector,
openSkills,
refocusTextarea: () => refocusTextareaRef.current(),
setAppView,
@@ -896,9 +886,6 @@ function App(props: TuiProps) {
void saveQueuedPromptEdit(id, prompt);
},
onToggleMode: toggleMode,
onOpenAgentSelector: () => {
void openAgentSelector();
},
runtimeInteraction,
onResolveToolApproval: runtimeBridge.resolveToolApproval,
onResolveAskQuestion: runtimeBridge.resolveAskQuestion,
+2 -6
View File
@@ -15,11 +15,7 @@ import type {
PendingPromptSubmittedEvent,
} from "../runtime/session-events";
import type { RepoStatus } from "../utils/repo-status";
import type {
ActiveAgentProfile,
CliCompactionMode,
Config,
} from "../utils/types";
import type { CliCompactionMode, Config } from "../utils/types";
import type { ClineAccountSnapshot } from "./cline-account";
import type {
InteractiveConfigData,
@@ -156,6 +152,7 @@ export interface TuiProps {
mode: AgentMode,
delivery?: "queue" | "steer",
attachments?: UserInputAttachments,
onCommandOutput?: (text: string) => void,
) => Promise<InteractiveTurnResult>;
onUpdatePendingPrompt: (input: {
promptId: string;
@@ -170,7 +167,6 @@ export interface TuiProps {
onCompactionModeChange: (mode: CliCompactionMode) => Promise<void>;
onModelChange: () => Promise<void>;
onModeChange: (mode: AgentMode) => Promise<void>;
onAgentProfileChange: (profile: ActiveAgentProfile | null) => Promise<void>;
onNewSession: () => Promise<void>;
onSessionRestart: () => Promise<void>;
onAccountChange: () => Promise<void>;
-3
View File
@@ -56,7 +56,6 @@ export function ChatView(props: {
editingQueuedPrompt?: QueuedPromptItem;
onQueuedPromptEditConfirm: (id: string, prompt: string) => void;
onToggleMode: () => void;
onOpenAgentSelector: () => void;
runtimeInteraction?: RuntimeToolInteraction | null;
onResolveToolApproval: (id: number, approved: boolean) => void;
onResolveAskQuestion: (id: number, answer: string | null) => void;
@@ -158,8 +157,6 @@ export function ChatView(props: {
gitBranch={repoStatus.branch}
gitDiffStats={repoStatus.diffStats}
onToggleMode={props.onToggleMode}
agentName={session.activeAgentName}
onOpenAgent={props.onOpenAgentSelector}
variant="chat"
/>
</box>
-3
View File
@@ -46,7 +46,6 @@ export function HomeView(props: {
textareaRef?: React.MutableRefObject<TextareaHandle | null>;
autocomplete?: AutocompleteDropdownProps;
onToggleMode: () => void;
onOpenAgentSelector: () => void;
}) {
const {
config,
@@ -157,8 +156,6 @@ export function HomeView(props: {
gitBranch={repoStatus.branch}
gitDiffStats={repoStatus.diffStats}
onToggleMode={props.onToggleMode}
agentName={session.activeAgentName}
onOpenAgent={props.onOpenAgentSelector}
variant="home"
/>
</box>
@@ -146,5 +146,50 @@ describe("onboarding auth telemetry forwarding", () => {
// emitted by completeClineDeviceAuth, so passing telemetry to the start
// helper would double-emit the event.
expect(hoisted.startClineDeviceAuth).toHaveBeenCalledWith();
expect(hoisted.openMock).toHaveBeenCalledWith(
"https://verify?user_code=uc",
{ wait: false },
);
});
it("falls back to displaying the device auth URL when browser open fails", async () => {
hoisted.openMock.mockRejectedValueOnce(new Error("no browser"));
hoisted.startClineDeviceAuth.mockResolvedValueOnce({
deviceCode: "dc",
userCode: "uc",
verificationUri: "https://verify",
verificationUriComplete: "https://verify?user_code=uc",
expiresInSeconds: 600,
pollIntervalSeconds: 5,
});
hoisted.completeClineDeviceAuth.mockResolvedValueOnce({
access: "a",
refresh: "r",
expires: 0,
});
const setStatus = vi.fn();
runDeviceCodeAuthFlow({
providerId: "cline",
providerSettingsManager: makeManager(),
isAborted: () => false,
setUserCode: vi.fn(),
setVerifyUrl: vi.fn(),
setStatus,
setError: vi.fn(),
onComplete: vi.fn(),
});
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
expect(hoisted.openMock).toHaveBeenCalledWith(
"https://verify?user_code=uc",
{ wait: false },
);
expect(setStatus).toHaveBeenCalledWith(
"Could not open browser. Visit the URL below.",
);
});
});
+10 -3
View File
@@ -89,11 +89,18 @@ export function runDeviceCodeAuthFlow(input: {
startClineDeviceAuth()
.then((result) => {
if (input.isAborted()) return;
const verifyUrl =
result.verificationUriComplete || result.verificationUri;
input.setUserCode(result.userCode);
input.setVerifyUrl(
result.verificationUriComplete || result.verificationUri,
);
input.setVerifyUrl(verifyUrl);
input.setStatus("Enter the code at the URL below");
try {
void open(verifyUrl, { wait: false }).catch(() => {
input.setStatus("Could not open browser. Visit the URL below.");
});
} catch {
input.setStatus("Could not open browser. Visit the URL below.");
}
completeClineDeviceAuth({
deviceCode: result.deviceCode,
+1
View File
@@ -28,6 +28,7 @@ export type ChatCommandContext = {
getState: () => Promise<ChatCommandState> | ChatCommandState;
setState: (next: ChatCommandState) => Promise<void> | void;
reply: (text: string) => Promise<void> | void;
submitPrompt?: (prompt: string) => Promise<void> | void;
reset?: () => Promise<void> | void;
abort?: () => Promise<void> | void;
stop?: () => Promise<void> | void;
@@ -65,4 +65,55 @@ describe("plugin chat commands", () => {
expect(reply).toHaveBeenCalledWith("echo:hello plugin");
await shutdown?.();
});
it("bridges plugin command submit prompts onto the chat command context", async () => {
const tempRoot = await mkdtemp(join(tmpdir(), "cli-plugin-commands-"));
tempRoots.push(tempRoot);
const pluginsDir = join(tempRoot, ".cline", "plugins");
await mkdir(pluginsDir, { recursive: true });
await writeFile(
join(pluginsDir, "submit.js"),
[
"export default {",
" name: 'submit-plugin',",
" manifest: { capabilities: ['commands'] },",
" setup(api) {",
" api.registerCommand({",
" name: 'goal',",
" description: 'Set a goal and submit it',",
" handler: async (input) => ({",
" reply: 'goal:' + input,",
" submitPrompt: input",
" })",
" });",
" },",
"};",
].join("\n"),
);
const { host, shutdown } = await createWorkspaceChatCommandHost({
cwd: tempRoot,
workspaceRoot: tempRoot,
});
const reply = vi.fn(async () => undefined);
const submitPrompt = vi.fn(async () => undefined);
const handled = await host.handle("/goal fix tests", {
enabled: true,
getState: async () => ({
enableTools: false,
autoApproveTools: false,
cwd: tempRoot,
workspaceRoot: tempRoot,
}),
setState: async () => undefined,
reply,
submitPrompt,
});
expect(handled).toBe(true);
expect(reply).toHaveBeenCalledWith("goal:fix tests");
expect(submitPrompt).toHaveBeenCalledWith("fix tests");
await shutdown?.();
});
});
+31 -2
View File
@@ -1,5 +1,6 @@
import {
type AgentExtensionCommand,
type AgentExtensionCommandResult,
type BasicLogger,
createContributionRegistry,
resolveAndLoadAgentPlugins,
@@ -45,13 +46,41 @@ function createPluginCommandDefinition(
names: [normalizedName.toLowerCase()],
run: async ({ args }, context) => {
const result = await command.handler?.(args.join(" "));
if (typeof result === "string" && result.trim()) {
await context.reply(result);
const { reply, submitPrompt } = normalizeCommandResult(result);
if (reply) {
await context.reply(reply);
}
if (submitPrompt) {
await context.submitPrompt?.(submitPrompt);
}
},
};
}
function normalizeCommandResult(
result: AgentExtensionCommandResult | undefined,
): { reply?: string; submitPrompt?: string } {
if (typeof result === "string") {
const reply = result.trim();
return reply ? { reply } : {};
}
if (!result || typeof result !== "object") {
return {};
}
const reply =
typeof result.reply === "string" && result.reply.trim()
? result.reply.trim()
: undefined;
const submitPrompt =
typeof result.submitPrompt === "string" && result.submitPrompt.trim()
? result.submitPrompt.trim()
: undefined;
return {
...(reply ? { reply } : {}),
...(submitPrompt ? { submitPrompt } : {}),
};
}
export async function createWorkspaceChatCommandHost(input: {
cwd: string;
workspaceRoot?: string;
-13
View File
@@ -17,16 +17,6 @@ export type CliReasoningEffort = NonNullable<
>;
export type CliCompactionMode = "agentic" | "basic" | "off";
/**
* An agent profile from .cline/agents applied to the main Cline agent for
* the current session. Session-only: never persisted to settings.
*/
export interface ActiveAgentProfile {
name: string;
/** Profile body, captured at selection time (survives file deletion mid-session) */
systemPrompt: string;
}
export interface Config extends Omit<CoreSessionConfig, "apiKey" | "mode"> {
apiKey: string;
knownModels?: Record<string, Llms.ModelInfo>;
@@ -40,7 +30,6 @@ export interface Config extends Omit<CoreSessionConfig, "apiKey" | "mode"> {
mode: CliAgentMode;
defaultToolAutoApprove: boolean;
toolPolicies: Record<string, ToolPolicy>;
agentProfile?: ActiveAgentProfile;
}
export interface ActiveCliSession {
@@ -107,6 +96,4 @@ export interface ParsedArgs {
teamName?: string;
defaultToolAutoApprove: boolean;
autoApproveOverride?: boolean;
/** Agent profile name from .cline/agents to apply to the main agent */
agent?: string;
}
+16 -34
View File
@@ -1,16 +1,16 @@
{
"name": "claude-dev",
"version": "3.89.0",
"version": "3.89.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-dev",
"version": "3.89.0",
"version": "3.89.2",
"license": "Apache-2.0",
"dependencies": {
"@anthropic-ai/sdk": "^0.37.0",
"@anthropic-ai/vertex-sdk": "^0.6.4",
"@anthropic-ai/sdk": "^0.50.4",
"@anthropic-ai/vertex-sdk": "^0.11.5",
"@aws-sdk/client-bedrock-runtime": "^3.922.0",
"@aws-sdk/credential-providers": "^3.922.0",
"@azure/identity": "^4.13.0",
@@ -156,42 +156,24 @@
}
},
"node_modules/@anthropic-ai/sdk": {
"version": "0.37.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.37.0.tgz",
"integrity": "sha512-tHjX2YbkUBwEgg0JZU3EFSSAQPoK4qQR/NFYa8Vtzd5UAyXzZksCw2In69Rml4R/TyHPBfRYaLK35XiOe33pjw==",
"version": "0.50.4",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.50.4.tgz",
"integrity": "sha512-zZOWyIuznx2uqiRcCNuidkAsLC8IBHgS9lTwSVEB29sUCwEcqL95MWpWP1nccHSKfiZga+hbZaI0btR2zjuMmw==",
"license": "MIT",
"dependencies": {
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4",
"abort-controller": "^3.0.0",
"agentkeepalive": "^4.2.1",
"form-data-encoder": "1.7.2",
"formdata-node": "^4.3.2",
"node-fetch": "^2.6.7"
"bin": {
"anthropic-ai-sdk": "bin/cli"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@anthropic-ai/sdk/node_modules/@types/node": {
"version": "18.19.130",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@anthropic-ai/sdk/node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"license": "MIT"
},
"node_modules/@anthropic-ai/vertex-sdk": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/@anthropic-ai/vertex-sdk/-/vertex-sdk-0.6.4.tgz",
"integrity": "sha512-rMBlO2jF53TfMRmsQMm1bPO2JRUh4jYddjq/OJLj8DSAkfbCrNWhc0yhDed6oLYJg5s+VpDbvlPzMggqHhTfMw==",
"version": "0.11.5",
"resolved": "https://registry.npmjs.org/@anthropic-ai/vertex-sdk/-/vertex-sdk-0.11.5.tgz",
"integrity": "sha512-V7sB5nY80unEQu8lSQaEzh1WhYwpIdpC3iXNRHUskghkuQDhS6dQu2ASZBgA5MNuJ1Yv5PhY61NM15dLaQhPQw==",
"license": "MIT",
"dependencies": {
"@anthropic-ai/sdk": ">=0.35 <1",
"@anthropic-ai/sdk": ">=0.50.3 <1",
"google-auth-library": "^9.4.2"
}
},
+3 -3
View File
@@ -2,7 +2,7 @@
"name": "claude-dev",
"displayName": "Cline",
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
"version": "3.89.0",
"version": "3.89.2",
"icon": "assets/icons/icon.png",
"engines": {
"vscode": "^1.84.0"
@@ -486,8 +486,8 @@
"typescript": "^5.4.5"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.37.0",
"@anthropic-ai/vertex-sdk": "^0.6.4",
"@anthropic-ai/sdk": "^0.50.4",
"@anthropic-ai/vertex-sdk": "^0.11.5",
"@aws-sdk/client-bedrock-runtime": "^3.922.0",
"@aws-sdk/credential-providers": "^3.922.0",
"@azure/identity": "^4.13.0",
@@ -11,7 +11,7 @@ import axios from "axios"
import JSON5 from "json5"
import OpenAI from "openai"
import { buildExternalBasicHeaders } from "@/services/EnvUtils"
import { ClineStorageMessage } from "@/shared/messages/content"
import { ClineStorageMessage, getBase64ImageSource } from "@/shared/messages/content"
import { getAxiosSettings } from "@/shared/net"
import { Logger } from "@/shared/services/Logger"
import { ApiHandler, CommonApiHandlerOptions } from "../"
@@ -302,10 +302,11 @@ namespace Gemini {
if (block.type === "text") {
parts.push({ text: block.text })
} else if (block.type === "image") {
const { mediaType, data } = getBase64ImageSource(block.source)
parts.push({
inlineData: {
mimeType: block.source.media_type,
data: block.source.data,
mimeType: mediaType,
data,
},
})
}
@@ -12,7 +12,7 @@ import { ClineStorageMessage, convertClineStorageToAnthropicMessage } from "@/sh
* @returns Array of Anthropic-compatible messages with cache control applied
*/
export function sanitizeAnthropicMessages(
clineMessages: Array<ClineStorageMessage | Anthropic.MessageParam>,
clineMessages: ClineStorageMessage[],
supportCache: boolean,
): Array<Anthropic.MessageParam> {
// The latest message will be the new user message, one before will be the assistant message from a previous request,
@@ -60,7 +60,7 @@ export function convertAnthropicContentToGemini(content: string | ClineStorageMe
.filter((part): part is Part => part !== undefined) // Filter out unsupported blocks
}
export function convertAnthropicMessageToGemini(message: Anthropic.Messages.MessageParam): Content {
export function convertAnthropicMessageToGemini(message: ClineStorageMessage): Content {
return {
role: message.role === "assistant" ? "model" : "user",
parts: convertAnthropicContentToGemini(message.content),
@@ -113,6 +113,7 @@ export function convertGeminiResponseToAnthropic(response: GenerateContentRespon
output_tokens: response.usageMetadata?.candidatesTokenCount ?? 0,
cache_creation_input_tokens: null,
cache_read_input_tokens: null,
server_tool_use: null,
},
}
}
@@ -3,6 +3,7 @@ import { AssistantMessage } from "@mistralai/mistralai/models/components/assista
import { SystemMessage } from "@mistralai/mistralai/models/components/systemmessage"
import { ToolMessage } from "@mistralai/mistralai/models/components/toolmessage"
import { UserMessage } from "@mistralai/mistralai/models/components/usermessage"
import { getImageDataUrl } from "@/shared/messages/content"
export type MistralMessage =
| (SystemMessage & { role: "system" })
@@ -33,7 +34,7 @@ export function convertToMistralMessages(anthropicMessages: Anthropic.Messages.M
return {
type: "image_url",
imageUrl: {
url: `data:${part.source.media_type};base64,${part.source.data}`,
url: getImageDataUrl(part.source),
},
}
}
@@ -400,6 +400,7 @@ export function convertO1ResponseToAnthropicMessage(
output_tokens: completion.usage?.completion_tokens || 0,
cache_creation_input_tokens: null,
cache_read_input_tokens: null,
server_tool_use: null,
},
}
@@ -5,6 +5,7 @@ import {
ClineStorageMessage,
ClineTextContentBlock,
ClineUserToolResultContentBlock,
getImageDataUrl,
} from "@/shared/messages/content"
export function convertToOllamaMessages(anthropicMessages: Omit<ClineStorageMessage, "modelInfo">[]): Message[] {
@@ -46,7 +47,7 @@ export function convertToOllamaMessages(anthropicMessages: Omit<ClineStorageMess
toolMessage.content
?.map((part) => {
if (part.type === "image") {
toolResultImages.push(`data:${part.source.media_type};base64,${part.source.data}`)
toolResultImages.push(getImageDataUrl(part.source))
return "(see following user message for image)"
}
return part.text
@@ -67,7 +68,7 @@ export function convertToOllamaMessages(anthropicMessages: Omit<ClineStorageMess
content: nonToolMessages
.map((part) => {
if (part.type === "image") {
return `data:${part.source.media_type};base64,${part.source.data}`
return getImageDataUrl(part.source)
}
return part.text
})
@@ -6,9 +6,9 @@ import {
ClineAssistantThinkingBlock,
ClineAssistantToolUseBlock,
ClineImageContentBlock,
ClineStorageMessage,
ClineTextContentBlock,
ClineUserToolResultContentBlock,
getImageDataUrl,
} from "@/shared/messages/content"
import { Logger } from "@/shared/services/Logger"
@@ -65,7 +65,7 @@ function transformToolCallIdForNativeApi(toolId: string, provider?: ApiProvider)
* @returns Array of OpenAI.Chat.ChatCompletionMessageParam objects
*/
export function convertToOpenAiMessages(
anthropicMessages: Omit<ClineStorageMessage, "modelInfo">[],
anthropicMessages: Anthropic.Messages.MessageParam[],
provider?: ApiProvider,
): OpenAI.Chat.ChatCompletionMessageParam[] {
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = []
@@ -144,7 +144,7 @@ export function convertToOpenAiMessages(
role: "user",
content: toolResultImages.map((part) => ({
type: "image_url",
image_url: { url: `data:${part.source.media_type};base64,${part.source.data}` },
image_url: { url: getImageDataUrl(part.source) },
})),
})
}
@@ -158,7 +158,7 @@ export function convertToOpenAiMessages(
return {
type: "image_url",
image_url: {
url: `data:${part.source.media_type};base64,${part.source.data}`,
url: getImageDataUrl(part.source),
},
}
}
@@ -421,6 +421,7 @@ export function convertToAnthropicMessage(completion: OpenAI.Chat.Completions.Ch
output_tokens: completion.usage?.completion_tokens || 0,
cache_creation_input_tokens: null,
cache_read_input_tokens: null,
server_tool_use: null,
},
}
try {
@@ -1,5 +1,5 @@
import { ResponseInput, ResponseInputMessageContentList, ResponseReasoningItem } from "openai/resources/responses/responses"
import { ClineStorageMessage } from "@/shared/messages/content"
import { ClineStorageMessage, getBase64ImageSource, getImageDataUrl } from "@/shared/messages/content"
/**
* Converts an array of ClineStorageMessage objects (extension of Anthropic format) to a ResponseInput array to use with OpenAI's Responses API.
@@ -177,7 +177,7 @@ export function convertToOpenAIResponsesInput(
const imageItem: any = {
type: "message",
role: "assistant",
content: [{ type: "output_text", text: `[image:${part.source.media_type}]` }],
content: [{ type: "output_text", text: `[image:${getBase64ImageSource(part.source).mediaType}]` }],
}
// Set message-level id if available (though images typically don't have call_id)
if (part.call_id) {
@@ -218,7 +218,7 @@ export function convertToOpenAIResponsesInput(
messageContent.push({
type: "input_image",
detail: "auto",
image_url: `data:${part.source.media_type};base64,${part.source.data}`,
image_url: getImageDataUrl(part.source),
})
break
case "tool_result": {
@@ -1,6 +1,6 @@
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import { ClineAssistantThinkingBlock, ClineStorageMessage } from "@/shared/messages/content"
import { ClineAssistantThinkingBlock, ClineStorageMessage, getImageDataUrl } from "@/shared/messages/content"
/**
* DeepSeek Reasoner message format with reasoning_content support.
@@ -87,7 +87,7 @@ export function convertToR1Format(messages: Anthropic.Messages.MessageParam[]):
hasImages = true
imageParts.push({
type: "image_url",
image_url: { url: `data:${part.source.media_type};base64,${part.source.data}` },
image_url: { url: getImageDataUrl(part.source) },
})
}
})
@@ -74,7 +74,7 @@ export function convertToVsCodeLmMessages(
: (toolMessage.content?.map((part) => {
if (part.type === "image") {
return new vscode.LanguageModelTextPart(
`[Image (${part.source?.type || "Unknown source-type"}): ${part.source?.media_type || "unknown media-type"} not supported by VSCode LM API]`,
`[Image (${part.source?.type || "Unknown source-type"}): ${(part.source?.type === "base64" && part.source.media_type) || "unknown media-type"} not supported by VSCode LM API]`,
)
}
return new vscode.LanguageModelTextPart(part.text)
@@ -87,7 +87,7 @@ export function convertToVsCodeLmMessages(
...nonToolMessages.map((part) => {
if (part.type === "image") {
return new vscode.LanguageModelTextPart(
`[Image (${part.source?.type || "Unknown source-type"}): ${part.source?.media_type || "unknown media-type"} not supported by VSCode LM API]`,
`[Image (${part.source?.type || "Unknown source-type"}): ${(part.source?.type === "base64" && part.source.media_type) || "unknown media-type"} not supported by VSCode LM API]`,
)
}
return new vscode.LanguageModelTextPart(part.text)
@@ -199,6 +199,7 @@ export function convertToAnthropicMessage(vsCodeLmMessage: vscode.LanguageModelC
output_tokens: 0,
cache_creation_input_tokens: null,
cache_read_input_tokens: null,
server_tool_use: null,
},
}
}
@@ -192,11 +192,13 @@ export async function executePreCompactHookWithCleanup(params: PreCompactHookPar
let contextRawPath: string | undefined
try {
// Get current active context (respects previous compactions)
// Get current active context (respects previous compactions).
// getTruncatedMessages types its output as Anthropic.MessageParam[], but it slices the Cline-stored
// conversation history (ClineStorageMessage[]) passed in, so narrow it back here.
const currentContext = params.contextManager.getTruncatedMessages(
params.apiConversationHistory,
params.conversationHistoryDeletedRange,
)
) as ClineStorageMessage[]
// Write context files for hook access
const contextFiles = await writePreCompactContextFiles(params.taskId, currentContext)
+2 -1
View File
@@ -13,6 +13,7 @@ import { HostProvider } from "@/hosts/host-provider"
import { ExtensionRegistryInfo } from "@/registry"
import { telemetryService } from "@/services/telemetry"
import { McpMarketplaceCatalog } from "@/shared/mcp"
import { ClineStorageMessage } from "@/shared/messages/content"
import { Logger } from "@/shared/services/Logger"
import { syncWorker } from "@/shared/services/worker/sync"
import { reconstructTaskHistory } from "../commands/reconstructTaskHistory"
@@ -233,7 +234,7 @@ export async function getMcpSettingsFilePath(settingsDirectoryPath: string): Pro
return mcpSettingsFilePath
}
export async function getSavedApiConversationHistory(taskId: string): Promise<Anthropic.MessageParam[]> {
export async function getSavedApiConversationHistory(taskId: string): Promise<ClineStorageMessage[]> {
const filePath = path.join(await ensureTaskDirectoryExists(taskId), GlobalFileNames.apiConversationHistory)
const fileExists = await fileExistsAtPath(filePath)
if (fileExists) {
+4 -1
View File
@@ -2018,7 +2018,10 @@ export class Task {
}
// Response API requires native tool calls to be enabled
const stream = this.api.createMessage(systemPrompt, contextManagementMetadata.truncatedConversationHistory, tools)
// ContextManager types its truncated output as Anthropic.MessageParam[], but the history it slices is the
// Cline-stored conversation history (ClineStorageMessage[]), so narrow it back for the provider boundary.
const truncatedConversationHistory = contextManagementMetadata.truncatedConversationHistory as ClineStorageMessage[]
const stream = this.api.createMessage(systemPrompt, truncatedConversationHistory, tools)
const iterator = stream[Symbol.asyncIterator]()
@@ -16,7 +16,7 @@ export function filterMessagesForClaudeCode(messages: Anthropic.Messages.Message
if (block.type === "image") {
// Replace image blocks with text placeholders
const sourceType = block.source?.type || "unknown"
const mediaType = block.source?.media_type || "unknown"
const mediaType = (block.source?.type === "base64" && block.source.media_type) || "unknown"
return {
type: "text" as const,
text: `[Image (${sourceType}): ${mediaType} not supported by Claude Code]`,
@@ -131,6 +131,27 @@ export function convertClineStorageToAnthropicMessage(
return { role, content: cleanedContent }
}
/**
* Cline stores images as base64, so an image block's source is always a base64 source.
* The Anthropic SDK types the source as a Base64ImageSource | URLImageSource union, so this
* narrows to the base64 variant for the transform layer. URL sources are not produced by Cline,
* so they degrade to empty values rather than throwing.
*/
export function getBase64ImageSource(source: Anthropic.ImageBlockParam["source"]): { mediaType: string; data: string } {
if (source.type === "base64") {
return { mediaType: source.media_type, data: source.data }
}
return { mediaType: "", data: "" }
}
/**
* Builds a base64 data URL from an image block's source. See getBase64ImageSource.
*/
export function getImageDataUrl(source: Anthropic.ImageBlockParam["source"]): string {
const { mediaType, data } = getBase64ImageSource(source)
return `data:${mediaType};base64,${data}`
}
/**
* Clean a content block by removing Cline-specific fields and returning only Anthropic-compatible fields
*/
+49 -49
View File
@@ -19,7 +19,7 @@
},
"apps/cli": {
"name": "@cline/cli",
"version": "3.0.22",
"version": "3.0.23",
"bin": {
"cline": "src/index.ts",
},
@@ -371,7 +371,7 @@
},
"sdk/packages/agents": {
"name": "@cline/agents",
"version": "0.0.46",
"version": "0.0.47",
"dependencies": {
"@cline/llms": "workspace:*",
"@cline/shared": "workspace:*",
@@ -380,7 +380,7 @@
},
"sdk/packages/core": {
"name": "@cline/core",
"version": "0.0.46",
"version": "0.0.47",
"dependencies": {
"@cline/agents": "workspace:*",
"@cline/llms": "workspace:*",
@@ -411,7 +411,7 @@
},
"sdk/packages/llms": {
"name": "@cline/llms",
"version": "0.0.46",
"version": "0.0.47",
"dependencies": {
"@ai-sdk/amazon-bedrock": "^4.0.89",
"@ai-sdk/anthropic": "^3.0.68",
@@ -445,14 +445,14 @@
},
"sdk/packages/sdk": {
"name": "@cline/sdk",
"version": "0.0.46",
"version": "0.0.47",
"dependencies": {
"@cline/core": "workspace:*",
},
},
"sdk/packages/shared": {
"name": "@cline/shared",
"version": "0.0.46",
"version": "0.0.47",
"dependencies": {
"aws4fetch": "^1.0.20",
"jsonrepair": "^3.13.2",
@@ -468,11 +468,11 @@
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.81", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-B1JDd9Ugq9R5AgIaW3674lhGCMMYJcPUxnrZh8fzbGojgg4QvHFRv6eZahGQAUsmGHbcf74G9bdSBDLWQGY2GA=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.123", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rL+3Sp9crOlfE7MwguFPS30qVp6HFcr9na0KYMb4CcQdxAIjBJec3EEdCjc94UyRVZWLmgQ6Yr605FmPlFIi0w=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.124", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-h8CrmbSG+8X0C+M/E1M4oiDHYevqwbzAPN+uLRHS0eJaatF2MZ+juNtOHXNOjk7Bsk9mD2RjYMjJO9dFkb9I7Q=="],
"@ai-sdk/google": ["@ai-sdk/google@3.0.80", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-5ORbm/yFUPO0MEvZsxBMN0cdKw2+lwU/wVn5KN3KF8Dmk1LughuDuUohMh/7iU/XFTiyB0OvmTW/tdV/J7O9zg=="],
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.141", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.81", "@ai-sdk/google": "3.0.80", "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-UGXQeV+z30Gk1/mhKZoXKbYO7Q0U7pg0Nlz44hb2B1FpYRFBu8+ziaI60BdetncaoxPocdNKhP41AD15IA6nJg=="],
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.142", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.81", "@ai-sdk/google": "3.0.80", "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bTWfj0ITBHjAVJHWCA0DB7PO+aDX8bWxTI9hpNAKH7e5uO74URKdi22zlQAOsROEufcLYiAw0LjrYmmXiksErw=="],
"@ai-sdk/mistral": ["@ai-sdk/mistral@3.0.37", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-KkdaMjs4C2y+vrZWJE990E3ZxBFiOTHQ94ZlquuIttpphcqJTMxNoIpnKT/4UzMVWXL0BUEE2vs+1UEVXkN8Kg=="],
@@ -484,7 +484,7 @@
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"@ai-sdk/react": ["@ai-sdk/react@3.0.197", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.27", "ai": "6.0.195", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-sx5K0pvIWbgduMYGz++s28ldj+hu+GfDpXw9T2kL4n+bhRQpQvrH+jU0z3OqvjMrhD5oz9cGJ7bv/0JQEKXIbw=="],
"@ai-sdk/react": ["@ai-sdk/react@3.0.198", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.27", "ai": "6.0.196", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-ozlxMidzvKXAefvnq95Y34rJ5MipXABIv1bg2RLEnWUBxGrKxNHrYl0fTfi6grTN88wSXMZYaMY2oHMCHDFuJw=="],
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
@@ -520,35 +520,35 @@
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
"@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1059.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.16", "@aws-sdk/credential-provider-node": "^3.972.49", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-HW3Oq2rL65tbuqkQzoRrjfF3jauRfra056Xv0K2YtBWt+LaeLrr95D8SlRmgGzXZHwy38FO/w0N1EKjsTYmDKw=="],
"@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1062.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.17", "@aws-sdk/credential-provider-node": "^3.972.51", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-QA5z/Pl3aTMR3+bmiHoC6MpKYa4FMk/9lNP7k104uKuUsjMqP4ysRa43IwdcbI9sH023T//kSJCLxrxa2CP/Tw=="],
"@aws-sdk/core": ["@aws-sdk/core@3.974.16", "", { "dependencies": { "@aws-sdk/types": "^3.973.10", "@aws-sdk/xml-builder": "^3.972.27", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.6", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-WXPvTfG7J2H4Ae6ewhd0285UC+8+9p/pKoibXXQlbXSqHexFLGM0oXHTwDfQEPmrNnvuWPpVjgoAUfW+cUFbXw=="],
"@aws-sdk/core": ["@aws-sdk/core@3.974.17", "", { "dependencies": { "@aws-sdk/types": "^3.973.10", "@aws-sdk/xml-builder": "^3.972.27", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.6", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-r8o4h2K7j6P9ngno+8ei0aK0U/4JwDb7A2fMMxGVoSqDN8AFlIzSDeZHME9LcVLR2codyhtr1WAAg+/nmkeeMA=="],
"@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.39", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.997.14", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-fp7hiew245BbBiaDGjLDaMXqxbOWnqzuhczWrJq/6/3gDNHtZvkorxHQXYpHYiddetR8sBWe3S49HfZ2BQbYSg=="],
"@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.41", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-byGPybEQe9ejeyUzhWjtjfh0ctv25HsRx2djF/Tl2j9+DAuAmhjq0NqSRqYZEoSe8vJObXz5RYDtJYAmdupBig=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.42", "", { "dependencies": { "@aws-sdk/core": "^3.974.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-0+MCOYqeHyADFdeVr5/e1G8JJoWm7/szZAiKssqJS84E9+tO07509YNyQRJ5a7x5wO9YFAV324syrwm6yIcs5w=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.43", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-g0XVQKzaA/4cq1vz1IvCQwYM+1Pkv01J9yHDpCTXekVuGZRDEz0wqBQ1AuYTq7FM6uik4uBGH8Tb5d9YvgeA7g=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.44", "", { "dependencies": { "@aws-sdk/core": "^3.974.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-auuhqlnv4PUdfqcdHLKGTdoCceXOuby6WNeCMoxtZmQGWNCibbz95/lSYzNWq9cExN17UlRFqo1nvTcC+zHfEg=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.45", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-w9PuOoKCt6+xoESvY+zlV0u3PKQ0mVL259PcsVR6a3S/uYJJHnIi4r1NxdJHEcNldUVRIciltWnFMGBR4YEm3g=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.47", "", { "dependencies": { "@aws-sdk/core": "^3.974.16", "@aws-sdk/credential-provider-env": "^3.972.42", "@aws-sdk/credential-provider-http": "^3.972.44", "@aws-sdk/credential-provider-login": "^3.972.46", "@aws-sdk/credential-provider-process": "^3.972.42", "@aws-sdk/credential-provider-sso": "^3.972.46", "@aws-sdk/credential-provider-web-identity": "^3.972.46", "@aws-sdk/nested-clients": "^3.997.14", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-G+8JuG0CfLcC2IQXFVqMR1+KDF1rksebr+YL6+HHYbNjs/hiwX53ye45sU3pJKljpS2uIXqOrOsicHIv02vWrw=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.49", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/credential-provider-env": "^3.972.43", "@aws-sdk/credential-provider-http": "^3.972.45", "@aws-sdk/credential-provider-login": "^3.972.48", "@aws-sdk/credential-provider-process": "^3.972.43", "@aws-sdk/credential-provider-sso": "^3.972.48", "@aws-sdk/credential-provider-web-identity": "^3.972.48", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-83r5MK+PERv9irzky1o5aNbXiLuaLfeB7N8MrktB9USpoebdNtuG0Ek9ieIxpGH1aZ9a0nIaDaLjEr3EmOV3Ng=="],
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.46", "", { "dependencies": { "@aws-sdk/core": "^3.974.16", "@aws-sdk/nested-clients": "^3.997.14", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-+H6h2/1Q+iIJ4w+FPCKM//xwy4C9yADknDTR68+K3PbOtB/uLup3zIH8PUKn6QwnttME4VM4ftSTnbvoDf5wXg=="],
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.48", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-amPGeF6fcvLInK4Pu2k2Y2jHFR6MpaIKrZrbaf0QUnV3tjzjWh442eifZ2+KcmzFdsqyvyjBqAhq2JNLt1C5gA=="],
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.49", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.42", "@aws-sdk/credential-provider-http": "^3.972.44", "@aws-sdk/credential-provider-ini": "^3.972.47", "@aws-sdk/credential-provider-process": "^3.972.42", "@aws-sdk/credential-provider-sso": "^3.972.46", "@aws-sdk/credential-provider-web-identity": "^3.972.46", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-hlyoc+2352BhC+HF91t9avIDJu1+EvQGGltxFB8ADCpAHGYNNLEQAZJIPzw3O/bRiiDSE2B8pdj/fFCXlDTEDg=="],
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.51", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.43", "@aws-sdk/credential-provider-http": "^3.972.45", "@aws-sdk/credential-provider-ini": "^3.972.49", "@aws-sdk/credential-provider-process": "^3.972.43", "@aws-sdk/credential-provider-sso": "^3.972.48", "@aws-sdk/credential-provider-web-identity": "^3.972.48", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-mbhSY3ytXIGMuBoJsWCivk+63dtVlenT6wstUra07Lar4Ln2MVL8/j5zCTIOog+ig5/FlFJ8gcFU4nQZV+Jh4Q=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.42", "", { "dependencies": { "@aws-sdk/core": "^3.974.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-r996IYVtQ7rWa5UfSs3fZLT/Dq/SSgH9wv9zahx9lcg6vvaPPQHFpTy45nZajZu/+OUdEQxEjTn9rOMons59mA=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.43", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-GPokLNyvTfCmuaHk+v3GKVs4ZT3cMu5kgS2a+NPkOMt96cq6fSIK0g+mZHpGS6Cd4QGrPKesANEaLUKgOskTzg=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.46", "", { "dependencies": { "@aws-sdk/core": "^3.974.16", "@aws-sdk/nested-clients": "^3.997.14", "@aws-sdk/token-providers": "3.1059.0", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-vd+UqRbiYLvXXH3kTAuTxq+Vdb3rOgg29/FeB35ETsgdJTTB7x3YuqtpRckATsL5bDRXFVQo0uBj0/vxCJ8hHg=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.48", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/token-providers": "3.1062.0", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-tf0sD47SeTgCDfOWYssctzGgwAuk8/ECjb7bom4wZ7P1om0qE8i2yjniUdvysmANm5haARr35O8vZnTe/UEtpQ=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.46", "", { "dependencies": { "@aws-sdk/core": "^3.974.16", "@aws-sdk/nested-clients": "^3.997.14", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-xuxBbMorygYsbDV6E/tUGQUgIDfhnzxz8uJYX8rByzehI6gLUu8RPsgOpLmh9YWerqeHZxJbqzgheBSB7tpooQ=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.48", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-YYsumc2oe09gl4l+fjfmR64JDn6+0o4Ql5HMBkMuhFazO1tZlE5NjSnZM3oXHwenPjh2qow0TFgSIVjfWfsojg=="],
"@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1059.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.1059.0", "@aws-sdk/core": "^3.974.16", "@aws-sdk/credential-provider-cognito-identity": "^3.972.39", "@aws-sdk/credential-provider-env": "^3.972.42", "@aws-sdk/credential-provider-http": "^3.972.44", "@aws-sdk/credential-provider-ini": "^3.972.47", "@aws-sdk/credential-provider-login": "^3.972.46", "@aws-sdk/credential-provider-node": "^3.972.49", "@aws-sdk/credential-provider-process": "^3.972.42", "@aws-sdk/credential-provider-sso": "^3.972.46", "@aws-sdk/credential-provider-web-identity": "^3.972.46", "@aws-sdk/nested-clients": "^3.997.14", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-cSdDFb/O3cQHGg78VxPaNruyy9zGxcoeS7DlwYTdwxmYkE0GXmBRoej5N+q+Av9YWBayZ2n3QsSYhtnUXa/GXw=="],
"@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1062.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.1062.0", "@aws-sdk/core": "^3.974.17", "@aws-sdk/credential-provider-cognito-identity": "^3.972.41", "@aws-sdk/credential-provider-env": "^3.972.43", "@aws-sdk/credential-provider-http": "^3.972.45", "@aws-sdk/credential-provider-ini": "^3.972.49", "@aws-sdk/credential-provider-login": "^3.972.48", "@aws-sdk/credential-provider-node": "^3.972.51", "@aws-sdk/credential-provider-process": "^3.972.43", "@aws-sdk/credential-provider-sso": "^3.972.48", "@aws-sdk/credential-provider-web-identity": "^3.972.48", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-QS2UT3srjNppZv6mq7V0igqK/ThYKqRWwDscxDsMEmmEE5JqCPPSqFW71aEpkvXaMdgmG8xEpt4RtNHpZ30cTA=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.14", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.16", "@aws-sdk/signature-v4-multi-region": "^3.996.31", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-T5CS1r4P27FjkBYIWwVibWqEuq32BbCga2Z5m5OBSdSdi2wPfW2vl6zLWAB/5MeeyC4s2pY/MY3cj2Gd3rgSkg=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.16", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.17", "@aws-sdk/signature-v4-multi-region": "^3.996.31", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-bGvfDgC2KQePjEmZdltScPPLKFoyjPElAXeZcLfvZ58J1AO283//WGtvp9GdnryLHTi7gis0UoCezqh0vl/nig=="],
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.31", "", { "dependencies": { "@aws-sdk/types": "^3.973.10", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-Kn2up9SlG1KC6wRtwf0d7waTGF6rvp9DxYqB54x6UCKdQ6kyaXCqHL4WGb5vUJga5kS8FxnjhY0LqM28aMvnNQ=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1059.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.16", "@aws-sdk/nested-clients": "^3.997.14", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-xql+7YBAE7WYb9xJfY0vcAXM8rJXfClmB2wkt+g/EoLUMog0pOb7o741fE96wFqha0uQTEgTo/5lGGguzavxmw=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1062.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-fvHh53zSm2FoQPgkw9thH5D7sd13bC0nPyuZb+mQJ85l5v7lQnsZ97u6e6YkJJN/LU1Mxm1/DLGrIIRR2L7tZw=="],
"@aws-sdk/types": ["@aws-sdk/types@3.973.10", "", { "dependencies": { "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-992QrTO7G9qCvKD0fx1rMlqcL14plUcRAbwmqqYVsuF3GrqcvlAL9qxR+baMafarEZ+l7DUQ5lCMmt5mbMhF7g=="],
@@ -656,9 +656,9 @@
"@chevrotain/types": ["@chevrotain/types@11.1.2", "", {}, "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw=="],
"@clack/core": ["@clack/core@1.4.0", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-7Wctjq6f7c1CPz8sPpkwUnz8yRgVANkpNupb81q432FjcJg4l+Sw7XANdNSdWfAKq0IHI0JTcUeK5dxs/HrGPw=="],
"@clack/core": ["@clack/core@1.4.1", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-FILJa1gGKEFTGZAJE9RpVhrjKz3c3h4ar60dSv6cGuDqufQ84YEIS3GAGvZiN+H6yaLbbvTFNejjCC4tXpZEuw=="],
"@clack/prompts": ["@clack/prompts@1.5.0", "", { "dependencies": { "@clack/core": "1.4.0", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-wKh+wTjmrUoUdkZg8KpJO5X+p9PWV+KE9mePseq9UYWkukgTKsGS47RRL2HstwVcvDQH+PenrPJWII8+MfiiyA=="],
"@clack/prompts": ["@clack/prompts@1.5.1", "", { "dependencies": { "@clack/core": "1.4.1", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-zccHj2z2oCCO4yrDiRSlFOxWerGqRiysP7a5jPK6uoI9URKAquwY42Dd/iUP8JWHxEzdRe4TlbvZCo8z1/mhrw=="],
"@cline/agents": ["@cline/agents@workspace:sdk/packages/agents"],
@@ -1302,19 +1302,19 @@
"@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
"@shikijs/core": ["@shikijs/core@4.1.0", "", { "dependencies": { "@shikijs/primitive": "4.1.0", "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-jLJtSJeuFffqX6/inRE1zqU5aFv2hrszvYgq3OjbAgFRZiWv7abKMDdQzYxuSDfmUPQozZvI/kuy6VMTvnvqTQ=="],
"@shikijs/core": ["@shikijs/core@4.2.0", "", { "dependencies": { "@shikijs/primitive": "4.2.0", "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ=="],
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.1.0", "", { "dependencies": { "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-YquhawCUgaBfhsS72e2Y/dI59gCBNPHu3fEO/tvLaXrTssxZrY5ddjtNLTwndrMgPo8b3IscE+xoICDzpTmlFQ=="],
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og=="],
"@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.1.0", "", { "dependencies": { "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-axLpjVs45YBvvINa+dJF+NPW+KtFkNXsFr4SDw2BMj9GdeMnGxVB9PQb2xXlJYovslt/nz6giedAyOANkfc7hg=="],
"@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-hTorK1dffPkpbMUk6Z+828PgRo7d07HbnizoP0hNPFjhxMHctj0Px/qoHeGMYafc6ju+u9iMldN4JbVzNQM++g=="],
"@shikijs/langs": ["@shikijs/langs@4.1.0", "", { "dependencies": { "@shikijs/types": "4.1.0" } }, "sha512-nwOMruEkbgdZfQ/b8CgpNBVOpvG1k0N5tbmgiFeqsan401+x3ILqlzZJowSla4Agmq4hG2Uf2wh5jLTEhR8VSg=="],
"@shikijs/langs": ["@shikijs/langs@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0" } }, "sha512-bwrVRlJ0wUhZxAbVdvBbv2TTC9yLsh4C/IO5Ofz0T8MQntgDvyVnkbjw9vi50r1kx7RCIJdnJnjZAwmAsXFLZQ=="],
"@shikijs/primitive": ["@shikijs/primitive@4.1.0", "", { "dependencies": { "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zx2/2Uwj2q9X3KSyYREEhXO23xBw5WUhP4orK2lE4r+t9JGITmEe0JH+wPmJhqHpOT2bRRs6lAL945+LDvOAGw=="],
"@shikijs/primitive": ["@shikijs/primitive@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-NOq+DtUkVBJtZMVXL5A0vI0Xk8nvDYaXetFHSJFlOqjDZIVhIPRYFdGkSoElDqNuegikcc3A76SNUa8dTqtAYA=="],
"@shikijs/themes": ["@shikijs/themes@4.1.0", "", { "dependencies": { "@shikijs/types": "4.1.0" } }, "sha512-emCcTnUM7yO2wltYbaxm+yLvcCI4+h8XBKc4KmJ7EZUXoSGjcCHifkI//R4OFit9ewpg7H2/9tjOuXrT2v/Knw=="],
"@shikijs/themes": ["@shikijs/themes@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0" } }, "sha512-RX8IHYeLv8Cu2W6ruc3RxUqWn0IYCqSrMBzi/uRGAmfyDNOnNO5BF/Px7o97n4XTpmFTo5GbRaazuOWj+2ak2w=="],
"@shikijs/types": ["@shikijs/types@4.1.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3EQWX54fMpniOrDblzAhiwiJwpiTMW6+B9DWyUd9ska483tbayFYuw47UxwuPknI31bKnySfVQ/QW+jFL4rFdA=="],
"@shikijs/types": ["@shikijs/types@4.2.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw=="],
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
@@ -1336,7 +1336,7 @@
"@smithy/core": ["@smithy/core@3.24.6", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug=="],
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.3.7", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-xj8gq/bjFABAh6qWPSDCYcY3kzQIm4b561C+YnHH4zGq8rOgzQ3Shk+JGlpUxSd41UGiO6FkLdUCtNX1FAeHgg=="],
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.3.8", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-5cAM+KZC02sTqDt6NaLXyu50M/GNMd1eTzDVR8Lb0BBsVtu7RWHo47VPPEEv1vt3Yub6uzr+M5FHC+GtoT0USg=="],
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.3.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "tslib": "^2.6.2" } }, "sha512-Ussyv240JxwQP8AmkYdm26wGP/1I8QmIv0ZosgDJDlSzD73FEdj1BOpXMc06VrxX5KxTKhadFNomT2SWutUnpg=="],
@@ -1344,7 +1344,7 @@
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-3fya8i7GrJilQouk4cZJKdy5k8MWQBpjfXrRNaXDedH8r779tr0jcxyH3+yoTmsluc2+vF4S343yFbnvu8ExDQ=="],
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.7", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A=="],
"@smithy/signature-v4": ["@smithy/signature-v4@5.4.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-Ojg4B6oIDlIr1R86xCDJt1zJWnYa0VINmqdjfe9qxWjdRivHalZ3iSlQgVqYbW0MdpFOC5XfHEWsnbmdnpIILQ=="],
@@ -1662,7 +1662,7 @@
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"ai": ["ai@6.0.195", "", { "dependencies": { "@ai-sdk/gateway": "3.0.123", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@opentelemetry/api": "^1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-IYZpuVz0boWbpIQYyinfWFrvQ1N0dG+EVB63it45B2YAU/MxxCnwz4zBswjYnPtHnJBedpMPNrwVbeczbl2GKg=="],
"ai": ["ai@6.0.196", "", { "dependencies": { "@ai-sdk/gateway": "3.0.124", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@opentelemetry/api": "^1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2T45UeqKL4a11KQ14I5i1YYHOvCFrMF478E1k6PVjlQSGUvXSv4xrxIaQbUL4qgv91DADSbddwv3oR49pPAK3g=="],
"ai-sdk-provider-claude-code": ["ai-sdk-provider-claude-code@3.4.4", "", { "dependencies": { "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.1", "@anthropic-ai/claude-agent-sdk": "^0.2.63" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-iHcup5SHh4Tul1RIi9J+bnpngen8WX66yC3lsz1YlbtwAmRhUEzZUuGKzmFGIN8Pmx9uQrerGfLJdbFxIxKkyw=="],
@@ -1714,7 +1714,7 @@
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
"axios": ["axios@1.16.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A=="],
"axios": ["axios@1.17.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw=="],
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
@@ -1982,7 +1982,7 @@
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
"dompurify": ["dompurify@3.4.7", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-2jBxDJY4RR06tQNy4w5FlFH7kfxsQZlufd0sbv+chfHCxeJwrFw2baUDsSwvBISD4K4RDbd0PTfy3uNXsR6siA=="],
"dompurify": ["dompurify@3.4.8", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-yb1cEmaOum7wFvOCSQxyfgVlv5D47Rc30iZWoMpbDIWTnJ6grDDQyu2KFJzB2k7u0pMuJcQ1zphH//fFnw2tjQ=="],
"dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
@@ -1996,7 +1996,7 @@
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"electron-to-chromium": ["electron-to-chromium@1.5.366", "", {}, "sha512-OlRuhb688YTCzzU3gXPLn6nGyd+F+53INE1qaKKlu6kETErE8FYsyDh0XqXEU+uBRn0MpCzz2vfNwORhkap8qg=="],
"electron-to-chromium": ["electron-to-chromium@1.5.367", "", {}, "sha512-4Mk/mrynCNQ+atY40D3UpmhLWB6AHMbYMlIrPhHcMF6x0L7O0b052FCAsxw1LlaR++UFuNg3D/A6XCuGDa0guQ=="],
"embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="],
@@ -2010,7 +2010,7 @@
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"enhanced-resolve": ["enhanced-resolve@5.22.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-6QEuw3zoX1SJQc7b87aBXke/no+mG2bTBgw29gWMQonLmpEkWoCAVkl+M49e48AZlWzxiDzDZzYdp6kobcyLww=="],
"enhanced-resolve": ["enhanced-resolve@5.22.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-0rxICaFZ7NQho/sHely2bvOPRP0Eu2B0NZ9zM54YvRvWMn7jfz3DmnOZDR9LlXDdDcqntAVc6Hfy4gr/tdH/Ag=="],
"enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="],
@@ -2676,7 +2676,7 @@
"object-treeify": ["object-treeify@1.1.33", "", {}, "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A=="],
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
"obug": ["obug@2.1.2", "", {}, "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg=="],
"omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="],
@@ -2962,7 +2962,7 @@
"shell-quote": ["shell-quote@1.8.4", "", {}, "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ=="],
"shiki": ["shiki@4.1.0", "", { "dependencies": { "@shikijs/core": "4.1.0", "@shikijs/engine-javascript": "4.1.0", "@shikijs/engine-oniguruma": "4.1.0", "@shikijs/langs": "4.1.0", "@shikijs/themes": "4.1.0", "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-l/ABZPUR5v70jI10EzqfMS/I96vjSGv2y0ihUV+WYFzv0EfvW4s54m0Lg8wCrrL+2IkwBzFTuxkZjPf8b2NX9Q=="],
"shiki": ["shiki@4.2.0", "", { "dependencies": { "@shikijs/core": "4.2.0", "@shikijs/engine-javascript": "4.2.0", "@shikijs/engine-oniguruma": "4.2.0", "@shikijs/langs": "4.2.0", "@shikijs/themes": "4.2.0", "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ=="],
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
@@ -3168,7 +3168,7 @@
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
"use-stick-to-bottom": ["use-stick-to-bottom@1.1.4", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-2w/lydkrwhWMv1vCaEhYbzMDhgbwIodHpAHPV0/xKJErRkbjDEUe1EWmvr6Fwb+qhiERjc1EWgAEZaSaF69CpA=="],
"use-stick-to-bottom": ["use-stick-to-bottom@1.1.5", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-A6vhRIbuQqqkwR9CbbMEP9oZcNaAVknjYL/GR9BnmpSUxwR8ncPx7k4O2CrJriObORKIYgvAsmVWcE+moJDmVg=="],
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
@@ -3272,7 +3272,7 @@
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"@cline/cline-hub-webview/@types/node": ["@types/node@24.12.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="],
"@cline/cline-hub-webview/@types/node": ["@types/node@24.13.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-5vtOqGQr4NJKeEzV441FcOi2MeG9UTWq9LqVLGneDdu4vlX17H8kQ2PA2UmNwCUGPVDj4oBjNhS7ReVEIWJJrg=="],
"@cline/code/lucide-react": ["lucide-react@0.564.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-JJ8GVTQqFwuliifD48U6+h7DXEHdkhJ/E87kksGByII3qHxtPciVb8T8woQONHBQgHVOl7rSMrrip3SeVNy7Fg=="],
@@ -3500,7 +3500,7 @@
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
"@typescript-eslint/typescript-estree/semver": ["semver@7.8.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg=="],
"@typescript-eslint/typescript-estree/semver": ["semver@7.8.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ=="],
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
@@ -3576,7 +3576,7 @@
"jest-util/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
"jsonwebtoken/semver": ["semver@7.8.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg=="],
"jsonwebtoken/semver": ["semver@7.8.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ=="],
"katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
@@ -3646,7 +3646,7 @@
"shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"sharp/semver": ["semver@7.8.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg=="],
"sharp/semver": ["semver@7.8.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ=="],
"slice-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
@@ -3666,7 +3666,7 @@
"type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
"webview/@types/node": ["@types/node@24.12.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="],
"webview/@types/node": ["@types/node@24.13.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-5vtOqGQr4NJKeEzV441FcOi2MeG9UTWq9LqVLGneDdu4vlX17H8kQ2PA2UmNwCUGPVDj4oBjNhS7ReVEIWJJrg=="],
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
@@ -3676,7 +3676,7 @@
"yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"@cline/cline-hub-webview/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"@cline/cline-hub-webview/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
@@ -3798,7 +3798,7 @@
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"webview/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"webview/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+8
View File
@@ -1,5 +1,13 @@
# Cline SDK Changelog
## 0.0.47
- Added support for overriding the API base URL
- Enforced a production singleton Cline Hub so only one hub daemon runs, and a stale hub is respawned after an upgrade
- Allowed plugin chat commands to submit prompts to the agent
- Fixed truncation of structured tool operation result strings so oversized tool output stays within limits
- Stopped echoing the full command text in run_commands tool results
## 0.0.46
- Added support for configured agents as subagent tools
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@cline/agents",
"version": "0.0.46",
"version": "0.0.47",
"repository": {
"type": "git",
"url": "https://github.com/cline/cline",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@cline/core",
"description": "Cline Core SDK for Node Runtime",
"version": "0.0.46",
"version": "0.0.47",
"repository": {
"type": "git",
"url": "https://github.com/cline/cline",
+53
View File
@@ -18,6 +18,7 @@ vi.mock("./runtime/host/host", () => ({
import type { AgentResult } from "@cline/shared";
import { ClineCore } from "./ClineCore";
import { NoOpFeatureFlagsProvider } from "./services/feature-flags";
function createStartInput(): ClineCoreStartInput {
return {
@@ -331,6 +332,58 @@ describe("ClineCore", () => {
expect(coreTelemetry.capture).not.toHaveBeenCalled();
});
it("wraps an injected feature flags provider", async () => {
const host = {
runtimeAddress: undefined,
startSession: vi.fn(),
runTurn: vi.fn(),
getAccumulatedUsage: vi.fn(),
abort: vi.fn(),
stopSession: vi.fn(),
dispose: vi.fn(),
getSession: vi.fn(async () => undefined),
listSessions: vi.fn(),
deleteSession: vi.fn(),
readSessionMessages: vi.fn(),
subscribe: vi.fn(() => () => {}),
updateSessionModel: vi.fn(),
};
createRuntimeHostMock.mockResolvedValue(host);
const provider = new NoOpFeatureFlagsProvider();
const core = await ClineCore.create({ featureFlags: provider });
expect(core.featureFlags.getProvider()).toBe(provider);
await core.dispose();
});
it("uses a no-op feature flags provider by default", async () => {
const host = {
runtimeAddress: undefined,
startSession: vi.fn(),
runTurn: vi.fn(),
getAccumulatedUsage: vi.fn(),
abort: vi.fn(),
stopSession: vi.fn(),
dispose: vi.fn(),
getSession: vi.fn(async () => undefined),
listSessions: vi.fn(),
deleteSession: vi.fn(),
readSessionMessages: vi.fn(),
subscribe: vi.fn(() => () => {}),
updateSessionModel: vi.fn(),
};
createRuntimeHostMock.mockResolvedValue(host);
const core = await ClineCore.create();
expect(core.featureFlags.getProvider()).toBeInstanceOf(
NoOpFeatureFlagsProvider,
);
await core.dispose();
expect(host.dispose).toHaveBeenCalledTimes(1);
});
it("hydrates list rows through the core API", async () => {
const host = {
runtimeAddress: undefined,
+27 -2
View File
@@ -42,6 +42,11 @@ import type {
StartSessionInput,
StartSessionResult,
} from "./runtime/host/runtime-host";
import {
FeatureFlagsService,
NoOpFeatureFlagsProvider,
} from "./services/feature-flags";
import { resolveCoreDistinctId } from "./services/telemetry/distinct-id";
import type { CoreSessionEvent } from "./types/events";
import type { SessionHistoryRecord } from "./types/sessions";
@@ -86,6 +91,7 @@ export class ClineCore {
readonly runtimeAddress: string | undefined;
readonly automation: ClineCoreAutomationApi;
readonly settings: ClineCoreSettingsApi;
readonly featureFlags: FeatureFlagsService;
readonly pendingPrompts: PendingPromptsServiceApi;
private readonly host: RuntimeHost;
private readonly prepare: ClineCoreOptions["prepare"] | undefined;
@@ -109,6 +115,7 @@ export class ClineCore {
logger: BasicLogger | undefined,
telemetry: ITelemetryService | undefined,
distinctId: string | undefined,
featureFlags: FeatureFlagsService,
automationOptions:
| (ClineCoreAutomationOptions & { logger?: BasicLogger })
| undefined,
@@ -121,6 +128,7 @@ export class ClineCore {
this.logger = logger;
this.telemetry = telemetry;
this.distinctId = distinctId;
this.featureFlags = featureFlags;
this.settings = createClineCoreSettingsApi(host);
this.pendingPrompts = createClineCorePendingPromptsApi(host);
this.automation = new ClineCoreAutomationController(() => {
@@ -187,9 +195,20 @@ export class ClineCore {
* ```
*/
static async create(options: ClineCoreOptions = {}): Promise<ClineCore> {
const distinctId = resolveCoreDistinctId(options.distinctId);
const capabilities = normalizeRuntimeCapabilities(options.capabilities);
const host = await createRuntimeHost({ ...options, capabilities });
const normalizedOptions = { ...options, capabilities, distinctId };
const host = await createRuntimeHost(normalizedOptions);
const automationOptions = normalizeAutomationOptions(options.automation);
const featureFlags = new FeatureFlagsService({
provider: options.featureFlags ?? new NoOpFeatureFlagsProvider(),
telemetry: options.telemetry,
logger: options.logger,
context: {
distinctId,
clientName: options.clientName,
},
});
const core = new ClineCore(
host,
options.clientName,
@@ -198,7 +217,8 @@ export class ClineCore {
capabilities,
options.logger,
options.telemetry,
options.distinctId,
distinctId,
featureFlags,
automationOptions
? { ...automationOptions, logger: options.logger }
: undefined,
@@ -376,6 +396,11 @@ export class ClineCore {
await this.automationService?.dispose();
await this.host.dispose(...args);
} finally {
await this.featureFlags.dispose().catch((error) => {
this.logger?.error?.("Error disposing feature flags provider", {
error,
});
});
this.unsubscribeBootstrapCleanup();
const sessionIds = [...this.activeSessionBootstraps.keys()];
await Promise.allSettled(
@@ -3,6 +3,7 @@ import type {
AgentConfig,
AutomationEventEnvelope,
BasicLogger,
IFeatureFlagsProvider,
ITelemetryService,
} from "@cline/shared";
import type { CronEventSuppression } from "../cron/events/cron-event-ingress";
@@ -205,6 +206,12 @@ export interface ClineCoreOptions {
* If omitted, telemetry is a no-op.
*/
telemetry?: ITelemetryService;
/**
* Feature flags provider for this ClineCore instance. Core wraps the provider
* in a cached FeatureFlagsService and exposes it as `cline.featureFlags`.
* If omitted, Core uses a no-op provider with default flag values.
*/
featureFlags?: IFeatureFlagsProvider;
/**
* Optional structured logger for core-side operational diagnostics such as
* runtime-host selection and fallback decisions.
@@ -37,9 +37,20 @@ interface PluginTool {
interface PluginCommand {
name: string;
description?: string;
handler?: (input: string) => Promise<string>;
handler?: (
input: string,
) => Promise<PluginCommandResult> | PluginCommandResult;
}
// Keep this local mirror in sync with AgentExtensionCommandResult from @cline/shared.
// The sandbox bootstrap runs in an isolated process and avoids host package imports.
type PluginCommandResult =
| string
| {
reply?: string;
submitPrompt?: string;
};
interface PluginRule {
id: string;
content: string | (() => string | Promise<string>);
@@ -706,7 +717,7 @@ async function executeCommand(args: {
pluginId: string;
contributionId: string;
input: string;
}): Promise<string> {
}): Promise<PluginCommandResult> {
const state = getPlugin(args.pluginId);
const handler = state.handlers.commands.get(args.contributionId);
if (typeof handler !== "function") {
@@ -4,6 +4,7 @@ import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import type {
AgentConfig,
AgentExtensionCommandResult,
AgentExtensionAutomationEventType,
AgentExtensionRule,
AgentRuntimeHooks,
@@ -434,7 +435,7 @@ function registerCommands(
description: cd.description,
handler: async (input: string) => {
try {
return await sandbox.call<string>(
return await sandbox.call<AgentExtensionCommandResult>(
"executeCommand",
{
pluginId: descriptor.pluginId,
@@ -448,7 +449,7 @@ function registerCommands(
throw error;
}
await reinitialize();
return await sandbox.call<string>(
return await sandbox.call<AgentExtensionCommandResult>(
"executeCommand",
{
pluginId: descriptor.pluginId,
@@ -11,7 +11,7 @@ import {
createSkillsTool,
createWindowsShellTool,
} from "./definitions";
import { TimeoutError } from "./helpers";
import { RUN_COMMAND_QUERY_PREVIEW_LIMIT, TimeoutError } from "./helpers";
import { INPUT_ARG_CHAR_LIMIT } from "./schemas";
import type { SkillsExecutorWithMetadata } from "./types";
@@ -594,6 +594,108 @@ describe("default run_commands tool", () => {
);
});
it("keeps short command echoes unchanged in tool results", async () => {
const execute = vi.fn(
async (command: string | { command: string }) =>
`ran:${typeof command === "string" ? command : command.command}`,
);
const tool = createBashTool(execute);
const result = await tool.execute(
{ commands: ["git status --short"] },
{
agentId: "agent-1",
conversationId: "conv-1",
iteration: 1,
},
);
expect(result).toEqual([
{
query: "git status --short",
result: "ran:git status --short",
success: true,
},
]);
});
it("truncates long command echoes in tool results without affecting execution", async () => {
const execute = vi.fn(
async (command: string | { command: string }) =>
`ran:${typeof command === "string" ? command.length : command.command.length}`,
);
const tool = createBashTool(execute);
const largeSource = "x".repeat(14000);
const command = `cat > /app/eval.scm << 'EOF'\n${largeSource}\nEOF`;
const result = (await tool.execute(
{ commands: [command] },
{
agentId: "agent-1",
conversationId: "conv-1",
iteration: 1,
},
)) as Array<{ query: string; result: string; success: boolean }>;
// The executor still receives the full command
expect(execute).toHaveBeenCalledWith(
command,
process.cwd(),
expect.anything(),
);
expect(result[0].success).toBe(true);
expect(result[0].result).toBe(`ran:${command.length}`);
// The provider-facing echo is bounded and self-describing
expect(result[0].query.length).toBeLessThan(
RUN_COMMAND_QUERY_PREVIEW_LIMIT + 100,
);
expect(result[0].query).toContain("cat > /app/eval.scm << 'EOF'");
expect(result[0].query).toContain("command truncated");
expect(result[0].query).toContain("full command is in the tool call input");
});
it("truncates long command echoes on the error path too", async () => {
const execute = vi.fn(async () => {
throw new Error("boom");
});
const tool = createBashTool(execute);
const command = `cat > /app/big.txt << 'EOF'\n${"y".repeat(10000)}\nEOF`;
const result = (await tool.execute(
{ commands: [command] },
{
agentId: "agent-1",
conversationId: "conv-1",
iteration: 1,
},
)) as Array<{ query: string; success: boolean; error?: string }>;
expect(result[0].success).toBe(false);
expect(result[0].error).toContain("boom");
expect(result[0].query.length).toBeLessThan(
RUN_COMMAND_QUERY_PREVIEW_LIMIT + 100,
);
expect(result[0].query).toContain("command truncated");
});
it("truncates long command echoes for the structured windows shell tool", async () => {
const execute = vi.fn(async () => "ok");
const tool = createWindowsShellTool(execute);
const command = `powershell -Command "${"z".repeat(9000)}"`;
const result = (await tool.execute({ commands: [command] } as never, {
agentId: "agent-1",
conversationId: "conv-1",
iteration: 1,
})) as Array<{ query: string; success: boolean }>;
expect(result[0].success).toBe(true);
expect(result[0].query.length).toBeLessThan(
RUN_COMMAND_QUERY_PREVIEW_LIMIT + 100,
);
expect(result[0].query).toContain("command truncated");
});
it("emits timeout telemetry without leaking raw command data", async () => {
const execute = vi.fn(
async (): Promise<string> =>
@@ -16,7 +16,7 @@ import { getToolContextTelemetry } from "../../services/telemetry/tool-context";
import {
formatError,
formatReadFileQuery,
formatRunCommandQuery,
formatRunCommandQueryPreview,
getEditorSizeError,
getReadFileRangeError,
normalizeRunCommandsInput,
@@ -310,6 +310,7 @@ export function createBashTool(
return Promise.all(
commands.map(async (command: string): Promise<ToolOperationResult> => {
const startedAt = Date.now();
const query = formatRunCommandQueryPreview(command);
try {
const output = await withTimeout(
executor(command, cwd, context),
@@ -317,7 +318,7 @@ export function createBashTool(
`Command timed out after ${timeoutMs}ms`,
);
return {
query: command,
query,
result: output,
success: true,
};
@@ -332,7 +333,7 @@ export function createBashTool(
}
const msg = formatError(error);
return {
query: command,
query,
result: "",
error: `Command failed: ${msg}`,
success: false,
@@ -376,6 +377,7 @@ export function createWindowsShellTool(
return Promise.all(
commands.map(async (command): Promise<ToolOperationResult> => {
const startedAt = Date.now();
const query = formatRunCommandQueryPreview(command);
try {
const output = await withTimeout(
executor(command, cwd, context),
@@ -383,7 +385,7 @@ export function createWindowsShellTool(
`Command timed out after ${timeoutMs}ms`,
);
return {
query: formatRunCommandQuery(command),
query,
result: output,
success: true,
};
@@ -398,7 +400,7 @@ export function createWindowsShellTool(
}
const msg = formatError(error);
return {
query: formatRunCommandQuery(command),
query,
result: "",
error: `Command failed: ${msg}`,
success: false,
@@ -124,3 +124,27 @@ export function formatRunCommandQuery(
);
return `${command.command} ${renderedArgs.join(" ")}`;
}
/**
* Max characters of the executed command echoed back in the tool result's
* `query` field. The full command already exists in the assistant tool-call
* input, so repeating it in the result only duplicates tokens in the
* provider request (expensive for large heredoc/file-generation commands).
*/
export const RUN_COMMAND_QUERY_PREVIEW_LIMIT = 200;
/**
* Bound the command echo placed in a provider-facing tool result.
* Short commands pass through unchanged; long commands keep a short
* prefix plus a truncation note so the result is still identifiable.
*/
export function formatRunCommandQueryPreview(
command: string | StructuredCommandInput,
): string {
const rendered = formatRunCommandQuery(command);
if (rendered.length <= RUN_COMMAND_QUERY_PREVIEW_LIMIT) {
return rendered;
}
const truncatedChars = rendered.length - RUN_COMMAND_QUERY_PREVIEW_LIMIT;
return `${rendered.slice(0, RUN_COMMAND_QUERY_PREVIEW_LIMIT)} ... [command truncated: ${truncatedChars} more chars; full command is in the tool call input]`;
}
@@ -320,17 +320,11 @@ describe("createSpawnAgentTool", () => {
},
);
const constructedConfig = agentConstructorSpy.mock.calls[0]?.[0] as {
systemPrompt: string;
};
expect(constructedConfig.systemPrompt.startsWith(inputSystemPrompt)).toBe(
true,
expect(agentConstructorSpy).toHaveBeenCalledWith(
expect.objectContaining({
systemPrompt: inputSystemPrompt,
}),
);
// The embedded workspace configuration is not injected a second time.
const markerCount = constructedConfig.systemPrompt.split(
"# Workspace Configuration",
).length;
expect(markerCount - 1).toBe(1);
});
it("resolves connection settings lazily at execution time", async () => {
@@ -1,66 +0,0 @@
import { describe, expect, it } from "vitest";
import type { DelegatedAgentRuntimeConfig } from "./delegated-agent";
import {
buildSubAgentSystemPrompt,
buildTeammateSystemPrompt,
} from "./subagent-prompts";
const PROFILE_BODY = "You are a reviewer. Focus on correctness.";
function makeConfig(
overrides: Partial<DelegatedAgentRuntimeConfig> = {},
): DelegatedAgentRuntimeConfig {
return {
providerId: "cline",
modelId: "model",
cwd: "/repo",
apiKey: "key",
clineIdeName: "Terminal",
clinePlatform: "linux",
workspaceMetadata: '{"workspaces":{}}',
...overrides,
};
}
describe("buildSubAgentSystemPrompt", () => {
it("fills the persona slot and keeps the agent harness for cline", () => {
const prompt = buildSubAgentSystemPrompt(PROFILE_BODY, makeConfig());
expect(prompt.startsWith(PROFILE_BODY)).toBe(true);
expect(prompt).toContain("Environment you are running in:");
expect(prompt).toContain("4. Working Directory: /repo");
expect(prompt).toContain(
"IMPORTANT: Always includes tool calls in your response until the task is completed.",
);
expect(prompt).toContain("# Workspace Configuration");
expect(prompt).not.toContain("You are Cline, an AI coding agent.");
});
it("keeps the harness for non-cline providers without cline metadata", () => {
const prompt = buildSubAgentSystemPrompt(
PROFILE_BODY,
makeConfig({ providerId: "openai" }),
);
expect(prompt.startsWith(PROFILE_BODY)).toBe(true);
expect(prompt).toContain("Environment you are running in:");
expect(prompt).toContain(
"IMPORTANT: Always includes tool calls in your response until the task is completed.",
);
expect(prompt).not.toContain("# Workspace Configuration");
});
});
describe("buildTeammateSystemPrompt", () => {
it("injects the role prompt as rules under the default persona for cline", () => {
const prompt = buildTeammateSystemPrompt(PROFILE_BODY, makeConfig());
expect(prompt).toContain("You are Cline, an AI coding agent.");
expect(prompt).toContain(`# Team Teammate Role\n${PROFILE_BODY}`);
});
it("returns the raw prompt for non-cline providers", () => {
const prompt = buildTeammateSystemPrompt(
PROFILE_BODY,
makeConfig({ providerId: "openai" }),
);
expect(prompt).toBe(PROFILE_BODY);
});
});
@@ -26,13 +26,15 @@ export function buildSubAgentSystemPrompt(
config: DelegatedAgentRuntimeConfig,
): string {
const trimmedPrompt = prompt.trim();
// The spawn prompt fills the persona slot; the provider-agnostic harness
// (env block, tool-call loop contract) is kept for every provider.
if (config.providerId.toLowerCase() !== "cline") {
return trimmedPrompt;
}
return buildClineSystemPrompt({
ide: config.clineIdeName || "Terminal",
workspaceRoot: config.cwd?.trim() || "/",
providerId: config.providerId,
personaPrompt: trimmedPrompt,
overridePrompt: trimmedPrompt,
metadata: config.workspaceMetadata,
platform: config.clinePlatform,
});
+14
View File
@@ -12,6 +12,7 @@ export type {
AgentEvent,
AgentExtension as AgentPlugin, // Public-facing alias for extensions
AgentExtensionCommand,
AgentExtensionCommandResult,
AgentExtensionCommand as AgentPluginCommand,
AgentHooks,
AgentMode,
@@ -32,9 +33,15 @@ export type {
ClineAccountActionRequest,
ConnectorHookEvent,
ContentBlock,
FeatureFlag,
FeatureFlagPayload,
FeatureFlagsAndPayloads,
FeatureFlagsContext,
FeatureFlagsSettings,
FileContent,
GetProviderModelsActionRequest,
HookSessionContext,
IFeatureFlagsProvider,
ImageContent,
ITelemetryService,
ListProvidersActionRequest,
@@ -81,6 +88,8 @@ export {
createContributionRegistry,
createTool,
emptyWorkspaceManifest,
FEATURE_FLAGS,
FeatureFlagDefaultValue,
formatDisplayUserInput,
noopBasicLogger,
normalizeSdkError,
@@ -434,6 +443,11 @@ export {
type DesktopToolApprovalOptions,
requestDesktopToolApproval,
} from "./runtime/tools/tool-approval";
export {
FeatureFlagsService,
type FeatureFlagsServiceOptions,
NoOpFeatureFlagsProvider,
} from "./services/feature-flags";
export type { GlobalSettings } from "./services/global-settings";
export {
filterDisabledPluginPaths,
@@ -2,8 +2,8 @@ import { EMPTY_CONTENT_TEXT } from "@cline/shared";
import { describe, expect, it } from "vitest";
import {
agentMessageToMessageWithMetadata,
messagesToAgentMessages,
messageToAgentMessages,
messagesToAgentMessages,
} from "./agent-message-codec";
describe("agent message codec", () => {
@@ -0,0 +1,115 @@
import type { IFeatureFlagsProvider } from "@cline/shared";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { FeatureFlagsService } from "./FeatureFlagsService";
const TEST_BOOLEAN_FLAG = "test_boolean_flag";
const TEST_PAYLOAD_FLAG = "test_payload_flag";
function createProvider(
overrides: Partial<IFeatureFlagsProvider> = {},
): IFeatureFlagsProvider {
return {
getAllFlagsAndPayloads: vi.fn(async () => ({
featureFlags: {
[TEST_BOOLEAN_FLAG]: true,
},
featureFlagPayloads: {
[TEST_PAYLOAD_FLAG]: 1234,
},
})),
enabled: true,
getSettings: vi.fn(() => ({ enabled: true, timeoutMs: 1000 })),
dispose: vi.fn(async () => {}),
...overrides,
};
}
describe("FeatureFlagsService", () => {
beforeEach(() => {
vi.useRealTimers();
});
it("polls provider values into the cache", async () => {
const provider = createProvider();
const telemetry = { capture: vi.fn() };
const service = new FeatureFlagsService({
provider,
telemetry: telemetry as never,
context: { distinctId: "machine-1", clientName: "unit-test" },
});
await service.poll("user-1");
expect(provider.getAllFlagsAndPayloads).toHaveBeenCalledWith({
flagKeys: undefined,
context: {
distinctId: "machine-1",
clientName: "unit-test",
userId: "user-1",
},
});
expect(service.getBooleanFlagEnabled(TEST_BOOLEAN_FLAG)).toBe(true);
expect(service.getFlagPayload(TEST_PAYLOAD_FLAG)).toBe(1234);
expect(telemetry.capture).toHaveBeenCalledWith({
event: "$feature_flag_called",
properties: {
$feature_flag: TEST_BOOLEAN_FLAG,
$feature_flag_response: true,
},
});
});
it("skips polling while the cache is fresh and user context is unchanged", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-06-10T10:00:00Z"));
const provider = createProvider();
const service = new FeatureFlagsService({ provider });
await service.poll("user-1");
expect(provider.getAllFlagsAndPayloads).toHaveBeenCalledTimes(1);
await service.poll("user-1");
expect(provider.getAllFlagsAndPayloads).toHaveBeenCalledTimes(1);
});
it("polls only once if two calls are made simultaneously with the same user context", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-06-10T10:00:00Z"));
const provider = createProvider();
const service = new FeatureFlagsService({ provider });
await Promise.all([service.poll("user-1"), service.poll("user-1")]);
expect(provider.getAllFlagsAndPayloads).toHaveBeenCalledTimes(1);
});
it("re-polls when the user context changes within the cache ttl", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-06-10T10:00:00Z"));
const provider = createProvider();
const service = new FeatureFlagsService({ provider });
await service.poll("user-1");
await service.poll("user-2");
expect(provider.getAllFlagsAndPayloads).toHaveBeenCalledTimes(2);
});
it("returns false or undefined before polling", () => {
const service = new FeatureFlagsService({ provider: createProvider() });
expect(service.getBooleanFlagEnabled(TEST_BOOLEAN_FLAG)).toBe(false);
expect(service.getFlagPayload(TEST_PAYLOAD_FLAG)).toBeUndefined();
});
it("disposes the provider", async () => {
const provider = createProvider();
const service = new FeatureFlagsService({ provider });
await service.dispose();
expect(provider.dispose).toHaveBeenCalledTimes(1);
});
});
@@ -0,0 +1,167 @@
import type {
BasicLogger,
FeatureFlagPayload,
FeatureFlagsAndPayloads,
FeatureFlagsContext,
IFeatureFlagsProvider,
ITelemetryService,
} from "@cline/shared";
import {
FEATURE_FLAGS,
type FeatureFlag,
FeatureFlagDefaultValue,
} from "@cline/shared";
import { CORE_TELEMETRY_EVENTS } from "../..";
const DEFAULT_CACHE_TTL_MS = 60 * 60 * 1000;
type CacheInfo = {
updateTime: number;
userId: string | null;
flagsPayload?: FeatureFlagsAndPayloads;
};
export interface FeatureFlagsServiceOptions {
provider: IFeatureFlagsProvider;
telemetry?: ITelemetryService;
logger?: BasicLogger;
cacheTtlMs?: number;
context?: FeatureFlagsContext;
}
export class FeatureFlagsService {
private readonly provider: IFeatureFlagsProvider;
private readonly telemetry?: ITelemetryService;
private readonly logger?: BasicLogger;
private readonly cacheTtlMs: number;
private context: FeatureFlagsContext;
private cache: Map<FeatureFlag, FeatureFlagPayload | undefined> = new Map();
private cacheInfo: CacheInfo = { updateTime: 0, userId: null };
constructor(options: FeatureFlagsServiceOptions) {
this.provider = options.provider;
this.telemetry = options.telemetry;
this.logger = options.logger;
this.cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
this.context = { ...(options.context ?? {}) };
}
setContext(context: FeatureFlagsContext): void {
this.context = { ...context };
}
async poll(
userId: string | null = this.context.userId ?? null,
): Promise<void> {
const timeNow = Date.now();
if (timeNow - this.cacheInfo.updateTime < this.cacheTtlMs) {
if (this.cacheInfo.userId === userId) {
return;
}
}
const previousCacheInfo = this.cacheInfo;
this.cacheInfo = { updateTime: timeNow, userId: userId || null };
try {
const values = await this.provider.getAllFlagsAndPayloads({
flagKeys: FEATURE_FLAGS.length > 0 ? FEATURE_FLAGS : undefined,
context: { ...this.context, userId },
});
if (this.cacheInfo.userId !== userId) {
// A new poll has started with a different userId, so we should not update the cache with the results of this poll
return;
}
this.cacheInfo.flagsPayload = values;
const nextCache = new Map<FeatureFlag, FeatureFlagPayload | undefined>();
for (const flag of this.getReturnedFlagKeys(values)) {
const payload = this.getFeatureFlag(flag);
nextCache.set(flag, payload ?? false);
}
this.cache = nextCache;
} catch (error) {
if (this.cacheInfo.userId !== userId) {
// A new poll has started with a different userId, so we should not update the cache with the results of this poll
return;
}
this.cacheInfo = previousCacheInfo.updateTime
? previousCacheInfo
: { updateTime: 0, userId: null };
this.logger?.error?.("Error polling SDK feature flags", { error });
throw error;
}
}
private getReturnedFlagKeys(
values: FeatureFlagsAndPayloads | undefined,
): FeatureFlag[] {
return [
...new Set([
...Object.keys(values?.featureFlags ?? {}),
...Object.keys(values?.featureFlagPayloads ?? {}),
]),
];
}
private getFeatureFlag(
flagName: FeatureFlag,
): FeatureFlagPayload | undefined {
try {
const payload =
this.cacheInfo.flagsPayload?.featureFlagPayloads?.[flagName];
const flagValue = this.cacheInfo.flagsPayload?.featureFlags?.[flagName];
const value =
payload ?? flagValue ?? FeatureFlagDefaultValue[flagName] ?? undefined;
if (!this.cache.has(flagName) || this.cache.get(flagName) !== value) {
this.telemetry?.capture({
event: CORE_TELEMETRY_EVENTS.FEATURE_FLAGS.FLAG_CALLED,
properties: {
$feature_flag: flagName,
$feature_flag_response: flagValue,
},
});
}
return value;
} catch (error) {
this.logger?.error?.(`Error checking SDK feature flag ${flagName}`, {
error,
});
return FeatureFlagDefaultValue[flagName] ?? false;
}
}
getBooleanFlagEnabled(flagName: FeatureFlag): boolean {
return this.cache.get(flagName) === true;
}
getFlagPayload(flagName: FeatureFlag): FeatureFlagPayload | undefined {
return this.cache.get(flagName) ?? FeatureFlagDefaultValue[flagName];
}
getProvider(): IFeatureFlagsProvider {
return this.provider;
}
get enabled(): boolean {
return this.provider.enabled;
}
getSettings() {
return this.provider.getSettings();
}
test(flagName: FeatureFlag, value: boolean): void {
if (process.env.NODE_ENV === "test" || process.env.IS_TEST === "true") {
this.cache.set(flagName, value);
}
}
async dispose(): Promise<void> {
await this.provider.dispose();
}
}
@@ -0,0 +1,5 @@
export {
FeatureFlagsService,
type FeatureFlagsServiceOptions,
} from "./FeatureFlagsService";
export { NoOpFeatureFlagsProvider } from "./providers";
@@ -0,0 +1,19 @@
import type {
FeatureFlagsAndPayloads,
FeatureFlagsSettings,
IFeatureFlagsProvider,
} from "@cline/shared";
export class NoOpFeatureFlagsProvider implements IFeatureFlagsProvider {
async getAllFlagsAndPayloads(): Promise<FeatureFlagsAndPayloads> {
return {};
}
enabled = false;
getSettings(): FeatureFlagsSettings {
return { enabled: false, timeoutMs: 1000 };
}
async dispose(): Promise<void> {}
}
@@ -74,6 +74,9 @@ export const CORE_TELEMETRY_EVENTS = {
ERROR: SDK_ERROR_TELEMETRY_EVENT,
TOOL_TIMEOUT: "sdk.tool_timeout",
},
FEATURE_FLAGS: {
FLAG_CALLED: "$feature_flag_called",
},
} as const;
export interface RunCommandsTimeoutTelemetryProperties {
@@ -1,5 +1,11 @@
import type { Message } from "@cline/shared";
import {
type AiSdkFormatterMessage,
formatMessagesForAiSdk,
type Message,
type ToolResultContent,
} from "@cline/shared";
import { describe, expect, it } from "vitest";
import { messagesToAgentMessages } from "../../runtime/config/agent-message-codec";
import { MessageBuilder } from "./message-builder";
describe("MessageBuilder", () => {
@@ -373,3 +379,296 @@ describe("MessageBuilder", () => {
]);
});
});
/**
* Regression coverage for the real `ToolOperationResult[]` shape emitted by
* the default Cline tools (run_commands, read_files, search_codebase).
*
* The runtime stores these structured results directly as the tool_result
* `content` array (see `agentPartToContentBlock` in
* `runtime/config/agent-message-codec.ts`): the entries are plain
* `{query, result, success, ...}` objects with no `type` discriminator, so
* they bypass the text/file-entry truncation paths unless MessageBuilder
* handles them explicitly.
*/
describe("MessageBuilder with structured ToolOperationResult content", () => {
const MIDDLE_SENTINEL = "__MIDDLE_SENTINEL_MUST_BE_TRUNCATED__";
const HEAD_MARKER = "__HEAD_MARKER__";
const TAIL_MARKER = "__TAIL_MARKER__";
interface ToolOperationResultLike {
query: string;
result: unknown;
error?: string;
success: boolean;
duration?: number;
}
function hugeText(size = 400_000): string {
const fillerLength = Math.floor(
(size -
MIDDLE_SENTINEL.length -
HEAD_MARKER.length -
TAIL_MARKER.length) /
2,
);
const filler = "x".repeat(fillerLength);
return `${HEAD_MARKER}${filler}${MIDDLE_SENTINEL}${filler}${TAIL_MARKER}`;
}
function toolUseMessage(
id: string,
name: string,
input: Record<string, unknown>,
): Message {
return {
role: "assistant",
content: [{ type: "tool_use", id, name, input }],
};
}
function structuredToolResultMessage(
toolUseId: string,
name: string,
operations: ToolOperationResultLike[],
): Message {
return {
role: "user",
content: [
{
type: "tool_result",
tool_use_id: toolUseId,
name,
// The runtime casts ToolOperationResult[] straight into the
// content array; mirror that here.
content: operations as unknown as ToolResultContent["content"],
},
],
};
}
function sumStringBytes(value: unknown): number {
if (typeof value === "string") {
return Buffer.byteLength(value, "utf8");
}
if (Array.isArray(value)) {
return value.reduce<number>((sum, item) => sum + sumStringBytes(item), 0);
}
if (value !== null && typeof value === "object") {
return Object.values(value).reduce<number>(
(sum, item) => sum + sumStringBytes(item),
0,
);
}
return 0;
}
it("truncates a huge nested `result` string in run_commands structured output", () => {
const builder = new MessageBuilder();
const messages: Message[] = [
toolUseMessage("call_1", "run_commands", {
commands: ["cat big.log"],
}),
structuredToolResultMessage("call_1", "run_commands", [
{
query: "cat big.log",
result: hugeText(),
success: true,
duration: 1234,
},
]),
];
const rawSerializedLength = JSON.stringify(messages).length;
const result = builder.buildForApi(messages);
const serialized = JSON.stringify(result);
expect(rawSerializedLength).toBeGreaterThan(390_000);
expect(serialized.length).toBeLessThan(120_000);
expect(serialized).not.toContain(MIDDLE_SENTINEL);
// Middle truncation must preserve the head and tail of the output.
expect(serialized).toContain(HEAD_MARKER);
expect(serialized).toContain(TAIL_MARKER);
});
it("truncates a huge nested `query` string in run_commands structured output", () => {
const builder = new MessageBuilder();
const messages: Message[] = [
toolUseMessage("call_1", "run_commands", {
commands: ["bash -c '...giant heredoc...'"],
}),
structuredToolResultMessage("call_1", "run_commands", [
{
query: hugeText(),
result: "ok",
success: true,
},
]),
];
const result = builder.buildForApi(messages);
const serialized = JSON.stringify(result);
expect(serialized.length).toBeLessThan(120_000);
expect(serialized).not.toContain(MIDDLE_SENTINEL);
expect(serialized).toContain(HEAD_MARKER);
expect(serialized).toContain(TAIL_MARKER);
});
it("truncates a huge file payload in read_files structured output", () => {
const builder = new MessageBuilder();
const messages: Message[] = [
toolUseMessage("call_1", "read_files", {
files: [{ path: "/tmp/big.txt" }],
}),
structuredToolResultMessage("call_1", "read_files", [
{
query: "/tmp/big.txt",
result: hugeText(),
success: true,
},
]),
];
const result = builder.buildForApi(messages);
const serialized = JSON.stringify(result);
expect(serialized.length).toBeLessThan(120_000);
expect(serialized).not.toContain(MIDDLE_SENTINEL);
expect(serialized).toContain(HEAD_MARKER);
expect(serialized).toContain(TAIL_MARKER);
// The latest read of a file must not be rewritten as outdated.
expect(serialized).not.toContain("[outdated");
});
it("truncates a huge fetch_web_content structured result", () => {
// The web-fetch executor allows responses up to 5MB, so this tool must
// be covered by the truncation targets like the other bulk-output tools.
const builder = new MessageBuilder();
const messages: Message[] = [
toolUseMessage("call_1", "fetch_web_content", {
requests: [{ url: "https://example.com/huge-page" }],
}),
structuredToolResultMessage("call_1", "fetch_web_content", [
{
query: "https://example.com/huge-page",
result: hugeText(),
success: true,
},
]),
];
const result = builder.buildForApi(messages);
const serialized = JSON.stringify(result);
expect(serialized.length).toBeLessThan(120_000);
expect(serialized).not.toContain(MIDDLE_SENTINEL);
expect(serialized).toContain(HEAD_MARKER);
expect(serialized).toContain(TAIL_MARKER);
});
it("applies the aggregate text budget to nested structured strings", () => {
const builder = new MessageBuilder(
50_000,
new Set(["run_commands", "read_files"]),
100_000,
);
// Five results of ~40k chars each: every nested string is below the
// per-result limit, but the aggregate (~200k) exceeds the budget.
const messages: Message[] = [];
for (let i = 0; i < 5; i++) {
const name = i % 2 === 0 ? "run_commands" : "read_files";
messages.push(
toolUseMessage(`call_${i}`, name, { commands: [`cmd ${i}`] }),
structuredToolResultMessage(`call_${i}`, name, [
{
query: `cmd ${i}`,
result: `chunk_${i}_`.repeat(4_000),
success: true,
},
]),
);
}
const result = builder.buildForApi(messages);
let toolResultStringBytes = 0;
for (const message of result) {
if (!Array.isArray(message.content)) {
continue;
}
for (const block of message.content) {
if (block.type === "tool_result") {
toolResultStringBytes += sumStringBytes(block.content);
}
}
}
expect(toolResultStringBytes).toBeLessThanOrEqual(100_000);
expect(JSON.stringify(result)).toContain("provider request budget");
});
it("does not mutate the original structured tool results", () => {
const builder = new MessageBuilder(
10_000,
new Set(["run_commands"]),
20_000,
);
const messages: Message[] = [
toolUseMessage("call_1", "run_commands", { commands: ["cat a.log"] }),
structuredToolResultMessage("call_1", "run_commands", [
{
query: "cat a.log",
result: hugeText(50_000),
success: true,
},
]),
toolUseMessage("call_2", "run_commands", { commands: ["cat b.log"] }),
structuredToolResultMessage("call_2", "run_commands", [
{
query: "cat b.log",
result: hugeText(50_000),
success: true,
},
]),
];
const snapshot = structuredClone(messages);
const result = builder.buildForApi(messages);
expect(messages).toEqual(snapshot);
expect(JSON.stringify(result)).not.toContain(MIDDLE_SENTINEL);
});
it("keeps huge nested strings out of provider-formatted AI SDK messages", () => {
const builder = new MessageBuilder();
const messages: Message[] = [
toolUseMessage("call_1", "run_commands", {
commands: ["cat big.log"],
}),
structuredToolResultMessage("call_1", "run_commands", [
{
query: "cat big.log",
result: hugeText(),
success: true,
},
]),
];
const built = builder.buildForApi(messages);
const agentMessages = messagesToAgentMessages(built);
const aiSdkMessages = formatMessagesForAiSdk(
undefined,
agentMessages.map(({ role, content }) => ({
role,
content,
})) as unknown as AiSdkFormatterMessage[],
);
const serialized = JSON.stringify(aiSdkMessages);
expect(serialized.length).toBeLessThan(130_000);
expect(serialized).not.toContain(MIDDLE_SENTINEL);
expect(serialized).toContain(HEAD_MARKER);
expect(serialized).toContain(TAIL_MARKER);
});
});
@@ -27,6 +27,7 @@ const TARGET_TOOL_NAMES = new Set([
"search_codebase",
"bash",
"run_commands",
"fetch_web_content",
]);
const READ_TOOL_NAMES = new Set(["read", "read_files"]);
const OUTDATED_FILE_CONTENT = "[outdated - see the latest file content]";
@@ -765,14 +766,56 @@ export class MessageBuilder {
const next = this.truncateMiddle(entry.content);
return next === entry.content ? entry : { ...entry, content: next };
}
if (entry.type !== "text") {
return entry;
if (entry.type === "text") {
const next = this.truncateMiddle(entry.text);
return next === entry.text ? entry : { ...entry, text: next };
}
const next = this.truncateMiddle(entry.text);
return next === entry.text ? entry : { ...entry, text: next };
if (isStructuredToolResultEntry(entry)) {
return this.truncateNestedStrings(entry) as typeof entry;
}
return entry;
});
}
/**
* Deep-truncates string values inside structured tool outputs (e.g.
* `ToolOperationResult[]` from run_commands/read_files), which carry the
* payload in untyped `{query, result, ...}` fields rather than text
* blocks. Image blocks are left intact so base64 payloads survive.
*/
private truncateNestedStrings(value: unknown): unknown {
if (typeof value === "string") {
return this.truncateMiddle(value);
}
if (Array.isArray(value)) {
let changed = false;
const next = value.map((item) => {
const out = this.truncateNestedStrings(item);
if (out !== item) {
changed = true;
}
return out;
});
return changed ? next : value;
}
if (value !== null && typeof value === "object") {
if (isImageContentLike(value)) {
return value;
}
let changed = false;
const next: Record<string, unknown> = {};
for (const [key, item] of Object.entries(value)) {
const out = this.truncateNestedStrings(item);
if (out !== item) {
changed = true;
}
next[key] = out;
}
return changed ? next : value;
}
return value;
}
private truncateMiddle(text: string): string {
return truncateMiddleByChars(
text,
@@ -852,6 +895,8 @@ export class MessageBuilder {
total += utf8ByteLength(entry.text);
} else if (entry.type === "file") {
total += utf8ByteLength(entry.content);
} else if (isStructuredToolResultEntry(entry)) {
total += countNestedStringBytes(entry);
}
}
}
@@ -904,6 +949,8 @@ export class MessageBuilder {
entry.content = value;
},
});
} else if (isStructuredToolResultEntry(entry)) {
collectNestedStringCandidates(entry, candidates);
}
}
}
@@ -971,6 +1018,110 @@ function cloneContentBlockForMutation(block: ContentBlock): ContentBlock {
}
return {
...block,
content: block.content.map((entry) => ({ ...entry })),
// Structured entries can nest the payload strings arbitrarily deep, so
// a shallow copy would leak budget-truncation mutations back into the
// original conversation history.
content: block.content.map((entry) =>
isStructuredToolResultEntry(entry)
? (deepCloneJsonLike(entry) as typeof entry)
: { ...entry },
),
};
}
/**
* True for tool_result content entries that are not the typed text/image/file
* blocks i.e. structured tool outputs such as `ToolOperationResult[]`
* entries that the runtime stores directly in the content array.
*/
function isStructuredToolResultEntry(entry: unknown): boolean {
if (entry === null || typeof entry !== "object") {
return false;
}
const type = (entry as { type?: unknown }).type;
return type !== "text" && type !== "image" && type !== "file";
}
function isImageContentLike(value: object): boolean {
return (value as { type?: unknown }).type === "image";
}
function countNestedStringBytes(value: unknown): number {
if (typeof value === "string") {
return utf8ByteLength(value);
}
if (Array.isArray(value)) {
let total = 0;
for (const item of value) {
total += countNestedStringBytes(item);
}
return total;
}
if (value !== null && typeof value === "object") {
if (isImageContentLike(value)) {
return 0;
}
let total = 0;
for (const item of Object.values(value)) {
total += countNestedStringBytes(item);
}
return total;
}
return 0;
}
function collectNestedStringCandidates(
container: unknown,
candidates: TruncationCandidate[],
): void {
if (Array.isArray(container)) {
container.forEach((item, index) => {
if (typeof item === "string") {
candidates.push({
byteLength: utf8ByteLength(item),
get: () => container[index] as string,
set: (value) => {
container[index] = value;
},
});
} else {
collectNestedStringCandidates(item, candidates);
}
});
return;
}
if (container !== null && typeof container === "object") {
if (isImageContentLike(container)) {
return;
}
const record = container as Record<string, unknown>;
for (const key of Object.keys(record)) {
const item = record[key];
if (typeof item === "string") {
candidates.push({
byteLength: utf8ByteLength(item),
get: () => record[key] as string,
set: (value) => {
record[key] = value;
},
});
} else {
collectNestedStringCandidates(item, candidates);
}
}
}
}
function deepCloneJsonLike(value: unknown): unknown {
if (Array.isArray(value)) {
return value.map(deepCloneJsonLike);
}
if (value !== null && typeof value === "object") {
const out: Record<string, unknown> = {};
for (const [key, item] of Object.entries(value)) {
out[key] = deepCloneJsonLike(item);
}
return out;
}
return value;
}
+15
View File
@@ -1,9 +1,19 @@
export type {
AgentRunResult,
AgentRunStatus,
FeatureFlag,
FeatureFlagPayload,
FeatureFlagsAndPayloads,
FeatureFlagsContext,
FeatureFlagsSettings,
IFeatureFlagsProvider,
WorkspaceInfo,
WorkspaceManifest,
} from "@cline/shared";
export {
FEATURE_FLAGS,
FeatureFlagDefaultValue,
} from "@cline/shared";
export { ClineCore } from "./ClineCore";
export type {
ClineCoreListHistoryOptions,
@@ -103,6 +113,11 @@ export type {
SubprocessSandboxOptions,
} from "./runtime/tools/subprocess-sandbox";
export { SubprocessSandbox } from "./runtime/tools/subprocess-sandbox";
export {
FeatureFlagsService,
type FeatureFlagsServiceOptions,
NoOpFeatureFlagsProvider,
} from "./services/feature-flags";
export type { GlobalSettings } from "./services/global-settings";
export {
filterDisabledPluginPaths,
-16
View File
@@ -1,22 +1,6 @@
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { defineConfig } from "vitest/config";
const rootDir = dirname(fileURLToPath(import.meta.url));
export default defineConfig({
resolve: {
alias: [
{
find: /^@cline\/shared$/,
replacement: resolve(rootDir, "../shared/src/index.ts"),
},
{
find: /^@cline\/shared\/(.+)$/,
replacement: resolve(rootDir, "../shared/src/$1"),
},
],
},
test: {
environment: "node",
include: ["src/**/*.test.ts"],
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@cline/llms",
"version": "0.0.46",
"version": "0.0.47",
"description": "Config-driven SDK for selecting, extending, and instantiating LLM providers and models",
"repository": {
"type": "git",
+234 -320
View File
@@ -14,7 +14,7 @@ export const GENERATED_PROVIDER_MODELS: {
version: number;
providers: Record<string, Record<string, ModelInfo>>;
} = {
version: 1781051263433,
version: 1781211653622,
providers: {
aihubmix: {
"glm-5v-turbo": {
@@ -3597,26 +3597,36 @@ export const GENERATED_PROVIDER_MODELS: {
name: "Z.AI GLM-4.7",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 40000,
capabilities: ["tools", "temperature"],
maxTokens: 40960,
capabilities: [
"tools",
"reasoning",
"structured_output",
"temperature",
],
pricing: {
input: 2.25,
output: 2.75,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2026-01-10",
releaseDate: "2026-01-07",
},
"gpt-oss-120b": {
id: "gpt-oss-120b",
name: "GPT OSS 120B",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 32768,
capabilities: ["tools", "reasoning", "temperature"],
maxTokens: 40960,
capabilities: [
"tools",
"reasoning",
"structured_output",
"temperature",
],
pricing: {
input: 0.25,
output: 0.69,
input: 0.35,
output: 0.75,
cacheRead: 0,
cacheWrite: 0,
},
@@ -4241,58 +4251,27 @@ export const GENERATED_PROVIDER_MODELS: {
},
},
groq: {
"moonshotai/kimi-k2-instruct-0905": {
id: "moonshotai/kimi-k2-instruct-0905",
name: "Kimi K2 Instruct 0905",
contextWindow: 262144,
maxInputTokens: 262144,
maxTokens: 16384,
"openai/gpt-oss-safeguard-20b": {
id: "openai/gpt-oss-safeguard-20b",
name: "Safety GPT OSS 20B",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 65536,
capabilities: [
"tools",
"reasoning",
"structured_output",
"temperature",
"prompt-cache",
],
pricing: {
input: 1,
output: 3,
cacheRead: 0.5,
input: 0.075,
output: 0.3,
cacheRead: 0.037,
cacheWrite: 0,
},
releaseDate: "2025-09-05",
family: "kimi",
},
"groq/compound": {
id: "groq/compound",
name: "Compound",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 8192,
capabilities: ["tools", "reasoning", "temperature"],
pricing: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2025-09-04",
family: "groq",
},
"groq/compound-mini": {
id: "groq/compound-mini",
name: "Compound Mini",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 8192,
capabilities: ["tools", "reasoning", "temperature"],
pricing: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2025-09-04",
family: "groq",
releaseDate: "2025-10-29",
family: "gpt-oss",
},
"openai/gpt-oss-120b": {
id: "openai/gpt-oss-120b",
@@ -4338,9 +4317,25 @@ export const GENERATED_PROVIDER_MODELS: {
releaseDate: "2025-08-05",
family: "gpt-oss",
},
"qwen/qwen3-32b": {
id: "qwen/qwen3-32b",
name: "Qwen3-32B",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 40960,
capabilities: ["tools", "reasoning", "temperature"],
pricing: {
input: 0.29,
output: 0.59,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2025-06-11",
family: "qwen",
},
"meta-llama/llama-4-scout-17b-16e-instruct": {
id: "meta-llama/llama-4-scout-17b-16e-instruct",
name: "Llama 4 Scout 17B",
name: "Llama 4 Scout 17B 16E",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 8192,
@@ -4354,41 +4349,9 @@ export const GENERATED_PROVIDER_MODELS: {
releaseDate: "2025-04-05",
family: "llama",
},
"openai/gpt-oss-safeguard-20b": {
id: "openai/gpt-oss-safeguard-20b",
name: "Safety GPT OSS 20B",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 65536,
capabilities: ["tools", "reasoning", "temperature", "prompt-cache"],
pricing: {
input: 0.075,
output: 0.3,
cacheRead: 0.037,
cacheWrite: 0,
},
releaseDate: "2025-03-05",
family: "gpt-oss",
},
"qwen/qwen3-32b": {
id: "qwen/qwen3-32b",
name: "Qwen3 32B",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 40960,
capabilities: ["tools", "reasoning", "temperature"],
pricing: {
input: 0.29,
output: 0.59,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2024-12-23",
family: "qwen",
},
"llama-3.3-70b-versatile": {
id: "llama-3.3-70b-versatile",
name: "Llama 3.3 70B Versatile",
name: "Llama 3.3 70B",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 32768,
@@ -4404,7 +4367,7 @@ export const GENERATED_PROVIDER_MODELS: {
},
"llama-3.1-8b-instant": {
id: "llama-3.1-8b-instant",
name: "Llama 3.1 8B Instant",
name: "Llama 3.1 8B",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 131072,
@@ -11251,6 +11214,52 @@ export const GENERATED_PROVIDER_MODELS: {
},
},
openrouter: {
"~anthropic/claude-fable-latest": {
id: "~anthropic/claude-fable-latest",
name: "Claude Fable Latest",
contextWindow: 1000000,
maxInputTokens: 1000000,
maxTokens: 128000,
capabilities: [
"images",
"files",
"tools",
"reasoning",
"structured_output",
"prompt-cache",
],
pricing: {
input: 10,
output: 50,
cacheRead: 1,
cacheWrite: 12.5,
},
releaseDate: "2026-06-09",
family: "claude-fable",
},
"anthropic/claude-fable-5": {
id: "anthropic/claude-fable-5",
name: "Claude Fable 5",
contextWindow: 1000000,
maxInputTokens: 1000000,
maxTokens: 128000,
capabilities: [
"images",
"files",
"tools",
"reasoning",
"structured_output",
"prompt-cache",
],
pricing: {
input: 10,
output: 50,
cacheRead: 1,
cacheWrite: 12.5,
},
releaseDate: "2026-06-09",
family: "claude-fable",
},
"nex-agi/nex-n2-pro:free": {
id: "nex-agi/nex-n2-pro:free",
name: "Nex-N2-Pro (free)",
@@ -11325,10 +11334,10 @@ export const GENERATED_PROVIDER_MODELS: {
"prompt-cache",
],
pricing: {
input: 0.4,
output: 1.6,
cacheRead: 0.08,
cacheWrite: 0.5,
input: 0.32,
output: 1.28,
cacheRead: 0.064,
cacheWrite: 0.4,
},
releaseDate: "2026-06-02",
family: "qwen",
@@ -11377,29 +11386,6 @@ export const GENERATED_PROVIDER_MODELS: {
},
releaseDate: "2026-05-29",
},
"anthropic/claude-fable-5": {
id: "anthropic/claude-fable-5",
name: "Claude Fable 5",
contextWindow: 1000000,
maxInputTokens: 1000000,
maxTokens: 128000,
capabilities: [
"images",
"files",
"tools",
"reasoning",
"structured_output",
"prompt-cache",
],
pricing: {
input: 10,
output: 50,
cacheRead: 1,
cacheWrite: 12.5,
},
releaseDate: "2026-06-09",
family: "claude",
},
"anthropic/claude-opus-4.8": {
id: "anthropic/claude-opus-4.8",
name: "Claude Opus 4.8",
@@ -12137,22 +12123,6 @@ export const GENERATED_PROVIDER_MODELS: {
releaseDate: "2026-04-21",
family: "kimi-k2.6",
},
"moonshotai/kimi-k2.6:free": {
id: "moonshotai/kimi-k2.6:free",
name: "Kimi K2.6 (free)",
contextWindow: 262144,
maxInputTokens: 262144,
maxTokens: 262144,
capabilities: ["images", "tools", "reasoning"],
pricing: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2026-04-21",
family: "kimi-k2.6",
},
"qwen/qwen3.6-max-preview": {
id: "qwen/qwen3.6-max-preview",
name: "Qwen3.6 Max Preview",
@@ -12194,20 +12164,21 @@ export const GENERATED_PROVIDER_MODELS: {
"qwen/qwen3.6-35b-a3b": {
id: "qwen/qwen3.6-35b-a3b",
name: "Qwen3.6 35B-A3B",
contextWindow: 262140,
maxInputTokens: 262140,
maxTokens: 262140,
contextWindow: 262144,
maxInputTokens: 262144,
maxTokens: 262144,
capabilities: [
"images",
"tools",
"reasoning",
"structured_output",
"temperature",
"prompt-cache",
],
pricing: {
input: 0.14,
input: 0.15,
output: 1,
cacheRead: 0,
cacheRead: 0.05,
cacheWrite: 0,
},
releaseDate: "2026-04-17",
@@ -12347,9 +12318,9 @@ export const GENERATED_PROVIDER_MODELS: {
"google/gemma-4-31b-it": {
id: "google/gemma-4-31b-it",
name: "Gemma 4 31B IT",
contextWindow: 256000,
maxInputTokens: 256000,
maxTokens: 8192,
contextWindow: 262144,
maxInputTokens: 262144,
maxTokens: 262144,
capabilities: [
"images",
"tools",
@@ -12360,7 +12331,7 @@ export const GENERATED_PROVIDER_MODELS: {
],
pricing: {
input: 0.12,
output: 0.36,
output: 0.35,
cacheRead: 0.09,
cacheWrite: 0,
},
@@ -12428,28 +12399,6 @@ export const GENERATED_PROVIDER_MODELS: {
releaseDate: "2026-04-01",
family: "trinity",
},
"z-ai/glm-5v-turbo": {
id: "z-ai/glm-5v-turbo",
name: "GLM-5V-Turbo",
contextWindow: 202752,
maxInputTokens: 202752,
maxTokens: 131072,
capabilities: [
"images",
"tools",
"reasoning",
"temperature",
"prompt-cache",
],
pricing: {
input: 1.2,
output: 4,
cacheRead: 0.24,
cacheWrite: 0,
},
releaseDate: "2026-04-01",
family: "glm",
},
"x-ai/grok-4.20": {
id: "x-ai/grok-4.20",
name: "Grok 4.20",
@@ -12536,19 +12485,20 @@ export const GENERATED_PROVIDER_MODELS: {
"minimax/minimax-m2.7": {
id: "minimax/minimax-m2.7",
name: "MiniMax-M2.7",
contextWindow: 196608,
maxInputTokens: 196608,
maxTokens: 196608,
contextWindow: 204800,
maxInputTokens: 204800,
maxTokens: 131072,
capabilities: [
"tools",
"reasoning",
"structured_output",
"temperature",
"prompt-cache",
],
pricing: {
input: 0.279,
output: 1.2,
cacheRead: 0,
input: 0.27,
output: 1.08,
cacheRead: 0.054,
cacheWrite: 0,
},
releaseDate: "2026-03-18",
@@ -12626,8 +12576,8 @@ export const GENERATED_PROVIDER_MODELS: {
"z-ai/glm-5-turbo": {
id: "z-ai/glm-5-turbo",
name: "GLM-5-Turbo",
contextWindow: 202752,
maxInputTokens: 202752,
contextWindow: 262144,
maxInputTokens: 262144,
maxTokens: 131072,
capabilities: ["tools", "reasoning", "temperature", "prompt-cache"],
pricing: {
@@ -12645,7 +12595,12 @@ export const GENERATED_PROVIDER_MODELS: {
contextWindow: 262144,
maxInputTokens: 262144,
maxTokens: 262144,
capabilities: ["tools", "reasoning", "temperature"],
capabilities: [
"tools",
"reasoning",
"structured_output",
"temperature",
],
pricing: {
input: 0.09,
output: 0.45,
@@ -13071,11 +13026,12 @@ export const GENERATED_PROVIDER_MODELS: {
"reasoning",
"structured_output",
"temperature",
"prompt-cache",
],
pricing: {
input: 0.15,
output: 1.15,
cacheRead: 0,
output: 0.9,
cacheRead: 0.05,
cacheWrite: 0,
},
releaseDate: "2026-02-12",
@@ -13596,7 +13552,7 @@ export const GENERATED_PROVIDER_MODELS: {
name: "GLM-4.6V",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 24000,
maxTokens: 32768,
capabilities: [
"images",
"tools",
@@ -13607,7 +13563,7 @@ export const GENERATED_PROVIDER_MODELS: {
pricing: {
input: 0.3,
output: 0.9,
cacheRead: 0.05,
cacheRead: 0.055,
cacheWrite: 0,
},
releaseDate: "2025-12-08",
@@ -14516,22 +14472,6 @@ export const GENERATED_PROVIDER_MODELS: {
releaseDate: "2025-08-21",
family: "deepseek",
},
"nvidia/nemotron-nano-9b-v2": {
id: "nvidia/nemotron-nano-9b-v2",
name: "Nemotron Nano 9B v2",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 16384,
capabilities: ["tools", "reasoning", "temperature"],
pricing: {
input: 0.04,
output: 0.16,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2025-08-18",
family: "nemotron",
},
"nvidia/nemotron-nano-9b-v2:free": {
id: "nvidia/nemotron-nano-9b-v2:free",
name: "Nemotron Nano 9B V2 (free)",
@@ -14867,22 +14807,6 @@ export const GENERATED_PROVIDER_MODELS: {
releaseDate: "2025-07-28",
family: "glm-air",
},
"z-ai/glm-4.5-air:free": {
id: "z-ai/glm-4.5-air:free",
name: "GLM 4.5 Air (free)",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 96000,
capabilities: ["tools", "reasoning", "temperature"],
pricing: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2025-07-28",
family: "glm-air",
},
"nvidia/llama-3.3-nemotron-super-49b-v1.5": {
id: "nvidia/llama-3.3-nemotron-super-49b-v1.5",
name: "Llama 3.3 Nemotron Super 49B v1.5",
@@ -14921,22 +14845,6 @@ export const GENERATED_PROVIDER_MODELS: {
releaseDate: "2025-07-25",
family: "qwen",
},
"z-ai/glm-4-32b": {
id: "z-ai/glm-4-32b",
name: "GLM 4 32B ",
contextWindow: 128000,
maxInputTokens: 128000,
maxTokens: 128000,
capabilities: ["tools", "temperature"],
pricing: {
input: 0.1,
output: 0.1,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2025-07-24",
family: "glm",
},
"qwen/qwen3-coder": {
id: "qwen/qwen3-coder",
name: "Qwen3 Coder 480B A35B",
@@ -15544,8 +15452,8 @@ export const GENERATED_PROVIDER_MODELS: {
"deepseek/deepseek-chat-v3-0324": {
id: "deepseek/deepseek-chat-v3-0324",
name: "DeepSeek V3 0324",
contextWindow: 163840,
maxInputTokens: 163840,
contextWindow: 32768,
maxInputTokens: 32768,
maxTokens: 16384,
capabilities: [
"tools",
@@ -17744,7 +17652,7 @@ export const GENERATED_PROVIDER_MODELS: {
contextWindow: 1000000,
maxInputTokens: 1000000,
maxTokens: 500000,
capabilities: ["tools", "reasoning", "temperature"],
capabilities: ["tools", "temperature"],
pricing: {
input: 2.5,
output: 7.5,
@@ -17784,8 +17692,8 @@ export const GENERATED_PROVIDER_MODELS: {
"prompt-cache",
],
pricing: {
input: 2.1,
output: 4.4,
input: 1.74,
output: 3.48,
cacheRead: 0.2,
cacheWrite: 0,
},
@@ -17821,10 +17729,16 @@ export const GENERATED_PROVIDER_MODELS: {
contextWindow: 262144,
maxInputTokens: 262144,
maxTokens: 131072,
capabilities: ["images", "tools", "reasoning", "temperature"],
capabilities: [
"images",
"tools",
"reasoning",
"structured_output",
"temperature",
],
pricing: {
input: 0.2,
output: 0.5,
input: 0.39,
output: 0.97,
cacheRead: 0,
cacheWrite: 0,
},
@@ -17874,6 +17788,28 @@ export const GENERATED_PROVIDER_MODELS: {
releaseDate: "2026-03-18",
family: "minimax",
},
"Qwen/Qwen3.5-9B": {
id: "Qwen/Qwen3.5-9B",
name: "Qwen3.5 9B",
contextWindow: 262144,
maxInputTokens: 262144,
maxTokens: 65536,
capabilities: [
"images",
"tools",
"reasoning",
"structured_output",
"temperature",
],
pricing: {
input: 0.17,
output: 0.25,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2026-03-03",
family: "qwen",
},
"Qwen/Qwen3.5-397B-A17B": {
id: "Qwen/Qwen3.5-397B-A17B",
name: "Qwen3.5 397B A17B",
@@ -17890,53 +17826,26 @@ export const GENERATED_PROVIDER_MODELS: {
releaseDate: "2026-02-16",
family: "qwen",
},
"MiniMaxAI/MiniMax-M2.5": {
id: "MiniMaxAI/MiniMax-M2.5",
name: "MiniMax-M2.5",
contextWindow: 204800,
maxInputTokens: 204800,
"zai-org/GLM-5": {
id: "zai-org/GLM-5",
name: "GLM-5",
contextWindow: 202752,
maxInputTokens: 202752,
maxTokens: 131072,
capabilities: ["tools", "reasoning", "temperature", "prompt-cache"],
capabilities: [
"tools",
"reasoning",
"structured_output",
"temperature",
],
pricing: {
input: 0.3,
output: 1.2,
cacheRead: 0.06,
cacheWrite: 0,
},
releaseDate: "2026-02-12",
family: "minimax",
},
"Qwen/Qwen3-Coder-Next-FP8": {
id: "Qwen/Qwen3-Coder-Next-FP8",
name: "Qwen3 Coder Next FP8",
contextWindow: 262144,
maxInputTokens: 262144,
maxTokens: 262144,
capabilities: ["tools", "reasoning", "temperature"],
pricing: {
input: 0.5,
output: 1.2,
input: 1,
output: 3.2,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2026-02-03",
family: "qwen",
},
"moonshotai/Kimi-K2.5": {
id: "moonshotai/Kimi-K2.5",
name: "Kimi K2.5",
contextWindow: 262144,
maxInputTokens: 262144,
maxTokens: 262144,
capabilities: ["images", "tools", "reasoning", "temperature"],
pricing: {
input: 0.5,
output: 2.8,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2026-01-27",
family: "kimi",
releaseDate: "2026-02-11",
family: "glm",
},
"essentialai/Rnj-1-Instruct": {
id: "essentialai/Rnj-1-Instruct",
@@ -17954,22 +17863,6 @@ export const GENERATED_PROVIDER_MODELS: {
releaseDate: "2025-12-05",
family: "rnj",
},
"deepseek-ai/DeepSeek-V3-1": {
id: "deepseek-ai/DeepSeek-V3-1",
name: "DeepSeek V3.1",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 131072,
capabilities: ["tools", "reasoning", "temperature"],
pricing: {
input: 0.6,
output: 1.7,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2025-08-21",
family: "deepseek",
},
"openai/gpt-oss-120b": {
id: "openai/gpt-oss-120b",
name: "GPT OSS 120B",
@@ -17986,13 +17879,34 @@ export const GENERATED_PROVIDER_MODELS: {
releaseDate: "2025-08-05",
family: "gpt-oss",
},
"openai/gpt-oss-20b": {
id: "openai/gpt-oss-20b",
name: "GPT OSS 20B",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 131072,
capabilities: [
"tools",
"reasoning",
"structured_output",
"temperature",
],
pricing: {
input: 0.05,
output: 0.2,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2025-08-05",
family: "gpt-oss",
},
"Qwen/Qwen3-235B-A22B-Instruct-2507-tput": {
id: "Qwen/Qwen3-235B-A22B-Instruct-2507-tput",
name: "Qwen3 235B A22B Instruct 2507 FP8",
contextWindow: 262144,
maxInputTokens: 262144,
maxTokens: 262144,
capabilities: ["tools", "reasoning", "temperature"],
capabilities: ["tools", "temperature"],
pricing: {
input: 0.2,
output: 0.6,
@@ -18002,38 +17916,6 @@ export const GENERATED_PROVIDER_MODELS: {
releaseDate: "2025-07-25",
family: "qwen",
},
"Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8": {
id: "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8",
name: "Qwen3 Coder 480B A35B Instruct",
contextWindow: 262144,
maxInputTokens: 262144,
maxTokens: 262144,
capabilities: ["tools", "temperature"],
pricing: {
input: 2,
output: 2,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2025-07-23",
family: "qwen",
},
"deepseek-ai/DeepSeek-V3": {
id: "deepseek-ai/DeepSeek-V3",
name: "DeepSeek-V3",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 131072,
capabilities: ["tools", "reasoning", "temperature"],
pricing: {
input: 1.25,
output: 1.25,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2024-12-26",
family: "deepseek",
},
"meta-llama/Llama-3.3-70B-Instruct-Turbo": {
id: "meta-llama/Llama-3.3-70B-Instruct-Turbo",
name: "Llama 3.3 70B",
@@ -18050,6 +17932,22 @@ export const GENERATED_PROVIDER_MODELS: {
releaseDate: "2024-12-06",
family: "llama",
},
"Qwen/Qwen2.5-7B-Instruct-Turbo": {
id: "Qwen/Qwen2.5-7B-Instruct-Turbo",
name: "Qwen 2.5 7B Instruct Turbo",
contextWindow: 32768,
maxInputTokens: 32768,
maxTokens: 32768,
capabilities: ["tools", "structured_output", "temperature"],
pricing: {
input: 0.3,
output: 0.3,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2024-09-19",
family: "qwen",
},
},
v0: {
"v0-1.5-lg": {
@@ -20009,6 +19907,22 @@ export const GENERATED_PROVIDER_MODELS: {
releaseDate: "2025-09-12",
family: "qwen",
},
"moonshotai/kimi-k2": {
id: "moonshotai/kimi-k2",
name: "Kimi K2 Instruct",
contextWindow: 131072,
maxInputTokens: 131072,
maxTokens: 131072,
capabilities: ["tools", "temperature"],
pricing: {
input: 0.57,
output: 2.3,
cacheRead: 0,
cacheWrite: 0,
},
releaseDate: "2025-09-05",
family: "kimi",
},
"moonshotai/kimi-k2-turbo": {
id: "moonshotai/kimi-k2-turbo",
name: "Kimi K2 Turbo",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@cline/sdk",
"description": "Cline SDK - user-facing alias for @cline/core",
"version": "0.0.46",
"version": "0.0.47",
"repository": {
"type": "git",
"url": "https://github.com/cline/cline",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@cline/shared",
"version": "0.0.46",
"version": "0.0.47",
"description": "Shared utilities, types, and schemas for Cline packages",
"repository": {
"type": "git",
@@ -8,9 +8,18 @@ import type { ClientContext, UserContext } from "./context";
export interface AgentExtensionCommand {
name: string;
description?: string;
handler?: (input: string) => Promise<string> | string;
handler?: (
input: string,
) => Promise<AgentExtensionCommandResult> | AgentExtensionCommandResult;
}
export type AgentExtensionCommandResult =
| string
| {
reply?: string;
submitPrompt?: string;
};
export interface AgentExtensionRule {
id: string;
content: string | (() => string | Promise<string>);
+61
View File
@@ -0,0 +1,61 @@
export type FeatureFlag = string;
export type FeatureFlagJsonValue =
| string
| number
| boolean
| null
| { [key: string]: FeatureFlagJsonValue }
| FeatureFlagJsonValue[];
export type FeatureFlagPayload = FeatureFlagJsonValue;
export type FeatureFlagsAndPayloads = {
featureFlags?: Record<string, FeatureFlagPayload>;
featureFlagPayloads?: Record<string, FeatureFlagPayload>;
};
export interface FeatureFlagsContext {
/** Stable SDK/client/user identifier used by providers that evaluate per identity. */
distinctId?: string;
/** Authenticated Cline account/user ID, when available. */
userId?: string | null;
/** Optional SDK consumer name, e.g. `my-production-app`. */
clientName?: string;
}
type AssertTrue<T extends true> = T;
type Primitive = string | number | boolean | bigint | symbol | null | undefined;
type HasNonPrimitiveFieldNames<T> = {
[K in keyof T]-?: Exclude<T[K], Primitive> extends never ? never : K;
}[keyof T];
type HasOnlyPrimitiveFields<T> =
HasNonPrimitiveFieldNames<T> extends never ? true : false;
export type FeatureFlagsContextPrimitiveValued = AssertTrue<
HasOnlyPrimitiveFields<FeatureFlagsContext>
>;
export interface FeatureFlagsSettings {
/** Whether the provider is enabled. */
enabled: boolean;
/** Optional timeout in ms for feature flag requests. */
timeoutMs?: number;
}
export interface IFeatureFlagsProvider {
getAllFlagsAndPayloads(options: {
flagKeys?: readonly string[];
context?: FeatureFlagsContext;
}): Promise<FeatureFlagsAndPayloads | undefined>;
readonly enabled: boolean;
getSettings(): FeatureFlagsSettings;
dispose(): Promise<void>;
}
export const FeatureFlagDefaultValue: Partial<
Record<FeatureFlag, FeatureFlagPayload | undefined>
> = {};
export const FEATURE_FLAGS: readonly FeatureFlag[] = Object.keys(
FeatureFlagDefaultValue,
);
+11
View File
@@ -31,6 +31,7 @@ export type {
AgentExtensionAutomationEventType,
AgentExtensionCapability,
AgentExtensionCommand,
AgentExtensionCommandResult,
AgentExtensionHooks,
AgentExtensionMessageBuilder,
AgentExtensionProvider,
@@ -48,6 +49,16 @@ export {
normalizePluginManifest,
} from "./extensions/contribution-registry";
export { PLUGIN_FILE_EXTENSIONS } from "./extensions/plugin";
export {
FEATURE_FLAGS,
type FeatureFlag,
FeatureFlagDefaultValue,
type FeatureFlagPayload,
type FeatureFlagsAndPayloads,
type FeatureFlagsContext,
type FeatureFlagsSettings,
type IFeatureFlagsProvider,
} from "./feature-flags";
export type { HookControl } from "./hooks/contracts";
export type {
AgentAbortHookPayload,
+11
View File
@@ -45,6 +45,7 @@ export type {
AgentExtensionAutomationEventType,
AgentExtensionCapability,
AgentExtensionCommand,
AgentExtensionCommandResult,
AgentExtensionHooks,
AgentExtensionMessageBuilder,
AgentExtensionProvider,
@@ -62,6 +63,16 @@ export {
normalizePluginManifest,
} from "./extensions/contribution-registry";
export { PLUGIN_FILE_EXTENSIONS } from "./extensions/plugin";
export {
FEATURE_FLAGS,
type FeatureFlag,
FeatureFlagDefaultValue,
type FeatureFlagPayload,
type FeatureFlagsAndPayloads,
type FeatureFlagsContext,
type FeatureFlagsSettings,
type IFeatureFlagsProvider,
} from "./feature-flags";
export type { HookControl } from "./hooks/contracts";
export type {
AgentAbortHookPayload,
@@ -1,109 +0,0 @@
import { describe, expect, it } from "vitest";
import { buildClineSystemPrompt } from "./cline";
import { DEFAULT_CLINE_PERSONA } from "./system";
const PERSONA = "You are Reviewer, a meticulous code review agent.";
describe("buildClineSystemPrompt", () => {
it("uses the default persona when no personaPrompt is provided", () => {
const prompt = buildClineSystemPrompt({
workspaceRoot: "/repo",
platform: "linux",
});
expect(prompt).toContain("You are Cline, an AI coding agent.");
expect(prompt).toContain("4. Working Directory: /repo");
});
it("applies personaPrompt while keeping the harness", () => {
const prompt = buildClineSystemPrompt({
workspaceRoot: "/repo",
platform: "linux",
ide: "Terminal",
personaPrompt: PERSONA,
});
expect(prompt.startsWith(PERSONA)).toBe(true);
expect(prompt).toContain("1. Platform: linux");
expect(prompt).toContain("4. Working Directory: /repo");
expect(prompt).toContain(
"IMPORTANT: Always includes tool calls in your response until the task is completed.",
);
expect(prompt).not.toContain(DEFAULT_CLINE_PERSONA);
});
it("appends workspace metadata for the cline provider with a persona", () => {
const prompt = buildClineSystemPrompt({
workspaceRoot: "/repo",
providerId: "cline",
personaPrompt: PERSONA,
metadata: '{"workspaces":{}}',
});
expect(prompt.startsWith(PERSONA)).toBe(true);
expect(prompt).toContain("# Workspace Configuration");
});
it("does not duplicate metadata when the persona already embeds it", () => {
const personaWithMetadata = `${PERSONA}\n\n# Workspace Configuration\n{"workspaces":{}}`;
const prompt = buildClineSystemPrompt({
workspaceRoot: "/repo",
providerId: "cline",
personaPrompt: personaWithMetadata,
metadata: '{"workspaces":{}}',
});
const markerCount = prompt.split("# Workspace Configuration").length - 1;
expect(markerCount).toBe(1);
});
it("omits workspace metadata for non-cline providers with a persona", () => {
const prompt = buildClineSystemPrompt({
workspaceRoot: "/repo",
providerId: "openai",
personaPrompt: PERSONA,
metadata: '{"workspaces":{}}',
});
expect(prompt.startsWith(PERSONA)).toBe(true);
expect(prompt).not.toContain("# Workspace Configuration");
});
it("lets overridePrompt win over personaPrompt", () => {
const prompt = buildClineSystemPrompt({
workspaceRoot: "/repo",
overridePrompt: "Full override.",
personaPrompt: PERSONA,
});
expect(prompt).toBe("Full override.");
});
it("ignores personaPrompt in yolo mode", () => {
const prompt = buildClineSystemPrompt({
workspaceRoot: "/repo",
mode: "yolo",
personaPrompt: PERSONA,
});
expect(prompt).toContain(
"You are Cline, a careful and helpful coding agent that works in the background.",
);
expect(prompt).not.toContain(PERSONA);
});
it("inserts rules containing replacement patterns literally", () => {
const prompt = buildClineSystemPrompt({
workspaceRoot: "/repo",
rules: "Use $& and $' carefully.",
});
expect(prompt).toContain("Use $& and $' carefully.");
});
it("keeps template-like tokens inside the persona literal", () => {
const persona =
"You report {{PLATFORM_NAME}} and honor {{CLINE_RULES}} verbatim.";
const prompt = buildClineSystemPrompt({
workspaceRoot: "/repo",
platform: "linux",
personaPrompt: persona,
rules: "Real rules here.",
});
expect(prompt.startsWith(persona)).toBe(true);
expect(prompt).toContain("1. Platform: linux");
expect(prompt).toContain("Real rules here.");
});
});
+13 -34
View File
@@ -1,8 +1,7 @@
import type { WorkspaceContext } from "../extensions/context";
import type { WorkspaceInfo } from "../session/workspace";
import {
AGENT_PERSONA_SLOT,
composeClineSystemPrompt,
DEFAULT_CLINE_SYSTEM_PROMPT,
YOLO_CLINE_SYSTEM_PROMPT,
} from "./system";
@@ -60,20 +59,14 @@ export interface ClineSystemPromptOptions
extends Omit<WorkspaceContext, "rootPath"> {
/**
* Workspace root path. Accepts either `rootPath` (from WorkspaceContext/WorkspaceInfo)
* or `workspaceRoot` (legacy alias) - whichever is provided will be used.
* or `workspaceRoot` (legacy alias) whichever is provided will be used.
*/
rootPath?: string;
/** Alias for rootPath - kept for backwards compatibility with existing call sites */
/** Alias for rootPath kept for backwards compatibility with existing call sites */
workspaceRoot?: string;
/** Per-request system prompt override */
overridePrompt?: string;
/**
* Agent-profile persona: replaces the default Cline persona (the identity
* intro) while keeping the agent harness, including the working guidelines.
* Ignored when `overridePrompt` is set or in yolo mode.
*/
personaPrompt?: string;
/** Provider ID - used to gate Cline-specific metadata injection */
/** Provider ID — used to gate Cline-specific metadata injection */
providerId?: string;
}
@@ -88,7 +81,6 @@ export function buildClineSystemPrompt(
metadata,
rules,
overridePrompt,
personaPrompt,
providerId,
} = options;
const workspaceRoot = options.workspaceRoot ?? options.rootPath ?? "";
@@ -106,33 +98,20 @@ export function buildClineSystemPrompt(
return trimmed;
}
const persona = mode === "yolo" ? undefined : personaPrompt?.trim();
// Keep the persona slot in place and fill it last, so `{{...}}` sequences
// inside a persona body stay literal.
const basePrompt =
mode === "yolo"
? YOLO_CLINE_SYSTEM_PROMPT
: composeClineSystemPrompt(
persona ? { persona: AGENT_PERSONA_SLOT } : {},
);
// Skip metadata injection when the persona already embeds a workspace
// configuration block (e.g. spawn prompts composed by a parent agent).
const includeMetadata =
isCline && !persona?.includes(WORKSPACE_CONFIGURATION_MARKER);
mode === "yolo" ? YOLO_CLINE_SYSTEM_PROMPT : DEFAULT_CLINE_SYSTEM_PROMPT;
// Replacer functions (not replacement strings) so values containing
// `$&`-style patterns are inserted literally.
return basePrompt
.replace("{{PLATFORM_NAME}}", () => platform)
.replace("{{CWD}}", () => workspaceRoot)
.replace("{{CURRENT_DATE}}", () => new Date().toLocaleDateString())
.replace("{{IDE_NAME}}", () => ide)
.replace("{{CLINE_METADATA}}", () =>
includeMetadata
.replace("{{PLATFORM_NAME}}", platform)
.replace("{{CWD}}", workspaceRoot)
.replace("{{CURRENT_DATE}}", new Date().toLocaleDateString())
.replace("{{IDE_NAME}}", ide)
.replace(
"{{CLINE_METADATA}}",
isCline
? buildWorkspaceMetadata(workspaceRoot, workspaceName, metadata)
: "",
)
.replace("{{CLINE_RULES}}", () => rules || "")
.replace(AGENT_PERSONA_SLOT, () => persona ?? "")
.replace("{{CLINE_RULES}}", rules || "")
.trim();
}
@@ -1,99 +0,0 @@
import { describe, expect, it } from "vitest";
import {
composeClineSystemPrompt,
DEFAULT_CLINE_PERSONA,
DEFAULT_CLINE_SYSTEM_PROMPT,
} from "./system";
// The canonical default system prompt. Pinned as a literal (including trailing
// whitespace) so accidental drift is caught; update deliberately when the
// default prompt is intentionally changed.
const EXPECTED_DEFAULT_CLINE_SYSTEM_PROMPT = `You are Cline, an AI coding agent. Your primary goal is to assist users with various coding tasks by leveraging your knowledge and the tools at your disposal. Given the user's prompt, you should use the tools available to you to answer user's question.
Always gather all the necessary context before starting to work on a task. For example, if you are generating a unit test or new code, make sure you understand the requirement, the naming conventions, frameworks and libraries used and aligned in the current codebase, and the environment and commands used to run and test the code etc. Always validate the new unit test at the end including running the code if possible for live feedback.
Review each question carefully and answer it with detailed, accurate information.
Environment you are running in:
<env>
1. Platform: {{PLATFORM_NAME}}
2. Date: {{CURRENT_DATE}}
3. IDE: {{IDE_NAME}}
4. Working Directory: {{CWD}}
</env>
Remember:
- If you need more information, use one of the available tools or ask for clarification instead of making assumptions or lies.
- Always adhere to existing code conventions and patterns.
- Use only libraries and frameworks that are confirmed to be in use in the current codebase.
- Provide complete and functional code without omissions or placeholders.
- Be explicit about any assumptions or limitations in your solution.
- Always show your planning process before executing any task. This will help ensure that you have a clear understanding of the requirements and that your approach aligns with the user's needs.
- Always use absolute paths when referring to files.
- Always verify the files you have edited or created at the end of the task to ensure they are completed and working as expected.
Begin by analyzing the user's input and gathering any necessary additional context. Then, present your plan at the start of your response along with tool calls before proceeding with the task. It's OK for this section to be quite long.
REMEMBER, be helpful and proactive! Don't ask for permission to do something when you can do it! Do not indicates you will be using a tool unless you are actually going to use it.
IMPORTANT: Always includes tool calls in your response until the task is completed. Response without tool calls will considered as completed with final answer.
When you have completed the task, please provide a summary of what you did and any relevant information that the user should know. This will help ensure that the user understands the changes made and can easily follow up if they have any questions or need further assistance. Do not indicate that you will perform an action without actually doing it. Always provide the final result in your response. Always validate your answer with checking the code and running it if possible.${" "}
If user asked a simple question without any coding context, answer it directly without using any tools.
{{CLINE_RULES}}
{{CLINE_METADATA}}`;
// Everything after the persona is the always-on harness (env block, working
// guidelines, tool-call contract, completion rules, rules/metadata). Derived
// from the default so the harness text has a single source of truth.
const HARNESS = EXPECTED_DEFAULT_CLINE_SYSTEM_PROMPT.slice(
DEFAULT_CLINE_PERSONA.length,
);
describe("composeClineSystemPrompt", () => {
it("composes the canonical default prompt", () => {
expect(DEFAULT_CLINE_SYSTEM_PROMPT).toBe(
EXPECTED_DEFAULT_CLINE_SYSTEM_PROMPT,
);
expect(composeClineSystemPrompt()).toBe(
EXPECTED_DEFAULT_CLINE_SYSTEM_PROMPT,
);
expect(composeClineSystemPrompt({})).toBe(
EXPECTED_DEFAULT_CLINE_SYSTEM_PROMPT,
);
// The exported persona is the real prefix; the rest is the harness.
expect(DEFAULT_CLINE_SYSTEM_PROMPT).toBe(DEFAULT_CLINE_PERSONA + HARNESS);
});
it("treats a blank persona as the default", () => {
expect(composeClineSystemPrompt({ persona: " " })).toBe(
EXPECTED_DEFAULT_CLINE_SYSTEM_PROMPT,
);
});
it("swaps the persona but keeps the harness verbatim", () => {
const persona =
"You are Reviewer, a meticulous code review agent. Focus on correctness.";
// Only the persona changes; the entire harness tail is byte-identical.
expect(composeClineSystemPrompt({ persona })).toBe(persona + HARNESS);
});
it("keeps the working guidelines, incl. the no-guessing norm, in the harness", () => {
const norm =
"If you need more information, use one of the available tools or ask for clarification instead of making assumptions or lies.";
// The norm and the rest of the working guidelines are harness, not
// persona, so a profile keeps them while the identity is swapped.
expect(HARNESS).toContain(norm);
expect(DEFAULT_CLINE_PERSONA).not.toContain(norm);
});
it("inserts persona content literally, including replacement patterns", () => {
const persona = "Echo the captured group $& and $' verbatim.";
expect(composeClineSystemPrompt({ persona })).toBe(persona + HARNESS);
});
it("keeps template-like tokens inside the persona literal", () => {
const persona = "Mention {{AGENT_GUIDELINES}} and {{AGENT_PERSONA}} as-is.";
expect(composeClineSystemPrompt({ persona })).toBe(persona + HARNESS);
});
});
+3 -38
View File
@@ -1,15 +1,8 @@
export const DEFAULT_CLINE_PERSONA = `You are Cline, an AI coding agent. Your primary goal is to assist users with various coding tasks by leveraging your knowledge and the tools at your disposal. Given the user's prompt, you should use the tools available to you to answer user's question.
export const DEFAULT_CLINE_SYSTEM_PROMPT = `You are Cline, an AI coding agent. Your primary goal is to assist users with various coding tasks by leveraging your knowledge and the tools at your disposal. Given the user's prompt, you should use the tools available to you to answer user's question.
Always gather all the necessary context before starting to work on a task. For example, if you are generating a unit test or new code, make sure you understand the requirement, the naming conventions, frameworks and libraries used and aligned in the current codebase, and the environment and commands used to run and test the code etc. Always validate the new unit test at the end including running the code if possible for live feedback.
Review each question carefully and answer it with detailed, accurate information.`;
// The agent harness: env block, working guidelines (including the no-guessing
// norm: use tools or ask instead of fabricating), tool-call loop contract,
// completion instructions, and rules/metadata placeholders. Only the
// {{AGENT_PERSONA}} slot holds the coding-agent identity and workflow
// prompting; an agent profile body replaces that slot while the rest of the
// harness is always preserved.
const CLINE_SYSTEM_PROMPT_TEMPLATE = `{{AGENT_PERSONA}}
Review each question carefully and answer it with detailed, accurate information.
If you need more information, use one of the available tools or ask for clarification instead of making assumptions or lies.
Environment you are running in:
<env>
@@ -20,7 +13,6 @@ Environment you are running in:
</env>
Remember:
- If you need more information, use one of the available tools or ask for clarification instead of making assumptions or lies.
- Always adhere to existing code conventions and patterns.
- Use only libraries and frameworks that are confirmed to be in use in the current codebase.
- Provide complete and functional code without omissions or placeholders.
@@ -41,33 +33,6 @@ If user asked a simple question without any coding context, answer it directly w
{{CLINE_RULES}}
{{CLINE_METADATA}}`;
/** The persona placeholder of the harness template. */
export const AGENT_PERSONA_SLOT = "{{AGENT_PERSONA}}";
export interface ComposeClineSystemPromptInput {
/**
* Replaces the default Cline persona (the identity intro) while keeping the
* agent harness. The working guidelines are part of the harness and are
* always retained, even when a custom persona (e.g. an agent profile body)
* is supplied.
*/
persona?: string;
}
export function composeClineSystemPrompt(
input: ComposeClineSystemPromptInput = {},
): string {
const persona = input.persona?.trim();
// The persona is inserted via a replacer function so `{{...}}` and
// `$&`-style sequences inside it stay literal.
return CLINE_SYSTEM_PROMPT_TEMPLATE.replace(
AGENT_PERSONA_SLOT,
() => persona || DEFAULT_CLINE_PERSONA,
);
}
export const DEFAULT_CLINE_SYSTEM_PROMPT = composeClineSystemPrompt();
export const YOLO_CLINE_SYSTEM_PROMPT = `You are Cline, a careful and helpful coding agent that works in the background.
You are tasked to solve an issue reported by the user who you cannot communicate with directly.
Your goal is to utilize the tools at your disposal to investigate and answer the question according to user's instructions with the aim to verify that the issue is resolved.
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
CLINE_ENVIRONMENT_ENV,
CLINE_ENVIRONMENT_OVERRIDE_ENV,
@@ -8,58 +8,75 @@ import {
resolveClineEnvironment,
} from "./cline-environment";
const ENV_KEYS = [
CLINE_ENVIRONMENT_ENV,
CLINE_ENVIRONMENT_OVERRIDE_ENV,
"CLINE_API_BASE_URL",
] as const;
const originalEnvValues = Object.fromEntries(
ENV_KEYS.map((key) => [key, process.env[key]]),
);
beforeEach(() => {
vi.unstubAllGlobals();
for (const key of ENV_KEYS) {
delete process.env[key];
}
});
afterEach(() => {
vi.unstubAllGlobals();
for (const key of ENV_KEYS) {
const value = originalEnvValues[key];
if (typeof value === "string") {
process.env[key] = value;
} else {
delete process.env[key];
}
}
});
describe("resolveClineEnvironment", () => {
it("defaults to production when no env var is set", () => {
expect(resolveClineEnvironment({ env: {} })).toBe("production");
expect(resolveClineEnvironment()).toBe(DEFAULT_CLINE_ENVIRONMENT);
});
it("reads CLINE_ENVIRONMENT", () => {
expect(
resolveClineEnvironment({
env: { [CLINE_ENVIRONMENT_ENV]: "staging" },
}),
).toBe("staging");
expect(
resolveClineEnvironment({
env: { [CLINE_ENVIRONMENT_ENV]: "local" },
}),
).toBe("local");
it("reads CLINE_ENVIRONMENT from process.env", () => {
process.env[CLINE_ENVIRONMENT_ENV] = "staging";
expect(resolveClineEnvironment()).toBe("staging");
process.env[CLINE_ENVIRONMENT_ENV] = "local";
expect(resolveClineEnvironment()).toBe("local");
});
it("prefers CLINE_ENVIRONMENT_OVERRIDE over CLINE_ENVIRONMENT", () => {
expect(
resolveClineEnvironment({
env: {
[CLINE_ENVIRONMENT_OVERRIDE_ENV]: "local",
[CLINE_ENVIRONMENT_ENV]: "staging",
},
}),
).toBe("local");
process.env[CLINE_ENVIRONMENT_OVERRIDE_ENV] = "local";
process.env[CLINE_ENVIRONMENT_ENV] = "staging";
expect(resolveClineEnvironment()).toBe("local");
});
it("normalizes case and surrounding whitespace", () => {
expect(
resolveClineEnvironment({
env: { [CLINE_ENVIRONMENT_ENV]: " STAGING " },
}),
).toBe("staging");
process.env[CLINE_ENVIRONMENT_ENV] = " STAGING ";
expect(resolveClineEnvironment()).toBe("staging");
});
it("ignores unknown values and falls through to the next source", () => {
expect(
resolveClineEnvironment({
env: {
[CLINE_ENVIRONMENT_OVERRIDE_ENV]: "qa",
[CLINE_ENVIRONMENT_ENV]: "staging",
},
}),
).toBe("staging");
process.env[CLINE_ENVIRONMENT_OVERRIDE_ENV] = "qa";
process.env[CLINE_ENVIRONMENT_ENV] = "staging";
expect(resolveClineEnvironment()).toBe("staging");
expect(
resolveClineEnvironment({
env: { [CLINE_ENVIRONMENT_ENV]: "qa" },
}),
).toBe(DEFAULT_CLINE_ENVIRONMENT);
delete process.env[CLINE_ENVIRONMENT_OVERRIDE_ENV];
process.env[CLINE_ENVIRONMENT_ENV] = "qa";
expect(resolveClineEnvironment()).toBe(DEFAULT_CLINE_ENVIRONMENT);
});
it("defaults to production when process is unavailable", () => {
vi.stubGlobal("process", undefined);
expect(resolveClineEnvironment()).toBe(DEFAULT_CLINE_ENVIRONMENT);
});
});
@@ -74,18 +91,31 @@ describe("getClineEnvironmentConfig", () => {
);
});
it("resolves from env when no explicit environment is passed", () => {
expect(
getClineEnvironmentConfig({
env: { [CLINE_ENVIRONMENT_ENV]: "staging" },
}),
).toBe(CLINE_ENVIRONMENTS.staging);
it("falls back to production by default", () => {
expect(getClineEnvironmentConfig()).toBe(CLINE_ENVIRONMENTS.production);
});
it("falls back to production by default", () => {
expect(getClineEnvironmentConfig({ env: {} })).toBe(
CLINE_ENVIRONMENTS.production,
);
it("uses the resolved process.env environment when no explicit environment is provided", () => {
process.env[CLINE_ENVIRONMENT_ENV] = "staging";
expect(getClineEnvironmentConfig()).toBe(CLINE_ENVIRONMENTS.staging);
});
it("applies CLINE_API_BASE_URL without mutating the catalog config", () => {
process.env.CLINE_API_BASE_URL = "http://127.0.0.1:3000";
expect(getClineEnvironmentConfig("local")).toEqual({
...CLINE_ENVIRONMENTS.local,
apiBaseUrl: "http://127.0.0.1:3000",
mcpBaseUrl: "http://127.0.0.1:3000/v1/mcp",
});
expect(CLINE_ENVIRONMENTS.local.apiBaseUrl).toBe("http://localhost:7777");
});
it("defaults to production when process is unavailable", () => {
vi.stubGlobal("process", undefined);
expect(getClineEnvironmentConfig()).toBe(CLINE_ENVIRONMENTS.production);
});
});
@@ -67,10 +67,8 @@ function readProcessEnv(): NodeJS.ProcessEnv {
return process.env;
}
export function resolveClineEnvironment(
options: ResolveClineEnvironmentOptions = {},
): ClineEnvironment {
const env = options.env ?? readProcessEnv();
export function resolveClineEnvironment(): ClineEnvironment {
const env = readProcessEnv();
return (
normalizeClineEnvironment(env[CLINE_ENVIRONMENT_OVERRIDE_ENV]) ??
normalizeClineEnvironment(env[CLINE_ENVIRONMENT_ENV]) ??
@@ -78,11 +76,32 @@ export function resolveClineEnvironment(
);
}
export function getClineEnvironmentConfig(
environmentOrOptions?: ClineEnvironment | ResolveClineEnvironmentOptions,
): ClineEnvironmentConfig {
if (typeof environmentOrOptions === "string") {
return CLINE_ENVIRONMENTS[environmentOrOptions];
function getEnvConfig(env?: ClineEnvironment) {
if (typeof env === "string") {
return CLINE_ENVIRONMENTS[env];
}
return CLINE_ENVIRONMENTS[resolveClineEnvironment(environmentOrOptions)];
return CLINE_ENVIRONMENTS[resolveClineEnvironment()];
}
function applyConfigOverrides(
config: ClineEnvironmentConfig,
env: NodeJS.ProcessEnv,
): ClineEnvironmentConfig {
if (env.CLINE_API_BASE_URL) {
config = {
...config,
apiBaseUrl: env.CLINE_API_BASE_URL,
mcpBaseUrl: `${env.CLINE_API_BASE_URL}/v1/mcp`,
};
}
return config;
}
export function getClineEnvironmentConfig(
env?: ClineEnvironment,
): ClineEnvironmentConfig {
const config = getEnvConfig(env);
return applyConfigOverrides(config, readProcessEnv());
}