* Clarify model-facing message when user rejects a tool call
* Include the rejected tool's name in denial reasons
* Move user-rejected tool reason into @cline/shared
* Route new user-rejection approval paths through shared reason builder
Since the original PR, several new approval surfaces landed on main with
their own terse denial strings (CLI connectors, ACP permissions, Cline Hub
webview, desktop webview, example VS Code extension). Route all of them
through buildUserRejectedToolReason so the model sees a consistent,
non-error rejection message.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add buildUserRejectedToolReason to the @cline/shared integration-test stub
The VS Code integration tests run the tsc-built CJS tree and stub the
ESM-only @cline/shared package in test-setup.js; the stub was missing the
new export, so tool-approval-denial.js threw at module load in CI.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Trim scope back to the minimal rejection-copy fix
Restore the connector deniedReason plumbing, ACP permission strings,
desktop webview reason, example extension reason, and hub server fallback
to their main versions. Those surfaces already attribute the denial to a
user and are outside ENG-2329. Keep the Cline Hub webview change since
that path emits its own rejection string the model sees.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Move rejection guidance suffix into agent runtime per review
* Apply review suggestions: neutral fallback reason and -- separator before rejection suffix
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Flipping the <markdown> streaming prop from true to false when an
assistant text segment settles makes MarkdownRenderable call
updateBlocks(true), which skips every block-reuse path and destroys and
recreates all block renderables. Until tree-sitter re-highlights them
the whole message renders blank/unhighlighted, which users see as the
text flashing at the end of each response. Keep streaming={true} for
the transcript markdown (opencode's TUI does the same); entry.streaming
still drives the spinner glyph.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): stop an empty capability list from stripping image input
`modelHasCapability` documents a missing or empty capability list as
carrying no signal, so each gate declares its own default. Two readers
bypassed it and read `capabilities` directly, where an empty list is not
nullish but `[].includes(x)` is false:
- the session runtime's `modelSupportsImages` metadata used
`capabilities?.includes("images") ?? true`, so the intended fail-open
never fired for an empty list and the file-read tool silently dropped
every image from the request;
- `toProviderModel` projected an empty list onto `false`, telling pickers
a model definitively lacks vision, attachments, and reasoning when
nothing had been declared.
Both now route through the shared helpers, which state their unspecified
default explicitly: `modelSupportsImageInput` fails open for a capability
gate, and `declaredCapability` preserves `undefined` for `ProviderModel`'s
tri-state booleans. A populated list stays authoritative in both.
A thinking config now short-circuits `supportsReasoning` instead of being
OR-ed with the capability read, so its absence no longer collapses the
tri-state to `false`.
* fix(llms): translate gateway capabilities in one place
Three producers built gateway model definitions from catalog `ModelInfo`,
and each carried its own hand-written `switch` over the capability list.
Nothing tied them together, so they drifted:
- builtin providers always emitted a capability list, so a model whose
catalog entry declares no capabilities became `["text"]` where the other
producers emitted `undefined`. `modelSupportsToolCalling` fails open only
for an absent or empty list, so that list read as an authoritative denial
and stripped every tool definition from requests to the affected language
models (dify, sapaicore, opencode, and the Codex CLI);
- the OpenAI-compatible path mapped an `audio` capability that
`ModelCapabilitySchema` does not define, while the other two dropped it;
- the pass-through capabilities (`streaming`, `files`, `temperature`, ...)
were enumerated explicitly in one, folded into `default:` in another,
and ignored in the third.
One exported `toGatewayModelCapabilities` now serves every producer. It is
built on a `Record<ModelCapability, GatewayModelCapability | null>` rather
than a `switch`, so extending `ModelCapabilitySchema` without deciding the
new capability's mapping fails to compile instead of silently falling
through to a default.
The conformance tests walk the capability state space taken from
`ModelCapabilitySchema` itself and assert the real producers agree with the
translator, so a future producer that maps capabilities on its own fails
even when the translator's own unit tests still pass.
---------
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
Co-authored-by: Ara <arafat.da.khan@gmail.com>
`modelHasCapability` documents a missing or empty capability list as
carrying no signal, so each gate declares its own default. Two readers
bypassed it and read `capabilities` directly, where an empty list is not
nullish but `[].includes(x)` is false:
- the session runtime's `modelSupportsImages` metadata used
`capabilities?.includes("images") ?? true`, so the intended fail-open
never fired for an empty list and the file-read tool silently dropped
every image from the request;
- `toProviderModel` projected an empty list onto `false`, telling pickers
a model definitively lacks vision, attachments, and reasoning when
nothing had been declared.
Both now route through the shared helpers, which state their unspecified
default explicitly: `modelSupportsImageInput` fails open for a capability
gate, and `declaredCapability` preserves `undefined` for `ProviderModel`'s
tri-state booleans. A populated list stays authoritative in both.
A thinking config now short-circuits `supportsReasoning` instead of being
OR-ed with the capability read, so its absence no longer collapses the
tri-state to `false`.
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
* fix(vscode): sanitize pasted provider API keys at the settings write boundary
Clipboards smuggle control and invisible formatting characters (newlines,
zero-width spaces, BOM) into pasted API keys. The masked key field hides
the corruption and providers reject the key with a 401 indistinguishable
from a genuinely wrong key. Strip those characters and surrounding
whitespace once in the provider config store write path, so both backing
stores (legacy state secrets and providers.json) receive the clean value.
A whitespace-only value now clears the key.
* feat(llms,vscode): classify provider 401/403 as auth errors and surface actionable guidance
Add an "auth" ProviderErrorClass, assigned when the HTTP layer reports
401/403 — status-only on purpose, since provider bodies can quote words
like "unauthorized" without the request being an auth failure. The class
rides the existing errorClass plumbing (finish -> run-failed ->
AgentErrorEvent), so every host receives it with no new wiring.
In the VS Code chat surface, rewrite classified credential rejections
from BYOK providers into actionable text pointing at the API key
configuration, keeping the provider's raw body as a diagnostic tail.
Raw bodies alone are dead ends: Mistral, for example, answers an
identical {"detail":"Invalid API Key"} for a wrong, empty, or
wrong-scope key. Cline-account providers keep the JSON path so the
webview still renders their auth failures as a sign-in card.
The SDK hooks adapter created every hook runner without a task id, and
StdioHookRunner gates all captureHookExecution calls on one being set —
so the next variant emitted zero hooks.execution events while discovery
telemetry fired normally. Pass the task id (and tool name for the tool
hooks) at all five factory.create call sites, and pin the threading
with a regression test.
* fix(vscode): prevent hook spawn failures from crashing the core process
A hook child-process spawn failure emitted "error" on HookProcess with no
listener registered, which Node's EventEmitter turns into an uncaught
exception - killing the entire cline-core process instead of failing the
one hook open. Guard the emit behind listenerCount so the rejection (which
StdioHookRunner handles) is the only propagation path.
The trigger was a workspace root that no longer exists on disk passed as
the spawn cwd: Node reports a nonexistent cwd as a misleading ENOENT on
the launcher binary ("spawn /bin/sh ENOENT"). Validate cwd existence in
HookProcess right before spawning - falling back to no explicit cwd with
a warning that names the missing directory - and when a spawn still fails
ENOENT because the directory vanished in between, name it in the error
message instead of blaming the shell.
* fix(vscode): fail hooks with a missing working directory instead of relocating them
Running a hook whose assigned cwd no longer exists from the host
process's own working directory would let its relative paths read and
write an unrelated location (e.g. the IDE install directory). Reject
before spawning, with an error naming the missing directory; the runner
reports the hook as failed and the task continues. Also carry pre-spawn
failure messages into HookExecutionError details so the cause is not
reduced to a bare "exited with code 1".
* fix(llms): recognize direct tracer providers
* fix(llms): make Langfuse tracer detection survive minified release builds
Release binaries are compiled with minify enabled, which renames classes,
so initializeLangfuseTelemetry's constructor-name guard never matched
"ProxyTracerProvider" and silently returned readiness=false in every
production build (hub log: "creating span processor" followed by
"initialized readiness=false" with no branch message in between). Dev runs
execute unminified source, which is why the same env vars worked there.
Replace every constructor-name comparison with checks that survive
minification: detect the proxy structurally via getDelegate, distinguish a
recording provider from the no-op fallback by its lifecycle methods, and
confirm our NodeTracerProvider registration by object identity. When a
foreign provider already owns the global slot, attach the Langfuse span
processor to it when it accepts processors, and otherwise shut down the
orphaned provider and report the rejection instead of bailing silently.
Verified by bundling the module with Bun minify:true against the real
OpenTelemetry packages: the previous code reproduces readiness=false
(provider class name mangles to "H2"), the new code initializes with
readiness=true.
* feat: add searchable session history
Rebased onto main and updated to supersede the sidebar search dialog
from #13533: the sidebar search icon now opens the indexed command bar
(Cmd/Ctrl+P) instead of a sidebar-local cmdk dialog that eagerly loaded
the entire session history via loadAllSessions(). CommandDialog gains a
shouldFilter passthrough so server-ranked FTS hits are displayed as-is.
* fix: harden session history search
* fix: evict failed restoration sessions from search
* fix: preserve deletion when search eviction fails
* fix: address session search review feedback
* fix: preserve search suppression during reconciliation
* test: cover sidecar search fallback on hub timeout and rejection
The existing search_sessions tests only exercised the index-hit and
empty-index-fallback paths with an immediately-resolved hub reply.
Add coverage for the two other realistic Hub-connection failure
modes the fallback is meant to tolerate: the hub call rejecting, and
the hub call hanging past the 750ms withSearchDeadline race.
---------
Co-authored-by: abeatrix <beatrix@cline.bot>
Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
* chore(llms): built-in model list update 1787907289186
Result of `bun run build:models`.
Includes updated model list and fixed formatting issues across codebase.
* test(llms): update GLM reasoning toggle expectation
* fix(desktop): render tool output images as attachments
Add support for displaying media returned by tool calls (e.g. screenshots)
as rendered images with expand-to-fullscreen capability instead of raw
base64 text. Introduces an `ImageCarousel` component for navigating
multiple images, propagates the expand handler to tool message blocks,
and extracts/validates output media in tool summaries.
* test: cover multi-image and canonical media extraction in tool output (#13645)
extractOutputMedia and the desktop tool-message rendering path were only
ever exercised with exactly one distinct valid image, and
canonicalInlineMedia (MCP-style type: "media" blocks for audio/video/file)
had zero coverage. Add tests for: multiple distinct images in one tool
result (parser + desktop carousel navigation), inline audio via the
mime_type key spelling, canonical video/file media blocks, and rejection
of an invalid canonical image block.
---------
Co-authored-by: Harrison <harrison@cline.bot>
* fix(e2e): stop VS Code e2e worker teardown from timing out
The ext-vscode-test-e2e job has been failing on main with 'Worker teardown
timeout of 60000ms exceeded' even though every test passes. Playwright only
reports an Electron app as closed once the process exits AND every holder of
its stdio pipes is gone (ChildProcess 'close' waits on the extra fd3/fd4
pipes Playwright creates for Electron). Any VS Code descendant that outlives
the main process (chrome_crashpad_handler, GLib's 'dconf watch' helper,
xdg-open browser handlers, VS Code 1.135's agent host CLI subprocess that
logs 'unable to kill the process') keeps those pipes open, so app.close()
never resolves and the worker teardown hangs on it until its 60s timeout
fails the job.
Harness fixes, each removing one source of that wedge:
- closeAppForTeardown now SIGKILLs the whole process group (taskkill /T on
Windows) when app.close() times out, instead of only the main pid — and
does so even when the main process already exited, which is exactly the
wedged state. Playwright launches Electron detached, so pid == pgid.
- Launch VS Code with --disable-crash-reporter so no crashpad handler
outlives the app holding the harness pipes.
- Seed the fresh user-data-dir with chat.disableAIFeatures: true so VS
Code's own AI features (rolled out via server-side experiments, so CI
breaks without any repo change) never start their agent host process.
- Drop the page.close() teardown: closing VS Code's last window quits the
whole app, and ElectronApplication.close() on an already-exited app
deadlocks; the app fixture's app.close() closes windows itself while the
app is alive.
- Codex sign-in no longer opens a real external browser under E2E_TEST; the
codex-oauth test drives the OAuth callback itself, and the browser was an
orphaned process holding the harness pipes on the runner.
* fix(core): deflake hub daemon e2e tests on Windows runners
sdk-test on windows-latest fails intermittently in the hub daemon e2e
files:
- shutdown.e2e.test.ts dies with a bare 'Error: socket hang up'. That
message is the ws handshake (http.ClientRequest) failing, not the
/shutdown fetch (an undici failure prints 'TypeError: fetch failed'):
a freshly spawned bun daemon on a loaded 2-core Windows runner
occasionally drops its first accepted connection before writing the
upgrade response. Real hub clients reconnect with backoff, and the test
asserts shutdown behavior rather than first-connection reliability, so
openAuthenticatedSocket now retries transient handshake failures within
a 15s budget.
- singleton.e2e.test.ts times out waiting for daemon discovery: it still
used the 10s hang guard that 0cfc90158 already raised to 30s in
shutdown.e2e.test.ts for the same reason. Use the same 30s guard.
- Raise the e2e testTimeout to 60s so a test that legitimately spawns two
daemons back to back can survive slow-runner startups instead of the
discovery hang guard being cut off by the test timeout.
* Add feature flags to the app
* React to account updates
* Address comments
* Add a GitHub integration step to the onboarding
* validate domain and fix errors on auth
* Hide the step behind a feature flag
* update version
---------
Co-authored-by: John Choi <john.choi@cline.bot>
Remote (SSE/streamable HTTP) MCP connects run on the session.create
critical path, which the hub caps at 30s. Without a connect budget an
unreachable server spent the full 60s default request timeout (with the
SSE transport stuck in a reconnect loop), stalling session.create past
the hub deadline and tearing the whole session down - the interactive
TUI exited and one-shot runs failed. Stdio servers already have a
bounded initialize budget for exactly this reason; give URL clients the
same treatment with a 10s default connect budget that an explicit
timeout overrides in either direction.
Fixes#13597
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat: add searchable session history
Rebased onto main and updated to supersede the sidebar search dialog
from #13533: the sidebar search icon now opens the indexed command bar
(Cmd/Ctrl+P) instead of a sidebar-local cmdk dialog that eagerly loaded
the entire session history via loadAllSessions(). CommandDialog gains a
shouldFilter passthrough so server-ranked FTS hits are displayed as-is.
* fix: harden session history search
* fix: evict failed restoration sessions from search
* fix: preserve deletion when search eviction fails
* fix: address session search review feedback
* fix: preserve search suppression during reconciliation
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
The VS Code Rules tab resolves the Documents folder via
'xdg-user-dir DOCUMENTS', which prints bare $HOME when no user-dirs
config exists (WSL/headless installs), so it reads and writes global
rules at ~/Cline/Rules. The SDK's rule search paths only covered
~/Documents/Cline/Rules, so those rules never reached the system prompt.
Add the missing path to the search list.
Fixes#13542
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): hide history cost estimates for subscription-billed tasks
The task-header fix for subscription providers cannot reach history:
history rows render the stored totalCost (an API-rate estimate) and do
not know which provider ran the task, so the history page printed
$X.XXXX on every row and the recent-task chips in an empty chat view
rendered a $ chip even for subscription-billed tasks.
The SDK session records already persist the provider — the CLI's
history view uses it for exactly this — but the VS Code mappers dropped
it. Map it through both transports (HistoryItem.apiProvider for the
state-pushed taskHistory, TaskItem.api_provider for getTaskHistory) and
suppress the dollar figure per row when that provider's
usageCostDisplay is not "show", via a new useUsageCostVisibility
predicate shared by both surfaces.
Rows without a recorded provider (tasks predating the field, legacy
imports) keep showing the stored value — there is nothing to key
suppression on.
* test(vscode): e2e-verify history cost suppression in real VS Code
Seeds SDK session records (one openai-codex subscription task, one
anthropic usage-billed task) into the isolated CLINE_DIR before the
webview loads, then asserts in a real VS Code instance that both the
recent-task chips and the full history page render the dollar figure
only for the usage-billed task. Covers the two boundaries the unit
tests stub: on-disk records reaching getTaskHistory with provider
populated, and the provider listings delivering the subscription mark
to the webview.
* fix(core): refuse checkpoint workspace restore when HEAD moved past the checkpoint
Restoring a checkpoint runs git reset --hard, which moves the current
branch pointer. If commits were made after the checkpoint (by the user
or by the agent), the reset silently knocked them off the branch,
leaving them reachable only through the reflog.
Guard the reset: if HEAD no longer matches the commit the checkpoint
was created on, throw a descriptive error (including how many commits
would be dropped) instead of destroying history. Chat-only restore is
unaffected, and users who really want to discard the commits can reset
the branch manually first.
Fixes#13550
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): close the guard-to-reset race with an atomic ref update
The moved-HEAD guard read HEAD, ran further git commands, then reset
unconditionally, so a commit landing in that window could still be
knocked off the branch. Replace the reset's branch move with git's
native compare-and-swap (git update-ref HEAD <new> <old>), which fails
if HEAD no longer points at the verified commit, and follow with a bare
reset --hard to sync the index and worktree to the already-moved HEAD.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't show providers as configured without real credentials
The desktop settings marked any provider with a persisted settings entry
as Configured, but legacy VS Code migration and empty saves can seed
entries (e.g. qwen-code, sapaicore) holding only a default model and no
credentials. Move the CLI's isProviderSettingsUsable readiness check into
@cline/core, expose it as a computed 'configured' flag on the provider
catalog, and use it in the desktop's isProviderConnected.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): resync catalog after saves so Configured badge updates live
Optimistic provider mutations can't know the sidecar-computed 'configured'
flag, so after connecting a keyless provider or saving cloud credentials
(e.g. a Vertex project id) the row stayed 'Not configured' until remount.
Silently refetch the catalog after each successful save, guarded by the
existing generation counter so newer edits discard stale responses.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): claim a generation in post-save resync so overlapping refreshes can't apply stale snapshots
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): bump catalog generation on OAuth login success
Every other optimistic provider mutation claims a new generation; the
OAuth success path didn't, so a catalog load or resync still in flight
could arrive late and overwrite the just-connected state.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): resync catalog after OAuth login instead of bare generation bump
The resync claims a new generation (discarding any stale in-flight
response) and its own fetch covers both the new OAuth connection and any
provider saved moments earlier, matching the post-save path.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): anchor agent-created schedules in the user's .cline schedules home
Agent-created schedules inherited whichever workspace folder the chat
session happened to run in, scattering user-level routines across chat
and project folders. They were invisible to workspace-scoped listings
elsewhere, tied to folders that may be cleaned up, and each chat's
tasks tool saw a different set when checking for duplicates.
Anchor them in ~/.cline/schedules instead: the hub's scheduled-task
session defaults now resolve to that home (created on demand), so
agent-created schedules live and run in one stable user-level scope.
The tasks tool guidance now tells agents that scheduled sessions run in
the schedules home, so prompts must carry absolute paths to any project
they operate on.
Schedules created explicitly with a workspace (CLI --workspace, desktop
routine wizard) are unchanged, and existing rows keep their current
workspaceRoot - they stay visible through the all-workspaces listing
paths (#13613, #13633).
* test(core): restore any pre-existing CLINE_DIR after the agenda hub test
The test's cleanup deleted CLINE_DIR outright, so an environment that
had it configured would leave later tests in the same worker on the
default storage directory. Save the previous value and restore it.
* test(core): restore CLINE_DIR even when hub test setup throws early
Restoring the override in the try/finally missed failures thrown during
transport construction or start(), before the try was entered. Register
the restore with onTestFinished instead, which runs regardless of where
the test fails.
* fix: make OpenAI Codex sign-in fail loudly instead of silently dead-ending
When callback port 1455 is already in use (e.g. by the Codex CLI or a
previous pending sign-in), startLocalOAuthServer returns a no-op server
and loginOpenAICodex would open the browser anyway, then dead-end:
the callback could never be received, and in the VS Code extension the
user just saw nothing happen after clicking 'Sign in to OpenAI Codex'.
- loginOpenAICodex now fails fast with an actionable 'port in use'
error before opening the browser, unless the host provides manual
code entry (the CLI's paste fallback keeps working)
- surface OAuth redirect errors (e.g. access_denied) instead of
collapsing them into 'Missing authorization code'
- the extension dedupes concurrent sign-in clicks: a re-click re-opens
the auth page of the pending flow instead of spawning a second flow
that would collide with our own callback server
- browser-open failures now show an error message with the URL to
open manually instead of only logging
- abandoned-flow timeouts no longer surface a confusing 'Missing
authorization code' toast
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop host-side codex login dedupe, keep flow identical to CLI
The SDK owns the failure handling now (fail-fast on an unbindable
callback port), so the extension keeps the exact same simple
loginOpenAICodex call the CLI uses. A second click while a flow is
pending gets the SDK's clear port-in-use error, same as running
'cline auth openai-codex' twice would. Keep only the CLI-parallel
onOpenUrlError surfacing (the CLI prints 'open the URL above
manually'; the extension's equivalent is an error toast with the
URL).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(e2e): cover Codex sign-in callback-port failure and redirect errors
Two driven-VS Code tests for the OpenAI Codex (ChatGPT subscription)
sign-in flow:
- with port 1455 occupied on both loopback families, clicking the
sign-in button surfaces the fail-fast port-in-use toast
- with the port free, the callback server binds and an OAuth redirect
error (access_denied) propagates to a visible error toast
The second test opens a real browser tab to the OpenAI auth page as a
side effect of the genuine sign-in click.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
* fix(sdk): don't log out Codex/OCA users when token refresh fails transiently
Port the cline-provider refresh semantics to openai-codex and oca:
a transient refresh failure (network error, timeout, server 5xx) with an
already-expired access token now rethrows instead of returning null.
A null return means the refresh token was REJECTED and re-auth is
required; treating an outage blip as a rejection is what turned it into
a forced 'openai-codex requires re-authentication.' task stop while the
settings UI still showed the user as signed in.
Both providers also emit user.auth_refresh_soft_failure telemetry on
transient failures (the 'prevented logout' counter the cline provider
already has) and attach status/errorCode details to the genuine
invalid_grant logout event.
* refactor: collapse duplicate soft-failure telemetry branches and test
Review feedback: compute tokenExpired once and emit the soft-failure
event once in both providers, then return current credentials or
rethrow. Fold the codex soft-failure telemetry assertions into the
existing still-usable-token test instead of a near-duplicate case.
* fix(core): stop watching agenda spec dirs while the todo tool is disabled
Since #13530 disabled the agent todo tool, the Agenda UI, and the
automation pump, the hub still created fs.watch watchers on the global
agenda specs dir and on every workspace root recorded in the task store
(at startup and on scope access). Nothing consumes the watcher-driven
task events while the feature is off, and the task.* hub commands
already reconcile spec files on demand, so the watchers are pure
overhead - one OS watch handle per known workspace.
Wire watchFiles to AGENDA_TODO_TOOL_ENABLED the same way
automationEnabled is, preserving a host's explicit watchFiles opt-out
for when the flag is turned back on. Schedules are unaffected: the
schedule list has no file watcher and updates through hub commands and
published schedule events.
* fix(core): reconcile external spec edits inside updateTask
With the spec watchers off there is no background reconciliation, so a
task spec edited directly on disk made every same-store task.update fail
the signature check with "task spec changed outside the manager" until
an unrelated task.get or task.list happened to reconcile the scope.
Reconcile the task's scope at the start of updateTask (mirroring what
refreshAndVerifyTaskIntent already does for approve/run), skipping it
when the file reconciler itself is the caller to avoid recursing from
reconcileFileStore. An external edit now surfaces as the store's normal
stale-revision conflict, and a re-read-and-retry succeeds. This also
closes the pre-existing watcher debounce race for updates.
- Defer updater installation to the user-initiated restart on Windows:
install() launches the NSIS installer and exits the process immediately,
so the background cycle now downloads only and stages the bytes, and
restart_to_apply_update installs them after stopping the sidecar.
- Spawn child processes (sidecar, git, cmd /C start) with CREATE_NO_WINDOW
so the GUI-subsystem app doesn't pop visible console windows.
- Fall back to USERPROFILE when HOME is unset resolving the MCP settings
path, matching the sidecar's homedir().
- Reap the sidecar after the Windows hard-kill so its exe file lock is
released before the NSIS installer replaces it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): show agent-created schedules on the Schedules page
Schedule hub commands are scoped to the workspace registered by the
connection, but the desktop app's hub client registers the app launch
directory while agent-created schedules live under each chat's own
workspace folder - so they never appeared on the Schedules page.
Grant token-authenticated hub connections (which can already bind any
workspace at registration) explicit cross-workspace schedule access via
an allWorkspaces payload flag, and have the desktop sidecar request it
for routine schedule commands. Workspace-bound clients (local browser
origins) and default CLI behavior stay scoped.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(core): strip allWorkspaces flag from schedule inputs and pin it in the sidecar payload
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): tunnel ProtoBus over Host Bridge
* fix(core): harden Host Bridge stream lifecycle
* fix(core): serialize concurrent chunked responses per request
Streaming handlers deliver updates fire-and-forget, so two logical
responses for one request_id can be in flight at once. Chunked payloads
made forwarding non-atomic: each chunk write is an await, so concurrent
forwards could interleave their chunk sequences and the receiver --
which reassembles purely by arrival order -- would splice two payloads
into one. Route all forwards for a request through one promise chain; a
failed write rejects every later forward so a torn payload is never
followed by more chunks.
Rename the lock manager's instanceAddress to instanceOwner: it holds an
opaque per-spawn instance ID on the token path and a listener address
only on the CLI-harness path. Delete the caller-less getInstanceByPort
query that interpreted the owner as an address.
Also: document message_json as a legal wire encoding for small
payloads, close the gRPC client when startup fails, note the
intentional discard of the cancellation confirmation, and add the
proto's trailing newline.
---------
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
* fix(hub): stop shipping full transcripts inside broadcast hub events
Every session.updated (and session.created/detached/run.started) event
embedded the session's ENTIRE message transcript via readCoreSessionSnapshot,
even though no consumer reads snapshot.messages off an event — clients fetch
messages with the session.messages command. For a multi-megabyte transcript
this turns every status flip into megabytes per subscriber, floods the
durable event log, and (until the send-queue backpressure fix lands) lets a
slow subscriber balloon the hub process by one full transcript copy per
event — reported as a 25GB cline process on a 16GB Mac.
Strip snapshot.messages centrally in HubServerTransport.publish() so every
current and future event publisher is covered, the event log stores slim
envelopes, and cursor replay stays byte-identical with live fan-out. All
other snapshot fields (status, usage, model, workspace, checkpoint) are kept,
and command replies are untouched.
* fix(hub): never capture the transcript into event/reply snapshots
Replaces the publish-boundary strip with the real fix: don't build
message-bearing snapshots in the first place. emitSessionSnapshot no longer
re-reads the entire transcript from disk on every status flip, and
readCoreSessionSnapshot no longer reads it for any event or reply — a
snapshot is a state notification (status, usage, model, workspace,
checkpoint); the transcript is fetched via the session.messages command.
Checkpoint-restore snapshots (session-versioning-service) are untouched:
restore replies carry messages in their own dedicated field.
* fix: install marketplace plugins and MCP servers in-process instead of spawning a cline binary
The desktop app sidecar and cline-hub shelled out to 'cline plugin install'
and 'cline mcp install' for marketplace installs. Packaged GUI apps inherit
launchd's minimal PATH on macOS and most desktop users have no cline CLI
installed at all, so installs failed with a red
'Executable not found in $PATH: "cline"' error.
Install via @cline/core's installPlugin/installMcpServer in-process instead,
matching what the VS Code extension already does. Also fix
parseMcpInstallArgs in @cline/core to treat the marketplace catalog's '--'
separator as end-of-options; previously the separator itself became the
stdio command.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop test-injection plumbing from marketplace installers
Call @cline/core's installPlugin directly instead of threading an
installer option through the marketplace entry points; tests stub the
core module instead.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert: keep cline-hub marketplace installs CLI-backed
The hub dashboard is launched via 'cline dashboard', so a CLI is always
present and CLINE_WRAPPER_PATH resolves it; the PATH bug only affects
the desktop app, which does not ship a CLI.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Each session row shows a trash icon on the right while hovered (or
when the button itself is focused), opening the same delete
confirmation dialog the row's context menu uses. The row is a button
and buttons cannot nest, so the trash is an absolutely positioned
sibling inside a group/row wrapper, overlaid where the timestamp sits:
row hover hides the timestamp, shows the trash, and moves the row's
hover background to the wrapper group so it holds while the pointer is
on the trash itself.
Quitting the mac app beach-balled for ~5-7s. The shutdown POST was
built from the ws transport URL (appending /shutdown lands inside the
query string), so the sidecar was never told to exit, and stop() then
polled the child for up to 7s on the main thread - on macOS inside
applicationWillTerminate - before SIGKILLing it.
stop() now sends SIGTERM and returns immediately. The sidecar handles
SIGTERM with the same bounded (5s) graceful shutdown as the /shutdown
endpoint and exits itself, finishing session persistence as an orphan.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): merge schedule details into one view and open run-now sessions
The schedule details dialog drops its Overview/Runs tabs: one scrollable
column with the meta grid, the configuration JSON (capped at max-h-64
with internal scroll so it cannot crowd out what follows), and a Runs
section beneath it showing the three most recent runs with a ghost
"Show all N runs" expander (collapsed again whenever a different
schedule's details open). The "Full configuration for this schedule"
subtext is gone; the dialog passes aria-describedby={undefined} so
Radix does not warn about the missing description.
Run now hands you into the session it starts. The trigger command
queues the run and returns before the runner attaches a session id, so
after the toast the handler polls the schedule overview once a second
for up to 15 seconds — which doubles as keeping the page's run status
fresh (refreshSchedules now returns the fetched overview to make that
single-stream) — until the triggered execution reports its session id,
then calls onOpenSession. Guarded so it never auto-navigates after the
user left the page.
* feat(desktop): hide runtime steering messages from transcripts
Scheduled/automation runs inject user-role steering messages each
iteration ("[SYSTEM] This run is not complete until you call
submit_and_exit...", plus a team-obligations variant). The chat view
rendered them as user bubbles, as if the person had typed them — in a
scheduled session the transcript was mostly [SYSTEM] noise.
They are machinery talking to the model, not something the person said
or needs to read, so the transcript now hides them entirely:
MessageBubble renders null for any [SYSTEM]-prefixed user message.
Grouping still treats them as working-row machinery via a single
isSystemSteeringMessage predicate — they collapse into the run's work
span, are never a turn boundary, can never be mistaken for a run's
answer, and never advance the run count even when metadata is missing —
so work-block folding and checkpoint/edit run numbering stay correct.
A finished scheduled session now reads as prompt, work summary, answer.
* fix(desktop): poll history while an attached session's event stream is dead
Opening a scheduled session while (or right after) it runs left the
view stuck on the thinking shimmer until the user switched away and
back. Root cause is in core: the hub daemon executes scheduled runs on
a private LocalRuntimeHost inside createLocalHubScheduleRuntimeHandlers,
while the hub server only projects live events from its own session
host — so session.attach succeeds but no assistant/tool/status events
ever flow. And since multiple hub daemons share cron.db, a run claimed
by a different daemon is invisible to this hub regardless. The proper
core rewiring is tracked as ENG-2474.
Client-side heal that covers every case: while an attached history
session reports a busy status and no chat_event chunk has arrived for
five seconds (and no assistant bubble is mid-stream), poll every three
seconds — re-read canonical history, merged through the same dedupe
path hydration uses, and the session record's status — so the
transcript and the thinking indicator settle in place. Locally driven
turns keep chunks flowing, so the quiet-window guard keeps the fallback
inert there.
* chore(desktop): format workspace selector components
Biome formatting drift that landed on main; picked up by a formatter
pass over components/views/chat.
* fix(desktop): keep stale-stream poll inert during locally driven turns
The fallback poll could fire between a local submit and the model's
first chunk (optimistic user bubble added, stream quiet past the
window, no assistant bubble yet). It then replaced the optimistic
bubble — raw prompt text — with its canonical history twin, which is
stored wrapped in a user_input envelope. The rekey handler that runs
when the stream starts looks for a trailing user bubble matching the
raw prompt, finds only the wrapped copy, and appends a second bubble:
duplicated messages in normal interactive chat.
The poll now stays inert while a local turn is in flight
(turnEpoch !== turnSettledEpoch, or outstanding optimistic user
messages), checked both before polling and again after the snapshot
returns. Hydration marks the turn settled — the mount defaults
(epoch 0, settled -1) otherwise read as an open turn and would keep
the fallback inert forever for the scheduled-session case it exists
for. Applying a polled snapshot also rebuilds the live tool routing
keys, same as hydration, so later tool events update canonical rows
in place instead of appending.
* fix(desktop): keep the working indicator alive for narrating scheduled runs
Watching a scheduled run live: the first tool row appeared, then the
thinking indicator vanished with nothing streaming, and the rest of
the run (final answer, submit_and_exit) only showed up seconds later
in one lump.
inferHydratedChatStatus treats a "running" session record whose
transcript ends on an assistant message as a session that died without
a status flip and reports "completed". That heuristic is right for
stale records, but scheduled/automation models narrate between tool
calls, so a polled snapshot can genuinely end on assistant text
mid-run — the completed flip hid the working indicator, folded the
run early, and disarmed the stale-stream poll (status left the busy
set), dead-ending live updates until an in-flight poll happened to
deliver the finished run.
The heuristic now only applies once the transcript has actually gone
quiet (newest message older than two minutes — comfortably past model
latency plus tool runs). A recently active transcript keeps the
record's "running" verdict, so the indicator stays up and polling
stays armed until the record itself settles.
* fix(desktop): stale-stream poll mirrors the session record instead of inferring
Replaces the previous fix for the vanishing working indicator (the
time-window guard added to inferHydratedChatStatus) with a version
that adds no inference at all: the heuristic is restored to exactly
its long-standing form, and the poll now maps the session record's
status verbatim (mapSessionRecordStatus).
The record is the right authority in the poll's context: the sessions
this fallback serves have a live host maintaining their record, and it
flips to a terminal status when the run ends. Transcript-shape
inference belongs only where it has always lived — hydrating sessions
whose records may be orphaned — and would misread a mid-run snapshot
ending on assistant narration as a finished session, hiding the
working indicator and disarming the poll.
* fix(desktop): address review findings on steering detection and run-now matching
Steering detection additionally requires the injected-message marker
(meta.userRunSpan === 0) beside the [SYSTEM] prefix, so a person's
genuine prompt that happens to start with "[SYSTEM]" stays visible
and turn-counted. The failure direction is deliberate: an unstamped
injected reminder would merely show as a user bubble, while the
content-only check could hide a real prompt.
Run-now only follows the execution id the trigger reply itself named;
the newest-execution-for-this-schedule fallback could open a previous
run's session when the trigger failed to enqueue one.
* fix(desktop): report a failed run-now instead of confirming a start
A trigger reply without an execution means no run was enqueued (the
schedule may have been disabled or deleted since the page loaded). The
handler previously toasted "Run started" regardless and then silently
skipped the session-open polling. It now shows a destructive
"Run not started" toast, refreshes the schedule list so the row
reflects reality, and skips the polling entirely.
* feat(desktop): split Customize into Installed and Marketplace pages
The Customize hub previously embedded a Browse section inside every tab
that had a catalog. That inlining made each tab long and buried the
catalog. Customize is now the installed inventory only (skills, MCP,
plugins, rules, hooks, tools tabs pass marketplaceVariant="installed"
to the embedded MarketplaceView; McpServersContent grew the same prop),
with an outline Marketplace button in the header.
Browsing moved to a dedicated Marketplace settings section that renders
the previously dead "directory" variant of MarketplaceView: one list
across all catalog types with type-filter chips, wrapping tag chips,
and light rules separating the filter tiers from each other and from
the results. The Clear control now renders inline at the end of the tag
row only while a tag is active, the Updated date is gone, and the
header hosts an Installed button mirroring the one on the Customize
page. Directory subheader copy: "A curated set of plugins, MCP
servers, and skills from the Cline community."
Tag and type chips wrap to new lines instead of scrolling
horizontally.
* feat(desktop): sidebar time view with sections, sort toggle, and scheduled detection
Restores the time-sorted session list as the default sidebar view, with
collapsible Pinned / Scheduled / Tasks sections (headers appear only
once something is pinned or scheduled) and the page-fill effect that
grows the fetched history window until a Show-more click makes visible
progress. Project grouping stays as the alternate mode behind a
one-click sort toggle whose icon reflects the active mode — the old
dropdown cost an extra click for a two-option choice.
Scheduled sessions are detected two ways: the hub-schedule origin
trigger in session metadata, plus a fallback that asks the hub which
session ids belong to schedule executions (list_routine_schedules,
fetched on mount and every two minutes, merged into a rolling set).
The fallback matters because locally executed scheduled runs do not
reliably stamp the trigger into session metadata — a real scheduled
session created today carried only {mode:"user"} provenance. The
scheduled clock icon now leads the row, left of the title; pin and
timestamp stay on the right.
The initial visible page grows from 10 to 30 rows so a tall sidebar
fills instead of stranding a stub of rows over empty space (history
fetches already start at 50).
The expanded sidebar's Customize row now hosts indented Installed and
Marketplace sub-tabs while a customize section is open; the active
sub-tab carries the full selected background while the parent keeps a
subtler one so the two simultaneous highlights read differently.
Also fixes the hover-card flash on click (logo card and session-row
cards): Radix HoverCardContent sits on a DismissableLayer, so a click
on the trigger registers as a pointer-down outside the card and
dismisses it, and the trigger's focus event immediately reopens it.
onPointerDownOutside preventDefault suppresses the dismissal; cards
still close on pointer leave.
* feat(desktop): schedule page row, dialog, and details UX polish
Schedule cards are now click targets: clicking anywhere on a card
outside its controls opens the details dialog (guarded via
closest("button,...") since every inline control, including the Radix
switch, renders a button element), with Enter/Space keyboard support.
The redundant eye button is gone. The remaining edit / run / pause /
delete buttons grow from the 12px icon-sm size to 28px targets with
16px icons, sized consistently with the adjacent enable toggle — the
icons use explicit size-4 classes so the Button base svg rule cannot
shrink them back.
The new/edit dialog gains breathing room between field labels and their
inputs (space-y-2 per field wrapper).
The details dialog no longer scrolls as a whole when the schedule JSON
is long: the dialog is a flex column capped at 85vh, the JSON pre
shrinks to the remaining space (min-h-0) and scrolls internally, and
the Runs tab list scrolls inside the tab the same way.
* feat(desktop): add Retina DMG background tooling
* feat(desktop): customize the macOS DMG layout
* ci(desktop): validate DMG background assets
* fix(desktop): adjust DMG Applications icon position
* ci(desktop): drop redundant DMG artwork validation from publish workflow
Tauri's beforeBuildCommand already runs dmg:background (with its own
validation) at the start of the build/sign/notarize step, and the
release/beta config overlays do not override the build section, so this
step duplicated work the publish job performs anyway. PR-time coverage
lives in desktop-test.yml.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): stop showing cost estimates for subscription-billed providers
Providers whose usage is covered by a flat-rate subscription (ChatGPT
Plus/Pro via openai-codex, ClinePass) are marked with
metadata.usageCostDisplay = "subscription" in the SDK, and the CLI
already suppresses dollar figures for them. The VS Code host collapsed
that value into "show" before it reached the webview, so the task
header and model pricing rows rendered API-rate cost estimates that
users read as real charges on top of their subscription.
Pass all three usageCostDisplay values ("show" | "hide" |
"subscription") through the catalog listing and render cost only when
the value is "show", matching the CLI's shouldShowCliUsageCost
policy.
* feat(llms): mark Claude Code as a subscription-billed provider
Claude Code is typically authenticated with a Claude Pro/Max
subscription, but its models reuse Anthropic API pricing metadata, so
Cline rendered per-token prices and API-rate cost estimates for usage
that is covered by the subscription. Set usageCostDisplay =
"subscription" on the provider (picked up by the CLI and the VS Code
webview) and suppress the price rows in the Claude Code settings card.
The Claude Code CLI can also run on API-key billing, where a real cost
exists; the provider cannot distinguish the two, so we prefer showing
no number over a misleading one.
* fix(vscode): suppress cost display until provider listings load
While the ListProviders request is in flight (or after it fails), the
usage-cost hook had no listing to consult and fell back to "show",
flashing the API-rate estimate at subscription users on every chat-view
mount — the exact display the previous commit removes. Return
"unknown" whenever listings are absent; consumers already render cost
only for "show", so they suppress it during that window with no
changes. Briefly hiding a real cost is harmless, briefly showing a fake
charge is not.
* capture richer workspace information for vs code extension
* fix(shared): redact credentials from workspace remotes
* fix(shared): avoid regex backtracking in remote redaction
---------
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
The model picker renders a radiogroup of styled buttons with role=radio
and aria-checked; biome's useSemanticElements flags the role as an
error, which fails the sdk-test Quality Checks lint for every PR
touching sdk/ or apps/ paths. Suppress with a justification — switching
to input type=radio needs a restyle and belongs to the desktop settings
work.
* feat(desktop): merge customization pages into a Customize hub with inline marketplace
Replaces the Plugins page and the dedicated Marketplace page with a single
Customize hub. Tabs: Skills, MCP, Plugins, Rules, Hooks, Tools, each with
live counts. Tabs backed by a marketplace catalog render the installed
items followed by an inline browsable Browse section (CLI-hub style), so
installing from the catalog immediately reflects in Installed above.
- Installed cards restyled to mirror the browse-card anatomy: bg-card p-4
containers, absolute top-right xs Uninstall matching Install, truncating
semibold titles, primary-tinted icons, real Badge components instead of
ad-hoc bordered spans, un-indented line-clamped descriptions
- Rules/Hooks/Tools rows brought into the same card language; redundant
intro paragraphs (duplicating the page description) removed; Tools group
headers match the Installed header style with counts
- Marketplace section header renamed to Browse; duplicate 'N results' row
removed (the header count is the single source)
- MCP embedded view now shows the full marketplace instead of
installed-only
* feat(desktop): overhaul sidebar sessions and navigation
Sessions list:
- Sort toggle removed; sessions are always grouped by project, with pinned
sessions leading each group (both subsets ordered by recency). The
Pinned/Scheduled/Tasks category sections and their time-mode paging
machinery (page-fill effect included) are deleted
- Scheduled sessions get an inline clock icon next to the pin position;
pin + clock render together when both apply, and the running/unread
status dot now coexists with them
- One font size (text-sm) across the list: titles, timestamps, project
headers, show-more buttons, empty states. sidebarText needed !text-sm
because the default button size's text-base wins the twMerge conflict
- Gradient fade under the Sessions header once the list scrolls, so rows
fade out instead of hard-clipping
- The session-detail hover card is controlled from the sidebar and closes
on scroll (Radix receives no pointer events while scrolling, so it used
to float over moving content)
- Sidebar min resize width raised 224->260px; the per-project show-more
label truncates so its nowrap text can't force rows to overflow and clip
timestamps at narrow widths
Navigation:
- Customize replaces the Plugins/Marketplace/Hooks/Rules/Tools sidebar
entries; Schedules and Customize are hidden from the expanded settings
nav (their top rows cover them) but stay reachable when collapsed
- The settings gear always opens General instead of resuming the last
section; the Account no-op hover special case is gone
- The New row highlights (aria-current) while the fresh not-yet-started
task page is showing and hands off to the session row once the task
starts; hitting New also focuses the prompt input via a window-event
signal (lib/prompt-input-focus.ts) since the sidebar and composer sit in
distant subtrees
- Fixed the xs button size collapsing any icon-bearing button to 12x12
(leftover has-[>svg]:size-3 from when xs was a micro button) — this was
why Uninstall buttons rendered broken next to Install
* feat(desktop): polish settings pages and chat composer
Models page:
- The provider detail panel is always open: no X button, no empty
no-selection state. It defaults to the first connected provider (falling
back to the first in the catalog), which also removes the layout shift
that happened when the page swapped between full-width and panel
variants on selection
- Fixed the list pane becoming unscrollable while the panel was open:
grid items default to min-size auto, so the pane grew past its track
inside the overflow-hidden grid and its ScrollArea had nothing to
scroll; wrapped it in a min-h-0 min-w-0 cell
- Add Provider opens a Dialog instead of swapping the page
(AddProviderContent gained a dialog variant that renders only the form)
- Embedded inputs (provider search, model search, detail fields) share one
EMBEDDED_INPUT_CLASS stripping the Input component's own border/dark bg
tint/shadow/ring, which rendered as a mismatched inner box; the model
search box uses the same h-9/px-3 frame as the provider search
- Model list flows with the page instead of a max-h capped inner scroller
Other pages:
- Account uses the shared PageFrame/PageHeader: left-aligned, text-3xl
title, Sign Out in the header actions slot
- Desktop notifications is one General section: header row plus the
Event/Notify/Sound matrix nested in a card, so its rows no longer read
as top-level peers of Dark mode; 'Available in the desktop app' label
removed
- Schedule page retitled from Schedules with a real description; Customize
description rewritten
Chat composer:
- The voice dictation button only renders once a voice model is
configured (Settings -> Voice); the unconfigured deep-link state is
gone (prop type kept for an easy restore)
* Redesign desktop Model Providers page and split voice input into its own settings page
- Group providers into Connected / Popular / All with auth-kind hints and
connection status instead of per-row enable toggles
- Show browser sign-in (not an API key field) for OAuth providers, with a
collapsed manual-key escape hatch where supported, plus explicit
Connect / Disconnect / Sign out actions
- Move voice input to a dedicated Settings > Voice page that only offers
connected transcription-capable providers, preselects a default model
(streaming preferred), and stays disabled in the sidebar until a
provider is connected
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Show native tooltip on the disabled Voice settings nav item
Disabled buttons drop pointer events, so the 'connect a model provider'
hint moves to a wrapping span for the browser tooltip to render.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Drop letter avatars and gray provider ids from provider rows and voice chips
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Drop model counts from provider list rows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename provider Connected status to Configured and drop the green styling
A settings entry is configuration, not a live connection; neutral gray
text avoids implying an active link, since the user still picks which
configured provider to use per chat.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Resync provider catalog from disk when a settings save fails
Connect/disconnect/credential edits update the list optimistically; a
failed save now reloads the catalog instead of leaving the optimistic
state (and the view's module cache) claiming a configuration that was
never persisted.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename oauthProvider test fixture to dodge CodeQL name heuristic
CodeQL's clear-text-storage query flags any identifier matching 'oauth'
as a credential source and traced the fixture's provider id into the
favorite-models localStorage write, which stores only provider/model id
strings. Renaming the fixture removes the false-positive source.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Guard catalog reloads against races and resync detail drafts on failed saves
Optimistic provider mutations now bump a generation that discards any
in-flight catalog response, so a failed-save recovery reload can't
overwrite a newer edit with an older disk snapshot. The recovery also
remounts the provider detail panel via a reset token so its local field
drafts reflect the reloaded on-disk state instead of unpersisted edits
or an optimistically cleared disconnect.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix failed-save recovery ordering and retry superseded reloads
Remount the provider detail only after the authoritative catalog reload
lands, so its drafts re-seed from disk state rather than the optimistic
values that failed to persist. When a concurrent edit supersedes the
recovery's in-flight response, retry the reload (bounded) instead of
dropping it, since that edit performs no reload of its own.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add Pinned/Scheduled/Tasks categories to desktop app sidebar
Replace the Schedules and Favorites filter-menu options with visible
collapsible category sections in the session sidebar, and rename the
Favorite action to Pin across the sidebar and sessions view.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Grow full history window when Tasks show-more outpaces loaded tasks
loadMoreSessions treats its argument as a limit on all sessions, but the
Tasks show-more count only tracks Task rows, so once pinned/scheduled
rows pushed the loaded total past the requested count the call no-oped
and clicks went dead. Grow the whole history window via
loadOlderSessions instead, and only when the loaded tasks cannot fill
the next page. Addresses Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Auto-fill the Tasks page instead of fetching once per show-more click
A single 50-session window growth can consist entirely of pinned or
scheduled sessions, leaving a show-more click with no visible Tasks
progress. Replace the one-shot fetch with a page-fill effect that keeps
growing the history window until the requested Tasks page fills or
history runs out. Addresses the follow-up Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Halt page-fill retries after a failed history fetch
A failed fetch leaves the task count and has-more state unchanged,
which are exactly the conditions the page-fill effect fires on, so one
failing request would retry and re-toast forever. Halt the effect after
a failure and let the next explicit show-more click retry. Addresses
the third Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: clean up sidebar navigation chrome
- Give New Task its own full-width labeled row below the logo row
instead of an ambiguous icon next to the agenda toggle
- Wire the New Task row to the home action so starting a new task
clearly takes you home (the logo still works as a fallback)
- Swap back/forward chevrons for browser-style arrow icons and
bump their size
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: sidebar New/Schedule/Customize rows and always-visible search
- Stack New (plus icon), Schedule, and Customize as full-width labeled
rows below the logo; whole row highlights on hover via sidebarItem
- New starts a fresh task (home), Schedule opens Settings > Schedules,
Customize opens the Customizations sections (Plugins first)
- Show the session search bar permanently above the sessions list
instead of hiding it behind a search icon toggle
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: move session search into a dialog behind a logo-row icon
- Replace the inline sidebar search bar with a search icon in the
logo row that opens a cmdk command dialog listing sessions
- Selecting a result opens that session and closes the dialog
- Remove the agenda/tasks toggle the icon replaces, along with the
now-unreachable sidebar Agenda panel (the welcome screen still
surfaces agenda tasks)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: load full session history when the search dialog opens
Addresses Greptile review on #13533: the dialog only searched the
currently loaded history batch, so older unloaded sessions could not
be found. Opening search now kicks off loadAllSessions() (the hook's
purpose-built global-search loader), and the empty state reads
'Searching older sessions...' while more history is streaming in.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): remove per-tool MCP auto-approve checkboxes from webview
MCP auto-approval is now governed solely by the global 'Use MCP servers'
toggle; the SDK approval path (shared with the CLI and desktop app) has no
per-tool granularity, so the per-tool and 'Auto-approve all tools'
checkboxes were no-ops that implied control that no longer exists. Remove
them from the MCP settings view and chat tool rows. The autoApprove arrays
in cline_mcp_settings.json and the toggleToolAutoApprove RPC are left
intact for the legacy extension.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): hide per-tool MCP auto-approve checkboxes behind a flag
Keep the checkbox components, handlers, and RPC plumbing intact but gate
rendering behind SHOW_MCP_PER_TOOL_AUTO_APPROVE=false: the SDK approval
path (shared with the CLI and desktop app) is all-or-nothing via the
global 'Use MCP servers' toggle, so the per-tool checkboxes were no-ops.
Flip the flag back on if the SDK gains per-tool approval granularity.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): prevent search_codebase from crashing the process on giant single-line files
searchWithRipgrep buffered all of rg's --json stdout into one string. Each
JSON event embeds the full text of the matched line (--max-columns is
ignored in JSON mode), so searching a directory of serialized trace dumps
(single-line multi-hundred-MB JSON files) accumulated gigabytes of stdout
until string concatenation threw RangeError: Out of memory inside the
stream data handler. That throw is outside the tool's try/catch, so it
escalated to an uncaughtException and killed the CLI/hub daemon.
Parse rg's JSON events incrementally line by line, drop events larger
than 256KB, truncate matched/context lines to MAX_LINE_CHARS, and stop
reading once maxResults is reached. The fallback regex scan now skips
files larger than 10MB (reporting the skip count) and truncates its
context lines the same way.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* simplify search_codebase crash fix to a minimal diff
Replace the incremental JSON-event parser with three small guards: stop
buffering rg stdout past 10MB, drop the trailing partial event before
parsing, and slice fallback context lines to MAX_LINE_CHARS. Drops the
fallback file-size skip and skip-count reporting.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
The /shutdown handler queued teardown on a microtask, which runs before
the event loop's write phase, so the daemon could process.exit() before
the accepted 202 was handed to the socket. Unix masked it (uv_try_write
lands small loopback writes synchronously); Windows has no such fast
path and lost the race regularly — the recurring shutdown.e2e.test.ts
'socket hang up' failures on windows-latest. Start teardown from the
response's write callback instead, with an idempotent 1s fallback so a
client that vanishes mid-write cannot strand the daemon, and send
Connection: close so the client gets a FIN rather than an abort.
Since the flakiness this compensated for is fixed at the source, restore
maxWorkers: 2 for the Windows core suite (serializing it cost ~3 min of
CI per run), and raise the e2e daemon discovery hang guard 10s→30s —
it guards against hangs, not runner speed.
ci-node-smoke.ts installs the packed SDK tarballs with a plain npm
install in a fresh temp dir, where the repo root package.json overrides
do not apply. When @sap-cloud-sdk 4.9.0 shipped (2026-08-24) it broke
@sap-ai-sdk/ai-api 2.14.0 (via @jerome-benoit/sap-ai-provider in
@cline/llms) with ERR_PACKAGE_PATH_NOT_EXPORTED, failing the smoke step
on every PR even though the root already pins @sap-cloud-sdk/* to 4.6.0.
Copy the root overrides block into the generated sandbox package.json
so the smoke install resolves the same pinned versions as the repo and
future third-party releases cannot break it independently.
* fix(hub): cap hub-events db size so it can't fill the disk
Row/time retention alone didn't bound disk usage: envelopes carrying
full session snapshots reach hundreds of KB each, so retained rows
could total tens of GB, sweeps only ran hourly, and DELETE never
shrinks a SQLite file. Enforce a 64 MiB size budget in prune() (oldest
rows first, VACUUM to return the space), and also prune after every
16 MiB appended so bursts can't outrun the hourly timer.
Fixes#13505
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): tolerate VACUUM failure on a full disk
VACUUM needs scratch space and can fail in exactly the state a
ballooned event log causes. The byte-budget deletes already bound live
data, so swallow the error and let the next sweep retry the reclaim
instead of aborting startup pruning and disabling the durable log.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): count the size budget in UTF-8 bytes, not characters
envelopeJson.length (UTF-16 units) and SQLite LENGTH() (characters)
undercount multibyte text by up to 3x, which could leave a CJK-heavy
log settled above budget and re-running VACUUM every sweep. Use
Buffer.byteLength and LENGTH(CAST(... AS BLOB)) instead.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): resolve hook workspace identity from the window, not shared global state
Hook discovery, hook cwd selection, and the workspaceRoots metadata passed
to hook scripts all read the workspaceRoots/primaryRootIndex global state
keys. Global state lives in ~/.cline and is shared by every Cline instance
(all VS Code windows, the CLI, the JetBrains plugin), and nothing writes
these keys anymore, so hooks resolved against whatever project some other
or older instance last recorded. With a second window open on another
project, a workspace's .clinerules/hooks scripts were never discovered.
Resolve workspace roots via a single guarded helper backed by
HostProvider.workspace.getWorkspacePaths() (in-process, window-scoped,
same as refreshHooks): blank paths are filtered, a host-bridge failure
degrades to no workspace roots instead of silently disabling global hooks
or skipping blocking PreToolUse guards, and one resolution is threaded
through hooks-dir discovery, cache misses, cwd selection, and hook input
metadata so they can't disagree (previously up to four host lookups per
hook execution — real gRPC round trips in the standalone host). Roots and
hooks dirs are matched on whole path segments with the longest root
winning, so prefix-sharing or nested workspace roots resolve to the right
project. The adapter creates the runner once per event and skips no-op
runners, making creation the single resolution point; the separate
hasHook/getHookInfo checks are removed. The dead workspaceRoots and
primaryRootIndex state keys are dropped, and the four hand-rolled
HostProvider.workspace test stubs are consolidated into one shared
helper.
* test(vscode): add e2e coverage for workspace-scoped hook discovery
Boots real VS Code with the packaged extension against the workspace
fixture, sends a prompt, and asserts the fixture's UserPromptSubmit hook
was discovered from the open window's workspace, executed with that
workspace root as its cwd, and received the same root in its
workspaceRoots input — the end-to-end contract the hook workspace
identity fix establishes.
* test(vscode): isolate the e2e hook fixture from the shared workspace
The UserPromptSubmit fixture hook lived in the shared e2e workspace, so
every prompt-sending spec executed it (hooksEnabled defaults to true) —
and its cold PowerShell spawn on Windows pushed chat.test.ts past the
5s expect timeout. hooks.test.ts now overrides workspaceDir to a
dedicated workspace-hooks fixture, so only the hooks spec pays the hook
spawn.
The $4.99 first-month promo is ending, so the CLI's first-launch "Try ClinePass" dialog should no longer advertise it. Also drops the leftover CLI_PROMO_CODE plumbing, which has been an empty string since the promo-code flow was removed.
* fix(vscode): honor MCP auto-approve settings for SDK tool calls
The SDK extension required both the global 'Use MCP servers' auto-approve
toggle AND each tool's per-tool autoApprove flag before silently approving
an MCP call, while the legacy extension treated them as either/or. Restore
the legacy OR semantics so toggling MCP auto-approve works again.
Also key toolPolicies by the registered SDK tool name (via
defaultMcpToolNameTransform, now exported from @cline/core) instead of raw
server__tool. Servers whose names contain sanitized characters (e.g.
marketplace names like github.com/user/repo) or exceed 64 chars produced
policy keys that never matched the registered tool, so those MCP tools ran
without any approval gate; the live auto-approve lookup now re-applies the
transform instead of string-splitting the name.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Revert "fix(vscode): honor MCP auto-approve settings for SDK tool calls"
This reverts commit 86c568fbba.
* fix(vscode): auto-approve all MCP tool calls when the MCP toggle is on
The SDK extension only auto-approved an MCP call when the global 'Use MCP
servers' auto-approve toggle AND that tool's per-tool autoApprove flag were
both set, so toggling MCP auto-approve appeared to do nothing and users had
to opt in each tool individually. The toggle alone now governs all MCP
tools; the per-tool flag is no longer consulted.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
The task.completed fallback lived only inside shutdownSession, but
stopSession/dispose route interactive sessions with a terminal reported
status through releaseSessionRuntime, which never emitted. Truthful
session-status reporting (shipped in 4.1.11) re-routed a large share of
interactive stops onto that branch and silently dropped the event.
Route the emission through a single choke point,
emitTaskCompletedOnTeardown, called from both shutdownSession and
releaseSessionRuntime. The completion criterion no longer reads
session.status: interactive sessions use the recorded final-turn
outcome (lastInteractiveTurnFinishReason), non-interactive sessions
keep the existing input.status === "completed" logic. A new
taskCompletedEmitted flag (also set by the submit_and_exit observer)
enforces exactly one task.completed per session. failSession now
records the errored final turn so a stale "completed" from an earlier
turn can never leak into the teardown emission. Telemetry only; no
user-facing behavior changes.
Four consecutive SDK publish runs failed on windows-latest, each on a
different test, all of them plain timeouts: two @cline/shared SQLite
tests at the 5s vitest default, core's bash executor at 10s, and the hub
singleton endpoint test at 10s. The 2-core Windows runner spawns forks
and takes SQLite locks slowly enough to blow those budgets under load.
These timeouts guard against hangs; they are not timing assertions (the
one suite that does assert elapsed time, shutdown.e2e, was fixed by
removing file-level parallelism instead). Raise core to 20s and give
@cline/shared an explicit 15s in place of the inherited 5s default.
singleton.e2e.test.ts (added in #13468) spawns real daemons and runs for
~15s. Vitest's default file parallelism let it run alongside
shutdown.e2e.test.ts, whose assertions are wall-clock bound: discovery
within 10s, exit within 5s, and a 2s shutdown watchdog. On the 2-core
windows-latest runner that contention alone broke those budgets, failing
the shutdown test two different ways across runs — once never observing
discovery, once with the daemon forced to exit before its HTTP 202
flushed (socket hang up). The test passed on Windows before #13468 and
has failed every SDK publish run since.
* fix(core): seed tools capability when custom model capabilities are synthesized from boolean flags
For a models.json entry with no explicit capabilities list, toStoredModelInfo
synthesized a capability array purely from boolean convenience flags (e.g.
supportsReasoning: true -> ["reasoning"]). modelSupportsToolCalling fails open
only for a missing or empty list, so the synthesized non-empty list read as an
authoritative denial and silently stripped every tool definition from requests
to custom OpenAI-compatible models (#13463).
Seed "tools" whenever the list was not explicitly authored and the boolean
projections made it non-empty, preserving the fail-open contract. Explicitly
authored capability lists remain authoritative and can still disable tools.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(core): cover stale catalog capability overrides
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: treat stored capability lists as non-authoritative for tool calling
The hasExplicitCapabilities guard still let two producers of tool-less
lists through:
- The VS Code legacy-override migration (legacyModelInfoToOverrides)
persists explicit partial lists like ["prompt-cache"] into models.json
for custom OpenAI-compatible models, which then read as an authoritative
"cannot call tools" and drop every tool - same symptom as #13463.
- Any hand- or UI-authored partial list on a non-catalog model.
Stored entries and user-authored provider metadata have no way to declare
"cannot call tools" (there is no supportsTools field, and every writer
that authors a full list includes "tools"), so seed "tools" into any
non-empty list for a language model. Only generated catalog capabilities
remain authoritative - a genuine no-tools catalog model stays that way -
and non-language models (e.g. image generation) never gain a tools claim.
Also make legacyModelInfoToOverrides write "tools" into the arrays it
fabricates, matching the providers.json migration, so models.json stops
being poisoned for older readers.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(hub): add drain and upgrade commands with replay support
* handles disconnection
* feat(hub): wire bot profiles, drain, and durable event/run-queue into the live transport
Completes the wiring the previous commits' primitives needed:
HubServerTransport gains isDraining(), hub.drain/hub.status/profile.get
command handling, and replayEventsAfter() (backed by the durable event
log), plus the sequence/sinceSequence wire types they depend on in
shared/hub.ts. run-queue-handlers.ts reads the active bot profile's
plugin roots when executing durable runs.
Also adds hub/profiles/: profile.json (identity/rules/plugins) ->
system prompt composition, --profile / CLINE_HUB_BOT_PROFILE
resolution, and the bundled cline-dad profile with its
cline_hub_support read-only diagnostics tool.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Revert "feat(hub): wire bot profiles, drain, and durable event/run-queue into the live transport"
This reverts commit 6696d5d202.
* fix(hub): dedupe replayed events by eventId, not just sequence
HubEventLogStore.append() returns a new envelope stamped with a
sequence rather than mutating the input, so a pending approval
re-issued sequence-less by subscribe() (it predates any durable-log
append) and its later sequence-stamped copy from the durable log are
two different objects carrying the same eventId. The replay-then-live
buffer in browser-websocket.ts only deduped by sequence, so the
sequence-less copy's guard never tripped and it was delivered a second
time when the buffer flushed after replay.
Track delivered eventIds alongside the sequence cursor; eventId
survives the append/stamp round-trip unchanged, so this dedupes the
exact-same logical event regardless of which copy arrives first.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): wire drain, durable event log, and run queue into the live transport
CI on this branch failed bun run build:sdk: browser-websocket.ts,
client/index.ts, and hub-websocket-server.ts (already on this branch)
reference sequence/sinceSequence, HubServerTransport.isDraining(), and
the "hub.drain" command — but the commit that reverted bot profiles
out of this branch also reverted this wiring, since it shared a commit
with the profiles work. That wiring is a hub concern, not a
bot-profiles one; split it back out.
- shared/hub.ts: sequence/sinceSequence types, run.enqueue/run.list/
hub.drain/hub.status/stream.replay capability, command, and event
names. profile.get intentionally excluded — stays bot-profiles-only.
- context.ts: isDraining() on HubTransportContext. botProfile field
intentionally excluded.
- hub-server-transport.ts: eventLog/runQueue fields and start/stop
lifecycle, publish() appends to the durable log, handleCommand cases
for run.enqueue/run.list/hub.drain/hub.status, drain-refusal check,
replayEventsAfter()/lastEventSequence(). startBotProfile()/
startHubSupportTool() and the profile.get case intentionally
excluded.
- run-queue-handlers.ts: added without handleProfileGet (needs
ctx.botProfile, which doesn't exist here).
- hub-upgrades.test.ts: added without its two bot-profile-injection
tests (they need a resolved bot profile to assert against).
Verified bun run build:sdk exits 0 (the exact CI command) and
bunx vitest run src/hub passes (311/312; the one failure is the
same pre-existing environment-timing flake already present before
this change).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): export instance-lock, event-log, and run-queue from the hub barrel
These landed as internal modules only; hub-server-transport.ts and
hub-websocket-server.ts import them by direct path, but nothing
re-exported them from the public @cline/core/hub surface the way
sibling discovery/server modules already are.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): wire the instance lock into the daemon entry point
The singleton lock (discovery/instance-lock.ts) and its consumption in
startHubWebSocketServer/ensureHubWebSocketServer were already on this
branch, but the daemon entry point's own half was not: retrying a bind
when a retiring predecessor still holds the lock, and exiting with a
distinct code (3) instead of the generic fatal path when a live Hub
already owns the data directory. Without this, a daemon racing a
retiring predecessor could fail outright instead of waiting the lock
out, and losing the singleton race looked identical to a crash.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): address drain/upgrade review findings (#13478)
- cline hub upgrade: check idleness at least once (--wait 0 works), reject
non-numeric --wait, and un-drain on every abort path so an aborted
upgrade can never leave the hub refusing new work
- add cline hub drain --off and the off query param to requestHubDrain so
POST /drain?off is reachable from shipped code
- HubEventLogStore/HubRunQueue: WAL journal mode + busy_timeout, and stamp
sequences from lastInsertRowid instead of SELECT MAX(sequence)
- HubInstanceLock.acquire: degrade to an unheld lock when SQLite is
unavailable instead of refusing hub startup; only BUSY/LOCKED still
raises HubLockHeldError
- ensureHubWebSocketServer: retire an unusable discovered hub through the
shared retireDiscoveredHub (busy hubs are attached to, drain precedes
shutdown, discovery cleared only when the hub actually retired)
- replay adapter: advance the cursor past eventId-deduped events, cap
replay pages, stop when the cursor stalls, and drop the dedupe set after
the buffered flush so it cannot grow for the socket lifetime
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(hub): derive the singleton e2e challenger cwd portably
The challenger's working directory was derived by round-tripping the
discovery path through a file: URL and stripping the last pathname
segment. On Windows that yields a POSIX-style '/C:/...' path, which is
not a valid spawn cwd, so the spawn fails ENOENT before the singleton
lock is ever contested and the Windows SDK test job goes red.
The data dir is simply the discovery file's parent: use dirname().
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
The PublishNightly environment gained required reviewers, so each cron
run parked on approval, held the workflow's concurrency group, and
silently cancelled every scheduled run queued behind it. 20 consecutive
scheduled nightlies died this way between 2026-07-31 and 2026-08-21;
the only nightlies that shipped in that window were manual dispatches.
Drop the cron rather than leave a trigger that cannot succeed unattended.
* fix telemetry session propagation
* feat telemetry client version metadata
* fix(core): address Langfuse review feedback — hub client identity + delegated agent session grouping (#13475)
* fix(core): rebuild hub session client identity from request headers
Hub-backed sessions do not transport extensionContext (it is local-only),
so the daemon's runtime built traces without the clientName/clientVersion
metadata even though the hub client bakes X-CLIENT-TYPE / X-CLIENT-VERSION
into the session's provider headers. Reconstruct extensionContext.client
from those headers during local runtime bootstrap so hub-backed Langfuse
traces carry the same client identity as local runtimes, and the daemon's
header re-resolution stops clobbering the original X-CLIENT-TYPE.
* fix(core): propagate parent distinctId/sessionId to delegated agents
Delegated agents (spawned sub-agents, configured agents, teammates) were
built without distinctId and sessionId, so their Langfuse traces had no
userId or sessionId and did not group with the parent user or session.
Thread the host-resolved distinctId through RuntimeBuilderInput and the
root sessionId through the delegated-agent config provider, and copy both
onto the delegated AgentConfig.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* Treat an empty preserved capability list as unspecified when seeding tools
toSdkModelInfo guarded the tools seeding with a strict
preservedCapabilities === undefined check, but modelHasCapability —
the runtime's own reader — treats undefined AND length === 0 as
"unspecified". A custom OpenAI-Compatible model whose stored
capabilities field is a defined-but-empty array (a config carried over
from before the field existed, or one round-tripped through a boundary
that defaults it to []) skipped the seeding; the first boolean
projection to run afterwards (e.g. supportsReasoning) then populated
the array, the runtime gate read the non-empty, tool-less list as
authoritative, and every tool definition was silently dropped from the
session (#13463).
The guard now covers the empty array too, matching the reader's
unspecified semantics.
* test: satisfy the store's isModelInfo gate so the empty-capabilities case actually reaches knownModels
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: yzxcj797 <yzxcj797@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): enforce enterprise MCP controls on the Customize marketplace
The unified Customize marketplace replaced the old MCP marketplace
without carrying over enterprise remote-config enforcement: the catalog
RPC returned every MCP entry and installs were never policy-checked,
so orgs with mcpMarketplaceEnabled=false or an allowedMCPServers
allowlist saw (and could install) all marketplace MCP servers.
- Filter MCP entries out of getMarketplaceCatalog when the marketplace
is disabled, and restrict entries to the allowlist when configured
(matching entry id, display name, installed server name, or source
repo URL, mirroring legacy GitHub-URL allowlist ids)
- Reject installMarketplaceEntry requests that violate the policy
- Map the published catalog's repo/homepage fields onto
sourceUrl/homepageUrl so URL-based allowlists can match
- Update the enterprise MCP server controls docs
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: simplify MCP marketplace policy enforcement
Fold the policy check into marketplace-helpers, drop the dedicated
test suite, and trim the docs edit to the strictly necessary line.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(deps): update Langfuse packages and bump app versions
Update @langfuse/otel to v5.10.1 and add @langfuse/vercel-ai-sdk v5.9.1 for improved observability with Vercel AI SDK.
Bump versions for @cline/code to 0.0.14 and @cline/ui to 0.2.0-next.6, updated via bun.lock.
Other Changes:
Added optional userId to AgentRuntimeConfig.
Propagated userId, sessionId, conversationId, runId, iteration, provider, and model context into AI SDK telemetry.
Added AI SDK 7 runtimeContext with explicit includeRuntimeContext.
Added stable OTEL_SERVICE_NAME=cline-sdk.
Added runtime metadata assertions in agent tests.
* add taskId
* Revert "add taskId"
This reverts commit f20d31d96d.
* fix(core): keep hub session status truthful across queue-drained turns
Queue-drained turns settle only through the event stream, but the hub
runtime host mistranslated their lifecycle in two ways:
- session.updated events carrying only a snapshot (persistence updates)
defaulted the projected status to "running". When one trailed the
final idle update after a turn, clients that track busy state from
status events (the desktop sidecar's workspace restore gate) stayed
busy forever. Use the snapshot's real status and emit nothing when
neither source reports one.
- the per-run agent.done dedup was only reset by run.started, which the
daemon-side queue drain never publishes, so a drained turn's done was
swallowed as a duplicate of the previous turn's. Reset the dedup on
session.pending_prompt_submitted, and suppress stale run.completed
events that land inside a drained turn's window so they can neither
emit a phantom done nor consume the drained turn's dedup slot.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(desktop): cover restore unlock after an event-settled queued turn
Exports the sidecar's core-session event handler so the queued-turn
lifecycle (busy via status events, cleared by the done agent event,
restore allowed afterwards) is testable end-to-end.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): start interactive sessions without a prompt as idle
The runtime host reported every new session as "running" until its
first turn ended. Interactive hosts (the desktop app) start sessions
with no prompt and dispatch turns through separate send calls, so a
created-but-never-prompted session stayed "running" forever — wedging
clients that gate workspace operations (checkpoint restore, message
edit) on active turns.
Interactive no-prompt starts now begin idle, start emits the session's
actual status (resumed sessions no longer masquerade as running), and
markTurn* transitions keep tracking in-memory status for lazily
persisted sessions so the first turn still reports running -> idle.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style: format hub-runtime-host test filter
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop the drained-turn done bookkeeping, keep the minimal fix
The stuck restore is fully explained by the two status defects (fabricated
"running" from snapshot-only session.updated events, and never-prompted
interactive sessions reporting "running"). The done-dedup machinery for
queue-drained turns addressed a separate cosmetic gap (queued turns emit no
chat_done, pre-existing) and required fragile run-window heuristics, so it
is removed to keep this change reviewable. Sidecar test now settles the
queued turn through the status event, matching the shipped mechanism.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* docs(sdk): document the truthful session-status contract
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
The edit preview computed proposed content with an exact old_text match, but
the SDK executor normalizes old/new text to the file's own line endings before
matching (#12305) - reads strip CR, so models emit LF-only text even for CRLF
files. Any multi-line old_text in a CRLF file therefore failed the preview's
match: the diff edit view silently never opened while the executor applied the
edit. Single-line edits (no line break in old_text) were unaffected, which is
why the diff view appeared to trigger inconsistently.
Mirror the executor's EOL normalization (and its literal $-sequence insertion)
in the preview computation.
Fixes#13296
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Classic Cline truncated long conversations by omitting an index range of
api_conversation_history from every API request (keep the first
user-assistant pair, drop everything through the range end, strip
orphaned tool_results from the first kept message). The range was
persisted on the history item while the full history stayed on disk.
legacyApiHistoryToSdkMessages ignored conversationHistoryDeletedRange
and converted the entire file, so resuming a migrated long task handed
the SDK an untruncated working context that could exceed the model's
context window by millions of tokens - every request failed with
'prompt is too long' and every compaction restarted from the full
history (#12996, confirmed by the reporter: the task was migrated from
an older version and broke after a restart, with each compaction
starting from ~3M tokens).
The migration now replays exactly what the classic extension sent:
slice out the deleted range and drop orphaned tool_results, mirroring
ContextManager.getTruncatedMessages (see origin/main). Malformed ranges
fall back to the full history (previous behavior).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): watch agenda task specs via the resolved long path
fs.watch on a path with 8.3 short components (e.g. C:\Users\RUNNER~1
temp dirs) trips a libuv assertion in fs-event.c on Windows and aborts
the whole process. Since the agenda task manager landed, every hub
server test spins up its spec watcher on such a path on hosted Windows
runners, killing the vitest worker and failing the sdk-test Windows job
on every branch. Resolve the specs dir with realpathSync.native before
watching so libuv only ever sees the long form.
* test(ui): stub ResizeObserver for @pierre/diffs in tool-diff tests
jsdom does not implement ResizeObserver, so every ToolFileDiff render
logged a ReferenceError from @pierre/diffs to stderr. Tests still
passed; this just silences the noise the same way the constructable
stylesheet shim does.
* fix(core): skip the agenda spec watcher when the dir does not resolve
Falling back to the raw path on realpath failure would reintroduce the
Windows short-path abort; log and go without the watcher instead.
* fix(hooks): collect PostToolUse hook output and honor its control
tool_result (PostToolUse) hooks ran fire-and-forget with stdout
ignored, so their entire JSON output — contextModification and cancel —
was discarded. Legacy awaited PostToolUse, injected its
contextModification into the conversation, and honored cancel.
- Run tool_result hook commands blocking (same 120s default timeout as
tool_call) in both the hook-config-file layer and the agent-hook
subprocess layer.
- Map their output: cancel stops the run with the hook's error message
as the reason; otherwise context is injected via afterTool
appendContext.
This restores legacy blocking semantics: tool results now wait for
tool_result hooks, but only in sessions that have one configured.
Ref: https://linear.app/cline-bot/issue/CLINE-2987
* fix(hooks): bound tool_result hook wait and isolate cancel reason
Address review findings:
- The agent-hook subprocess layer forwarded an unset timeoutMs
unchanged, so a tool hook command that never exits would block the
agent indefinitely. Default both tool_call and tool_result to the
120s bound the hook-config-file layer already used.
- A cancelling hook's error message was folded into the same context
field as other hooks' injectable context, so merging controls could
leak unrelated hook context into the cancellation reason. Carry it as
a separate cancelReason, and surface it as the stop reason for
beforeTool cancels too.
* fix(hooks): prefer errorMessage as a cancelling hook's stop reason
When a cancelling hook returns both contextModification and
errorMessage, the context-first parse precedence made the injectable
context the cancel reason and discarded the actual error. Parse the two
fields separately: errorMessage wins as the cancel reason (matching
legacy), and a lone errorMessage still folds into injectable context
for non-cancelling hooks as before.
* fix(vscode): honor PostToolUse hook cancel and contextModification
The adapter awaited PostToolUse hooks but discarded their output
entirely. Map cancel to a stop control (with errorMessage as the
reason) and contextModification into the runtime appendContext channel,
matching the PreToolUse mapping and legacy semantics.
* fix(hooks): whitespace-only errorMessage no longer suppresses the cancel reason
A cancelling hook returning meaningful context alongside a blank
errorMessage lost both: the parsers selected the whitespace as the
reason and the result mappers trimmed it away. Require a non-blank
errorMessage before it wins, so context serves as the fallback reason.
Apply the same fallback in the extension adapter's stop mapping.
* fix(hooks): deliver tool hook contextModification to the model
On the next engine, a tool_call (PreToolUse) hook's contextModification
was parsed into HookControl.context and then silently dropped: the
runtime beforeTool/afterTool result contract had no channel for
injecting conversation context. Legacy consumed it (ToolExecutor /
ToolHookUtils pushed <hook_context> blocks into the next user turn), so
this was a regression of documented behavior.
- Add appendContext to AgentBeforeToolResult/AgentAfterToolResult.
- AgentRuntime collects appendContext across hooks during an
iteration's tool executions and appends one <hook_context> user
message after the tool results, keeping tool-result parts contiguous.
- Map HookControl.context into appendContext in both subprocess hook
layers (skipped when the hook cancels, matching legacy, where the
message doubled as the error).
- Truncate injected context at 50KB per hook output, matching legacy.
- Concatenate appendContext across merged hook layers.
tool_result (PostToolUse) hooks still run detached with stdout ignored;
making them blocking so their context can be collected is a follow-up.
Ref: https://linear.app/cline-bot/issue/CLINE-2987
* fix(hooks): stamp tool identity on injected hook context blocks
Contexts are batched into one message after the tool results, and
parallel tool execution collects them in completion order, so position
alone cannot attribute a block to its tool call. Add tool_name and
tool_call_id attributes to each <hook_context> block.
* fix(hooks): sanitize hook context block markup
Attribute values (tool_name, tool_call_id) are stripped of quote/angle
characters and embedded </hook_context> closers in hook output are
neutralized, so neither provider-supplied ids nor hook text can corrupt
or spoof a block's stamped identity.
* fix(hooks): neutralize forged opening hook_context tags in hook output
The previous sanitization only neutralized closing tags, so hook output
could still open a forged <hook_context> block claiming another tool's
identity. Escape both opening and closing embedded tags with one rule.
* fix(hooks): hide injected hook context from user-facing transcripts
Stamp the injected hook-context user message with displayRole 'system'
(the compaction-summary convention) so it reaches the model but does
not render as a user bubble in live or replayed transcripts. Without
this, resuming a session showed the raw <hook_context> block as if the
user had typed it.
* fix(hooks): neutralize case-variant embedded hook_context tags
The tag-neutralization regex was case-sensitive, so hook output could
still smuggle a forged tag as <HOOK_CONTEXT>. Match case-insensitively.
* fix(vscode): map PreToolUse contextModification into runtime appendContext
The extension's hooks adapter bridged file hooks into the SDK runtime
but forwarded only cancel/errorMessage, so a PreToolUse hook's
contextModification never reached the model. Map it into the runtime's
appendContext channel; HookFactory already truncates it at 50KB.
* fix(vscode): hide hook-injected context from replayed transcripts
Live sessions never rendered the injected <hook_context> user message,
but session reload replayed it as a user bubble (and post-resume turns
kept doing so). Treat these messages as synthetic in the user-message
mapping: honor the displayRole 'system' stamp the runtime sets, with a
text-prefix guard for paths where metadata is unavailable. This also
keeps edit/regenerate ordinal mapping aligned with visible bubbles.
* fix(hooks): run file hooks through exactly one layer per host
The VS Code extension registered two independent hook execution layers:
its own hooks adapter (config.hooks) and the SDK core's file-hook
extension from the runtime bootstrap. When both discover the same hook
files, every hook executes twice per event — and with context injection
wired, each contextModification would be injected twice.
Add a 'hooks' runtime config extension kind (in the default set, so the
CLI keeps core file hooks unchanged) and gate the bootstrap's file-hook
extension on it. The extension excludes 'hooks' at session start, so
its adapter — which also provides the hook status UI and the
hooksEnabled setting — is its single execution path.
* fix(vscode): discover hooks from the session workspace, not only global state
Hook discovery read workspaceRoots from global state shared across
every Cline instance, so another window repointing it made workspace
hooks silently stop being discovered. With the extension's adapter now
the single hook execution layer, that meant no hooks at all.
HookFactory takes an optional sessionWorkspaceRoot and unions that
root's .clinerules/hooks into discovery (and into cwd resolution), fed
from the session config's cwd. Shared-state discovery still works, so
behavior in the single-window case is unchanged.
* fix(hooks): keep sanitized hook attribute values distinguishable
Replacing every markup delimiter with the same underscore could
collapse two tool call ids that differ only by such a character into
identical stamps. Escape each delimiter with a distinct token instead.
* fix(hooks): make hook attribute sanitization injective
Escaping the underscore itself turns the attribute escaping into a
uniquely decodable code, so no two distinct tool call ids can collapse
to the same sanitized stamp (previously an id containing a literal
escape token could collide with an id containing the delimiter).
* fix(vscode): reconstruct hook status rows when replaying transcripts
hook_status messages are emitted live but never persisted, so reloading
a session dropped every hook row. The injected <hook_context> blocks
carry the hook source and tool name, so the replay translator now
rebuilds a completed hook status row from each block. The injection is
also no longer treated as a user turn boundary, so the final turn's
completion retag is unaffected by it.
The job sets working-directory: apps/vscode, but that default applies to run
steps only, not to `uses:` steps. Since #10961 moved the extension under apps/
and added that default, the artifact path has resolved against the repo root,
matched nothing, and every failing run logged "No files were found with the
provided path: test-results/playwright/" instead of uploading recordings.
Widen to test-results/ so Playwright's error-context snapshots ship alongside
the videos.
The ui-publish smoke check pins a set of Tailwind candidates the packed
sources must emit; #13410 grew the SearchCombobox options list from
max-h-56 to max-h-64, so the publish run failed on the stale candidate.
All other pinned candidates verified against the current sources.
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* feat(desktop): recommended-feed badges and descriptions in provider settings
Review suggestion on #13410: the provider settings page has room for
more model detail than the composer's picker. The cline/cline-pass
provider cards now refresh their model list through
list_provider_models (the catalog snapshot deliberately skips the
recommended-feed overlay so the startup catalog fetch never blocks on
the feed) and render Recommended/Free tier badges plus feed tags (NEW)
next to the model name, with the model description underneath. The
refreshed list also surfaces the live entries instead of the bundled
snapshot.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): scope the settings featured model list to its provider and revision
The fetched featured list was unscoped component state: switching
between cline and cline-pass reused the component instance, so the
previous provider's models stayed visible while the new request was
pending (or forever, when it failed), and the retained copy shadowed
later provider.modelList updates — adding a second custom model
submitted the stale list as the complete configuration and dropped the
first addition.
The fetched list now only applies to the provider and modelList
revision it was fetched for (falling back to the catalog snapshot
otherwise and refetching on membership changes), and add-model submits
the union of the displayed and configured ids so an update can never
silently unconfigure existing entries.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Unify desktop plugins, apps, MCP, and skills into one Plugins hub with a Browse directory mode
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Open the marketplace directory as a modal over the Plugins hub instead of swapping the page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename directory to Marketplace: Browse Marketplace button, Marketplace modal title with icon, search placeholder
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix search input focus ring clipped by the Marketplace modal scroll container
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Address Greptile review: keep selected tag chip visible when its count drops to zero, and remount installed tab when a marketplace install completes after the modal closed
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Track marketplace modal mutation flag in a ref so a close click racing a queued render cannot skip the inventory remount
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make Marketplace its own settings page under Customizations and restore Channels as a standalone page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Remove icon from Marketplace page header for consistency with other settings pages
* Notify mounted inventory views when the marketplace invalidates the cache so late install completions refresh the Plugins hub
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix @ file mentions breaking on paths with spaces
Quote mentions generated by getFileMentionFromPath (Add to Cline /
Fix / Explain / Improve commands) when the relative path contains
spaces, so the mention regex no longer truncates the path at the
first space. Also quote the path part of workspace-prefixed mentions
(workspace:/path with spaces) inserted from the @ context menu, which
previously bypassed quoting because the value does not start with '/'.
Fixes#13338
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix import ordering in mentions test (biome organize imports)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reduce fix to minimal scope
Revert the webview quoting refactor and extra tests; keep only the
getFileMentionFromPath quoting fix with a single regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Normalize mention paths to posix separators for Windows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix code actions failing with 'command not found' on VS Code 1.134
Code action commands carried arguments (expandedRange, diagnostics),
which routes them through VS Code's CommandsConverter cache. VS Code
1.134 disposes the cached entries before the clicked action executes,
so every lightbulb action failed with 'Actual command not found,
wanted to execute cline.addToChat'.
Drop the arguments so the command id is passed through directly, and
recover the context in the handler instead: getContextForCommand now
expands an empty selection by 3 surrounding lines (matching the old
provider behavior) and gathers document diagnostics intersecting the
range when none are passed explicitly.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Scope gathered diagnostics to the selection/cursor
Match the old CodeActionContext.diagnostics behavior: only include
diagnostics intersecting the range the action was requested for, not
the surrounding lines the text gets expanded to.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix @ file mentions breaking on paths with spaces
Quote mentions generated by getFileMentionFromPath (Add to Cline /
Fix / Explain / Improve commands) when the relative path contains
spaces, so the mention regex no longer truncates the path at the
first space. Also quote the path part of workspace-prefixed mentions
(workspace:/path with spaces) inserted from the @ context menu, which
previously bypassed quoting because the value does not start with '/'.
Fixes#13338
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix import ordering in mentions test (biome organize imports)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reduce fix to minimal scope
Revert the webview quoting refactor and extra tests; keep only the
getFileMentionFromPath quoting fix with a single regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Normalize mention paths to posix separators for Windows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't show 'No sessions found' while session history is still loading
Replace the isLoadingHistory flag with hasLoadedHistory, set only once the
backend has actually answered a list_discovered_sessions request. The sidebar
and Sessions view now keep their loading state until that first definitive
response, so the empty-state copy can no longer appear while history is still
being fetched (or while a failed fetch is being retried).
Also retry a failed initial fetch on the 2s event cadence instead of stranding
the UI until the 12s periodic poll, which is what stretched the misleading
empty state to ~10 seconds after a webview reload when the websocket lost the
race with the page load.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop history fast-retry from re-arming after hook unmount
A failed initial fetch that settles after the hook unmounted could schedule a
new retry timer after cleanup had already cleared the refs, leaving the
abandoned hook polling the backend every 2s. Guard scheduleRefresh with a
disposed ref set by the mount effect's cleanup.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): anchor work summary duration on the answer row, not attached pre-tool reasoning
The collapsed 'Worked for Xs' row undercounted wall time whenever a turn's
assistant message contained thinking + tool_use with no narration text: the
canonical projection emitted the reasoning-only row after the tool row (both
stamped before the tool executed), the webview attached that row to the final
answer, and collapseCompletedWork used the answer's earliest attached
reasoning timestamp as the end anchor - excluding the entire tool execution
(e.g. 'Worked for 5s' for a turn with an 8s command).
- webview: end the work span at the answer row's own timestamp, clamped to
the last collapsed row so a fallback answer bubble with a synthetic early
timestamp cannot shrink the duration either
- sidecar: flush pending thinking before a tool_use row so rehydrated
transcripts keep the live-stream order (thinking before its tool call) and
pre-tool reasoning no longer rides on the next answer
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep interleaved thinking between the tool calls it separates
Address Greptile review: when one assistant message interleaves thinking
between multiple tool_use blocks, each reasoning segment now projects at its
own position (attached to a text row from its own segment when present,
otherwise as its own row) instead of merging into the first reasoning row,
which displayed later thinking before a tool call it actually followed.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
streamdown renders each Shiki token line as a bare inline span with no
newline text between non-empty lines, and only applies its block line
class when lineNumbers is on. With lineNumbers off (the desktop app's
config) every multi-line fenced block collapsed into one run-on line.
Make the direct line spans under code-block-body display: block in the
shared markdown.css; empty lines keep their height via their lone "\n"
child under white-space: pre.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): treat ClinePass as OAuth-managed in chat credential gate
ClinePass shares the Cline account OAuth credentials (its auth handler
stores under the "cline" provider), so the webview never sees a plain
API key for it. The chat pre-flight check only exempted cline/oca/
openai-codex, so switching to ClinePass while signed in via OAuth
blocked with "Missing API key" even though the sidecar resolves the
stored access token fine (which is why the CLI worked).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style: format helpers.test.ts with biome
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): surface provider-executed tool activity as observational events
Provider-executed tool parts (e.g. every tool the Claude Code CLI runs
inside its own session) were dropped by the model-tool guard added for
web search: only declared model tools were re-emitted, everything else
hit continue with nothing yielded. Those sessions modified the workspace
with no tool activity in runtime events, transcripts, or the UI.
Route all providerExecuted parts onto the observational path instead:
emit execution-tagged tool-call-delta and tool-result events, matched by
tool-call ID for providers that omit the flag on the result half. They
stay out of AgentRuntime's execution/approval loop, and the runtime
already persists them as modelToolActivities and projects them for
display.
The AgentModelEvent tool-result variant widens toolName from
ModelToolName to string to carry the provider's own tool names.
* fix(agents): keep turns that are only provider-executed tool activity
A turn consisting solely of observational tool activity has an empty
assistant content array - the activity lives in message metadata, since
projecting it into content would replay tool_use blocks the model never
gets results for. The empty-content guard threw on such turns, erroring
the run and losing the activity from the transcript. Count model-tool
activity as content for the emptiness check (error finishes still
throw); replay stays safe through the codec's empty-content placeholder.
Also drop the trailing text delta from one gateway test so the tool-only
stream shape stays covered end to end.
Publish workflows now always do clean npm installs (no dependency
cache in their test gates), the e2e workflow's cache keys are
exact-match only, and the e2e job drops an id-token permission it
never used.
Slack section blocks reject text longer than 3000 characters. The Slack
action logs that rejection as ##[error] but does not fail the step, so an
over-long changelog drops the release announcement while the run stays
green — cline@3.0.50 (3272 chars) published to npm, tagged, and cut a
GitHub release with no Slack post and nothing red to notice.
Every publish workflow pasted the changelog section verbatim into one
section block, so all six were exposed; the SDK, desktop, and extension
sections were only 150-350 chars under the ceiling.
Add a slack_content output alongside content: unchanged when the section
fits, otherwise trimmed on a line boundary with a link to the full
release notes. Only the Slack payload uses it — GitHub release bodies and
the desktop updater manifest still get the whole section.
* fix(shared): run PowerShell commands with fail-fast error semantics
The run_commands PowerShell wrapper never set $ErrorActionPreference, so
the default 'Continue' applied: a pipeline erroring per item (e.g. a
malformed Where-Object over Get-ChildItem -Recurse) emitted one error
record per enumerated file - tens of thousands of stderr records on
large trees, looking like a hang - and could still resolve as SUCCESS
with exit 0.
Prepend $ErrorActionPreference='Stop'; to the script content executed
by the ScriptBlock so the first error terminates the command with a
non-zero exit and a single error message. Concatenated on the same line
as the user command so error line numbers stay unshifted.
Fixes#13285
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(shared): set the fail-fast preference in the bootstrap scope
Setting $ErrorActionPreference='Stop' by string-prepending it into the
scriptblock source displaced a leading param(...) from its mandatory
first-statement position, so scripts beginning with a param block failed
with CommandNotFoundException. Preference variables are dynamically
scoped, so setting Stop in the -Command bootstrap gives the invoked
scriptblock identical fail-fast semantics while keeping the user script
byte-identical (param works, error positions unshifted) and drops the
doubled-quote escaping.
* docs(shared): document the fail-fast tradeoffs in the PowerShell wrapper
Stop promotes every non-terminating error, not only per-item pipeline
floods: partial-result commands (recursive listings over access-denied
junctions) now stop at their first error, and Windows PowerShell 5.1
turns in-script stderr redirection of succeeding native commands fatal.
State this in the wrapper comment as a deliberate tradeoff, with the
GitHub Actions precedent and the per-command opt-outs.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
Turns that settle through the event stream (queued prompts, including the
first prompt of a fresh session) resolve their send() RPC early, so nothing
cleared the streaming shimmer or reconciled live-streamed content against
the persisted transcript at turn end. A turn whose deltas were incomplete
stayed visually streaming forever and only healed when a later non-queued
send rehydrated history.
chat_done (and chat_session_ended / the queue-drain double check) now clears
the active assistant streaming id and schedules a short-delayed
read_session_messages + applyCanonicalHistory, guarded by turn epoch,
session id, and in-flight send submissions so it never clobbers a newer
turn or duplicates the blocking send path's own finalization.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): show typed slash command instead of expanded skill markdown
The sidecar expands /skill and /workflow tokens into their instructions
before dispatching, so the runtime's persisted transcript only contains the
expanded text. After a turn (and when reopening a session) the webview
re-hydrates from that history and rendered the whole SKILL.md body as the
user's message; queue events echoing the expanded prompt could also add a
second user bubble, and fresh sessions were titled with the markdown's first
line. The CLI never shows this because its TUI keeps the typed text in its
own transcript and only sends the expanded prompt to the model.
Mirror that separation inside the desktop sidecar's display boundaries:
- history projection (readSessionMessages) inverts user text that starts
with a configured command's instructions back to '/name remainder',
which also repairs sessions recorded before this fix
- queue snapshots and chat_queued_prompt_start events echo the typed
prompt recorded at expansion time, so the webview's optimistic-bubble
re-key matches again
- an untitled session sent an expanded prompt gets titled from the typed
command instead of the instructions' first line
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't overwrite a mid-turn rename with the typed-command title
The untitled check ran before dispatch, so renaming a fresh slash-command
session while its first turn was running got clobbered by the post-turn
typed-command title. Re-check at write time and only replace a missing title
or the one the runtime auto-derived from the expanded prompt.
Also documents the inherent prefix-inversion ambiguity flagged in review:
text hand-typed with a command's exact instructions persists byte-identically
to that command's expansion, so stored history alone cannot distinguish them.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop expanding skill commands; let the skills tool load them
Pasting the skill body into the prompt is why the transcript could ever show
it: the desktop webview re-hydrates from the runtime's persisted history, so
whatever the sidecar splices into the user message renders as if the user
typed it. The runtime already registers the skills tool, whose description
requires the model to invoke it whenever the user references a slash command
— so send the typed /skill text through and let the tool deliver the
instructions as a tool result (previously they arrived twice: pasted and via
the tool). The persisted user message, session title, and queue entries are
then simply the typed command, which deletes the typed-prompt registry, the
queue event/snapshot rewriting, and the title machinery from the previous
approach.
Workflows are not served by the skills tool and keep textual expansion, so
the read-time display inverter stays: it collapses expanded workflow prompts
— and skill prompts persisted before this change — back to the typed
/command in the history projection.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): option to keep skill slash commands typed for the skills tool
resolveRuntimeSlashCommandFromWatcher (and the hub snapshot proxy) accept
expandSkillCommands: hosts whose sessions register the skills tool pass
false so the typed /skill goes through and the model loads the instructions
as a tool result, keeping the persisted transcript as what the user typed.
Workflows always expand — the tool does not serve them. isSkillsToolAvailable
exposes the catalog check hosts use to decide (yolo preset and the skills
tool toggle leave textual expansion as the only delivery path).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): skill slash commands load via the skills tool instead of expanding
The TUI user-command wrap and buildUserInputMessage now keep a typed /skill
as-is when the session's mode/toggles register the skills tool, matching the
desktop app; workflows keep expanding, and yolo (zen) keeps expanding skills
because its preset has no skills tool. This also fixes CLI resume/history
surfaces showing the skill body: the persisted user message is now the typed
command.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): keep configured skill slash commands typed for the skills tool
expandSlashCommands no longer splices a configured skill's instructions into
the model text; the SDK session's skills tool delivers them as a tool result
(previously they arrived twice). Builtin pseudo-skills like /deep-planning
are not served by that tool and keep expanding, as do workflows.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): use the shared skill-expansion option in the sidecar
Replaces the sidecar's workflow-detection dance with core's
expandSkillCommands option and gates on isSkillsToolAvailable, restoring
textual expansion where the tool is missing (yolo mode or the skills tool
toggle) — a gap in the previous desktop-only change.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): drop the display inverter for expanded transcripts
Accepted trade-off to keep the change minimal: sessions recorded before
skills switched to the skills tool, workflow sends (deprecated), and
yolo-mode skill sends persist expanded instructions and now render that text
as-is instead of being collapsed back to the typed /command at projection
time.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): make TUI dialog colors follow theme changes live
Dialog content previously read the static palette constant, so open
dialogs (including the theme picker itself) kept the default dark-blue
accents while scrolling through theme previews. Add getDialogPalette /
useDialogPalette, which resolve dialog colors from the active theme's
dialog accents and re-render on every theme change, and migrate all
dialog-rendered components to it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(cli): derive dialog panel background from the active theme
Dark themes now lift their own background one OKLAB step for the dialog
surface, so panels keep the theme's hue instead of the library's fixed
#262626. DialogThemeSync pushes the surface into the dialog container
for new dialogs and repaints open panels, so the surface also follows
live theme previews. Light themes keep the neutral dark panel to match
the dark accent fallback and the light-on-dark dialog text.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: run_commands object form without args routes through the shell
The structured { command, args? } form of run_commands was always spawned
directly with shell: false. When a model emitted a full command line in
command with no args (e.g. { command: "echo hello" }), spawn failed with
ENOENT for any command containing a space, breaking command execution for
the whole session.
Direct exec now only applies when a non-empty args list is provided; the
object form without args is routed through getShellInvocation like the
string form. Schema descriptions are tightened so models put arguments in
args instead of embedding them in command.
Fixes#13279
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: trim structured-command schema descriptions
The union schema is only used for lenient validation of input the model
already sent; its descriptions never reach a model prompt. Keep them
short instead of restating executor behavior.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: simplify direct-exec comment in shell executor
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert: keep original structured-command schema description
The description never reaches a model prompt and the executor now handles
both shapes, so the wording change was cosmetic noise.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: gate direct exec on args key presence, not array length
Review feedback: an explicit empty args array is intentionally structured
input and stays direct exec; only an object with no args key is treated
as a full shell command line. Matches the key-presence rule already used
by the VS Code host's formatCommandForTerminal. Also replaces the
empty-args shell test (which was PowerShell-incompatible) with a test
pinning the direct-exec contract.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
@cline/ui's generated-media imports @cline/shared/browser, which resolves to
shared's dist output. The build-shared step sat after typecheck/test/build,
so the first ui-publish dispatch since #13025 failed at Typecheck UI with
TS2307. Move the step to right after install.
* feat(ui): share the markdown pipeline, chat polish CSS, and ThinkingBlock
The desktop app and the cloud dashboard both consume @cline/ui yet rendered
assistant output differently, because Markdown policy and the thinking-trace
row lived app-side. This moves the shareable parts into the package:
- components/markdown (new export): the lazy Shiki code highlighter (GitHub
light/dark, pinned language set) and agentMarkdownControls — the standard
Streamdown configuration. streamdown/shiki/@shikijs/* become optional peer
dependencies, mirroring @pierre/diffs.
- components/markdown.css: the desktop's chat polish moves in — chat-scale
headings, outside list markers, single quiet code blocks with a
hover-revealed copy control, table cards. Kept unlayered so it beats
Streamdown's layered Tailwind utilities without !important.
- ThinkingBlock + formatThoughtLabel in agent-chat: the standard thinking
row (brain icon, Thinking/Thought-for-Ns label, streaming shimmer, rail
presentation, capped scrollable body). The shimmer and the
reasoning-hover-suppression rule move into agent-chat.css; triggers gain
the color transition the desktop applied locally.
Version bumps to 0.2.0-next.5 for the dashboard to pick up.
* refactor(desktop): consume shared markdown and thinking primitives from @cline/ui
The local Shiki highlighter, Streamdown controls, chat markdown polish CSS,
streaming-title shimmer, and reasoning hover-suppression rule are deleted in
favor of the @cline/ui versions (the highlighter test moves to the package's
suite). ReasoningBlock becomes a thin wrapper that hands MemoizedMarkdown to
the shared ThinkingBlock, and formatThoughtLabel re-exports from the package
so grouping code and tests keep their import path.
globals.css now imports @cline/ui/components/markdown.css (unlayered, so the
polish keeps beating Streamdown's layered utilities); the app keeps only what
is genuinely app-specific: link/image policy in markdown.tsx, selectability
rules, accent palettes, and the view-enter transition.
* style(ui/desktop): make thinking-trace prose legible
Thinking body text rendered too faint: plain muted-foreground plus the
desktop's font-thin weight. The shared thinking content now leans 75% of the
way back toward the body text color (still slightly de-emphasized), and the
desktop drops the thin font weight.
* feat(ui): add WorkActivity collapsed-run summary and float message actions as a pill
WorkActivity/WorkActivityTrigger/WorkActivityContent fold a finished agent
run's working rows (tool calls, thinking traces, narration) behind a single
"Worked for 4m 12s · 14 tool calls" disclosure built on the shared animated
disclosure primitives, with formatWorkActivityLabel/formatWorkDuration
exported for consumers.
Message hover actions no longer rely on the transcript reserving blank space
below each message: the action row is now a self-backed pill (border,
blurred background, shadow) that floats over whatever follows, so
conversations can pack rows tightly without hover chrome colliding with the
next message.
* feat(desktop): collapse finished runs into a work summary and tighten chat spacing
collapseCompletedWork post-processes the grouped transcript: once a run ends
on assistant text with no further tool calls, its working rows fold into one
expandable WorkActivity row while the final answer stays visible. Runs are
delimited by user messages; the trailing run only collapses when the session
has stopped running and actually produced an answer, so live streams and
cancelled/failed tails keep their rows. Assistant messages carrying images
or media are treated as deliverables and never collapse.
The conversation list gap drops from gap-8 to gap-4 now that hover actions
are self-backed pills that need no reserved space, and user messages add
their own top margin so turn boundaries stay visually distinct.
* refactor(ui/desktop): work summary label wording, flat expansion, stable in-run rhythm
Feedback round on #13315:
- Label reads "Worked for 4m 12s and made 14 tool calls" instead of joining
with a dot; without a duration it falls back to "Made N tool calls".
- Expanded work rows render at transcript level — no rail or extra indent —
since tool rows and thinking traces already carry their own nesting when
expanded. The work content keeps the tight working-row rhythm.
- Live working rows (thinking traces + tool calls) now group into a 'run'
render item with the same tight 0.25rem rhythm, so there is no oversized
gap under a "Thought for Ns" row and every row keeps its exact position
when the finished run folds into the work summary. A trailing
answer-in-progress stays outside the group at transcript level, and pure
prose spans keep normal spacing.
- The transient "Thinking..." indicator moves inside the transcript column
and mirrors a trigger row's geometry, so the first real row replaces it in
place with no jump.
* style(ui/desktop): hover-pill metrics, right-pointing work chevron, scroll and spacing fixes
Another feedback round on #13315:
- Hover action pill: +2px internal padding, a trailing inset after the
timestamp (it sat flush against the pill border), and more clearance
between the message content and the pill (2px -> 6px; the hover bridge
grows to match).
- The work summary chevron points right while collapsed and continues
counterclockwise to point up when expanded.
- Conversation bottom padding drops pb-20 -> pb-8: the composer sits below
the scroller, so the padding only needs to clear a pinned action pill.
- Sending a message scrolls back to the bottom even if the reader had
scrolled up (new AutoScrollOnSend on the user-message count, which ignores
optimistic-bubble re-keying; @cline/ui now exports useConversation for
this).
- An assistant answer directly under its run's working rows pulls itself
0.5rem closer than the full transcript gap.
* style(desktop): leave a visible gap between a pinned action pill and the composer
pb-8 exactly matched the pill's ~40px footprint, so the last row's hover
actions sat flush against the composer top; pb-12 restores ~8px of daylight.
* style(desktop): widen the gap between the pinned action pill and the composer to ~24px
pb-12 left only ~8px of daylight under the pill; pb-16 reads comfortable
without reverting to pb-20's dead space.
* fix(desktop): keep the thinking indicator at the working-row offset mid-run
The indicator matched a trigger row's geometry but sat a full transcript gap
(1rem) below the last working row, while the tool/thinking row replacing it
joins the tight run group at 0.25rem — a visible upward jump. When the last
transcript item is working rows (or streamed assistant output), the
indicator now pulls up to the same tight offset; only at the start of a run,
under the user message, does it keep the normal gap.
* style(ui): calm the hover actions surface per team feedback
Borderless rectangle instead of the bordered pill: radius drops to
var(--radius), the side padding goes entirely (the icon buttons carry their
own hit areas), and the vertical padding halves. Blurred background and
shadow stay so it remains legible over following content.
* feat(ui/desktop): full-band hover reveal and iOS-style disclosure easing
The hover actions only appeared while the pointer was inside the message
box itself. The invisible bridge under each message now spans the full
height of the band the floating actions occupy (full row width), so
hovering anywhere in that strip reveals them. Sibling row types
(.cline-chat-tool, .cline-chat-work, and the desktop's run/tool groups)
become position: relative so they paint above the bridge — their own
content keeps its hover and clicks, and the bridge only wins in the band's
genuinely empty space.
All expandable rows (work summary, tool panels, thinking) open and close on
a 240ms symmetric ease-in-out cubic-bezier instead of the 60ms snap, with
chevron rotation on the same curve. Reduced-motion still disables both.
* revert(ui/desktop): drop the full-band hover reveal; quicken disclosure easing to 180ms
The full-band hover bridge (and the position: relative changes that made it
safe) is reverted per feedback — back to the narrow bridge that only spans
the gap under the message. The iOS-style ease-in-out on disclosures stays
but speeds up from 240ms to 180ms.
* fix(ui): recover live tool diffs that mount as a blank pierre skeleton
Live-streamed edit rows could show an empty diff for the whole run, with the
diff only appearing after the collapsed work row was expanded (fresh mount).
Root cause, confirmed by driving a live session and inspecting the element:
React StrictMode double-invokes @pierre/diffs' ref callback; the first
instance's async highlight work aborts on its immediate cleanup, and the
second instance adopts the abandoned half-rendered shadow tree as if it were
complete prerendered output — zero height, no code, no theme stylesheet,
permanently. A rendered diff always carries style[data-theme-css] in its
shadow root, so ToolFileDiff now checks for it shortly after mount and
remounts FileDiff (bounded attempts) when missing; the fresh host element
takes the normal render path and recovers within ~400ms. Verified live: the
diff now renders during the run.
* fix(desktop): keep interrupted runs expanded even with partial trailing text
The trailing-run collapse gated on 'ended with assistant text', which
misread a Stop that landed mid-answer as a finished run and folded the tool
calls the user wants to inspect. The gate is now the terminal status itself:
only completed (or restored-idle) sessions collapse the trailing run;
cancelled/failed/error tails keep their rows regardless of partial text.
(Greptile P1 on #13315 — matches the PR's stated rule.)
* feat(desktop): show provider web-search support under the settings toggle
The global Web search toggle silently does nothing unless the session's
provider offers native web search, which made the setting read as if it
worked with any provider. The desktop General settings row now explains
that only providers with built-in web search honor it, and shows a live
status line: which connected providers are ready to use it (no extra
setup needed), or an amber warning with a link to the Models section
when none of them support it.
Support is resolved in the webview via a new providerOffersModelTool
helper in @cline/llms (browser export), sharing the same builtin-manifest
source of truth as the runtime's supportsModelTool attachment check.
* fix(desktop): address review — refetch web-search status on catalog invalidation, clarify per-model support
Greptile P2: the one-time catalog fetch could race an in-flight provider
save and show stale status; the row now refetches when the provider
catalog cache is invalidated (fired after saves complete).
Greptile P1: the ready line implied every model on the provider works;
Vertex excludes Claude routes, so the copy now scopes the promise to
models that support it.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched
Toggling an auto-approve setting while a task is open writes
autoApprovalSettings into the StateManager's task-settings overlay
(updateAutoApprovalSettings -> setTaskSettings). The SDK controller never
cleared that overlay on clearTask/showTaskWithId (the legacy controller
did), so after New Task the stale overlay kept shadowing global settings
in getGlobalSettingsKey(): toggle RPCs were accepted into global state,
but every posted state still carried the overlay's old version, which the
webview rejects as not newer - the auto-approve checkboxes froze forever.
Restore legacy parity in SdkTaskControlCoordinator: drop the overlay
(persisting pending writes first) in clearTask() and before installing a
different task's proxy in showTaskWithId().
Fixes#13260
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* changeset
* test(vscode): add end-to-end regression test for auto-approve freeze after New Task
Wires the real StateManager, the real updateAutoApprovalSettings handler,
and the real SdkTaskControlCoordinator.clearTask() together with the
webview's version gate modeled on ExtensionStateContext, pinning the
end-to-end invariant behind #13260: checkbox toggles must keep reaching
the webview after a mid-task toggle followed by New Task. Verified the
test fails when the clearTaskSettings() call is removed from clearTask().
* fix implicit any in regression test
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
The branch dispatch input was a free-form string with no validation. Both
jobs checked it out and ran full npm lifecycle scripts from it: the publish
job next to VSCE_PAT/OVSX_PAT (and npm run publish:marketplace executes a
script from that same ref with the PATs in env), and the test job with NO
environment approval at all while inheriting the workflow-level
contents/packages/checks/pull-requests write grants. A dispatch pointing at
e.g. refs/pull/N/head would run outside-contributor code with the
marketplace keys behind one approval, or with a repo-write token behind
none.
Remove the input and hardcode the protected legacy-extension branch, drop
the workflow-level permissions to contents: read, and elevate only the
publish job to contents: write (tag push + GitHub release). The branch
input's default was legacy-extension, so normal publishes are unchanged.
publish-extension skill dispatch command updated to match.
The combined-VSIX workflow took legacy-ref as a free-form dispatch input
with no publish-time validation (next-ref has one: publish requires main).
Any typed ref — a PR merge ref, an unprotected branch — would be built
into the published VSIX by the environment-less build job, and the publish
environment approver only ever sees an opaque prebuilt artifact, so the
approval protected the marketplace PAT but not the shipped bytes.
Remove the input entirely and hardcode the protected legacy-extension
branch, which makes that branch's protection rules load-bearing for
releases. The tested-sha pinning between test-legacy and build is
unchanged. publish-extension skill dispatch command updated to match.
* fix(vscode): point Mistral signup URL at the general API keys console
The Mistral provider's signup link led to the Codestral console, which
issues Codestral-scoped keys that fail with 401 on api.mistral.ai — the
endpoint the provider actually calls. Point it at the general API keys
page instead.
Fixes#13288
* fix(vscode): deep-link DeepSeek and Fireworks signup URLs to their API key pages
Both pointed at marketing homepages; link straight to the key-creation
pages instead, matching the rest of the registry and the desktop app's
provider-key-urls map.
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilt the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
The preserved conversation history is the source of truth on resume, so
the fallback prompt now just asks the model to reassess the history and
continue, matching the legacy resume prompt which also never resent the
original task. User-typed text still takes precedence when provided.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): continue the surviving idle session on follow-ups instead of rebuilding
Stopping a turn keeps the session alive, but every idle follow-up (bare
Resume after Stop, and typed follow-ups after a completed turn) tore
that session down and rebuilt it from persisted task history before
sending. Continue the matching idle session in place instead, the same
way the CLI reuses the live session after an abort. Rebuilding from
history now only happens when no live session matches the displayed
task (task opened from history, extension host reload).
A bare resume still needs a prompt to start a turn, so it sends the
neutral [TASK RESUMPTION] prompt (shared with the rebuild fallback and
hidden from the transcript); user-typed content is echoed and sent
as-is. If the send lands while the abort is still settling, the runtime
auto-queues it and drains once the abort completes.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): consolidate follow-up send paths in SdkFollowupCoordinator
Now that idle follow-ups continue the live session in place, the
two-mode sendToActiveSession helper was redundant: its non-queued branch
duplicated continueIdleSession minus the bare-resume prompt. Split it
into a single-purpose queueToActiveSession and fold the idle no-task
send into continueIdleSession, flattening askResponse's decision tree
to: queue onto a running turn, continue a matching live idle session,
rebuild from history, or abandon.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): reuse the existing neutral resumption prompt for bare resumes
Drop the newly invented long resumption wording in favor of the phrase
that already existed as the no-history fallback and that the transcript
hiding logic and test fixtures recognize: '[TASK RESUMPTION] Please
continue where you left off.' The net change to resumeSessionFromTask
against main is now just deleting the branch that resubmitted
historyItem.task as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilds the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
Bare resumes now always use the neutral prompt that already existed as
the no-history fallback; user-typed text still takes precedence. This
matches the legacy resume prompt (responses.taskResumption), which only
ever included user-supplied text as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): hide synthetic prompts from the queued-prompt echo
A send that races a settling abort is auto-queued by the runtime, so a
bare Resume can reach the pending_prompt_submitted echo carrying the
synthetic [TASK RESUMPTION] prompt. Echoing it leaked model-facing text
as a visible user bubble and shifted the visible-user-message ordinals
that edit/regenerate mapping relies on. Filter synthetic prompts with
isSyntheticUserPrompt, keeping user attachments visible (matching
isSyntheticSdkUserMessage semantics).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
A restore that reuses the source session id rolled the workspace back but
left the persisted transcript describing the discarded turns, so the chat
kept showing turns whose file changes had just been reverted.
Before #13075 the restore reply carried the trimmed messages and the
webview rendered them directly. Now the webview always re-reads through
read_session_messages, which prefers the persisted file over the live
session, so the trimmed history the sidecar puts on the live session is
never read. Persist it as well.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Beta builds (prerelease versions from desktop-experimental, shipped as
'Cline Code Beta') now identify themselves everywhere users look: a Beta
pill in the sidebar footer, the product name in the sidebar hover card,
an About row in Settings > General with version + channel, the runtime
window title, and the tray menu/tooltip (via package_info, which carries
the overlay's productName).
Channel detection is a pure version-string check (-beta suffix) in the
new webview/lib/app-channel.ts — the version is baked into package.json
at build time and reported by the sidecar's get_process_context, so it
works in both the Tauri shell and web dev mode with no new plumbing.
Stable builds render no channel UI at all.
* feat(desktop): add beta release channel from desktop-experimental branch
Adds a 'channel' input (stable|beta) to desktop-publish.yml. Beta releases
are tagged desktop-vX.Y.Z-beta.N on the desktop-experimental branch, built
with the tauri.beta.conf.json overlay (Cline Code Beta / bot.cline.app.beta,
side-by-side install with stable), published as prerelease GitHub releases,
and served by a separate rolling desktop-beta update feed. Both channels
dispatch from main so the PublishDesktop signing gates are unchanged.
Guards: stable channel now rejects prerelease tags (previously a beta tag
could clobber desktop-latest and auto-update every stable install onto it),
feed selection is fail-closed and cross-checked in the release job, and the
build asserts the compiled binary embeds exactly its own channel's feed URL.
Changelog extraction is exact-version now that stable and beta sections
interleave across branch merges.
Process doc in apps/examples/desktop-app/EXPERIMENTAL.md; publish-desktop
skill now asks stable-or-beta.
* docs(desktop): warn against renaming the desktop-latest feed
* docs(desktop): document the code-trust model for publish approvals
The beta dispatch-from-main invariant protects the workflow definition, not
the checked-out tag's build scripts, which run with signing secrets in scope
for stable and beta alike. Make explicit that the PublishDesktop reviewer
approval is the trust gate for that code, and that desktop-experimental
therefore needs main-grade merge controls.
* feat: add image generation support
* fix(llms): preserve mixed image model behavior
* fix(llms): validate generated image models
* fix(llms): preserve mixed image response streaming
* fix(llms): preserve runtime tool ownership
* fix(llms): address image generation review feedback
* fix(desktop): relay images for attached hub sessions
* chore(llms): regenerate provider and model catalog
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(vscode): preserve SDK model capabilities across the catalog boundary
The new modelSupportsToolCalling gate treats a populated capability list
without "tools" as authoritative. But the VS Code host round-trips model
metadata through the legacy ModelInfo shape, and toSdkModelInfo
reconstructed capability arrays from the legacy booleans alone — which
have no "tools" projection. Every model with any capability flag set
came back as "cannot call tools", so sessions registered zero tools and
the file-edit e2e failed on all platforms (the editor tool call resolved
to "Unknown tool" and the edit never reached disk).
Fix, following the modalities-passthrough pattern so stacked capability
PRs can reuse it:
- Preserve the SDK capability list verbatim on legacy ModelInfo at the
catalog boundary (adaptSdkModelInfo); union user overrides into it
without ever fabricating a list from overrides alone.
- Seed toSdkModelInfo from the preserved list, and when none survived,
emit an explicit "tools" signal (honoring legacy supportsTools=false)
so reconstructed arrays can never silently disable tool calling.
- Add a shared modelHasCapability(model, capability,
{assumeWhenUnspecified}) helper: missing or empty capability lists
carry no signal and each check declares its own default. Future
capability gates should route through it instead of reading
model.capabilities directly.
Verified: file-edit e2e (Single Root + Multi-Roots) passes locally;
shared/core/llms/model-catalog/session-factory suites and typechecks
pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(llms): refresh generated model catalog
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The Vertex case asserted that a bare providerId resolves to a model
without web search, which only held because the generated catalog's
Vertex default happened to be a Claude route. models.dev has since moved
that default to gemini-3.7-flash, which does support native web search,
so the assertion failed on the next catalog regen while the behavior it
guarded was unchanged.
Drop the catalog-dependent case and cover the default-model fallback
against a synthetic manifest instead, where the excluded route is stated
by the test rather than inherited from upstream data.
The outdated_hub notice reports a state the user cannot act on: this CLI
is already the newer build, the Hub is behind only because retiring it
would kill the sessions it is serving, and the swap happens on its own at
the next launch. A toast that interrupts to say "no action needed" is
still an interruption, and the desktop surface already concluded the same
thing by rendering nothing for this reason.
It also could not deliver the message it existed for. Toast caps at
maxWidth = Math.min(44, width - 4), and the 61-character string did not
wrap, so what actually rendered was "Update finishes the next time Cline"
- a sentence cut off before the reassuring half. Identical at 120 and 200
columns, so widening the terminal did not help.
The classification stays in core and still earns its keep at this call
site: outdated_hub is what stops the update-and-restart prompt from
firing at someone who has nothing to update. Only the rendering goes.
The build_mismatch direction, where the user does have something to do,
is untouched.
Render assistant markdown with internalBlockMode="top-level" so each
top-level markdown block gets its own renderable. The default coalesced
mode merged the entire message into one block that was rebuilt and
re-highlighted on every streamed chunk, flashing settled headings and
links back to raw uncolored markdown (visible ###, unconcealed syntax)
until the async tree-sitter highlight landed, and re-wrapping rows so
the transcript jumped vertically.
Top-level blocks are reused by token identity, so settled content never
re-renders; only the trailing unstable block updates per chunk. Pass
tableOptions style=grid to keep the bordered table rendering coalesced
mode used by default.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): stop concurrent Hub installs from retiring each other
Two Cline installations on different builds would shut each other's Hub
daemon down in a loop, and every session died with an abnormal socket
close (code=1006) as its daemon was killed mid-handshake.
The retire decision was a one-sided predicate: each client independently
asked "may I reuse this Hub?", and two clients on differing builds both
answered no. #13177 added build-epoch ordering to break the tie, but left
every unordered case - missing epoch, missing build id - retiring as
before, so any pair involving a build from before epochs were embedded
still looped.
Derive the decision from a total order instead. compareHubBuilds orders
two builds by embedded epoch, then core release version, then build id,
and is antisymmetric by construction, so at most one side of a pair can
ever decide to retire. A Hub that is newer or cannot be ordered is
attached over the compatible wire protocol and left to the build-mismatch
watcher to prompt about. Genuine protocol incompatibility still replaces.
Identity is now read from the same fields on both sides. Filling in a
coreVersion locally that the wire record omits made a build's identity
depend on which role it was playing, and the two directions of a pair were
then decided by different tiers with both concluding they were newer -
a second, independent way to produce the loop.
Also:
- Scope the development Hub owner by build id, so differing dev builds run
their own daemon side by side instead of contending for one record.
Production keeps its singleton.
- Break the circuit after repeated retirements of the same URL, bounding
any future ordering bug to a stale-build prompt rather than an
unusable Hub.
- Report `cline doctor fix` honestly: separate processes that survived a
kill from ones that appeared while the fix ran, name the live parent
respawning a daemon, and mark a startup lock held by a running process
as held rather than leaked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(cli): only blame a live parent for processes seen during doctor fix
The advice printed under "started during fix" asserted that every such
process was respawned by a live parent, and told the user to go stop it. A
process can also start on its own mid-repair - someone opening a new
session - and then the instruction points at an unrelated process, or at
none at all.
Derive the wording from whether a live parent actually exists: name it
when every process has one, state the facts when none do, and split the
list when it is mixed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(core): order the builds in the stale-discovery hub server case
The case stubbed two build ids and expected the second to replace the
first, but supplied nothing that says which came first: no epochs, and both
servers report the same core version. Ordering therefore fell to the
build-id tiebreak, where "new-build" sorts before "old-build" and the
replacement was judged the older of the two.
Give the case the epochs its name implies, and add the missing sibling for
an unorderable pair, which is attached to rather than retired - the
behavior that keeps two installations from shutting each other down.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(core): defer replacing a Hub that is serving live sessions
Retiring a Hub kills its established WebSockets, so replacing one under a
running session ends that turn with an abnormal close (code=1006). The
replacement is correct - the newer build should own the Hub - but the
timing is not the user's to absorb mid-turn.
Defer instead while the Hub reports live sessions: the newer client
attaches to the older Hub over the compatible wire protocol, and the swap
happens once those sessions end. Attaching rather than spawning matters -
a second daemon would race the busy one for the port.
Deferring silently would be worse than the interruption it avoids, because
a long-lived session pins the Hub to old code indefinitely with nothing to
show for it. The build-mismatch watcher only ever prompted in the
direction where updating the client resolves the mismatch; its own comment
notes that older Hubs "are retired and replaced automatically, so
prompting would only flash a stale dialog", which stops being true once
replacement can be deferred.
Add the missing direction as `outdated_hub`, reported only when a mismatch
survives consecutive checks - an idle older Hub is replaced within moments
of being seen, so a single sighting would flash exactly the stale dialog
the original comment warns about. The CLI and desktop dialogs render it as
information rather than an update prompt: nothing to install, the Hub
swaps itself when the sessions end.
The direction is decided by compareHubBuilds rather than reusability,
because a Hub that is newer and one that carries too little metadata to
order are both "reusable" but need opposite advice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(core): key the outdated-Hub check by daemon instance, not build
The consecutive-sighting check that keeps a routine replacement from
flashing an informational dialog was keyed by build id. Two daemons from
the same build share one, so an outdated Hub replaced by another daemon of
the same older build satisfied the check and reported exactly the churn the
check exists to hide.
Carry a hubInstanceId on the mismatch event - the Hub's own id, falling
back to pid and start time - and key the pending sighting by it. A
replacement instance now restarts the count instead of confirming it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(core): source Hub instance identity from the discovery record
The instance id added in the previous commit was read from the probe
response, but the watcher probes without an auth token and `/health`
deliberately reports only build and address fields - no hubId, pid, or
startedAt. So the id was always undefined in production and the check it
guards still conflated two daemons of the same build. The test missed it by
injecting a hubId into a mocked probe, a shape `/health` never returns.
Take identity from the discovery record instead, which every daemon version
writes with all three fields and which a replacement daemon rewrites as its
own. The probe is still preferred when it does carry an id, since that is
the process just spoken to.
The tests now use the real `/health` payload shape and vary identity through
the discovery record, including the pid-and-start-time fallback for records
written before Hubs carried an id.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(core): confirm the Hub record still describes the daemon just probed
Instance identity is read from discovery before the probe and build data
comes back after it, so a daemon replaced between those two steps was
described with its predecessor's identity - the replacement then satisfied
the prior daemon's pending sighting and emitted the notification the
consecutive-instance check exists to suppress.
Re-read discovery after the probe and report nothing when the record no
longer describes the same daemon. A Hub mid-swap is churn; the next check
sees whatever it settles into.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* revert(core): drop the watcher instance-identity hardening
Reverts the three follow-up commits that keyed the outdated-hub
consecutive-sighting check by daemon instance (42a83beae, 931431371,
9d634f7b3). They guarded one scenario - a different daemon of the same
outdated build swapping in between two watcher ticks - where the only
consequence is an informational dialog showing one interval early or
late. The unauthenticated probe carries no instance fields in
production, which is why the first attempt needed two more patches; the
original reason+buildId consecutive-sighting suppression from this PR's
base commit already covers the case that matters (not flashing a dialog
for a hub that is mid-replacement).
* fix(core): only count sessions that stopping the hub would actually harm
hasActiveHubSessions treated every non-terminal status as busy. But a
session's hub-side runtime outlives its client: a TUI that is killed or
crashes never stops its session, which then sits in the hub with no
participants and a status that never reaches a terminal state. Under the
defer-while-busy rule that pinned the displaced hub as "serving
sessions" forever - it was never retired, every new CLI kept attaching
to the old build, and the outdated-hub dialog recurred with a promise
("replaced once those sessions end") that could never come true.
Verified empirically: a cleanly detached+disposed client leaves its
session status "running" indefinitely.
Busy now means: someone is attached (participants), or a turn may be
executing hub-side (running/pending, which covers headless and scheduled
runs). An idle session with a confirmed-empty participant list is
resumable persisted state, not live work. Hubs from core < 0.0.75 omit
the participants field entirely, so idle stays conservative (busy)
there - an attached client cannot be ruled out.
updatedAt-freshness was considered and rejected as the discriminator:
the sessions row only updates on status transitions, so a single long
agentic turn looks stale while genuinely executing.
* fix(core): gate hub busyness on attached participants only
Simplifies the busy-check to the one signal that cannot go stale:
participants are live socket subscriptions the hub drops the moment a
client's connection closes, so a crashed client can never leave a ghost
that counts as busy. Session status is deliberately not consulted - a
client killed mid-turn strands its session in a non-terminal status
forever, and QA reproduced that pinning an outdated hub as "serving
sessions" until reboot. This replaces the earlier status+participants
heuristic (and drops the aging bound it was growing) with the rule the
deferred-update design stated from the start: the hub is busy while a
client is connected to a session, and replaceable otherwise.
The accepted cost: a participant-less background run executing at the
exact moment of a hub swap dies with the old hub. Rare, and its next
scheduled tick runs normally on the replacement.
* fix(cli): tell the truth about when the outdated Hub is replaced
The outdated-hub dialog and toast said the Hub is replaced "once those
sessions end". It is not: nothing retires a hub except a fresh launch
running the ensure path, so a user who quits the busy session and
watches sees the old hub stay put and concludes something is stuck
(observed in hands-on QA). Say what actually happens - the newer build
takes over the next time Cline starts after those sessions end.
* fix(cli): speak to users, not architecture, in the pending-update notice
"Cline Hub is running an older build" assumes the reader knows what the
Hub is and why builds differ. The user-relevant facts are only: your
update is not fully active yet, your work is safe, and it finishes by
itself. Say exactly that, in both the TUI and desktop dialogs and the
toast, with the version tucked in parentheses for bug reports.
* fix(cli): drop the outdated-hub dialog for a single quiet toast
The dialog interrupted the user to say that nothing is wrong and no
action is needed - the ideal number of modals for that message is zero.
The TUI now shows one info toast ("Update finishes the next time Cline
starts. No action needed.") and the desktop app shows nothing for the
outdated_hub reason; both dialog components return to their shipped
update-and-restart form, which still appears for the build_mismatch
direction where the user genuinely has something to do. The watcher
keeps reporting outdated_hub - surfaces decide, core informs.
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(core): bridge protections for updates landing under pre-3.0.55 clients
Three pieces, each proven against real released artifacts:
- postinstall shield: CLI versions <= 3.0.54 restart the hub daemon after a
background auto-update even while it serves live sessions, and their
fingerprint check then rejects every replacement hub, bricking the running
TUI. That code is on users' machines and cannot be patched — but it runs
only after the install completes, and it bails out harmlessly when no hub
discovery record exists. The newly installed package's postinstall sets
the record aside so the old updater never fires.
- superseded-record fallback: the set-aside record is also the only source
of the auth token and pid the next new-build launch needs to retire the
displaced hub (a port probe carries neither); ensure reads it back.
- bind retry: a hub retired on the fixed port can hold it ~2s after acking
shutdown (watchdog force-exit); the replacement daemon retries EADDRINUSE
for up to 5s instead of dying and leaving no hub at all.
* fix(cli): defer auto-update install until no CLI is attached to the hub
Installing while cline processes run swaps the npm package under them:
their respawn paths break on the new build fingerprint, and the updater
then restarted the hub daemon out from under live sessions (the 'Hub
connection closed (code=1006)' incident). Guarding the restart treats the
symptom; the fix is to never install under a running process.
The startup check now only records that an update is available. The
install runs at process exit, and only when the hub confirms no other
cli* client is attached — desktop sidecars and connectors ship their own
binaries, so only cli* clients make the swap unsafe. With nothing old
running at install time, no hub restart is needed at all: the next launch
retires the stale hub through the existing ensure path. Deletes
restartHubServerIfRunning, ensureCliHubServerAfterUpdate, and their
support code; manual 'cline update' still installs immediately and now
just notes that the update applies on next start.
* fix(cli): apply deferred update from the entrypoint exit sequence
The CLI entrypoint always terminates with an explicit process.exit(),
which never emits beforeExit — the hook the deferred installer waited on,
so it would never have run (caught by review). Invoke applyDeferredUpdate
directly from the entrypoint's exit sequence after disposeAll(), where
every normal termination passes; crash paths deliberately skip it. Also
clear the pending update once an install spawns so the apply is
idempotent.
* test(cli): isolate unit tests from the real ~/.cline
A full vitest run could leave a real hub daemon running against the
developer's actual ~/.cline discovery record (observed while validating
this PR: a daemon spawned from the globally installed cline binary,
attached to the real data dir). Point CLINE_DIR, CLINE_DATA_DIR, and
CLINE_HUB_DISCOVERY_PATH at a per-worker temp dir and disable auto-update
before any test file loads; subprocesses inherit the isolation via env.
* fix(core): discard the superseded discovery record once consumed
The set-aside record is one-shot recovery metadata, but nothing deleted
it, and it feeds a pid into retireDiscoveredHub's SIGTERM. Weeks later a
launch that finds no live record (routine after any retirement) could
read the stale file and signal whatever process the OS recycled that pid
onto (review finding by @abeatrix). Unlink it at every ensure resolution
that ends with a live, verified hub; failure paths keep it for the next
attempt.
* fix(cli): harden the exit-time update gate
Three review findings on the deferred-apply path:
- A wedged hub could stall an otherwise-finished CLI for tens of seconds
via the hub client's default timeouts; the whole exit-time query is now
bounded to 3s, with timeout counting as attached (never install unless
the hub positively confirms).
- Sub-second commands exited before the startup version check resolved
and silently dropped the update every time for one-shot-only usage;
exit now grants the in-flight check a 250ms grace.
- client.list can lose a TUI's registration during transport churn while
its session connection survives, so an empty client list is not proof
of safety; cross-check sessions with participants. Participants rather
than session status: finished sessions linger idle forever and must
not pin updates, and participant-less scheduled runs live in the hub
process, which the binary swap does not touch. Verified live: a
session-holding client invisible to client.list defers the install,
and the gate opens once it disconnects.
* docs(cli): fix stale beforeExit reference in the exit-gate comment
* style(cli): apply biome formatting to update deferral code
* fix(cli): let doctor see a hub whose record the update shield set aside
During the shielded update window the discovery record is renamed to
.superseded so pre-3.0.55 updaters cannot restart a busy hub. Doctor
read only the primary record, so in that window it reported the live
daemon - the one serving the user's still-open old session - as a stale
hub daemon and advised 'cline doctor fix', which kills it and reproduces
the exact 1006 incident the shield exists to prevent (found by QA).
Doctor now falls back to the set-aside record the same way the ensure
path does, and doctor fix clears the set-aside file along with the
primary record so a deliberate reset does not leave stale retirement
metadata pointing at a recyclable pid.
* fix(core): keep shielded sessions on one Hub authority (#13244)
* fix(core): recover shielded busy hub discovery
* chore(core): instrument shielded hub recovery
* fix(core): recover shielded hubs with attached clients
* fix(cli): recognize shielded hubs in doctor
* refactor(core): keep shield recovery minimal
* fix(core): retain shared Hub idle helper semantics
* chore(core): align busyness helper with the #13231 wording
The participants-only hasActiveHubSessions here duplicates the change on
bee/hub-lifecycle (this branch needs its semantics for the participant
gate). Matching that version byte for byte lets the two merges resolve
cleanly instead of conflicting. Also restores the module-registry reset
comment this branch dropped - it documents a real local-vs-CI gotcha.
* fix(core): stop concurrent Hub installs from retiring each other
Two Cline installations on different builds would shut each other's Hub
daemon down in a loop, and every session died with an abnormal socket
close (code=1006) as its daemon was killed mid-handshake.
The retire decision was a one-sided predicate: each client independently
asked "may I reuse this Hub?", and two clients on differing builds both
answered no. #13177 added build-epoch ordering to break the tie, but left
every unordered case - missing epoch, missing build id - retiring as
before, so any pair involving a build from before epochs were embedded
still looped.
Derive the decision from a total order instead. compareHubBuilds orders
two builds by embedded epoch, then core release version, then build id,
and is antisymmetric by construction, so at most one side of a pair can
ever decide to retire. A Hub that is newer or cannot be ordered is
attached over the compatible wire protocol and left to the build-mismatch
watcher to prompt about. Genuine protocol incompatibility still replaces.
Identity is now read from the same fields on both sides. Filling in a
coreVersion locally that the wire record omits made a build's identity
depend on which role it was playing, and the two directions of a pair were
then decided by different tiers with both concluding they were newer -
a second, independent way to produce the loop.
Also:
- Scope the development Hub owner by build id, so differing dev builds run
their own daemon side by side instead of contending for one record.
Production keeps its singleton.
- Break the circuit after repeated retirements of the same URL, bounding
any future ordering bug to a stale-build prompt rather than an
unusable Hub.
- Report `cline doctor fix` honestly: separate processes that survived a
kill from ones that appeared while the fix ran, name the live parent
respawning a daemon, and mark a startup lock held by a running process
as held rather than leaked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(cli): only blame a live parent for processes seen during doctor fix
The advice printed under "started during fix" asserted that every such
process was respawned by a live parent, and told the user to go stop it. A
process can also start on its own mid-repair - someone opening a new
session - and then the instruction points at an unrelated process, or at
none at all.
Derive the wording from whether a live parent actually exists: name it
when every process has one, state the facts when none do, and split the
list when it is mixed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(core): order the builds in the stale-discovery hub server case
The case stubbed two build ids and expected the second to replace the
first, but supplied nothing that says which came first: no epochs, and both
servers report the same core version. Ordering therefore fell to the
build-id tiebreak, where "new-build" sorts before "old-build" and the
replacement was judged the older of the two.
Give the case the epochs its name implies, and add the missing sibling for
an unorderable pair, which is attached to rather than retired - the
behavior that keeps two installations from shutting each other down.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat(core, llms): Cline custom provider & web search
* fix(llms): preserve reasoning model token parameter
* fix(llms): keep ClinePass provider options on the wire in the shared Cline provider
The shared Cline provider hardcoded the AI SDK provider name to "cline",
but the openai-compatible model reads request-body passthrough options from
providerOptions[<name>]. Option routing emits ClinePass options under the
"cline-pass"/"clinePass" buckets, so gateway reasoning (extended thinking
budgets) silently stopped reaching the wire for cline-pass after it moved
off the generic openai-compatible module.
Thread the gateway provider id through as the provider name, and restore
strictJsonSchema: false for the new "cline" provider-options target so the
wire format matches the previous openai-compatible behavior. Add cline-pass
coverage at both the option-routing and request-body levels.
* feat(sdk): persist provider-executed tool activity (#13077)
* feat(core, llms): Cline custom provider & web search
* fix(llms): preserve reasoning model token parameter
* feat(sdk): persist provider-executed tool activity
* fix(vscode): restore state proto and settings section reverted by merge
The merge of origin/bee/websearch into this branch resolved conflicts by
keeping this branch's pre-#13126 copies of apps/vscode files, which
deleted the auto_approve_all_toggled = 174 proto field (without reserving
the number) and dropped a formatting line in FeatureSettingsSection.tsx.
Neither file is in scope for this PR. Restore both to main's content so
the proto source matches the checked-in generated code again.
* chore(vscode): match main byte-for-byte in FeatureSettingsSection.tsx
The pre-commit biome hook strips a blank line that exists on main, which
kept this out-of-scope file in the PR diff. Commit the exact main content
with --no-verify so the PR no longer touches apps/vscode at all.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* refactor(llms): key ClinePass provider options to the shared cline bucket
Both Cline gateway ids (cline and cline-pass) are served by the same
shared "cline" AI SDK provider and hit the same Cline API, so threading
the gateway provider id through as the AI SDK provider name (78dc6f3e7)
was unnecessary indirection. Revert the name threading and instead
normalize option-routing bucket keys: buildProviderAndAliasPatch now
keys both Cline gateway ids to the shared "cline" providerOptions
bucket, which is the only bucket the openai-compatible model reads for
request-body passthrough.
Also tighten the regression coverage that motivated the original fix:
the previous effort-based test rows were vacuously satisfied through the
portable-reasoning early return (effort reasoning never reaches provider
option buckets by design). The rows now use explicit reasoning budgets,
which do flow through the gateway bucket path, and the wire-level test
composes real provider options end to end instead of hand-feeding
buckets.
* revert(llms): drop the cline strictJsonSchema special case in generic-compatible
Restores buildCompatibleProviderOptions to its pre-78dc6f3e7 state. The
strictJsonSchema passthrough is verified inert for the gateway (nothing
in @cline/llms sets a response format), so keeping a hardcoded provider
target in the generic helper bought nothing. If structured outputs are
ever added, strictness for the cline target can be decided deliberately
then.
* fix(llms): claim native web search for openai-native, not the openai alias
supportsModelTool listed "openai", but that id aliases to
openai-compatible (PROVIDER_ID_ALIASES), whose module has no native web
search. The actual native OpenAI builtin id is "openai-native", which is
served by the OpenAI Responses module that does implement
buildModelTools with provider.tools.webSearch(). Without this, the
web_search tool was never offered to native OpenAI users, and was
wrongly offered for the compatible alias.
* refactor(llms): declare model tools in provider manifests
* feat(sdk): project provider tool activity in session history
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Picks up the shared button primitives (#13164), the one-row-per-tool-call
chat rendering (#13186), the refined session chat layout (#13205), and the
@pierre/diffs hunk renderer (#13201) that landed since 0.2.0-next.3.
* Render desktop diff view hunks with shared @pierre/diffs renderer
Replace DiffView's hand-rolled DiffHunk +/- line rows with ToolFileDiff
from @cline/ui (backed by @pierre/diffs), matching the chat tool rows.
Hunks carrying complete new contents (created files) render with real
line numbers; fragment hunks hide them, mirroring ToolCallRow. All of
DiffView's chrome (collapse, copy, open-in-editor, counts) is unchanged.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make ToolFileDiff syntax palette follow the app theme, not browser preference
@pierre/diffs declares 'color-scheme: light dark' on its shadow :host, so
its light-dark() token colors resolve from the browser's preferred scheme.
Apps themed by the .dark class (desktop app) got the light palette's
near-black text on dark surfaces. Inline colorScheme: inherit on the host
wins over the :host rule and follows the app's color-scheme, which the
@cline/ui theme already flips with .dark. Skipped when a caller pins an
explicit themeType.
Also key diff-view hunks by index so repeated same-shaped hunks (a file
created twice with identical contents) don't collide.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
The Claude Code provider was unusable for agentic work (#13146):
- The claude-code manifest lacked the provider-tools capability, so the
gateway sent Cline's tool definitions (which the provider drops as
unbridgeable) while the CLI's native tools stayed enabled with no
approval plumbing - every write was refused and no prompt appeared.
- ai-sdk-provider-claude-code defaults settingSources to [], so the
spawned session read neither ~/.claude/settings.json nor project
settings, silently ignoring user-configured permission rules.
- No cwd was passed, so the session inherited the extension host's
cwd (/ on macOS) and refused writes outside it.
Changes:
- Mark claude-code with provider-tools (same treatment as the Codex
CLI provider): stop sending unbridgeable external tools and let the
CLI execute its own, tagged executionMode=provider for the runtime.
- Forward the session workspace cwd from @cline/core into the
claude-code gateway provider options and lift it into the agent
session settings.
- Default settingSources to [user, project] and permissionMode to
acceptEdits (file edits under cwd auto-approved; command execution
stays gated by the user's own Claude settings), all overridable via
explicit defaultSettings.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't let a stale queued send response wedge the composer
A fresh session is still busy while its interactive loop starts, so the
sidecar coerces the first send onto the pending-prompt queue and replies
{queued:true} with a queue snapshot taken at enqueue time. The turn itself
runs via the runtime's queue drain and completes through stream events
(chat_queued_prompt_start -> deltas -> chat_done). On cold/slow sidecars the
RPC response lands only after those events; the webview then applied the
stale snapshot and unconditionally set status back to "running", leaving
the composer on "Agent is working..." forever and resurrecting a phantom
queue entry.
Webview: capture the turn epoch at send dispatch; chat_queued_prompt_start
bumps it, so a mismatch when the queued response arrives means the stream
already advanced the turn lifecycle and the response is ignored. Aborts now
resolve the queued branch to "cancelled" like the direct path.
Sidecar: the queued send response no longer routes its enqueue-time snapshot
through applyPendingPrompts, which overwrote the event-maintained
session.promptsInQueue and rebroadcast the stale list to every webview.
Includes deterministic regression tests for the stale-response orderings
plus temporary [P0DBG] debug instrumentation (region-marked, to be removed
after runtime verification).
* fix(desktop): ignore stale hub 'running' status after turn settles
The sidecar core is hub-attached, so chat_session_status events are
asynchronous projections of the hub's session record. A stale 'running'
can trail the stream's chat_done and flip a settled turn back to busy,
wedging the composer on 'Agent is working…' with nothing left to
reconcile. Track the epoch at which the turn settled and drop 'running'
status events until a new turn bumps the epoch.
* chore: remove stray QA screenshot artifacts from repo root
* chore(desktop): remove P0 debug instrumentation and fault injection
Strips all [P0DBG] logging, the /p0dbg sidecar route, the webview log
mirror + heartbeat, and the P0DBG_STARTUP_BUSY_MS /
P0DBG_DELAY_QUEUED_RESPONSE_MS fault-injection paths used to reproduce
the stuck-composer P0. The two real fixes (stale queued-response epoch
guard + stale-running-after-settle guard in the webview, and the
sidecar's non-clobbering queued-send snapshot) and the regression tests
remain.
* refactor(desktop): replace turn-epoch guards with an explicit turn lifecycle
The stuck-composer fixes left the hook with two hand-rolled epoch refs
(turnEpochRef / turnSettledEpochRef) mutated and compared inline across
eight call sites. Extract the rules into a pure TurnLifecycle module that
is now the only writer of the session status:
- a settled turn cannot be reopened: stale hub 'running' projections and
stale queued-send acknowledgements are dropped by the lifecycle instead
of by inline epoch comparisons
- async work (send RPC responses, queue reconciliation) captures an opaque
token and the lifecycle decides whether the world moved on, instead of
handlers comparing counters
- every status write goes through a named operation (begin, turnStarted,
settle, projectStatus, apply, reset), so the state machine is explicit
and unit-testable in isolation
No behavior change: the 5 wedge regression tests and the full hook suite
pass unchanged, plus 10 new unit tests for the lifecycle module itself.
* Revert "refactor(desktop): replace turn-epoch guards with an explicit turn lifecycle"
This reverts commit c480aaabe8.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(vscode): stop legacy-migration backlog telemetry spam, emit real migration outcomes
* refactor(vscode): slim migration telemetry fix to minimal surface
* fix(vscode): emit legacy migration outcome only after seeded session start settles
The completed event fired at in-memory conversion time, before the
seeded session start persisted the migration, so a start/persistence
failure was misreported as a successful migration and never produced an
error outcome. Conversion now records a pending migration; the followup
and compaction coordinators settle it after the session start resolves
(completed) or rejects (error/session_start_failed).
* fix(vscode): surface seeded-persistence failures in migration outcomes
LocalRuntimeHost.startSession deliberately swallows seeded-message
persistence failures (the in-memory session still works), so a resolved
start was not proof the legacy conversion became durable. The start
result now reports seededMessagesPersistence, and the resume/compaction
coordinators settle the migration from that result: completed only when
the seed write succeeded, error/seed_persistence_failed when the start
resolved but the write failed, error/session_start_failed when the
start rejected. durationMs now spans conversion through settlement.
Adds the core boundary test forcing persistSessionMessages to fail and
asserting the start still resolves with the failure visible on the
result, plus coordinator tests for both failure modes.
* refactor(vscode): drop per-task migration outcome events, keep volume fixes only
Scope the PR down to the zero-behavioral-risk telemetry fixes, per
review: keep the backlog event transition gating and the one-line
migratedSdkTaskCount fix (counting resumed legacy sessions via their
legacyTask metadata), and revert the per-task terminal outcome
plumbing (pending-migration settlement, coordinator hooks, and the
core StartSessionResult.seededMessagesPersistence field) along with
the success->completed outcome rename on the now-uncalled
captureLegacyTaskMigration. The per-task outcome events can land
separately on the observable persistence boundary.
* fix(llms): reject truncated tool-call JSON with unterminated strings
* fix(llms): scope truncation guard to jsonrepair only and handle single quotes
* fix(shared): keep jsonrepair ahead of bare-object repair for typed literals
Restores main's precedence for inputs both strategies can handle:
{"flag": True} must repair to a typed true, not the string "True".
The truncation guard now gates only the jsonrepair step, which is the
only strategy that can invent a string terminator.
---------
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
A message whose content array held only empty text parts slipped past the
existing empty-content guards in formatMessagesForAiSdk (which cover
content: "" and content: []). The AI SDK then strips empty text parts,
producing {"role":"user","content":[]} on the wire, which strict
providers reject — seen in prod as Vercel 400s for kimi-k3:
"user message must have content".
* fix(telemetry): emit disjoint per-request token buckets in task.tokens
SDK usage events follow the AI SDK convention where inputTokens is the
full request input including cache reads/writes. task.tokens forwarded
that value as tokensIn while also reporting cacheReadTokens and
cacheWriteTokens, so every event re-counted the whole (mostly cached)
conversation context and per-task token sums inflated ~5x on
cache-heavy sessions relative to the legacy contract (tokensIn =
uncached input only, disjoint buckets).
task.tokens now subtracts the cache buckets from tokensIn at the
capture site (mirroring the webview's normalizeUsageEvent), defaults
the cache buckets to 0 instead of undefined, and stamps the provider
attribute for parity with the legacy event schema. Event and attribute
names are unchanged.
* fix(core): normalize registered ApiHandler usage to cache-inclusive inputTokens
Review follow-up: two producer contracts shared AgentUsage.inputTokens.
Native AI SDK usage reports the full cache-inclusive prompt size, but the
ApiHandler adapter forwarded classic disjoint chunk.inputTokens unchanged,
so the task.tokens cache subtraction would zero out real uncached input
for a cache-reporting registered handler.
Normalize at the adapter boundary (inputTokens + cacheReadTokens +
cacheWriteTokens) so every producer entering AgentUsage satisfies the
same cache-inclusive invariant, document that invariant on
AgentTokenUsage.inputTokens, and reframe the telemetry clamp as a
defensive guard rather than a supported producer shape. Adds an adapter
normalization test and a boundary test from an ApiStreamUsageChunk
through task.tokens asserting the disjoint buckets round-trip.
* Revert "fix(core): normalize registered ApiHandler usage to cache-inclusive inputTokens"
This reverts commit 9a9aff374a.
* fix(telemetry): report involuntary Cline logouts from the SDK auth service
The SDK auth service cleared credentials silently when a refresh token was
rejected (invalid grant), both mid-session and during startup restore, so
user.auth_logged_out never captured involuntary logouts on the next bundle.
Emit token_invalid at both credential-clearing sites and restore_error when
startup restore throws, matching the reason vocabulary the legacy bundle now
uses so the same warehouse query measures involuntary logouts across rollout
variants. Startup with no stored session still emits nothing.
* refactor(telemetry): trim logout-reason parity change to the minimum
* fix(telemetry): report Cline invalid-grant logouts as token_invalid in the SDK resolver
getValidClineCredentials is the single owner of the involuntary-logout
event for the Cline provider; normalize its reason to the legacy
extension's LogoutReason vocabulary (token_invalid) so warehouse queries
cover both bundles. The raw OAuth code stays in errorCode. Codex/OCA
paths keep emitting invalid_grant and are unaffected.
* fix(telemetry): let the SDK resolver own token_invalid; keep restore_error for real restore failures
Address review on the SDK-adapter half of the logout-reason split:
- drop both adapter-side token_invalid emissions - the SDK resolver
already emits user.auth_logged_out on the same telemetry instance, so
the adapter was double-counting the exact signal being measured
- transient failures refreshing the stored session on startup (resolver
throws: network/timeout/5xx) no longer book as restore_error; stored
credentials are kept and the SDK books auth_refresh_soft_failure, so
an offline startup is not a logout
- single-source LogoutReason in services/auth/types.ts and re-export it
from the SDK auth service instead of maintaining two parallel enums
- boundary test runs the real getValidClineCredentials and asserts
exactly one auth_logged_out (reason=token_invalid) total, so a
reintroduced adapter emission fails the suite
* feat(hub): prompt update and restart when another install replaces the shared Hub
* feat(hub): make managed Hub build-watch interval configurable via CLINE_HUB_BUILD_WATCH_INTERVAL_MS
* feat(hub): reuse newer managed Hub builds instead of retiring them
Embed a build epoch alongside the deterministic runtime fingerprint so
managed-Hub compatibility can order builds in time. When fingerprints
differ, a Hub produced after the client's own build is attached over the
compatible wire protocol (and the build-mismatch watcher prompts the user
to update) instead of being retired, so concurrent installations converge
on the newest build rather than replacing each other's daemons. Older,
unordered, or metadata-less Hubs are retired and replaced as before.
* refactor(hub): simplify mismatch status derivation and dedupe sidecar event encoding
* fix(cli): only watch for managed Hub build mismatches in hub-attached sessions
Yolo and sandbox sessions force the local backend and never attach to the
shared managed Hub, so a newer Hub owned by another installation must not
interrupt them with the blocking update dialog.
* fix(desktop): stage an app update before hub-mismatch restart
'Update and restart' previously invoked restart_to_apply_update directly,
which only relaunches the current bundle. With no update staged by the
background 2h updater loop, the app came back on the same version, hit the
same newer Hub, and re-prompted immediately.
Add a check_for_update_now Tauri command that runs one updater
check/download/stage cycle on demand and reports the resulting status. The
dialog now stages the update first and restarts only when the updater
reports 'ready'; otherwise it stays open and explains that no update is
downloadable yet (or that the check failed) instead of restarting into the
same version. Addresses the outstanding Greptile P1 on the dialog.
* fix(desktop): reset the no-update hint when a new hub mismatch arrives
Without this, a dialog for a fresh mismatch reopened pre-set to 'Try again'
with the previous prompt's stale hint.
* fix(desktop): serialize updater cycles so overlapping checks cannot clobber a staged update
The periodic update loop and the on-demand check_for_update_now command
run the same check/download/stage cycle against shared state. Without
exclusion, two overlapping cycles could download the same bundle
concurrently, and the later one could overwrite a freshly staged "ready"
status with "idle" or "error" decided from its stale pre-await
ready_version snapshot - making the update dialog deny that a staged
update exists. A tokio::sync::Mutex now serializes whole cycles; the
ready_version snapshot is read under the lock, so it stays authoritative
for the cycle that took it.
* fix(core): harden Hub daemon lifecycle
* fix(core): wait for Hub listener before replacement
* fix(core): recover after Hub cleanup errors
* fix(core): assign the close memo handle before socket termination re-enters beginClose
On every shutdown with a connected client, the daemon logged
'unhandledRejection: AggregateError: hub server close failed' and exited
with code 1 instead of 0. Root cause: beginClose() terminated the
tracked WebSockets before assigning closeHandle. terminate() fires close
events whose microtask continuations advance the daemon coordinator's
deferred cleanup into server.beginClose() while the first invocation is
still mid-body, so the memo guard passes twice and a second set of
wss.close()/server.close() calls runs against the already-closing server,
rejecting with 'Server is not running' and spuriously failing the close
aggregate. The rejection then rode the daemon's unhandledRejection
fatal path and escalated the exit code.
Construct the close promises and assign the memo handle first, and only
then terminate sockets and run detach handlers; a re-entrant call now
hits the memo guard. Also observe the /shutdown handler's
fire-and-forget closeServer() so a genuine close failure is reported
solely by the owner's own await on the same memoized promise instead of
the unhandledRejection path.
Verified with a real daemon: shutdown with zero clients stays graceful
(exit 0, ~26ms); shutdown with a held-open authenticated WebSocket now
exits 0 with no unhandled rejection, still bounded by the 2s coordinator
deadline for the genuine Bun listener-close stall, with the discovery
record cleaned. Hub suites (228) and the shutdown e2e (5) pass.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(llms): update AI SDK deps to fix streamed tool calls with non-zero indexes
LiteLLM's Anthropic passthrough emits chat-completions tool_call deltas
whose index mirrors the Anthropic content-block index (1 when a text
block precedes the tool call; see BerriAI/litellm#11580).
@ai-sdk/provider-utils 5.0.18 stored streamed tool calls in a sparse
array keyed by that index and crashed at stream flush with
"Cannot read properties of undefined (reading 'hasFinished')",
aborting the agent turn. Upstream fixed this in provider-utils 5.0.21
("Fix streamed tool calls with non-zero, non-contiguous, reused, or
missing indexes.").
Update the ai / @ai-sdk packages so every chat-completions streaming
path resolves @ai-sdk/provider-utils 5.0.25, and drop the root
">=4.0.0" override on @ai-sdk/provider-utils: with intersect semantics
it pinned the workspace to the already-locked 5.0.18 even after parents
began requiring 5.0.25, and it force-upgraded dify-ai-provider two
majors past its declared ^3 range. Each package now resolves the
version line it declares.
Fixes#13119
* test(llms): pin non-zero streamed tool_call index regression (#13119)
Wire-level regression test: an openai-compatible SSE stream whose only
tool_call delta carries index 1 (Anthropic content-block numbering via
LiteLLM) must complete and emit the tool-call part instead of throwing
at flush.
* fix: address review findings from merge-conflict resolution
- Restore apps/vscode/proto/cline/state.proto to main's version: the
merge commit's pre-commit hook regenerated it with a stale generator,
deleting auto_approve_all_toggled = 174 and moving a reserved line,
creating drift against the checked-in descriptor. The deletion was
never intended.
- Restore FeatureSettingsSection.tsx to main's version (the same hook
reformatted main's file during the merge).
- Regenerate bun.lock narrowly from main's lockfile without --force so
the diff contains only the @ai-sdk family and its direct transitives;
drop the spurious webview-ui-scoped @radix-ui duplicate entries the
previous install introduced (hoisted resolutions still satisfy
webview-ui's unchanged ranges; verified with --frozen-lockfile).
- Align @ai-sdk/provider to ^4.0.7 in @cline/llms to match the rest of
the AI SDK family and avoid parallel provider resolutions.
Revalidated: wire repro streams to finishReason=tool-calls, @cline/llms
suite passes incl. the index-1 regression test, all workspaces
typecheck, SDK builds clean.
* fix: restore FeatureSettingsSection.tsx to main's formatting
The branch's pre-commit biome hook (--semicolons=as-needed, --write
--staged) strips a blank line from this file whenever it is staged,
which is how the unintended diff appeared in the merge commit. Commit
with --no-verify to keep the file byte-identical to main; this PR does
not touch the VS Code webview.
* refactor(desktop): extract chat transcript logic to messages/
Verbatim moves out of chat-messages.tsx (2,415 -> ~1,300 lines), with no behavior changes.
- Extract shared constants, grouping and reasoning helpers, tool summaries, and tool icons into messages/.
- Add unit tests for the extracted pure logic.
- Update test:chat-ui to include tests under messages/.
chat-messages.test.tsx remains unchanged and continues to pass.
* refactor(desktop): extract chat message components to messages/
Moves MessageBubble, ReasoningBlock, ToolMessageBlock, ToolApprovalPanel
(+ formatApprovalTimestamp and the ToolApprovalRequestItem type), and the
image lightbox out of chat-messages.tsx into their own modules under
messages/. Memo wrappers, comparators, and prop contracts are unchanged;
chat-messages.tsx keeps only the ChatMessages orchestration (~780 lines).
chat-messages.test.tsx is untouched and still passes.
* refactor(desktop): extract chat transcript logic to messages/
Verbatim moves out of chat-messages.tsx (2,415 -> ~1,300 lines), with no behavior changes.
- Extract shared constants, grouping and reasoning helpers, tool summaries, and tool icons into messages/.
- Add unit tests for the extracted pure logic.
- Update test:chat-ui to include tests under messages/.
chat-messages.test.tsx remains unchanged and continues to pass.
* refactor(desktop): extract chat message components to messages/
Moves MessageBubble, ReasoningBlock, ToolMessageBlock, ToolApprovalPanel
(+ formatApprovalTimestamp and the ToolApprovalRequestItem type), and the
image lightbox out of chat-messages.tsx into their own modules under
messages/. Memo wrappers, comparators, and prop contracts are unchanged;
chat-messages.tsx keeps only the ChatMessages orchestration (~780 lines).
chat-messages.test.tsx is untouched and still passes.
* refactor(desktop): extract chat message components to messages/
Moves MessageBubble, ReasoningBlock, ToolMessageBlock, ToolApprovalPanel
(+ formatApprovalTimestamp and the ToolApprovalRequestItem type), and the
image lightbox out of chat-messages.tsx into their own modules under
messages/. Memo wrappers, comparators, and prop contracts are unchanged;
chat-messages.tsx keeps only the ChatMessages orchestration (~780 lines).
chat-messages.test.tsx is untouched and still passes.
* fix(telemetry): stop mirroring per-token stream deltas into telemetry
Gate assistant-text-delta, assistant-reasoning-delta, and tool-updated
runtime events out of the unconditional telemetry.capture mirror in
AgentRuntime.emit. These fire once per streamed token or tool progress
chunk and accounted for ~97% of all agent.* telemetry volume in the
field with no analytical value. Listeners, hooks.onEvent, and the
run-failed sdk.error reporting are unchanged; the gate is a static
Set lookup so no per-event allocation is added.
* refactor(telemetry): inline stream-delta telemetry gate as a switch
Replace the module-level Set constant with case labels directly at the
capture site; same behavior, less indirection.
* feat(ui): styled label parts, terminal-style commands, and patch fidelity fixes
Label segments: ToolSummary gains labelParts ({text, code?}[]) so
consumers can render code-ish segments (file names, commands, queries,
URLs) in a monospace face. Single commands now read like a terminal
prompt — '$ bun test' — and an untruncated single command no longer
duplicates itself as a detail line.
Review fixes folded in:
- apply_patch preserves hunk boundaries: per-hunk oldText/newText on
ApplyPatchFile and file items (re-diffing concatenated hunks let a
deletion in one hunk pair with an addition in another), plus action
metadata — Delete File labels as 'Deleted x' with no phantom diff,
'*** Move to:' renames display as 'old → new'.
- run_commands accepts every RunCommandsInputUnionSchema shape (single
entry, bare arrays, top-level {command,args}, {cmd}).
- makeUnifiedDiff treats empty text as zero lines, so creating an empty
file or deleting all content no longer reports a phantom +1.
- parseWebFetchInput drops non-string urls instead of stringifying
objects into labels.
- hoisted a double normalizeValue in the unknown-tool fallback.
* refactor(desktop): render each tool call as its own chat row
Drops the consecutive-call grouping ('Read 3 files · Ran 2 commands')
in favor of one row per tool call — each with its own icon, status,
disclosure, and treatment per kind:
- commands read like a terminal: '$ bun run test' in monospace, with
the captured output in a capped scrollable mono block on expand and
'$ '-prefixed detail lines for multi-command calls
- edit rows carry mono filenames, the +/- badge, and their pierre
diffs pre-expanded (one diff per hunk for multi-hunk patches),
keeping the user-toggle override from the grouped implementation
- reads/searches/fetches keep inline specifics with mono code segments
via the shared labelParts
Also fixes the test:chat-ui exit-1 regression flagged in review:
@pierre/diffs' custom element calls CSSStyleSheet.replaceSync, which
jsdom lacks — a prototype polyfill in the suite keeps the real
component in the test tree (and the pre-expand assertions meaningful)
while letting the run exit 0. This suite gates ui-publish.yml.
* fix(desktop): keep the thinking indicator up during quiet turn stretches
The indicator only covered the gap right after a user message, so the
turn looked frozen while the model composed its next step — most
noticeably while streaming tool-call arguments, when neither text nor
a tool row is on screen. It now shows whenever the turn is running and
nothing else is visibly active (no streaming text, no in-progress tool
row, no pending approval/question).
* feat(ui): action-first tool labels
Every row leads with the plain action phrase — 'Ran command',
'Read file', 'Edited file', 'Created file', 'Deleted file' — with the
specifics (command, file name, line range) following as a monospace
segment. The mono segment renders at full size; the previous 0.92em
downscale made it look smaller than the surrounding prose.
* fix(desktop): chat polish — indicator alignment, action spacing, no expanded fade
- The Thinking indicator now mirrors the tool-row trigger metrics
(min-h-7, py-1, gap-2, 16px icon, font-medium, 8px rhythm) so the
text no longer shifts when the indicator swaps with an arriving
tool row.
- The copy/fork/timestamp action row sat 4px up into the message text
above it (-translate-y-1); it now rests 2px below the message block.
- Expanded reasoning/tool panels rendered at 70% opacity with
hover-to-unfade; expanded content is what the user is reading, so it
now renders at full opacity.
* feat(ui): violet active rows, gray finished rows, no green hover
Tool-row colors follow activity: running/pending rows (and the row
spinner) carry the brand violet, finished rows settle into
muted-foreground gray, and hover brightens toward the foreground
instead of hue-shifting to the success green. Errors stay red.
Also: maxInlineChars default raised 60 → 200 so real commands stop
getting truncated (the cap is now only a guard against pathological
payloads; layout handles overflow), and expanded editor rows lead with
the fuller file path above the diff, matching read rows.
* refactor(ui): let layout own label overflow instead of char caps
maxInlineChars now defaults to unlimited — labels carry the full
command/task/question text (whitespace collapsed to one line) and
.cline-chat-tool-label ellipsizes at the container edge via CSS
(nowrap + text-overflow) instead of wrapping. The cap remains as an
opt-in for width-constrained surfaces like TUIs. Since the label can
now be visually cut by layout, single-command rows always carry the
full command in their expanded details.
* fix(ui): drop stale green base color on tool triggers
The redesign moved finished tool rows to muted gray and running rows to
brand violet, but a leftover .cline-chat-tool-trigger { color:
var(--success-text) } rule later in the sheet overrode the gray base, so
every settled row still rendered green.
* fix(desktop): give message actions clear separation from message text
2px below the text read as touching; 6px (translate-y-1.5) gives the
copy/fork/timestamp row visible breathing room.
* feat(ui): spinner replaces the tool icon while a call is in flight
The progress ring used to append to the right of the label, so running
rows sprouted chrome instead of reading as one glyph + label. It now
takes the icon slot and fills the same 1rem box, so the label never
shifts when the icon swaps back in on completion.
* fix(desktop): align thinking indicator with the tool row that replaces it
The indicator sits outside the message column, so it already inherits the
conversation gap; its own mt-2 stacked on top and rendered it 8px lower
than the tool row that swaps in.
* style(desktop): message actions match chat text scale in a lighter gray
Copy/edit/restore/fork icons go from 12-14px to the 16px the rest of the
chat chrome uses, the timestamp moves from 11px to text-sm, and the whole
row renders at 70% muted-foreground so it reads as secondary chrome;
hover still brightens to full foreground.
* style(ui): running tool rows share the thinking indicator's gray
Violet-on-running read as a different system than the muted thinking
state it replaces; the spinner alone now signals activity. The progress
ring draws in currentColor so it stays gray on normal rows and red on
error rows without extra rules.
* style(desktop): nudge message actions down 2px and scale them down a step
Actions row moves from 6px to 8px below the message text; icons go
16px -> 14px and the timestamp text-sm -> text-xs after the previous
bump overshot.
* fix(ui): don't unstick conversation follow when content grows
Stick-to-bottom flipped off whenever a scroll event landed between a
content-height jump (tall diff rows mounting) and the resize observer's
re-pin: the handler read the new distance-from-bottom as the user having
left the bottom. Sticking is now released only by an actual upward
scroll and always restored on reaching the bottom, so the transcript
keeps following while rows stream in.
* feat(ui): user message bubbles on a filled brand-violet surface
The card-colored bubble sat too close to the app background to read at
a glance. New brand-violet-surface tokens (deep enough for near-white
text in both themes) fill the user bubble.
* style(desktop): give the conversation bottom padding above the composer
The last message (and its hover actions hanging below) butted against
the composer border.
* fix(desktop): composer keeps its two-line height when unfocused
Collapsing to one row on blur made the input and the conversation above
it jump on every focus change; the focus-tracking state existed only to
drive that resize.
* refactor(ui): simplify AgentAskQuestion and move it to the brand accent
The 'Follow-up question' heading, intro sentence, and box-in-box nesting
made a one-question prompt read like a form. The question now leads the
card directly (icon + text + option buttons) and the accent shifts from
blue to brand violet, with the section still labelled for assistive
tech.
* fix(desktop): pending questions and approvals render at the end of the transcript
They rendered above the whole conversation like a banner, so a follow-up
question appeared at the top of the chat instead of where the
conversation actually is.
* fix(desktop): keep message actions reachable and make hover/focus feedback instant
The 8px offset under a message was a translated gap — dead space that
dropped the parent's :hover midway to the buttons, hiding them before
they could be clicked. The offset is now padding on the actions element
so the hover chain stays unbroken. Also removes the opacity fade on the
actions row and the composer's focus border transition: both read as lag
rather than polish.
* feat(ui): add shared tool-summary presentation module
Pure, framework-free tool-call presentation logic under
@cline/ui/components/agent-chat/tool-summary: buildToolSummary and
buildGroupedToolLabel turn raw {toolName, input, result} payloads into
rich row labels (file names with line ranges, inline commands, search
queries, URLs), per-item details, +/- diff counts, per-file unified
diffs (editor old/new text and apply_patch envelopes), team_* labels,
and MCP-aware output text extraction. Merges the desktop app's
buildToolSummary layer with the CLI's tool-parsing/diff utilities so
every @cline/ui consumer renders tool rows consistently.
Exports the new subpath from package.json, extends the packed-tarball
smoke test to cover it, documents the boundary change in ADOPTION.md,
and bumps the package to 0.2.0-next.3.
* refactor(desktop): adopt shared tool-summary for chat tool rows
Replaces ~950 lines of app-local tool extraction (buildToolSummary,
teamSummary, parsers, grouped-label logic) in chat-messages.tsx with
the @cline/ui tool-summary module. Desktop tool rows gain single-call
specifics inline (Read app.tsx (10-80), Ran bun test, Edited util.ts
with +/- badge), line ranges on reads, shortened paths with directory
context in expanded details, per-file unified diffs in the expanded
panel, and grouped labels joined with a middot. Detail keys switch to
index-based to fix duplicate-line key collisions. Icons re-key on the
shared ToolKind classification.
* fix(ui): stop fabricating line positions in fragment tool diffs
Editor str_replace payloads carry old_text/new_text as fragments of the
file, but makeUnifiedDiff treated them as whole files and emitted hunk
headers anchored at line 1, mislocating the change in expanded edit
rows (Greptile P1 on #13151). Fragment diffs now use a neutral
'@@ … @@' separator; only whole-file content (editor create,
apply_patch Add File sections) keeps real hunk positions.
File items also expose the raw oldText/newText (reconstructed from
hunks for apply_patch) plus a fragment flag, so rich diff renderers
can consume the texts directly instead of re-parsing unified output.
* feat(ui): render tool-row edit diffs with @pierre/diffs
Adds @cline/ui/components/agent-chat/tool-diff exporting ToolFileDiff,
a thin wrapper over @pierre/diffs (optional peer dependency) that
renders a tool-summary file item as a syntax-highlighted, theme-aware
unified diff. Fragment diffs hide line numbers instead of showing
misleading ones. The desktop chat renders edit diffs through it, and
tool groups containing an edit diff now open pre-expanded so the diff
is immediately visible.
ADOPTION.md reframes the shared-module story: extracting presentation
logic products would otherwise duplicate is the direction @cline/ui is
headed, with tool-summary and tool-diff as the first two modules. The
packed-tarball smoke test covers the new subpath in both consumers.
* fix(ui): blend tool diffs into the app surface
Two polish fixes to ToolFileDiff from design review:
- Normalize trailing newlines on both sides before diffing so tool
payload fragments (which rarely end in a newline) don't litter every
diff with 'No newline at end of file' markers.
- Map @pierre/diffs' background hooks (--diffs-light-bg/--diffs-dark-bg)
to the host app's --background token (stock white/black fallback),
so the diff surface and all its color-mixed tints (context lines,
gutters, separators) derive from the app background instead of
pierre's pure white/black. Overridable via a new background prop.
Storybook's ToolSummaries story now renders file items through
ToolFileDiff pre-expanded (matching the apps) and adds a multi-file
apply_patch fixture.
* fix(desktop): pre-expand tool groups when edit diffs arrive mid-stream
defaultOpen only applies at mount, but a streaming tool group mounts
with its first (often read) call and gains the edit later, so live runs
never saw the promised pre-expanded diff. Drive the disclosure with
controlled state that opens when a file diff first appears, unless the
user has toggled the row themselves. Covers the streaming path with a
rerender test.
* chore(ui): replace font dependencies
* refactor(ui): migrate shared typography tokens
* refactor(ui): adopt Inter and Geist Mono in apps
* fix(hub): preserve variable font weight tokens
* feat(ui): tune font weights for dark mode
* docs(ui): add font migration screenshots
* (chore)ui: misc typography adjustments
* fix(hub): make dark-mode font-weight overrides take effect
Tailwind's @theme inline bakes literal values into utilities, so the
.dark --font-weight-* overrides were dead code and dark mode rendered
the heavier light-mode weights. Declare the weights in :root instead so
font-* utilities keep their var() references, matching the @cline/ui
tokens approach. Also rewrap --font-mono to satisfy biome format.
* fix(ui): restore light-mode semibold to 640 and pin weight scales in test
The PR intent is a 480/560/640/640 light scale with 400/500/600/600
dark overrides, and the Hub already uses 640; tokens.css had drifted to
600 for light semibold. Regenerate scoped-tokens.css and assert both
the light and dark weight scales in the theme contract test.
* chore(desktop): remove stray double space in provider header class
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Verbatim moves out of chat-messages.tsx (2,415 -> ~1,300 lines), with no behavior changes.
- Extract shared constants, grouping and reasoning helpers, tool summaries, and tool icons into messages/.
- Add unit tests for the extracted pure logic.
- Update test:chat-ui to include tests under messages/.
chat-messages.test.tsx remains unchanged and continues to pass.
getAuthToken captured expiresAt before refreshing, then validated the new
token against that stale value. When the old token was already past expiry
(not just inside the 5-minute buffer), a successful refresh was thrown
away and null returned, so the first call after long idle failed despite
valid credentials. Re-read the expiry from the refreshed auth info.
* fix(hub): don't forward recoverable agent errors to dashboard peers
Recoverable error events are in-run notices, not turn outcomes: the
MistakeTracker emits one for every recorded mistake (e.g. a plan-mode
guard-blocked run_commands call) while the run continues. The hub
dashboard forwarded every error event to peers, so the webview dropped
out of the sending state and appended an error row mid-turn — the same
host bug fixed for VS Code and the CLI in #12953.
Gate the forward on recoverable, matching those hosts: the tool failure
is already shown inline via the failed tool_event, and the turn's
outcome stays decided by how it actually ends (turn_done or a
non-recoverable error). Recoverable errors are logged server-side.
* fix(hub): forward recoverable flag to peers instead of filtering server-side
Per review: the server is a translation layer between agent events and
the webview protocol, so it should not embed display policy or console
logging. Forward every agent error with its recoverable flag on the
peer message and let each peer decide — the webview keeps recoverable
errors out of the transcript and keeps the turn state, matching how the
CLI gates display on the same flag while the information stays
available to any peer that wants it.
* add custom model selection to the vertex provider
* fix race conditions from PR review
* fix linter warnings
* fix test failures
* refactor(vscode): drop Vertex global-endpoint picker filtering
The SDK catalog is live (models.dev), so a static host allowlist of
global-endpoint-capable models lags every model launch and silently hides
new models from users on vertexRegion=global. Remove the allowlist, the
host override that injected supportsGlobalEndpoint, and the picker filter;
show the full catalog for every region.
An unsupported pick now fails loudly at request time: map Vertex's
'model not available in region: global' (and Google's Publisher Model
locations/global not-found body) to recovery guidance in the error row.
Also drop Anthropic's universal pricing from the Vertex Fable 5 overlay —
Vertex bills region-dependently, so the copied price understated recorded
cost; the record now carries no pricing instead of a wrong one.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix: remove stale Double-Check Completion feature tip
The rotating feature tips still told users to enable "Double-Check
Completion" in settings, but that toggle was removed in the new UI —
the Features section now offers Auto Compact, Feature Tips, Background
Edit, Checkpoints, Worktrees and Hooks. Following the tip sent users
searching the settings panel for something that isn't there.
Drop the tip. The remaining ten were checked against the current UI and
all still hold, including the "Settings → Features → Feature Tips" path.
* chore: remove dead CLI settings e2e page object and orphaned test
`page-objects/settings.ts` asserted the CLI settings Features tab shows
"Double-check completion" — the same removed setting behind the stale
feature tip. Nothing in the live tui-test suite (apps/cli/src/tests)
imported it; only chat.ts and auth.ts page objects are in use.
Its one importer, apps/vscode/tests/e2e/cli/interactive.test.ts, is a
leftover from the pre-2026-06-02 SDK migration squash: all three of its
imports resolve to files that don't exist, there's no tui-test config in
that tree, and no npm script runs it. It cannot execute.
* fix: respect user max output tokens in compaction summarizer requests
The compaction summarizer hardcoded max_tokens to 1024 and the VSCode host
never mirrored the user's Max Output Tokens onto providerConfig, so summary
requests were always capped at 1024 tokens. Reasoning models can spend that
entire budget thinking; the reasoning stream is discarded, so no summary
text arrives and compaction is skipped on every attempt.
- Mirror maxTokensPerTurn onto providerConfig.maxOutputTokens in the VSCode
session factory so consumers that build handlers straight from it (the
compaction summarizer) honor the user's setting, matching the CLI.
- Resolve the summarizer output budget from explicit config, then model
info, then knownModels, before the default; raise the default to 4096.
- Log a diagnostic warning (reasoning chars, incompleteReason, likely
cause) when the summarizer returns no summary text instead of silently
skipping.
* fix: clamp summarizer default output budget by model metadata instead of adopting it
Model maxTokens is reported capability, not a product default: without an
explicit configuration the summarizer now requests the 4096 default, lowered
by model metadata when the model reports less, never raised by it. Explicit
values still win as-is.
* feat(vscode): remove YOLO mode setting, migrate old users to auto-approve all
The SDK extension's YOLO toggle was cosmetic: nothing in the approval
path read it, so runs were silently governed by the per-action
auto-approval settings underneath (cline/cline#13114). Instead of
keeping a parallel override system, remove the setting entirely and
make the auto-approve menu the single source of truth:
- drop yoloModeToggled (and the equally dead autoApproveAllToggled)
from state keys, settings handlers, state posts, telemetry, the
remote-config yoloModeAllowed transform, and the settings protos
(field numbers reserved)
- remove the Yolo Mode toggle from Settings -> Features (the whole
Experimental section, it was the only entry) and the
"Auto-approve: YOLO" AutoApproveBar takeover
- add a v3 storage migration that folds a previously-enabled YOLO /
auto-approve-all toggle into autoApprovalSettings by enabling every
action, so previously-unattended setups keep running unattended;
the dead keys are cleared from the file store
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): keep dead yolo keys in place instead of clearing them
Current builds never read the removed keys (the state loader only visits
known keys), so deleting them buys nothing - and the file store is shared
with older builds that still know them, so clearing would flip YOLO off
for a user who downgrades. Same downgrade-safety rule as the v1 export.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): rename wasUnattended to shouldEnableAllActions in yolo migration
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): drop dead toggleActModeForYoloMode and stale yoloModeAllowed comment
The method was a legacy-controller carryover nothing called, and it set
the mode without rebuilding the session, which is wrong for the SDK
architecture. The comment cited yoloModeAllowed as a live remote-config
example; it no longer maps to anything.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): refresh checked-in proto descriptor_set.pb
The tracked descriptor set had not been regenerated since the repo
move and still advertised long-changed schemas (including the removed
yolo_mode_toggled fields) to gRPC reflection clients. Sync it with the
output of bun run protos.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: paste clipboard images into the composer as attachments (CLIENTS-78)
Pasting a screenshot into the composer did nothing: only drag-and-drop
and the paperclip file picker fed the attachment pipeline. Add an
onPaste handler on the composer textarea that extracts image files from
the clipboard, renames them to timestamped pasted-image-*.png files, and
routes them through the existing onAttachFiles flow. Text pastes are
untouched.
* desktop: only extract clipboard images in formats message serialization supports
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: context-aware welcome suggestions for non-code folders (CLIENTS-98)
* desktop: treat pending branch discovery as its own state for welcome cards
The welcome-card classifier read the "no-git" sentinel as a confirmed
non-repo, but page.tsx also used that value for the initial state and
while a workspace switch was awaiting branch discovery, so a git repo
could briefly show the plain-folder cards. Branch state is now null
while discovery is pending: the welcome screen shows no cards until the
folder is classified, and chat-mode cards (which never depend on git
state) still show immediately. Other branch consumers keep the string
contract via a "no-git" fallback.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: carry nullable branch state to all consumers
Propagate the pending-discovery null through ChatInputBar,
WorkspaceSelector, and the welcome workspace controls instead of
coercing to "no-git" at the page boundary, so only display leaves
fall back and the welcome classifier is the single consumer that
distinguishes pending from confirmed non-repo.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: never let 'Add project…' fail silently; add manual folder path entry (CLIENTS-73)
- sidecar picker tries zenity then kdialog on Linux and throws a descriptive
error when neither exists, instead of returning null (indistinguishable
from user cancel); picked paths are trimmed of trailing separators
- picker failures now surface as visible error messages in both workspace
selectors, with a manual path-entry fallback (typed absolute or ~ paths
in the search box offer an 'Open folder' action)
- failed workspace switches (invalid/nonexistent paths) show an inline
error instead of silently doing nothing
- validate_workspace_directory expands ~ and returns the resolved path
* desktop: keep workspace menu search/error state through catalog refreshes
The welcome-screen workspace picker reset its search text and error
message whenever onRefreshWorkspaces changed identity, which happens on
every session-history poll. Typing a path or reading an inline error
raced against the timer: the menu would silently wipe mid-interaction.
Hold the refresh callback in a ref so the reset only runs when the menu
actually opens.
* desktop: format welcome-workspace-controls test
* desktop: distinguish picker launch failures from user cancellation
A zenity/kdialog rejection with a non-ENOENT spawn error (EACCES, EMFILE,
ENOMEM) or a crash signal was classified as a user cancel, which skipped
the kdialog fallback and suppressed the inline error - recreating the
silent no-op this branch is meant to eliminate. Only a clean exit code 1
from a dialog that actually opened now counts as cancellation; broken
backends fall through to the next candidate and surface a descriptive
error otherwise. Picker logic moved to sidecar/workspace-picker.ts with
an injectable exec so the classification is unit-tested.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Classify picker launch failures separately from user cancellation
zenity/kdialog failures like EACCES, EMFILE, or ENOMEM were treated as
user cancellation, suppressing the kdialog fallback and the inline
manual-entry error. Only a clean exit code 1 now counts as a cancel;
any other failure falls through to the next backend or throws the
picker-unavailable error. Picker logic moved to sidecar/folder-picker.ts
with an injectable exec so the classification is unit tested.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Revert "Classify picker launch failures separately from user cancellation"
This reverts commit 24d27a004d.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): preserve queued prompts across user-initiated aborts
Pressing stop while prompts were queued silently destroyed them:
abort() called clearAborted(), which emptied the pending prompt queue
with no way to recover the typed input. The prompts vanished from the
UI, were never sent, and left no trace in session artifacts.
Aborting now only stops the in-flight turn. Queued prompts stay in the
queue and drain once the abort settles, matching the drain behavior
that already existed for self-aborted turns (loop detector / mistake
limit). The thrown-abort path (completeAbortedInteractiveTurn) now
schedules the same drain that runTurn schedules for turns resolving
with an aborted finish.
* fix(core): full-stop semantics and abort-window queue edits for surviving queues
Follow-up to the queued-prompt survival change: aborting a user turn keeps
the queue and auto-runs it, but two gaps remained.
1. No full stop: aborting a queue-initiated turn also kept draining, so
every Escape consumed one queued prompt and started a fresh provider
call - a session with queued messages could never be brought to rest.
Aborting a drained turn now discards the remaining queue: the first
Escape skips to your queued follow-ups, a second Escape stops the
queued work too.
2. Queue operations were still rejected while an abort settled: a prompt
typed right after Escape was silently dropped, and queued prompts were
briefly uneditable and undeletable even though they were about to
auto-run. enqueue/update/delete now work during the abort window;
scheduleDrain/drain still wait for the abort to settle.
* test(core): cover abort + host restart + seeded recovery durability
Adds an e2e regression guard for the reported "cancel a turn, lose the
conversation" failure: a cancelled turn, a daemon restart, a
client-side recovery seeded from disk, and a second restart before that
replacement ever runs a turn. Reverting the eager seeded-history
persistence makes the final read come back empty.
Materializing a seeded session at start also left its history row with
no prompt and no title, since there is no first prompt to derive one
from. Seed the title from the inherited transcript using the same
inference listSessionHistory hydration applies, so forks and recoveries
stay identifiable in unhydrated surfaces too.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): retitle seeded sessions from their first user prompt
Eagerly-materialized seeded sessions kept the interim transcript-
inferred title forever, a behavior change from pre-eager persistence
where a fork's history row was titled by the first post-fork prompt.
The interim title now only covers the window where no turn has run
(previously those rows were simply absent), and the first user prompt
after the seed backfills the row's prompt and retitles it — unless the
user renamed the session in the meantime, in which case only the prompt
column is backfilled. The resident manifest and session metadata are
updated in step so the end-of-turn usage-metadata merge cannot clobber
the title back through a stale in-memory fallback.
The e2e mock's updateSession now mirrors the real persistence-service
contract (row + manifest file), and the durability e2e covers both the
retitle and the rename guard; removing the retitle call fails the
'now add tests' assertion.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(core): collapse seeded-session titling to the old mechanism
The interim transcript-derived title, retitle flags, rename comparison,
and resident-manifest syncing existed only to title forks that never
run a turn - a new nicety, not parity. Dropping it collapses the whole
design back to what rows did before eager persistence: the persistence
service derives the title from the prompt when a row gains one, so the
host only needs to backfill the promptless row with the first user
prompt via updateSession. Renames win automatically because the service
preserves an existing title when no explicit title is passed.
Net production change vs main is a single 20-line backfill block in
executeTurn. The e2e mock's updateSession now models the service's
title semantics (explicit title wins, existing title preserved,
untitled rows derive from prompt), and the durability e2e asserts the
raw row stays untitled until first prompt while history hydration
infers a display title from the transcript.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Pressing stop while prompts were queued silently destroyed them:
abort() called clearAborted(), which emptied the pending prompt queue
with no way to recover the typed input. The prompts vanished from the
UI, were never sent, and left no trace in session artifacts.
Aborting now only stops the in-flight turn. Queued prompts stay in the
queue and drain once the abort settles, matching the drain behavior
that already existed for self-aborted turns (loop detector / mistake
limit). The thrown-abort path (completeAbortedInteractiveTurn) now
schedules the same drain that runTurn schedules for turns resolving
with an aborted finish.
* fix(core): keep a hung MCP server from taking down session creation
A stdio MCP server that never finishes initializing used to hold its
connect open for the full DEFAULT_MCP_CONNECT_TIMEOUT_MS (doubled across
the newline/framed attempts). MCP tool discovery runs on the
session.create critical path, so that wait blew past the 30s hub command
timeout and the CLI tore the whole interactive session down instead of
just skipping the bad server.
- Bound MCP tool loading during session build with a startup budget that
is safely under the hub command timeout. Servers that connect in time
contribute their tools; slower/hung servers are skipped for the session
(their error still surfaces via the MCP manager) instead of failing
session creation. Budget is overridable via CLINE_MCP_STARTUP_BUDGET_MS
for tests.
- Add StdioMcpClient.close() (and optional McpServerClient.close) that
marks the client disposed so an in-flight connect() aborts its retry
loop instead of respawning the framed fallback.
- Dispose the manager by closing clients up front, outside the per-server
operation locks, so a server hung in initialize can no longer stall
teardown for the full connect budget.
Adds regression tests covering both the non-blocking build and prompt
disposal while a client is hung in connect().
* refactor(core): simplify hung-MCP-server fix to a startup budget
Replace the bespoke per-server race/tracking in loadConfiguredMcpTools
with a small withStartupBudget() wrapper around the existing
Promise.allSettled: a server that exceeds the budget becomes a normal
rejection that the existing loop already logs and skips. The connect
budget, MCP settings display (initialize timeout 30s), and the rest of
the loader are left untouched.
The client close()/manager.dispose() cleanup is kept minimal: it is what
lets teardown abort a still-in-flight connect instead of blocking on the
per-server lock (and clears the pending request timer).
* fix(mcp): cap the default initialize budget at 3s to protect session creation
Supersedes the startup-budget approach on this branch with the simple
constant fix.
MCP initialize runs on the session.create critical path, which the hub
caps at 30s, and connect() can spend the budget twice (newline then
Content-Length framing). The 30s default from #13067 meant a server that
never initializes held session.create for up to 60s, so the hub RPC
timed out and the CLI tore the whole session down and exited.
Return to the pre-#13067 shape with a bigger probe: 3s instead of 1.5s.
That still covers the ~2s starters the old probe killed (#13035) and
keeps the worst case at ~6s per server, far under the hub deadline.
Genuinely slow starters (JVM-based servers like Oracle SQLcl) now need
an explicit timeout in cline_mcp_settings.json, which continues to
override the default in either direction.
Tests: update the slow-start regression tests to the new policy (2s
connects by default, 4s connects with a configured timeout), refresh the
displayed initialize-timeout assertions, and add an invariant test that
keeps the doubled default well under HUB_DEFAULT_COMMAND_TIMEOUT_MS so
the budget cannot silently creep past the session deadline again.
* fix(desktop): stop opening a session from replacing the remembered model
The composer's ModelSelector mirrored every provider/model prop change
into the remembered last selection (localStorage), which seeds new
sessions via getInitialChatConfig(). Opening an existing session drives
those props to that session's config, so merely viewing an old session
silently replaced the user's explicitly picked default model.
The remembered selection is now written only from the explicit picker
handlers (provider select and model select). Passive prop changes, such
as opening a session, no longer touch it.
* fix(desktop): re-seed remembered provider/model on chat reset
reset() kept the previous config's provider/model and only cleared the
session ID, so a chat pane that had hydrated a historical session could
carry that session's model into the next chat. Re-seed provider/model
(and apiKey when the provider changes) from the remembered defaults --
the same source a freshly mounted thread uses -- so reset and remount
behave identically.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop treating leftover plugin install dirs as installed
isOfficialPluginInstalled() only checked that the marketplace install
directory existed. A failed or interrupted install can leave that
directory behind with no plugin inside, and the next install attempt
then short-circuited with a fake 'already installed' success: the
marketplace button flipped to Uninstall with no error while nothing
actually worked, and the installed-entries listing kept reporting the
broken entry as installed.
The check now requires a loadable plugin module inside the directory
(via discoverPluginModulePaths) before reporting the entry as
installed, so partial directories fall through to a real install
attempt whose outcome is surfaced to the UI.
* fix(desktop): reclaim leftover partial plugin install dirs with --force
* fix(desktop): canonicalize diff panel paths against the session cwd
Tool calls address the same file inconsistently across a session: one
edit uses a workspace-relative path (journal.txt), a later one the
absolute path (/tmp/ws/journal.txt). mergeToolDiffs keyed entries by the
raw string, so the same file was listed twice in the diff panel with
split +/- counts and inconsistent naming, most visibly after git was
initialized mid-session and the model switched to absolute paths.
Diff paths are now canonicalized against the session cwd before
merging: entries for the same file collapse into one, files inside the
cwd display as workspace-relative paths, and files outside it display
their resolved path. Without a cwd the previous raw-key behavior is
kept.
* style: collapse editorReplaceEvent signature per biome format
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): collapse dot segments and keep root cwd in diff path keys
* fix(desktop): compare Windows diff path keys case-insensitively
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): hide View Changes on completion rows until there are changes to show
The button previously always rendered on the latest completion row, faded
and disabled when the count check came back 0 - which covers both 'nothing
changed since your last message' and 'no checkpoint to compare against'
(non-git workspace, repo with no commits, comparison failure). A dead
button with a misleading tooltip in the non-git case is worse than no
button: now the row renders nothing until the host confirms there are
actual changes, and the button is always enabled when shown.
* fix(vscode): reset View Changes state when showViewChanges toggles
Greptile review: a stale positive hasChanges from a previous evaluation
could flash the button before the host confirms the new comparison when
showViewChanges flips false and back true on the same row. Reset to
'still checking' whenever the effect re-runs.
* fix(core): never run a foreign compiled plugin-sandbox bootstrap for a source host
When @cline/core runs from source (e.g. the desktop hub daemon in dev)
with CLINE_WRAPPER_PATH set, resolveBootstrap() picked the compiled
plugin-sandbox-bootstrap.js from a separately installed CLI platform
package (such as a published version sitting in the package-manager
cache) before falling back to the source bootstrap. That bootstrap
resolves modules against the other installation's layout, so every
plugin failed to load with "Cannot find module '@cline/core'" - and
the settings pipeline swallowed the failure, leaving Settings > Tools
showing "No plugin tools found" and plugins showing no contributions
even though the same plugins loaded fine in chat sessions.
Bootstrap selection now prefers, in order: a compiled bootstrap next to
this module (always matches the host build), the source bootstrap when
the host runs from source, and only then wrapper/executable-derived
bootstraps - which remain the path for compiled binaries where
import.meta points inside the bunfs bundle.
* chore(core): restore untouched settings-service formatting
* fix(core): keep session context durable across aborts and hub restarts
Users on slow self-hosted endpoints reported sessions losing their entire
conversation after cancelling a long-running request: the TUI still showed
the transcript, but the next turn greeted them like a brand-new session.
Root cause is a stack of two failures:
1. The hub daemon exits on any unhandled rejection that is not an
AgentRuntimeAbortError, so a floating abort-family rejection from a
cancelled provider stream kills every resident session.
2. When the CLI recovers the missing session it rebuilds from the persisted
messages file - but aborting a turn never flushed the transcript, and
lazy session persistence (SDK 0.0.70) kept seeded history (mode-switch
restarts, forks, previous recoveries) memory-only until the first
completed turn. Recovery then seeds an empty session: silent context wipe.
Fixes:
- completeAbortedInteractiveTurn now flushes the transcript to disk, so an
aborted exchange survives a hub restart.
- Sessions started with initialMessages persist them (and any compaction
sidecar) immediately; brand-new empty sessions stay lazy, so closing an
unused runtime still leaves no empty history entry.
- The hub daemon ignores abort-family unhandled rejections (DOMException
AbortError, Node ABORT_ERR) the same way it already ignores
AgentRuntimeAbortError, instead of exiting with every session resident.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): write seeded history atomically with session materialization
Greptile review flagged a residual crash window in the seeded-session
persistence: ensureSessionPersisted created the session row (with an empty
messages file) and only then called persistSessionMessages, so a crash
between the two left a discoverable session whose seeded history was gone.
Close the window by threading initialMessages/systemPrompt through
createRootSessionWithArtifacts: the messages artifact is now written with
the seeded transcript before the session row is committed, so every crash
point leaves either nothing discoverable or complete data. The follow-up
persistSessionMessages call at session start is gone; the seed travels
inside session materialization.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Revert "fix(core): write seeded history atomically with session materialization"
This reverts commit 5a7e0b37f1.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Turns drained from the pending-prompt queue resolve their errored
AgentResult inside PendingPromptsController.drain(), which discards it,
and the legacy 'error' agent event had no projection in the hub's
session-event projector — so a failed queued turn never produced any
terminal hub event. Interactive clients (e.g. the desktop app) hung on
'Thinking...' with no error shown.
The projector now publishes run.failed (with the error text and a core
session snapshot) for non-recoverable lead-agent error events, but only
when no RPC-driven turn is awaiting sessionHost.runTurn for that
session — the awaiting run.start handler already publishes the
authoritative terminal event, so this avoids double-reporting a turn
that resolves through both paths.
* fix(core/cli): drain queued prompts after self-aborted turns and surface the stop
When a run ends with finishReason "aborted" without a user abort request
(loop detector hard escalation or the consecutive-mistake safety stop),
runTurn skipped the pending-prompt drain, stranding user-queued messages
forever, and the CLI rendered nothing - the task appeared to silently
stop with queued messages never consumed (#13030).
- core: schedule the drain after every completed turn, including
aborted/error finishes. User-initiated aborts are unaffected because
abortSession() already clears the queue, and drain() stops after one
failed send so an erroring provider cannot spin the queue.
- cli: when a turn comes back aborted without the user having requested
an abort, append a "Task stopped before completion." status entry
instead of ending the turn silently.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): hold queued prompts on error finishes instead of consuming them
Addresses the Greptile P1 review on #13061: a drained prompt whose turn
resolved with finishReason "error" returned normally, so the
exception-only requeue path treated the send as successful - the failed
prompt was consumed and draining continued firing the remaining queue
into a failing provider.
- drain() now stops the chain when a drained send resolves with an
error finish. The errored entry itself is not requeued (its turn ran:
the prompt is in the conversation and the error is surfaced), but the
rest of the queue is held.
- runTurn() no longer schedules a drain after "error" finishes (the
skip is removed only for "aborted", which is the #13030 fix).
Held prompts still drain via the existing enqueue/update/delete
triggers or the next successful turn.
- Two new unit tests cover both layers.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert(cli): drop the 'Task stopped before completion' status line
Keep the change scoped to the queue-drain fix in @cline/core. The CLI
no longer prints a notice for non-user-initiated aborted finishes;
apps/cli is back to parity with main. When messages are queued, the
drain itself makes the stop visible (the queued message runs); richer
stop-reason surfacing can be a follow-up.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
The litellm builtin spec pinned protocol: "openai-responses", so every
request went to POST {baseUrl}/responses. Self-hosted LiteLLM proxies
commonly implement only /chat/completions, so all prompts failed with
404 Not Found on the SDK path (CLI, and now the Next extension bundle).
Drop the override so litellm inherits the openai-compatible family
default (openai-chat -> /chat/completions), matching every sibling
openai-compatible builtin and the Legacy extension behavior.
Fixes#13003, fixes#10781
* fix(cli): render MCP tool result text instead of escaped JSON in TUI
MCP tools return {content: [{type: "text", text}]} which
extractFullOutputText JSON-stringified, escaping newlines into one giant
line that word-wrapped across the whole terminal and never triggered the
line-based collapse. Extract the text parts with real newlines so the
existing collapse works.
Fixes#13038
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): keep placeholders for non-text blocks in mixed MCP results
Addresses Greptile review on #13066: text-only filtering silently
dropped image/resource/audio blocks from mixed MCP content. Render them
as [type] placeholders instead.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): surface non-text MCP block metadata in TUI output
Extract embedded resource text, and include resource/resource_link URIs
and image/audio mime types in placeholders so expanded mixed MCP
results keep identifying metadata.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
The stdio MCP client gave servers without a configured `timeout` only
1.5 seconds to answer initialize before killing the process, so
slow-starting servers (e.g. Oracle SQLcl's JVM-based `sql -mcp`) could
never load and were silently skipped at session start.
Raise the default connect budget to 30s, in line with the startup
budget other MCP clients allow. A configured `timeout` still overrides
it in either direction, dead commands still fail fast through the spawn
error/exit path, and the newline -> Content-Length framing fallback is
unchanged.
Fixes#13035
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): route /team prompts through core runtime
Rewrite desktop `/team` commands as structured user command blocks before sending them to the core runtime. Validate task input and respect the globally disabled Teams tool setting.
Remove legacy agent spawn and team enablement flags from session configuration, and add coverage for prompt rewriting and disabled-tool behavior.
* fix(desktop): preserve team tool defaults
* fix(desktop): display queued /team prompts as their slash form
Queued prompts are stored in their runtime form, so a queued /team
command showed its raw <user_command> envelope in the prompt queue chip
and edit textarea. Fold queue items through formatDisplayUserInput for
display; saving an edit re-resolves the slash form through the sidecar,
so the round trip is lossless.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(hub): align builtin tool catalog flags with the desktop sidecar
The desktop sidecar pins enableSpawnAgent/enableAgentTeams when listing
the builtin tool catalog; the hub's parallel listing did not, so the two
would drift if the preset defaults ever change. Pin the same flags in
the hub and cross-reference the two call sites.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(desktop): drop inert enableSpawn/enableTeams config leftovers
buildCoreSessionConfig no longer reads these keys, so remove the dead
schema fields, default-config initializers, and chat-test payload
entries. The chat-session regression test still sends them on purpose
to prove legacy flags cannot override the runtime's tool presets.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): reject /team when the mode's tool preset disables teams
The /team guard only checked the global disabled-tools setting, but the
runtime resolves tool availability from the mode's preset, so a preset
without team tools (yolo) would still send the model a spawn-a-team
instruction it cannot act on. Resolve the teams catalog entry for the
session's mode and reject /team when it is unavailable, mirroring the
runtime's own availability logic.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): surface OAuth authorization for SSE MCP servers on 401
A 401 from an SSE MCP server never persisted authorizationRequired: the
fetch-boundary UnauthorizedError was consumed by EventSource and re-thrown
as a status-less SseError, so the instanceof check routed it to
markConnectionError and hosts never offered the OAuth connect action.
Give the SSE stream request a raw fetch so a 401 fails the connection with
the SDK's typed SseError(401), and recognize 401s across transports with a
single isMcpUnauthorizedError predicate at every detection site.
* style(core): apply biome formatting to MCP oauth changes
Toggling Plan/Act while a turn was streaming or waiting on a tool approval
aborted the turn but left the TurnStateTracker on its last live phase: the
aborted session's done event is fenced off as stale once the rebuild
unsubscribes it, so nothing ever settled the phase. The webview then kept
rendering that phase forever - an eternal Thinking spinner with the input
disabled (aborted while streaming), or dead Approve/Run Command buttons wired
to an approval that clearPending had already denied (aborted while awaiting
approval). Users experienced this as 'switched to act mode and nothing
happened / it never wrote the files'.
Mirror cancelTask: after aborting the turn for the mode change, append a
resume_task ask row and set the phase to resumable, so the footer offers
Resume Task with the input enabled in the new mode.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): retry mid-stream network interruptions before any model output
* fix(llms): scale network retry backoff by network retry count, not shared attempt number
* desktop: native-feel polish and render-path performance fixes
- Suppress the WebView browser context menu on app chrome (keep it for
editable fields and active text selections)
- Make UI chrome unselectable app-wide; opt chat messages, markdown,
code, diffs, and error banners back into text selection
- Contain overscroll so inner scrollers don't rubber-band the window
- Lazy-load Settings/Sessions/Onboarding/Diff views out of the entry chunk
- Memoize ChatInputBar and AgentHeader; stabilize their props in the chat
pane so stream flushes only re-render the affected message bubble
- Stop refocusing the composer textarea on every keystroke (caret flicker)
- Cache slash commands across menu opens (stale-while-revalidate)
- Avoid rebuilding reversed message arrays and ask-question JSX per render
- Drop core info/debug console logging on the streaming hot path behind a
cline:debug-logs opt-in; remove leftover [webview:delete] debug logs
- SearchCombobox (provider/model picker): Escape closes and restores focus
- Remove unused @vercel/analytics, recharts, embla-carousel deps and the
unused chart/carousel UI components
* desktop: surface failed-turn errors instead of leaving the chat blank
On a failed run the runtime reports its error string in result.text.
The webview rendered that as an assistant bubble, which the canonical
history rehydration then wiped (the failed turn is never persisted),
so provider errors like a retired model id left the user staring at a
silently empty chat. Route failed-turn text to a persistent error-role
message added after rehydration instead.
* desktop: fade the welcome/conversation swap instead of hard-cutting
Sending the first message replaced the hero layout with the message
grid in a single commit, which read as a white flash. A 180ms enter
animation now plays when either side becomes visible; disabled under
prefers-reduced-motion.
* desktop: render new-chat panes instantly from the last catalog load
Clicking + remounts ChatThreadPane, which refused to render until the
provider catalog (a large fetch) and workspace list resolved again —
about a second of blank pane plus boot spinner on every new chat.
Seed remounts from a module-level snapshot of the last successful
load; the mount effect still refreshes both in the background.
* desktop: invalidate the provider-catalog snapshot with the cache
Seeding remounted chat panes from the last catalog load left a window
where a pane created right after a credential change could act on the
old keys. The snapshot now lives in the catalog module and is dropped
by invalidateProviderCatalogCache(), so credential edits force the
next remount to wait for fresh data.
* fix(cli): harden tool input/output formatters against malformed payloads
Tool inputs cross the model/tool boundary and may not match their
TypeScript annotations (e.g. run_commands with { command: null }).
truncate() called str.replace() on such values, crashing the TUI with
'.replace is not a function' and making persisted sessions containing
the payload non-resumable, since hydration replays the same input
through formatToolInput().
Normalize untrusted values at the formatting boundary: truncate() now
accepts unknown and safely stringifies null/undefined/objects (including
circular structures and throwing toJSON), formatStructuredCommand no
longer returns non-string commands verbatim, and fetch_web_content
request summaries tolerate malformed entries.
Fixes#13036
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): keep valid empty-string args in structured command summaries
Greptile review: filtering normalized args by truthiness also dropped
genuine empty-string argv entries, so summaries could show a different
argument list than the one executed. Filter only nullish entries before
normalization instead.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): fall back to session cwd/Desktop for @-mention search in empty windows
* fix(vscode): use the shared chat workspace as the no-folder fallback root
ensureGitRepository cached a negative probe for the lifetime of the hook
instance, so a session started in a non-git folder never got checkpoints
even after the user ran git init. Cache only the positive answer and
re-probe otherwise; the probe runs at most once per user turn.
* fix(desktop): treat signed-out state as a typed result instead of a command error
* fix(desktop): sign out when the organization balance fetch reports the typed signed-out result
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: retrigger checks after runner outage
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(ui): introduce Cline-owned semantic color system
* refactor(desktop): adopt shared semantic theme roles
* refactor(ui): set 15px root and recalibrate xs/sm type scale
Scale rem steps so xs/sm stay 12/13px visually, and slightly lift dark-mode neutral-4.
* refactor(ui): align SearchCombobox with package type and hover tokens
Use host-safe cline-ui utilities and keep option font inheritance from CSS.
* fix(ui): use standard stroke-2 utility on approval spinner
* refactor(desktop): modernize shared UI primitives for Tailwind v4
Replace legacy arbitrary/has selectors with current utility syntax.
* refactor(desktop): bump chat chrome typography to text-sm
Keep composer controls and pickers on the shared sm type step.
* refactor(desktop): use max-w-344 for page frame content width
* chore(desktop): disable Next.js dev indicators
* chore: ignore desktop-app Cursor settings
* docs(pr): add before/after screenshots for #12941
* chore: retrigger checks
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* desktop: fix silent turn failures, message duplication, and stuck composer; add first-run setup guidance
Findings from two full computer-use UX audits of the desktop app:
- Surface failed turns in the transcript: queued turns (incl. the first
prompt of a fresh session) only signal errors via chat_done, which the
UI previously ignored - sending a message with no credentials failed
in complete silence. Failed turns now show an error message enriched
with the latest core error log and a pointer to Settings -> Models.
- Fix duplicated user messages: a live send's optimistic user message
was materialized a second time by the runtime's queued-prompt-start
event.
- Fix composer stuck on 'Agent is working...': drop prompts from the
local queue snapshot when they start, emit a fresh queue snapshot from
the sidecar on pending_prompt_submitted, and double-check the server
queue on turn completion.
- Add a 'Connect a model' notice on the welcome screen when no provider
has credentials, with actions to reopen onboarding at the connect step
or jump to model settings; it reacts live to credential changes.
- Add 'Get an API key' links for popular providers in onboarding and
Settings -> Models (the catalog docUrl is never populated), and link
the Cline dashboard from the Cline API key form.
- Explain what Cline is on the onboarding welcome step.
- Make the stop button visible (was 8px with no padding) and support
Esc to stop; add Cmd/Ctrl+N (new session) and Cmd/Ctrl+, (settings).
- Remove leftover [webview:delete] console.error debug logging that
surfaced an error badge after deleting a session.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: remove remaining delete debug logging in session history hook
The sidebar right-click delete path had the same leftover [webview:delete]
console.error instrumentation, which made the Next dev-mode issues badge
appear after every deletion.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: fix Biome a11y error in WelcomeSetupNotice
biome's lint/a11y/useSemanticElements errors on role="status" divs;
use the semantic <output> element (implicit status role) instead. This
was failing the repo's 'bun run lint'.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: count structured-config and keyless providers as connected
The welcome setup notice previously only recognized apiKey/OAuth
credentials, so users running Bedrock/Vertex (structured configValues)
or a deliberately enabled keyless local endpoint (e.g. Ollama) were
nagged to connect a model they already use. isProviderConnected now
also counts an enabled provider whose required config fields are all
filled, or an enabled provider that has no API-key field at all.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: keep re-key eligible when chat_done lands in the same batch as its prompt start
When a turn fails fast, chat_queued_prompt_start and chat_done can be
dispatched in one React batch. Clearing the outstanding-optimistic-
bubble registry synchronously in the chat_done handler ran before the
re-key updater enqueued by the prompt-start event, so the optimistic
bubble was appended a second time instead of re-keyed. Clear the
registry inside a state updater so it executes in event order after
the re-key. Caught by the queued-turn-failure regression test.
* desktop: make the queued-prompt re-key updater idempotent under StrictMode
React StrictMode double-invokes state updaters in dev. The
chat_queued_prompt_start re-key updater consumed the optimistic
bubble's id from outstandingOptimisticUserIdsRef on its first run, so
the second run against the same prev found no eligible candidate and
appended the same user message a second time (and, without a promptId,
makeId() minted a different id per invocation). Hoist the message id
out of the updater and remember which optimistic bubble each queued
message id re-keyed so a re-run reaches the identical result. The memo
resets alongside the outstanding set (error state, reset, hydration).
Root-caused with runtime instrumentation: the duplicate only appeared
on turns that exercised the queue-drain re-key path, and hydration
later collapsed it to one message because the duplicate never existed
in persisted state.
* desktop: preserve failure messages across post-send canonical hydration
Persisted history never contains UI-only error bubbles, so the two
post-send read_session_messages replacements in sendPrompt wiped the
failure explanation appended from chat_done ~40ms after it rendered
(confirmed with runtime instrumentation). Re-append the active
session's error messages after the canonical history. Includes a
regression test reproducing the chat_done-error-then-RPC-resolution
race.
* desktop: don't let an optional API-key field veto a connected provider
Greptile P1 follow-up: Bedrock's catalog entry carries an optional
apiKey field ('Optional Bedrock bearer token') alongside IAM/profile
authentication, and keyless local endpoints can also surface one — so
treating the mere presence of an apiKey field as proof of disconnection
kept nagging configured users. An enabled provider (the user
deliberately persisted settings for it) now counts as connected unless
a required config field is unmet; auth may legitimately live outside
the catalog (IAM, env vars, local endpoints). Brand-new users have no
enabled providers, so the first-run notice still shows for them.
* desktop: tighten credential-error guidance and stop re-pinning stale failure bubbles
* desktop: invalidate the shared provider catalog after settings OAuth login
Greptile P1 follow-up: runOAuthProviderLogin only updated the settings
view's local provider state, so the shared catalog cache and its
invalidation subscribers (the composer selector and the welcome
screen's 'Connect a model' notice) kept reporting the provider as
disconnected until an unrelated invalidation or a pane remount. Notify
the shared cache on successful OAuth login, like the account view and
the API-key save path already do.
* desktop: clear the remembered core error on turn end, reset, and hydration
Greptile flagged that turn-start events are the only thing clearing
lastCoreErrorBySessionRef, and websocket events are not replayed: a
transport interruption that drops a turn's start event lets a later
detail-less failure resurrect an earlier turn's error. The remembered
error belongs to exactly one turn, so clear it whenever a turn ends
(chat_done, any reason) as well as on reset() and history hydration.
Regression test covers the dropped-start-event sequence.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(llms): regenerate model catalog from models.dev
* feat(llms): surface meta/muse-spark-1.2-contributor for the Cline provider
* test(llms): guard Vercel-only Cline model allowlist
* feat(vscode): explain when a free model promotion ends
Once a free promotion ends, the cline-free/ model is removed from the
catalog and the backend answers 'model not found' to requests against it.
The CLI has shown a dedicated 'Free model promotion ended' banner for this
since #12593; the extension instead rewrote the answer into generic
model-not-found guidance with no model-picker offramp.
Detect the case in the host where the active model id is known
(reshapeErrorForWebview, fed by a new MessageTranslatorState model-id
source), stamp the payload with a cline_free_promotion_ended code, and
render a dedicated card in the webview with a button into the model
picker. Classification is gated on the cline-free/ prefix so ordinary
model-not-found errors keep their generic path, and it runs before the
auth branch since the 404 status falls inside the generic auth range.
* fix(vscode): prefer the live task model over session-start metadata
A mid-task model-only switch updates the running session's model in place
(updateActiveSessionModel) and refreshes the task API shim, but never
touches the session's startConfig/manifest. Preferring the session-start
snapshot could therefore misclassify after such a switch: a genuine
retired-model 404 would miss the promotion-ended card, and the reverse
switch could show it for the wrong model. Provider switches restart the
session, so both sources agree there; the shim starts as "unknown"
(filtered out), so fresh sessions still resolve through start metadata.
* fix(desktop): dedupe chat_queued_prompt_start emitted for the same prompt
PendingPromptService.drain() emits a pending_prompts snapshot (head
removed) and a pending_prompt_submitted event back-to-back for the same
prompt. The sidecar translated both into chat_queued_prompt_start, so
the webview rendered the user's message twice until the chat was
re-hydrated from history. Track the last announced prompt id per live
session and emit the start chunk once.
* fix(desktop): re-key optimistic user bubble when the runtime queues the prompt
The send path renders an optimistic user bubble for prompts dispatched
while the session is idle, keyed by a random id. When the runtime
routes that prompt through its pending queue (e.g. during session
startup), the queued-prompt-start event appended a second bubble under
queued_user_<promptId> — the same message rendered twice until the
chat was re-hydrated from history. Re-key the trailing optimistic
bubble to the event's id instead of appending.
* fix(desktop): re-key only outstanding optimistic bubbles on queued prompt start
Review follow-up: matching by content alone could swallow a new queued
prompt that repeats the text of a message left at the transcript tail
by an earlier cancelled/failed turn. Track in-flight optimistic bubble
ids explicitly (registered on optimistic append; cleared on re-key,
turn end, error, and history hydration) and only re-key those.
* fix(core): don't count plan-mode guard-blocked commands as model mistakes
The plan-mode command guard (#12906) rejects file-editing run_commands
calls with a tool error. The orchestrator counted that error as a failed
tool call, so a turn whose only tool call was guard-blocked fed the
MistakeTracker, which emits a recoverable "error" AgentEvent
("1 tool call(s) failed: [run_commands] ...").
Hosts render that event as a failed turn. In the VS Code extension the
turn ended in the "error" phase (Retry / Start New Task footer), the
final plan text was never retagged to plan_completion_result, and
toggling to Act therefore rebuilt the session without the auto-continue
send - the toggle appeared to do nothing and the presented plan was
never acted on. In the CLI TUI the same event flipped the footer to
idle mid-turn.
A guard rejection is deliberate session policy, not a model mistake:
the run continues and the model is expected to fold the change into
its plan. Tag the guard error with a stable marker sentence, expose
isPlanModeBlockedCommandError, and skip the failed-tool bookkeeping for
matching results so no mistake is recorded and no error event is
emitted. Repeated blocked attempts are still bounded by loop detection
and maxIterations.
* docs(core): flag plan-mode guard error string matching for typed skip channel
FIXME on isPlanModeBlockedCommandError: recognizing guard rejections by
sniffing the error text is brittle. The intended replacement is a typed
skipSource/skipCode on the tool-finished runtime event so the
orchestrator (and the VS Code approval-denial suppression) can identify
skipped tools structurally instead of via string matching.
* Revert core mistake-counting change for plan-guard blocks
A model attempting a file-editing command in plan mode is disobeying
its instructions - that IS a model mistake, and the MistakeTracker
should keep counting it (it is the brake that stops weak models from
flailing at blocked commands indefinitely). The real bug is host-side:
a recoverable mid-turn mistake must not kill a turn that afterwards
completes with a presented plan. The follow-up commit fixes that in
the hosts instead.
* fix(vscode,cli): treat recoverable agent errors as in-run notices, not turn outcomes
The MistakeTracker emits a recoverable error event for every recorded
mistake while the run continues - e.g. a plan-mode guard-blocked
run_commands call as the turn's only tool call. Both hosts treated any
error event as terminal:
- The VS Code translator cleared the pending completion retag, set
errorSeen (turn phase "error": Retry / Start New Task footer), marked
the turn complete, and rendered the error recovery UI. A plan turn
that recovered from the mistake and completed cleanly therefore never
produced plan_completion_result, so togglePlanActMode's planPresented
check failed and switching to act mode rebuilt the session without
the auto-continue send - the toggle appeared to do nothing.
- The CLI TUI flipped isRunning/isStreaming to idle mid-turn, so the
footer lied about the still-running turn.
Recoverable errors are informational: the turn's outcome is decided by
how it actually ends (done/error). VS Code now logs them and keeps them
out of the chat (the tool failure is already shown inline on its tool
row, and provider-failure telemetry already ignores recoverable events
for the same reason); the CLI keeps its running state and surfaces them
only in verbose mode, as it already did for display. Genuine run
failures carry recoverable: false and keep the existing error UI.
Anthropic (and several providers OpenRouter fans out to) rejects any tool
whose input_schema has oneOf, allOf, or anyOf at the top level, failing the
whole request with:
tools.N.custom.input_schema: input_schema does not support oneOf, allOf,
or anyOf at the top level
MCP servers commonly advertise tools whose input schema is a union of object
shapes (e.g. generated from a Zod union), so one such tool bricked every
turn of the session. Merge union branch properties into a single object
schema at the provider boundary; tools still validate their real input
shapes in execute().
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add plan-mode command blocklist to run_commands
Plan mode kept run_commands available (needed for read-only
investigation) but relied on prompting alone to prevent file edits,
and weaker models routinely ignore that. Add a hard guard in
@cline/core's createShellTool that inspects each command before
execution and rejects file-editing constructs with a plan-mode tool
error instead of running them.
The guard is a quote/heredoc-aware scan that blocks file-manipulation
commands (rm/mv/cp/tee/touch/...), in-place editors (sed -i, perl -i,
gawk -i inplace, sort -o), output redirection to files (allowing /dev
sinks and /tmp for the documented output-capture pattern), mutating
git subcommands, package-manager installs, find -delete/-exec, and
nested command strings (sh -c, eval, sudo, xargs, ...). Windows and
PowerShell equivalents are covered too.
Enabled via a new blockFileEditingCommands flag on DefaultToolsConfig,
set by the plan tool preset (CLI and core runtime) and plumbed through
the VS Code extension's custom run_commands tool from the session mode.
The tool description and PLAN_MODE_INSTRUCTIONS now state the hard
block so models are forewarned.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Simplify plan-mode command guard to a plain blacklist
Replace the char-by-char shell tokenizer (heredoc queues, process
substitution, recursion into sh -c/eval, find -exec analysis) with a
simple scan: mask quoted text/heredoc bodies/escapes/comments so they
cannot false-positive, split on shell separators, and compare the
leading command word of each part against flat blacklists (commands,
mutating subcommands, in-place edit flags), plus one redirect check.
Quoted nested commands (bash -c 'rm x') are a documented false
negative. Also drop the guard from the package's public exports; it
is internal to createShellTool.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Move plan-mode command guard into a built-in beforeTool hook
Review feedback (abeatrix): command blocking is session policy, not
shell-executor configuration. Replace the blockFileEditingCommands
flag threaded through preset -> tool config -> VS Code host with a
core extension registered by the runtime builder for plan-mode
sessions. The beforeTool hook intercepts every run_commands tool in
the runtime - the SDK builtin, host replacements like the VS Code
terminal tool, and delegated sub-agents - and rejects file-editing
calls with the plan-mode error before tool policy and user approval,
so users are no longer prompted to approve a command that would only
fail. All VS Code wiring for the guard is removed.
Also adds block telemetry (sdk.plan_mode_command_blocked with the
blocked construct, never raw command content), per review.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Address review feedback on the plan-mode command blacklist
False positives (mkondratek):
- perl -Ilib / uppercase value-taking flags no longer match the
in-place check; the flag cluster must end at a lowercase i
(sed -Ei still blocked)
- awk inplace detection is tied to the -i/--include flag instead of
matching the substring anywhere (filenames like inplace-notes.txt
no longer trip it)
- read-only git forms allowed: stash list/show, worktree list,
submodule status/summary, and any git subcommand with --help/-h
- arithmetic expansion (1) is masked before the redirect scan
Hardening and coverage (mkondratek, dominiccooney):
- temp-path redirect allowance rejects .. traversal (/tmp/../...)
- Windows gets a temp escape hatch: %TEMP%/%TMP%/$env:TEMP redirect
targets are allowed and the block error mentions it
- curl -o/-O/--output/--remote-name and wget downloads blocked
(--spider and -qO- stdout forms stay allowed)
- python -m pip resolves to the pip subcommand check
- unambiguous PowerShell aliases (mi, ri, cpi, rni, ac, clc) plus a
case-variant test
- more package managers: winget, nuget, gem, composer, dotnet add,
go install/get; bare classic yarn blocked again
- find -exec/-execdir/-ok chains and xargs -I {} placeholders are
checked for mutating commands
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(llms): regenerate model catalog from models.dev to pick up reasoning options
The baked fallback catalog was last regenerated before toModelInfo started
mapping models.dev reasoning_options into ModelInfo.reasoningOptions, so it
carried no reasoning metadata. Whenever the live models.dev fetch fails or a
model resolves from the baked catalog, adaptive-era Claude models (4.6+/5.x)
fell through the missing-reasoningOptions path to Anthropic manual thinking
and the API rejected the request with 'thinking.type.enabled is not
supported'.
This regen also picks up upstream models.dev drift; test expectations that
hardcoded stale catalog values (GLM 5.2 context window, OpenRouter GLM 4.7
reasoning controls, Vercel AI Gateway Qwen 3.6 Plus budget controls) are
updated to the current published values.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): infer adaptive thinking for adaptive-era Claude ids when catalog reasoning options are missing
Claude 4.6+ and 5.x models reject the manual thinking wire shape
(thinking.type 'enabled') on the Anthropic API. When a model resolves
without reasoningOptions metadata (offline baked catalog before the regen,
or user-typed unlisted ids such as claude-opus-4-6:1m), the reasoning
policy previously fell through to anthropic-manual and every
reasoning-enabled request failed with a hard API error.
Add isClaudeAdaptiveEraModelId as a narrowly scoped id fallback (name-first
Claude ids with version 4.6+ or 5.x, plus the Fable line) and use it in the
missing-reasoningOptions branch of resolveAnthropicReasoningRequestPolicy.
Genuinely old or unknown Claude-compatible ids keep the manual default,
which remains the safe shape for third-party Claude-compatible endpoints.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): prefer adaptive thinking over manual when a model advertises an effort control
A numeric reasoning.budgetTokens (e.g. a thinkingBudgetTokens setting
migrated from the legacy extension) used to force the anthropic-manual
policy whenever the model advertised a budget_tokens control. Claude 4.6+
models advertise both effort and budget_tokens on models.dev but reject
thinking.type 'enabled' on the Anthropic API, so those requests failed.
Effort now wins: adaptive is selected and the numeric budget is ignored.
Budget-only models (Sonnet 4.5 and older) keep honoring explicit budgets
via the manual shape.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(llms): guard baked catalog reasoning options for adaptive-era Claude models
Resolve adaptive-era Claude models through the generated (offline fallback)
catalog and assert their entries carry effort reasoning options that the
Anthropic reasoning policy resolves to adaptive thinking.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(core): update GLM 5.2 context window to current models.dev value
The catalog regen picked up upstream drift: models.dev now publishes a
1,000,000-token context window for zai/glm-5.2 (was 1,040,000).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert(llms): drop the Claude id-based adaptive-thinking fallback
Keep the fix surface minimal: the catalog regen covers every model
models.dev lists (the overwhelming share of the production failures), and
the effort-over-budget policy covers listed models that advertise both
controls. Unlisted id variants (e.g. claude-opus-4-6:1m) keep the
pre-existing manual fallback rather than introducing id-version parsing in
model-facts.ts; if they appear in models.dev the catalog picks them up
automatically.
This reverts commit 6d725b1ecd7c896a08c3c58dbb37afeaf34bb31e, keeping the
regenerated catalog and the effort-precedence change.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): default unknown Claude ids to adaptive thinking when catalog options are missing
Reintroduce the id-based fallback with the forward-compatible policy the
ecosystem converged on (vercel/ai#17804 for @ai-sdk/anthropic's capability
lookup; opencode's transform.ts after repeated allowlist misses for
opus-4.7, sonnet-5, and opus-5): when catalog reasoningOptions metadata is
unavailable, treat unrecognized Claude ids as newer than the known model
list and use adaptive thinking, since new Claude releases reject the manual
wire shape. Known legacy families (Instant, 2.x, 3.x, and name-first 4.0-4.5)
keep manual, as do non-Claude Anthropic-compatible ids and unknown Claude
ids carrying an explicit numeric budget (a custom-endpoint signal).
Unlike the earlier reverted allowlist (which defaulted unknown ids to
manual), this fails open for future models: claude-opus-4-6:1m-style
variants and next year's Claude work without a code change.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): retry empty model turns on all providers, not just Ollama
Production telemetry shows 'Model returned empty response' hard failures
on hosted backends (openrouter, cline, openai-compatible endpoints), not
just local Ollama — 46 tasks / 120 events in 24h on the SDK extension vs
~0 on legacy, which has its own empty-response fallback.
The retry-empty-response middleware already existed but was wired only
into the Ollama vendor. Move the wrap to the central AI SDK composition
point (createAiSdkProvider in ai-sdk.ts), where every vendor's model is
constructed, so all providers get it: retry only when a turn produced
genuinely nothing (no text, no reasoning, no tool call), tool-call-only
turns are never retried, non-empty turns stream through live, and error/
token-limit finishes pass through unchanged. Vendors can opt out or tune
attempts via ProviderFactoryResult.retryEmptyResponses. The agent
runtime's loud failure after persistently empty turns is unchanged.
* docs(sdk): drop changelog edit — release commits own the changelog
v0.0.69 is already published; its section must not be edited
retroactively. The next release commit will describe this change.
* ci: re-trigger checks (flaky Windows runner test timeouts)
* ci: re-trigger checks (flaky Windows runner test timeouts)
* fix(llms): classify stream parts exhaustively, buffer retry attempts, aggregate usage
Review follow-up (dominiccooney): the retry predicate and the response
parser were two independent, incomplete interpretations of the
LanguageModelV4StreamPart union, and rejected attempts leaked structural
parts and dropped billable usage.
- stream-part-classification.ts is now the single exhaustive boundary:
every part is converted content, explicitly unsupported output,
structural metadata, stream-start, finish, or error, with a never
check so new AI SDK part types fail compilation. Retry eligibility
derives from it: only turns with no output at all are retried;
unsupported-but-real output (custom, reasoning-file, source,
provider-executed tool-result) is never retried.
- Generated file parts are converted end to end: emitAiSdkEvents emits
a new file AgentModelEvent and the agent runtime assembles it onto
the assistant message (image part for image/*, file part otherwise),
so a file-only turn is no longer an empty message. The legacy
ApiStream bridge explicitly skips file events (no chunk type).
- Each retry attempt is buffered until it proves non-empty (first
output or error part), so discarded attempts leak nothing — one
retried request produces one clean stream with exactly one
stream-start.
- finish.usage from discarded attempts is aggregated field-by-field
(cache and reasoning detail included) into the emitted finish, so a
three-request turn reports three requests' worth of tokens.
* fix(vscode): show cwd-relative tool paths in the chat view
The SDK message translator copied the model's absolute file paths straight
into the ClineSayTool messages, so chat cards like "Cline wants to read this
file" showed full absolute paths. Relativize them against the task's cwd for
display (classic getReadablePath behavior: relative inside the cwd, basename
for the cwd itself, absolute when outside), including apply_patch's
"*** Update File:" markers which DiffEditRow parses for its headers.
Also restores the readFile card's click-to-open target by setting content to
the absolute path, matching the classic extension.
* refactor: apply display-path relativization as a single ClineSayTool transform
Instead of threading cwd through every case of sdkToolToClineSayTool, leave
the tool mapping untouched and apply one toDisplaySayTool transform (with a
filesystem-path tool whitelist) at the points where tool cards are emitted.
Same behavior, much smaller footprint; MCP/unknown tools keep their exact
prior behavior.
* fix: keep absolute readFile open-target untouched on Windows; match '..' as whole segment
path.resolve(cwd, absPath) rewrites a drive-less absolute path onto the
current drive on Windows, breaking the readFile card's click-to-open target
(and the tests asserting it). Guard with path.isAbsolute instead.
Also match '..' only as a whole path segment in toDisplayPath so an in-cwd
entry literally named '..config' is not misclassified as outside the cwd
(greptile P1).
* fix: keep Desktop-fallback paths absolute; relativize '*** Move to:' destinations
When VS Code has no workspace open, getWorkspaceRoot() falls back to the
Desktop; classic getReadablePath deliberately keeps full absolute paths in
that case so the user can see where operations occur. Restore that guard in
toDisplayPath.
Also enroll PATCH_MARKERS.MOVE in relativizePatchPaths so a rename renders
both source and destination relative (covers the split-patch path too).
* Fix Bedrock prompt caching: emit Converse cachePoint markers instead of anthropic cache_control
The Bedrock provider manifest routed prompt caching through the
anthropic-cache-control format, so requests carried cache_control
provider options that @ai-sdk/amazon-bedrock silently drops - its
Converse message converter only reads providerOptions.bedrock.cachePoint.
Bedrock never received a cache checkpoint, cacheRead/cacheWrite were
always 0, and a stray top-level cache_control field leaked into the
Converse request body.
Adds a bedrock-cache-point prompt-cache format that attaches a
message-level cachePoint marker to the last user message, which the
converter appends as a cachePoint content block, caching the whole
prefix up to it.
Fixes#12913
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Format gateway.test.ts assertion
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): show skills in slash command menu
* fix(core): normalize runtime slash command names
* fix(core): disambiguate colliding slash commands
* fix(core): preserve same-kind slash commands
* fix(core): stabilize colliding slash command aliases
* fix(core): avoid slow runtime command regex
* fix(core): remove quadratic hyphen trim
* fix(core): prefer skills over workflows on slash command collisions
Workflows are effectively deprecated in favor of skills, so when a
workflow's normalized name collides with a skill the skill now owns the
token and the workflow is dropped. This removes the collision
qualification machinery (-skill/-workflow/-hash aliases), which silently
renamed established CLI and VS Code command tokens, and removes the
duplicate-token throw that sat in the CLI send path, the hub snapshot
capability, and the desktop list_user_instruction_configs command.
Same-kind collisions resolve to the first entry of the deterministic
(name, id) sort, stable across discovery order.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): resolve typed workflow filenames through record ids
Name normalization broke the legacy /my-workflow.md fallback for
workflows renamed via frontmatter: the configured record name (e.g.
"Ship It") no longer compares equal to the normalized command token
("ship-it"), so a typed filename stopped expanding. Match the discovered
record to its runtime command by the stable record id instead, keeping
the canonical-name comparison as a fallback for callers that pass
records without ids.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): normalize snapshot names in hub slash command proxy
The hub-side proxy normalizes the typed token but compared it against
snapshot command names verbatim. Snapshots served by older clients carry
raw configured names (e.g. "Ship It"), which could previously exact-match
typed input and would now never match. Normalize both sides of the
comparison so mixed-version hub setups keep resolving.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): expand slash commands in the sidecar send path
Selecting a skill or workflow from the desktop slash menu inserted the
token but the sidecar dispatched it verbatim, so the model received
literal text like '/publish-ui write docs' instead of the configured
instructions. handleSend now expands a leading runtime slash command via
the core user-instruction service before dispatch (mirroring the CLI's
buildUserInputMessage), keeping the raw token as the session's display
prompt. Built-in webview commands (/fork, /team) and unknown tokens pass
through unchanged, and discovery failures fall back to the raw prompt.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): expand slash commands when editing queued prompts
Editing a pending prompt stored the raw slash token, which the runtime
later delivered to the model unexpanded — only the initial send path
went through expandRuntimeSlashCommand. handleUpdatePendingPrompt now
expands a leading skill/workflow token before persisting the update,
matching the enqueue behavior in handleSend.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): preserve Unicode letters in slash command tokens
Normalization stripped all non-ASCII characters, so a skill named 发布
got an unrelated generated token while typing /发布 could never resolve —
a regression from pre-normalization behavior where the exact name
matched. Keep Unicode letters and numbers in normalized tokens and only
collapse whitespace and symbol runs into hyphens.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(shared): emit AI SDK 7 file parts for images
formatMessagesForAiSdk still built the retired shapes: user images as
{type:'image'} message parts and tool-result images as {type:'image-data'}
content parts. AI SDK 7 auto-migrates both at runtime, but logs a
DeprecationWarning through process.emitWarning on every image-bearing
request, and the shims are slated for removal in the next major.
Emit the canonical shapes instead: {type:'file', data, mediaType} for
user images and {type:'file', data:{type:'data', data}, mediaType} for
tool-result media. mediaType is required on file parts, so URL-backed
images without a known type use the bare 'image' top-level segment,
which AI SDK 7 resolves per provider.
* fix(llms): allow the AI SDK 7 major of ai-sdk-provider-claude-code peer
The AI SDK 7 upgrade moved the ai-sdk-provider-claude-code
devDependency to ^4 but left the peer range at ^3.4.3, so consumers
resolving the peer would install the AI SDK 6 (Provider V3) major.
Align the peer range with the version the package is built against.
* fix(llms): route Bedrock foundation models through geo inference profiles
AWS Bedrock offers no on-demand throughput for newer foundation models;
they must be invoked through an inference profile. The SDK Bedrock vendor
passed model ids through unmodified, so every request with a bare modern
model id (e.g. anthropic.claude-sonnet-4-6) failed with "Invocation of
model ID ... with on-demand throughput isn't supported".
Resolve the wire-level model id in the Bedrock vendor: honor the existing
useCrossRegionInference / useGlobalInference settings (already plumbed
through provider config but previously ignored), and auto-prefix bare ids
of models known to have no on-demand throughput so they work without the
toggle. Ids that are already profile-prefixed, ARNs, and custom-model
configurations are never rewritten; unknown regions fall back to the raw
id. Country profiles (jp./au.) are preferred over apac. where the model
catalog shows AWS ships them.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): future-proof Bedrock profile-required model patterns
Match Anthropic tier-first naming generically (excluding the frozen
legacy claude-3-*/claude-v2/claude-instant naming schemes) instead of
enumerating tier names, so future profile-only Claude tiers work without
pattern-list updates. Also cover the profile-only Amazon Nova 2 series.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): gate Bedrock geo profiles on catalog availability
Address review feedback on inference-profile resolution:
- Never manufacture apac. (or other unconfirmed) profile ids: prefer a
catalog-confirmed variant among the region's candidates (jp./au./apac.),
and otherwise keep the raw id so AWS returns the actionable on-demand
error instead of "provided model identifier is invalid". Profile-only
models still fall back to us./us-gov./eu. prefixes, where AWS reliably
ships geo profiles for such models.
- Drop the customModelBaseId short-circuit: legacy migration copies the
base id without the custom-selected flag, so its presence must not
disable profile routing for a normal catalog model. Custom/provisioned
ids stay raw on the cross-region path because no catalog variant can be
confirmed for them, and ARN-based custom models were already passed
through.
Adds a per-region wire-id table test, an injected-catalog apac test, and
a stale-customModelBaseId regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): require catalog confirmation for every Bedrock geo profile
Remove the us./us-gov./eu. pattern fallback: AWS documents
inference-profile availability per model and geography, so no geographic
prefix is assumed valid without a catalog-confirmed variant (the catalog
had bare amazon.nova-lite/micro/pro ids with no geo variants, which the
fallback would have rewritten to unconfirmed eu./us. ids). The pattern
list now only gates eligibility for automatic routing; the catalog
always picks the actual prefix, and the raw id is preserved when no
variant is confirmed.
Adds boundary tests asserting pattern-matched models without confirmed
variants stay raw (with and without cross-region inference), plus
injected-catalog positive tests for us-gov. and future tier-first ids.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix installed plugins all displaying as "index" in the desktop app
Hoist getPluginDisplayName (nearest-ancestor package.json name with
basename fallback) into @cline/shared storage paths, re-export it via
@cline/core, and replace the duplicated copies in cline-hub, the CLI
TUI, and VS Code marketplace helpers. Fix the desktop sidecar and
'cline config plugins', which still named plugins by entry-file
basename, so package-backed installs showed up as "index".
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Use shared getPluginDisplayName in desktop sidecar after #12933
Merging main brought in PR #12933, which fixed the desktop plugin
naming with another local copy of the helper. Drop that copy in favor
of the shared @cline/shared implementation this branch introduces, and
remove the node:path imports it needed.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
The webview posts an optimistic say:'task' message carrying the user's
images/files, and only clears it once an identical authoritative message
arrives from the extension. emitInitialTaskMessage omitted attachments,
so the optimistic copy was never confirmed and withPendingUserMessage
kept re-injecting the old task into the transcript even after New Task
cleared it - leaving the chat permanently stuck on the previous task.
- Include images/files on the authoritative initial task message so the
optimistic pending copy is confirmed and cleared as designed.
- Defensively drop any unconfirmed optimistic message in startNewTask so
an explicit New Task click always yields a clean slate.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): track external git branch changes in the TUI status bar
The branch shown below the prompt was read once at startup and only
refreshed after an agent turn, so checkouts made from another terminal
or an editor left the TUI showing a stale branch (#12911).
Watch the repo's git dir for HEAD changes (git replaces HEAD via
rename, so a directory watch is used) and refresh the status bar
immediately, with a slow 5s poll as a fallback for filesystems where
fs.watch is unreliable. State updates are skipped when nothing changed
to avoid needless re-renders.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(cli): replace subprocess polling with stat-based HEAD backstop
Drop the unconditional 5s git-subprocess poll from useRepoStatus. The
fs.watch directory watcher stays for instant updates where the runtime
delivers HEAD events, but Bun on Linux drops them, so add fs.watchFile
on the HEAD file as the backstop: one in-process stat() every 2s that
only triggers git subprocesses when HEAD actually changed. Verified in
the Bun-run TUI that external checkouts show up within ~2s.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(cli): simplify HEAD watching to a single fs.watchFile
Drop the fs.watch directory watcher (Bun on Linux never delivers its
HEAD events, making it dead weight on the runtime the CLI ships on) and
the debounce it required. watchGitHead now just stat-watches the single
.git/HEAD file via fs.watchFile, which survives git's rename-based HEAD
updates and works on network mounts.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(cli): use a plain 5s poll for repo status
Remove the HEAD watcher entirely per review preference for minimal
code: root.tsx now just polls readRepoStatus every 5 seconds, skipping
state updates (via isSameRepoStatus) when nothing changed so idle ticks
don't re-render the app.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): skip repo status poll ticks while a read is in flight
Bounds concurrent git subprocesses when a read exceeds the 5s interval
(slow git on huge repos) and prevents an older completion from
overwriting newer status. Addresses Greptile review feedback.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
The copy/fork (and user copy/edit/restore) action row was pulled up 8px
(-translate-y-2), which made the icons collide with the descenders of the
message's last line of text. Reduce the raise to 4px (-translate-y-1) so the
actions sit with a small, deliberate gap under the message content while
still hugging the message closely enough to read as attached to it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(telemetry): dedupe sdk.error across layers and rate-limit repeated failures
Every provider failure was emitted twice — once by the model layer
(provider.stream, handled: true) and again verbatim by the agent loop
(agent.run, handled: false) — and unattended retry loops emitted the
same failure every iteration, unbounded. 24h of CLI data: 300K events
from 1.7K users, top 10 machines at 70% of volume.
Two changes:
- The agent loop no longer re-reports model stream failures. run-failed
events carry errorClass exactly when the run failed on a model stream
error, and the model layer already reports those at its own error
boundary — so the run loop reports only failures that originate in
the loop itself (empty response, max iterations, ...).
- captureSdkError caps identical failures per process: 5 per hour per
(event, component, operation, error_type, normalized message), with
digit runs collapsed so retry counters coalesce. Suppressed emissions
surface as suppressed_count on the next emission after the window
rolls over. In-memory only; the cap never blocks reporting.
Event name, attributes, and all call sites are unchanged;
suppressed_count is the only additive field.
* fix(telemetry): make sdk.error dedup ownership explicit and key limiter on status/code
Review follow-ups (#12931):
- Reporting ownership is now an explicit signal instead of being inferred
from errorClass. captureSdkError returns whether the failure was
recorded, the model layer forwards that as errorReported on the finish
event, and the run loop skips only failures marked reported. Custom
AgentModel implementations that never call captureSdkError leave the
bit unset, so their failures still produce exactly one sdk.error from
the run loop (regression test added).
- The rate-limit key now includes the structured error_status and
error_code that normalizeSdkError already extracts, so an HTTP 429
hot loop cannot consume an HTTP 401's budget even though their
messages differ only by digits (tests added for both fields).
- resetSdkErrorRateLimiterForTests is tagged @internal; it stays
re-exported because package test suites can only reach it through the
package entry point.
The publish job ran under the shared `Publish` GitHub environment, whose
required reviewers turned every @cline/ui release into a two-person
ceremony. Nothing in the job reads secrets from that environment — it
authenticates to npm purely over OIDC trusted publishing — so the
environment bought us an approval prompt and nothing else. sdk-publish
and cli-publish already publish unattended the same way.
The npm trusted publisher for @cline/ui was registered with
`environment: Publish`, which pins the OIDC token's environment claim, so
it has been re-registered without it (same repo, workflow file, and
permissions). That change is already live; landing this without it would
have broken publishing.
Access is still gated by workflow_dispatch (write access required), the
`refs/heads/main` ref check, and the typed `publish` confirmation.
* Remove model-initiated plan-to-act switching from the VS Code extension
Match the legacy extension: the model can no longer call switch_to_act_mode
to move itself from plan mode to act mode. The user must flip the Plan/Act
toggle manually. The CLI keeps the tool and its prompt unchanged.
- Stop registering the switch_to_act_mode extra tool in plan-mode sessions
and drop the pending-mode-change queue, beforeModel stop hook, and idle
apply path that existed only for the tool-initiated switch.
- Add a planModeSwitchTool option to buildClineSystemPrompt (default true,
CLI output unchanged) and a PLAN_MODE_INSTRUCTIONS_MANUAL_SWITCH variant
that directs the model to ask the user to toggle to Act mode instead of
calling a tool it does not have; the extension passes false.
- The user-driven toggle path (togglePlanActModeProto), including
auto-continue when a completed plan is presented, is unchanged.
* Use a generic completing-tool name in translator retag test
Review feedback: submit_and_exit is a yolo-mode tool and does not exist
in plan/act sessions. The test exercises tool-agnostic translator
behavior, so use a neutral example name and clarify the comment.
* fix(cli): claim connector instance before socket connect
Prevent racing foreground or detached connector launches from
both opening socket-mode with the same bot token by exclusively
claiming the state file via tryClaimConnectorStateFile before
connecting, and exit with CONNECT_ALREADY_RUNNING_EXIT_CODE when
another live instance already holds the claim.
* serializes stale-generation replacement without a removable mutex
* lint
* fix
* base
* fix(cli): keep pre-claim Slack state files manageable
State files written by CLI versions that predate connector claiming have
no claimId, so requiring it in readConnectorState made a live legacy
connector invisible: stop deleted its state without stopping it, which
let the next connect open a second socket-mode connection with the same
bot token. Treat claimId as optional metadata; claiming itself never
relied on the validator.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(telemetry): remove duplicate capture defs owned by @cline/core
The cline-core bundle layer (apps/vscode, which also compiles into the
cline-core.js that JetBrains runs) still carries event constants and
public capture methods inherited from the legacy architecture. Where
@cline/core now emits the same event, the bundle-layer twin is a second
capture path on the same telemetry service — which is how the
task.provider_api_error double-emission happened (cline/cline#12820,
follow-up to the removals in cline/cline#12818).
Removes 20 such methods and the EVENTS constants only they referenced:
- 15 whose signal @cline/core emits today (task lifecycle, tokens, tool
and skill usage, auth start/success/failure, opt-out, workspace init,
and summarize_task, which core replaced with task.compaction_*).
- 3 obsolete on both architecture lines: captureModelSelected (its
model_selected signal survives as an action on
captureOnboardingProgress), captureRulesMenuOpened, captureHostEvent.
- 2 whose trigger moved into core/sdk, so this layer can no longer
observe them and re-wiring here would be wrong:
captureWorkspacePathResolved (core already owns workspace.path_resolved)
and captureGeminiApiPerformance (providers live in core; generic
provider-timing events supersede it).
Deliberately NOT removed: capture methods with no caller here but a live
caller on legacy-extension. Those emit signals originating in this bundle
(webview UI, VS Code storage, host terminal, checkpoints, focus chain,
legacy-task migration), so core cannot emit them and the missing piece is
a call site on this line, not a redundant definition. They are the
SDK-parity backlog and are flagged as such in the file.
Verified against the JetBrains plugin repo: it references none of these
methods or event names, and no proto surface changes.
Also drops the unused TokenUsage interface, the taskTurnCounts and
taskToolCallCounts maps (only deleted methods wrote to them), and EVENTS
constants that were already orphaned before this change.
Tests that only exercised a removed method are gone; tests that used one
merely as a vehicle for provider/metadata assertions now use a surviving
method, so that coverage is preserved.
* fix(telemetry): keep agent identity on events dispatched after session teardown
A small share of task.tool_used events (~285 of 229k over 48h on
extension_variant=next) arrive without any agentId/agentKind/isSubagent
attributes. Root cause: AgentEventBridge.dispatchAgentEvent resolves
identity solely from the live-session map (AgentEvent metadata never
carries agentId in practice), and session teardown deletes the map entry
before the agent's run fully drains — dispose/stopSession paths can skip
or fail agent.shutdown() without aborting first. Late events from the
still-draining run then hit the session-map miss branch, which passed no
identity at all, so buildTelemetryAgentIdentity returned undefined and
the event was emitted bare.
Fix: snapshot the identity stamped on each session's events while the
session is registered (bounded FIFO map) and reuse it on a session-map
miss. Purely additive — no event is added, removed, or renamed; the
live-session and sub-agent paths emit byte-identical properties.
* feat(sdk): add session initiation mode and lazy session persistence
- Introduce top-level `StartSessionInput.mode` (`user`, `automation`, `subagent`, `team`) alongside `source`, so persisted history records both the client surface and how the session began; missing mode defaults to `user`.
- Make root-session persistence lazy: starting a runtime allocates the session ID in memory without creating a database row, manifest, or messages artifact. The first accepted user turn persists that same ID, so closing a runtime before any user turn leaves no empty history entry, and persistence never allocates a replacement ID for unknown sessions.
- Require automation runtime adapters to explicitly persist `mode: "automation"` for every run.
- Document the provenance model in `sdk/ARCHITECTURE.md`, update the VS Code session factory comment, and add tests for the automation runtime handlers.
* fix(sdk): persist automation trigger source as session provenance
The runtime adapters stopped writing the cron request source into the
session row when source became the client surface, which silently
dropped the spec-defined trigger label. Record it as
sessionHistoryOrigin.trigger instead, surface it in the messages-file
origin, and sort the new history-origin import in HubRuntimeHost.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(ollama): use native AI SDK provider
* fix(ollama): patch ollama-ai-provider-v2 wire contracts and lock them with real-provider tests
The pinned ollama-ai-provider-v2@4.0.1 breaks four native Ollama wire
contracts (review findings on #12892). Patch the package via Bun
patchedDependencies:
- omit think from the request when no reasoning setting resolves,
instead of forcing think: false (lets the server default apply)
- surface mid-stream {"error": ...} objects as error stream parts with
an error finish reason, instead of dropping them before a clean finish
- serialize attachment-only user turns as string content (""), not []
- include the documented tool_name field on tool result messages
Add ollama.wire.test.ts exercising doStream through the vendor module
against the real (patched) package with a stubbed fetch, asserting on
the actual /api/chat request bodies and parsed stream so regressions in
the dependency's request converter or stream parser are caught.
* fix ollama model list refresh
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix: prevent duplicate connector launches during doctor/connect
Mark connectors as starting before the hub daemon spawns so autostart
skips in-flight instances, and improve doctor process filtering with
container-aware namespace/cgroup checks plus detached log rotation.
* Update apps/cli/src/connectors/common.ts
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
* feat(hub): supervise connector processes
* feat(connectors): enable tools by default, and stop replaying the Slack greeting
Tools were on by default only for Telegram (via --no-tools); Slack, Discord,
Linear, Google Chat and WhatsApp all required an explicit --enable-tools. All
six now default to tools on and opt out with --no-tools.
--enable-tools still parses everywhere, including Telegram which never accepted
it, so deployed scripts, systemd units and persisted autostart arguments keep
working. Passing both resolves to the safer answer: --no-tools wins. This also
affects hub/webview starts, which never emitted a tools flag and so ran those
five connectors with tools off.
Slack no longer posts the "Connected to Cline." first-contact message. It was
gated on per-thread welcomeSentAt, so a connector restart or a cleared history
made the next user message look like first contact and replayed the greeting.
The host mechanism is unchanged and the other adapters still greet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(connectors): recover a thread whose session is wedged mid-run
A connector thread keeps a long-lived mapping to a hub session. When that
session's runtime still had a run in flight and no abort had been requested,
every message in the thread came back as "SessionRuntime.shutdown called while a
run is in progress" instead of an answer, and stayed that way until someone
cleared the binding by hand. Observed on the Cline Mom Slack bot after a stack
restart.
The connector host already recovers from a session the hub no longer knows
about: it forgets the mapping and replays the turn once against a fresh session.
This widens the trigger from "session not found" to "session cannot serve
another turn" via isUnusableSessionError, so a wedged runtime takes the same
path.
The shutdown error now carries a stable code (SessionRunInProgressError,
session_run_in_progress) so callers can recognise it structurally. The predicate
also matches on message, because an error reaching a connector has crossed the
hub's JSON boundary and arrives as a bare message - and because a host commonly
runs a hub and CLI of different versions. Ordinary run failures still propagate
untouched: replacing the session on those would hide real errors and drop the
conversation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(connectors): serialise turns that share a session
Answering "what happens if I message the bot in another thread while it is still
replying": channel threads were already independent, but DMs were not.
findBindingForThread deliberately reuses one binding — and therefore one runtime
session — for every message in a DM channel, so a DM stays one continuous
conversation. The turn queue, though, was keyed by thread id, and a DM thread id
carries the message timestamp. Two messages in flight in the same DM therefore
got two independent queues and ran concurrently against a single session, which
fails with "shutdown called while a run is in progress" or interleaves two
conversations in one session history.
The queue key now follows the same identity rule as the binding lookup, via
resolveThreadTurnQueueKey next to findBindingForThread so the two cannot drift.
DM messages queue behind each other on the shared session; channel threads keep
their own key and still run in parallel. Applied to all six adapters, which all
had the same mismatch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(core): abort an in-flight run before tearing its session down
Where the Slack bot's "plugin-sandbox process exited (code=null, signal=SIGTERM)"
came from, and its "shutdown called while a run is in progress" sibling: both are
one event, a session released while a run was still going.
stopSession aborts the agent first "so shutdown can proceed", but callers that
reach shutdownSession or releaseSessionRuntime another way did not - hub
dispose() on a restart being the one that hurt. Without an abort the runtime
refuses to shut down, that error is rethrown from the cleanup, and the plugin
sandbox is SIGTERMed while tool calls are still pending, so those calls reject
with "plugin-sandbox process exited". A connector turn awaiting the run reports
whichever surfaced first instead of answering.
Both paths now abort and let the run drain before shutting the agent, runtime and
sandbox down, guarded on session.aborting so callers that already aborted do not
abort twice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(connectors): stop announcing "Steering current task."
Every follow-up sent while the bot was replying added an acknowledgement line to
the thread, and the wording overstated what happens: the host treats delivery
"steer" the same as "queue", enqueuing the prompt for the session rather than
injecting it into the loop already running. The follow-up is now handed over
silently.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(core): retire a dead supervised entry before replacing it
A start arriving while an instance sat in backoff left the old entry's
restart timer live. The timer closes over the old entry object, so when
it fired it spawned a second process for the same (channel, instanceId)
- untracked by the supervisor's map, so invisible to list() and
unreachable by stop() - two connectors holding one bot token, which is
the exact failure supervision exists to prevent. Its exit handler then
kept reaping the live instance's state and rescheduling restarts.
The same window exists before the timer is even scheduled: the
exit-cleanup chain runs first, and a replacement made mid-chain would be
followed by a restart scheduled for the retired entry.
start() now retires a dead existing entry explicitly - cancel its timer,
mark it stopped, drop its exit listener. Both the timer callback and the
cleanup chain already stand down on "stopped", so one mark covers both
phases.
* fix(core): serialise supervisor start/stop and wait for stopped processes to die
Found by exercising a hub restart against a live webhook connector: the
new hub's boot reconnect restarts the adopted survivor - which suspends
inside stop() on the CLI cleanup - while the user's `cline connect`
arrives as connector.start. With no per-instance serialisation the two
starts interleaved across that suspension and both spawned. The map
tracked one process while the other lived on untracked, holding the
connector's webhook port; the tracked chain crash-looped on EADDRINUSE
through all five attempts and ended state=failed, while the ghost kept
running with no way to reach it through list() or stop().
Two changes:
- start/stop (and the backoff-restart spawn) now run under a per-
instance-key promise queue, so one instance has exactly one lifecycle
operation in flight. The exit-cleanup chain also stands down when its
entry is no longer the one in the map.
- stop() waits for the process to actually die after SIGTERM (bounded,
then SIGKILL) instead of returning while it still holds its listen
port - the race that turned the double-spawn into a crash loop, and
that could burn a backoff cycle on any webhook connector restart.
process.kill is now injectable (killProcess), which also stops the test
suite from signalling arbitrary real pids like 600 on the host.
Verified live: the same kill-hub-then-reconnect sequence now converges
to one tracked running process, with the concurrent user start
correctly answered "already running under the hub".
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Tauri's universal-apple-darwin target lipos the Rust binary but expects
sidecars to already be fat binaries, so build-sidecar-bin.ts now compiles
both Bun slices and merges them when the target triple is universal.
The publish workflow builds one universal bundle instead of a two-leg
matrix, verifies every Mach-O in the bundle carries both slices, and the
updater manifest points both darwin-aarch64 and darwin-x86_64 at the same
universal artifact so existing per-arch installs migrate automatically.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add user-selectable color themes to the CLI TUI
Adds a theme system to the interactive TUI (cline -i):
- New tuiTheme global setting persisted in global-settings.json
- Built-in themes: Auto (terminal-adaptive, default), Cline Dark,
Cline Light, Tokyo Night, Gruvbox Dark, Nord, Dracula, Catppuccin
Mocha, One Dark, Solarized Dark, Solarized Light
- /theme command, command palette entry, and a Theme row in
/settings General tab, all opening a live-preview theme picker
- Named themes paint their background, default foreground, accents,
syntax highlighting, and derived diff colors across the TUI
- CLINE_THEME env var overrides the persisted theme at startup
Closes#12872
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Widen theme picker dialog and label column
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Format theme picker
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Give each theme a descriptive picker blurb
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Theme all main-surface components instead of static palette colors
The ask-question / tool-approval element, toasts, queued prompts,
autocomplete dropdown, chat error cards, searchable lists, and the
onboarding screens hardcoded the brand palette (act blue, selection
highlight, black-on-selection text) and fixed dark grays, so they
ignored the active theme.
- ResolvedTheme gains selection/textOnSelection; the selected-row text
flips between black and white by WCAG contrast against the accent
- Inline ask-question / tool-approval, Toast, QueuedPrompts,
AutocompleteDropdown, SearchableList, and chat error cards now use
theme accents and the themed selection pair
- Onboarding screens derive subtle borders/details from the theme
background instead of #333333/#555555, and use themed accents
- Dialog surfaces (settings, pickers, history) intentionally keep their
static dark surface styling
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: correct Linux keybinding label in Plan/Act mode tooltip
On Linux, event.metaKey maps to the Super (Win) key, not Alt.
detectMetaKeyChar was returning "Alt" for Linux, causing the Plan/Act
mode toggle tooltip to display "Alt+Shift+A" instead of "Super+Shift+A".
Fixes#11026
* fix: update platformUtils.spec.ts Linux test expectation to Super
---------
Co-authored-by: Dominic Cooney <dominic.cooney@cline.bot>
* ci(desktop): drop the Rust build cache from the code-signing job
The `build` job is the only one that can read the Apple Developer ID
certificate and the Tauri updater signing key, and it restored a
swatinem/rust-cache archive before running them. A restored cache archive
is attacker-controlled the moment the Actions cache is poisoned, which is
the pivot used against this repo's nightly workflow in Feb 2026 and the
reason actions/cache was stripped from the credential-bearing publish
jobs at the time. This workflow was added months later and reintroduced
the pattern. The updater key is the worst thing here to leak: it signs
every auto-update the installed desktop app accepts.
The cache was also not buying anything. Across the eight runs of this
workflow, seven logged "No cache found" on both matrix legs; only the run
32 minutes after another one hit, saving 1-3 minutes. A release cadence
measured in days does not outlive the entry under the repo's 10 GB LRU
eviction, so the steady state was a cold build regardless. Cold builds
took 5-7 minutes against a 90-minute timeout.
No behaviour change otherwise: the step had no id and no outputs, so
nothing referenced it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* ci(desktop): trim the cache-removal comment to the constraint
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
The @chat-adapter/telegram library intercepts any message whose leading
entity is a bot_command and routes it to slash-command handlers instead
of the mention/subscribed-message handlers. The Telegram connector
registered no onSlashCommand handler (unlike Discord and Slack), so
commands like /clear were consumed by the library and silently dropped.
Register a slash-command handler that rebuilds the originating chat
thread and forwards the original message text (preserving @bot
addressing for group chats) into the same turn pipeline as regular
messages, so connector commands reach the chat command host.
Fixes#12871
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): surface a clear error when a provider has no API key
A key-based provider with no API key sends the request without an Authorization header, and the provider's raw 401 reached the chat panel unclassified because reshapeErrorForWebview falls through to the raw message and ClineError's auth regexes do not match it. Rewrite the missing-Authorization-header case into actionable guidance naming the provider, alongside the existing model-not-found matcher. Matching is limited to the no-header signature so a present-but-wrong key is never relabelled as missing, and no preflight is added because authMethod misclassifies local providers and 175 of 179 builtins resolve keys from the environment.
* fix: don't name a fallback provider in the missing-key message
reshapeErrorForWebview defaults providerId to "cline" for its
ClineError-JSON branches, but state.activeProviderId() can be undefined —
the missing-credential message would then blame the cline provider for a
key it doesn't take. Keep the "cline" fallback for the JSON branches and
pass the raw id to the credential matcher, which now only names a provider
it was actually given.
---------
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
* refactor(llms): classify typed AI SDK errors before the structural walk
Add a typed pre-pass to classifyProviderError that recognizes real AI SDK
error instances via their symbol-based isInstance() guards: RetryError
unwraps to its last attempt, APICallError is judged on message/responseBody/
data with its typed statusCode as the sole authoritative status,
TypeValidationError on the payload in value, and any other AISDKError
recurses into its cause. The detection rules (overflow patterns, provider
codes, rate-limit vetoes, invalid-request status gate) are extracted into a
shared verdict function used unchanged by both the typed pass and the
existing structural walk, which remains the fallback for gateway-forwarded
plain-JSON payloads that only name an AI SDK error (ENG-2394).
* fix(llms): classify a RetryError by its final attempt even when untyped
When a RetryError's last attempt was not a typed AI SDK error, the typed
pre-pass fell back to structurally walking the whole wrapper, letting
signals from earlier (retried-away) attempts veto or fake the final
attempt's verdict — e.g. a retryable 429 on attempt one vetoing a plain
overflow rejection on the final attempt. Walk the final attempt alone
instead; a RetryError with no recorded attempts still falls back to the
plain structural walk.
* fix(llms): gate typed APICallError verdicts on the authoritative statusCode
verdictFromSignals checks the explicit context_length_exceeded code before
the rate-limit veto and the invalid-request status gate, so a typed
APICallError with statusCode 429 or 500 whose body echoed that code was
still classified as an overflow, contradicting the branch's contract that
the typed statusCode is the sole authoritative status. Gate the whole
payload verdict on the typed statusCode first (absent a statusCode the
payload still decides), and cover the explicit-code case at 429/500/400
with real instances.
* feat(sdk): detect and recover from context-window overflow errors
Port of the legacy arch's context-window-exceeded handling to the SDK
arch (the SDK arch previously surfaced these as raw unclassified stream
errors with no recovery; see ENG-2394 root-cause investigation).
- llms: new classifyProviderError() walks the raw provider error
structure (AI SDK wrappers, gateway value.error_message, responseBody,
cause chains) and classifies it before extractErrorMessage flattens
it. Reuses the legacy detectors' message patterns with rate-limit
vetoes and an invalid-request status gate.
- shared: ProviderErrorClass union; errorClass on the model finish
event, run-failed event, runtime snapshot, and prepare-turn contexts.
- core: prepare-turn overflowRecovery flag forces a compaction that
bypasses the token-estimate trigger (the estimate just proved wrong)
and runs the deterministic basic strategy directly, so recovery never
depends on another successful LLM request. New overflow_recovery
compaction mode in status notices and compaction telemetry.
- agents: on a classified overflow the runtime force-compacts and
retries once per run, emitting a status notice. Terminal states fail
with actionable messages instead of raw provider dumps: nothing to
compact (first-prompt overflow), no prepare-turn pipeline, or a retry
that still overflows. The doomed request is not re-sent when forced
compaction cannot shrink the transcript.
- telemetry: task.provider_api_error gains errorClass and
task.provider_stream_failed gains error_class, populated from the
same classification, so context-overflow failures become countable.
* fix(core): keep overflow-recovery compaction deterministic with custom compactors
A session-supplied compaction.compact previously took precedence over
the overflow_recovery basic-strategy branch, so an LLM-backed custom
compactor could hit the same context overflow mid-recovery. The custom
compactor still gets first shot (it sees mode overflow_recovery and
owns its transcript invariants), but if it throws or declines, basic
compaction now runs so recovery never depends on another successful
LLM request. Cancellation still propagates.
* fix(core): fall back to basic compaction when a custom compactor does not shrink during overflow recovery
A custom compactor that returns unchanged or larger messages would
previously satisfy the recovery branch, and the runtime would then
reject the retry as non-shrinking and fail terminally even though
basic compaction could still prune the transcript. Recovery now
treats a non-shrinking custom result like a decline and runs basic
compaction.
* fix(core): hold custom overflow-recovery compaction to the recovery token target
A custom compactor result that was only marginally smaller than the
input passed the shrink check, skipped the basic fallback, and spent
the run's single recovery retry on a request that still could not fit.
The custom result is now accepted only when it is strictly smaller AND
within the recovery token target basic compaction aims for; otherwise
basic compaction runs.
* fix(core): reject empty custom compaction results during overflow recovery
An empty transcript from a custom compactor passed both the shrink and
token-target checks (trivially smaller, zero tokens) and suppressed the
basic fallback, so the retry would have been sent without the request
it was supposed to re-send. The acceptance bar now covers the full
input space in one predicate: non-empty AND strictly smaller AND within
the recovery token target.
* feat(core): expose the turn abort signal to custom compactors
CoreCompactionContext now carries the prepare-turn abort signal, so a
custom compact implementation that calls a model or external service
can observe cancellation instead of blocking the turn (including the
overflow-recovery path) on a stalled request. Builtin strategies
already received the signal via providerConfig; this closes the gap
for custom compactors across auto, manual, and recovery modes.
* fix(sdk): classify provider errors from registered ApiHandler models
Registered handlers (VS Code LM and any other host-supplied provider)
reach the runtime through createAgentModelFromApiHandler, which flattens
failures to a message string — so context-window rejections on that path
were never classified and never entered overflow recovery.
- The adapter now classifies at its own error boundary, where the raw
error is still structured (status codes, response bodies), for both
thrown errors and failed done chunks. Aborts stay unclassified.
- The runtime falls back to classifying the finish message when a model
supplies no class, so custom AgentModel implementations are covered
too.
- Hold the custom-compactor acceptance check to token estimates on both
sides instead of mixing serialized length with a token target, and
document why the runtime's shrink backstop keeps a serialized-size
proxy (the shared estimator is linear in characters, so the verdict is
identical) with a TODO to surface real estimates from prepareTurn.
- Drop the now-unused errorClass parameter from captureProviderApiError:
#12820 removed core's capture site, so host adapters own that event.
* test(core): reuse the handler harness for the overflow classification case
The hand-rolled throwing generator had no yield, which biome's
correctness/useYield rejects as an error (the repo's lint gate runs on
sdk/ and apps/, and biome does not honor the eslint require-yield
directive the existing harness carries). fakeHandler now accepts the
error to throw, so the new case reuses it instead.
* fix(mcp): refresh lists on list_changed notifications instead of toasting
Servers emit notifications/tools/list_changed in bursts (a toolset change
or shutdown can produce a dozen at once), and the fallback notification
handler surfaced every one of them as a host toast, flooding the user
with identical messages (ENG-2298, found testing the JetBrains IDE MCP
server integration).
Handle tools/resources/prompts list_changed notifications by refreshing
the corresponding cached lists, debounced 300ms per server and list
kind, then pushing the update through notifyWebviewOfServerChanges() so
the webview and the SDK session tool-list check pick it up. Downgrade
remaining unhandled notification types to logger output.
* fix(mcp): guard list_changed refreshes against races and failed fetches
Address review: serialize per-key refreshes by chaining onto any
in-flight one, so overlapping fetches can't complete out of order and
publish a stale list. Make the fetch helpers return undefined on
failure (instead of an empty list) so the refresh path can keep the
previous cached list and skip the webview notification, rather than
erasing valid entries on a transient error; connect-time call sites
keep their old empty-list fallback.
* fix(mcp): drop in-flight list refresh when the connection was replaced
Address review: refreshChangedList captured the connection object before
awaiting the list fetches, so a reconnect mid-fetch wrote the result to
the removed connection while the replacement kept its own state. Re-check
connection identity after the fetches and drop the result when it
changed — the replacement fetched fresh lists at connect time, after the
change that produced the notification, so the in-flight result is older.
* fix(mcp): retry failed list refreshes and publish state after reconnect
Address review. A list_changed notification consumes the server's change
signal, so a transiently failed refresh left the cached list stale until
the next notification; retry with exponential backoff (1s/2s/4s, max 3)
per server and list kind, with a fresh notification superseding any
pending retry. Also publish server state after a successful streamable
HTTP reconnect: connectToServer() loads fresh lists but never sent them,
leaving the webview on 'connecting' with pre-reconnect capabilities.
* fix(mcp): don't restart a live connection when post-reconnect publish fails
Address review: the post-reconnect notifyWebviewOfServerChanges() sat
inside the connect retry loop's try block, so a publication failure
(e.g. a settings file read error) was treated as a transport failure
and re-ran connectToServer() against the already-live connection,
leaking its client/transport. Publication now happens outside the
connect try/catch and only logs on failure.
* fix(mcp): drop superseded in-flight list refreshes instead of publishing
Address review: a newer list_changed notification queued its refresh
behind one already in flight without invalidating it, so the older run
could briefly publish an obsolete list (and churn the SDK session)
before the newer refresh corrected it. Each schedule now starts a new
generation per server+kind; a run whose generation is no longer current
skips fetching (when caught early), drops its result before publishing,
and doesn't schedule retries — the superseding refresh covers it.
* fix(mcp): harden list refresh and reconnect publication paths
Address review (post-reconnect publish failure leaving consumers stuck
on 'connecting' with stale lists) plus an adversarial pass over the
whole change to close the remaining gaps in one batch:
- Retry publications bounded (publishServerChanges) after a successful
reconnect AND in both terminal disconnected paths, which are equally
terminal; never throw from handleError, whose promise transport.onerror
discards. Guard the stdio/SSE onerror publishes the same way.
- Treat an undeclared capability or a method-not-found answer as an
authoritatively empty list instead of a retryable failure, so servers
without e.g. resources/templates/list don't burn the full retry ladder
on every list_changed notification.
- Retry when the fetch succeeded but the webview publish failed: the
cache is updated but consumers haven't seen it.
- Cap debounce deferral at 2s so a sustained sub-300ms notification
stream can't starve the refresh indefinitely.
- Cancel pending refresh timers in deleteConnection; return 'skipped'
(not 'failed') when a fetch failure coincides with connection
teardown or supersession, so no retry fires against a replacement
connection that already fetched fresh lists.
- Clear the pre-existing toolListChangeDebounceTimer in dispose().
* fix(mcp): supersede in-flight refreshes during connection teardown
Address review: deleteConnection removes the connection from
this.connections only after awaiting transport/client close, so a list
refresh completing inside that window passed its identity check and
published state for a connection being torn down. Bump the per-key
generation at the start of deleteConnection so any in-flight refresh is
superseded and drops its result; bumping (never resetting) keeps
generations monotonic across reconnects.
* fix(mcp): close reconnect-retry and teardown-publication races
Address review (cline-cloud):
1. The streamable HTTP reconnect loop revalidated only after the first
backoff. Later retries could resurrect a server removed or disabled
from settings during a delay, or displace a replacement connection
another path had installed — connectToServer() drops a same-name
connection without closing it, leaking its transport. The loop now
revalidates before every attempt: it aborts when a live replacement
exists (our own original connection and the 'disconnected' husk left
by our own failed attempt don't count) or when fresh-read settings no
longer define the server as enabled (isStillWanted callback; a
settings read failure keeps the chain alive).
2. deleteConnection removed the connection from this.connections only
after awaiting transport/client close, so a publication passing its
suspension points inside that window could still serialize and
publish the dying connection's state. The connection is now removed
from published state before the close handshake is awaited.
The exhausted-retries test's partial-connection mock now carries status
'disconnected', matching what connectToServer's error path actually
leaves behind — that status is what distinguishes our own husk from a
live replacement.
* fix(mcp): don't displace an OAuth-required replacement during reconnect retries
Address review: the retry loop's replacement guard treated every
'disconnected' connection as our own failed-connect husk. An
OAuth-required connection is also 'disconnected' but retains its
client, transport, and authProvider for authentication — a retry that
displaced it would orphan that session and clobber the pending auth
state. Distinguish by client presence: the husk's creation sites set
client: null, so a 'disconnected' connection holding a client is a
replacement and aborts the retry chain.
* fix(mcp): distinguish OAuth replacements by flag, not client presence
Address review: an ordinary post-registration connect failure leaves a
'disconnected' connection with its (already-closed) client still
attached, so the client-presence check classified it as an OAuth-style
replacement — aborting the reconnect chain after a single failure,
including our own retries. Discriminate on server.oauthRequired
instead: only the OAuth-required connection retains live
client/transport/authProvider state worth protecting; ordinary failed
connections closed their client before being marked disconnected, so
retrying past them displaces nothing live. The exhausted-retries test
mock now carries the real husk shape (closed client attached) to pin
this regression.
* test(mcp): cover retry succeeding after a failed attempt's registered connection
Requested in review: the guard must recognize the 'disconnected'
connection a failed non-OAuth connectToServer() leaves behind (closed
client still attached) as our own attempt, and the following retry must
proceed and succeed.
* fix(mcp): don't retry reconnects with a config settings no longer define
Address review: a retry reconnects with the config captured at
connection creation, so if the user changed the server's config during
a backoff delay (and the watcher's reconnect with the new config
failed, leaving a disconnected husk our guard rightly retries past),
the retry would resurrect the obsolete URL/headers/command — and a
successful stale connection would contradict settings until the next
file touch. isStillWanted now also compares the captured config against
current settings via configsRequireRestart (connection-relevant fields
only), aborting the chain when they differ: the settings watcher owns
reconnection after a config change.
* feat(desktop): show token usage in input toolbar
Load per-model context window sizes from the provider catalog and
pass the active model's limit down to ChatInputBar. Render a token
ring that visualizes current token usage against the model's context
window, and hydrate token usage plus cumulative cost from messages
and chat_usage events so the indicator stays accurate across turns
and session reloads. Add tests covering the ring rendering and usage
hydration.
* bigger ring
* move submit button to input box
* fix
* add cost tracker
* fix
* fix queued turn cost tracking
* ci(desktop): gate desktop publish secrets behind PublishDesktop environment
The Apple signing/notarization and Tauri updater secrets were repository
secrets, readable by any workflow in the repo and by anyone with push
access via a branch carrying a modified workflow. Move them behind the
PublishDesktop environment, which requires reviewer approval and
restricts deployments to main.
The build job now declares the environment, so those secrets are readable
only there and only after an approval. Add a preflight check because a
missing secret fails dangerously rather than loudly: Tauri silently skips
code signing when APPLE_CERTIFICATE is empty and skips notarization when
APPLE_API_KEY is empty, so a misconfigured environment would still
publish an unsigned, un-notarized bundle. Only a missing updater key was
already caught, by the .sig check in Collect artifacts.
validate stays ungated so a bad tag fails in seconds rather than after an
approval, matching the ungated-build/gated-publish split in
ext-vscode-ab-package. The shared Slack and telemetry secrets stay where
they are; scoping them to this environment would silently empty them in
the CLI, SDK, and extension publish workflows.
* ci(desktop): verify signing secrets are not repository-scoped
The preflight added in the previous commit checks that the signing
secrets are non-empty, which proves presence but not scope, and then
reported that they had resolved from PublishDesktop. An environment-gated
job resolves repository and organization secrets too — environment values
merely take precedence — so a credential left at repository level would
pass that check while the message claimed the migration had worked. This
workflow already demonstrates it: the gated build job reads the shared
Slack and telemetry secrets, none of which are on the environment.
Add the complementary check to validate, which declares no environment: a
signing secret that resolves there can only be repository- or
organization-scoped, so it fails the run and names the offenders. Neither
check establishes provenance alone; together they do. validate is
ungated, so a misplaced secret now fails before the approval rather than
after it.
Also drop the provenance claim from the build message and correct the
skill doc, which stated that a repository-level secret would be invisible
to the gated job.
Reported by greptile on #12854.
Local backends (Ollama especially) intermittently return a turn that
finishes normally but carries no text, reasoning, or tool call. In the
SDK runtime an empty assistant turn is a hard failure ("Model returned
empty response"), so one flaky generation kills the whole task.
Adds a LanguageModelV3 middleware that retries the stream only when a
turn produced genuinely nothing, wired as the outermost middleware on
the Ollama vendor. A tool-call-only turn counts as content and is never
retried; non-empty turns stream through live with no added latency; and
turns that error or hit the token limit are passed through unchanged.
This is the streaming-safe slice of ai-sdk-ollama's reliability story:
its own reliability layer lives in doGenerate and owns the tool loop
(executes tools and force-synthesizes text), which is incompatible with
Cline running its own loop over doStream.
* fix(migration): fall back to the default Cline model for unknown legacy model ids
Some migrated users ended up making Cline provider requests with a model
id the new extension doesn't have because the legacy migration carried
their stored model id over verbatim and never applied a default.
Two small fixes in the provider settings migration:
- Drop a legacy Cline model id the catalog doesn't know so the entry
falls back to the default model instead of carrying the unknown id
into inference requests.
- getDefaultModelForProvider only accepted defaults present in the
generated model block; Cline's generated block holds a few free models
while its declared default (anthropic/claude-sonnet-5) lives in the
collection catalog, so the fallback previously landed on an arbitrary
free model instead of the default.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(migration): validate legacy Cline models against the full runtime catalog
The known-model check used the curated Cline collection plus the tiny
generated cline block, but the runtime Cline catalog is OpenRouter-backed
and also resolves Vercel AI Gateway alias ids. Legacy users on
runtime-served ids outside the curated collection (e.g. the z-ai/glm-5
family) would have been wrongly defaulted. Suffixed variant ids like
...:1m still fall back to the default Cline model.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(migration): validate Cline models against the canonicalized runtime catalog
Greptile review: the raw generated-catalog checks accepted alias ids
(e.g. OpenRouter's z-ai/...) that buildClineModels canonicalizes away
(to zai/...), persisting models absent from the exposed runtime catalog.
Validate against the collection model list (which the runtime catalog
mirrors exactly) and fold alias spellings onto their canonical ids via
the shared VERCEL_OPENROUTER_MODEL_ID_ALIAS_RULES, so legacy z-ai users
keep their model under the canonical id instead of being defaulted.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): retry Ollama response-start timeouts through the AI SDK retry loop
The pre-SDK handler wrapped Ollama chat calls in withRetry({ retryAllErrors:
true }), which silently rode out model cold loads: Ollama holds /api/chat open
while loading and only sends response headers once the model is ready, so the
first attempt of a large model routinely times out at 30s and a later retry
lands on the loaded model. The SDK path lost that behavior twice over: the
response-start timeout rejected with a plain Error (the AI SDK only retries
APICallError with isRetryable), and ai-sdk-ollama wraps every doStream failure
in its own OllamaError, hiding even a correctly-typed error from the retry
predicate. Net effect: one attempt, a surfaced timeout error, and no automatic
recovery - a regression vs the legacy extension for local models that load
slower than the timeout (cline/cline#12829).
Fix: withOllamaResponseTimeout now rejects with APICallError(isRetryable:
true) when its own timer fired (upstream aborts still propagate untouched),
and a restoreOllamaApiCallErrorMiddleware unwraps the buried APICallError from
OllamaError cause chains so streamText's built-in retry (2 retries with
backoff, ~96s of cold-load coverage) engages.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style: biome format
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* rework: raise Ollama response-start default to 5 minutes instead of retrying
Replaces the APICallError/retry-middleware approach: the 30s guillotine was
the actual root problem (Ollama sends response headers only after the model
cold-loads; killing a healthy request forces error/retry churn), so give the
response-start budget the same order of generosity other AI SDK-based agents
use (opencode: no default header timeout for custom providers, 5 minutes for
its only default) and delete the retry machinery. Unreachable servers still
fail instantly at the connection level, users can still cancel from the UI,
and an explicit requestTimeoutMs is still honored.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert Ollama timeout description copy, keep the new default values
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: forward Ollama request timeout and context window to standalone handlers
Greptile review catch on #12839: buildSdkProviderConfig never carried
requestTimeoutMs, so handlers built via buildApiHandler (commit message
generation) ignored an explicit user timeout — pre-existing, but material now
that the fallback default is 5 minutes. Reuse the session factory's
resolveOllamaProviderConfig so the standalone path honors the configured
timeout and the user's context window (num_ctx) instead of Ollama's 4096
default, keeping the two paths on one source of truth.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
OpenAI Compatible and LiteLLM gated their base URL onChange (and API key
writes via canWrite) on the async provider config having loaded. Text typed
in that window hit a no-op onChange after the debounce cleared the
pending-edit flag, so the late initialValue resync wiped it and nothing was
saved. write() never needed loaded config, and useProviderConfig's request
sequencing already drops the stale initial read, so the guards are removed.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Sending a message (Enter or send button) cleared the isTextAreaFocused
flag without blurring the textarea. Since the DOM element stayed focused,
onFocus never re-fired (programmatic .focus() on an already-focused
element is a no-op), so the mode-colored outline stayed hidden until a
real blur/refocus cycle - which is why toggling Plan/Act mode brought it
back. Stop clearing the flag on send; blur is already handled by the
onBlur handler.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Editing a previous message while a tool approval prompt was pending left the
old session's approval promise parked forever: the superseded run stayed
suspended awaiting an answer that could never come, and the stale resolver
kept intercepting later ask responses. Clear pending interactions before
starting the replacement session, exactly like cancelTask / clearTask /
task-switch / mode-change already do.
Ref #12827
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
The runtime baseUrlMap in resolveBaseUrl lacked the asksage ->
asksageApiUrl mapping (present in store.ts, effective-config.ts, and the
legacy migration), so a custom AskSage API URL saved in legacy state was
never read and requests fell through to the builtin default
https://api.asksage.ai/server.
Also write the URL through the SDK provider-config store in
AskSageProvider.tsx (mirroring AnthropicProvider) so providers.json
stays in sync for CLI/desktop hosts; the store mirrors baseUrl back to
the legacy asksageApiUrl state key, keeping the /get-models fetch and
legacy readers working.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Convert the Qwen and Moonshot regional API line dropdowns from
legacy-state-only writes to useProviderConfig().write({ apiLine }),
matching the Z AI pattern. The host store mirrors the write back to the
legacy qwenApiLine/moonshotApiLine state keys, so a single write keeps
providers.json (read by the CLI and desktop app) and the legacy
StateManager (read by the VS Code session factory) in sync.
Adds store tests pinning the dual-write mirroring and webview component
tests for the dropdowns' write and display behavior.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): include untracked files in commit message generation
getGitDiff only ran git diff --staged and git diff HEAD, neither of which reports untracked files, so an add-only working tree failed with 'No changes in workspace for commit message'. Gather untracked files and diff each against /dev/null via execFile (argv, no shell) so add-only trees work and special-char filenames are safe.
Closes#12060
* fix(vscode): include untracked files alongside tracked changes
Address review: append untracked-file diffs in the non-staged path instead of gating on an empty diff, so a mix of edited tracked files and new untracked files includes both. Re-throw git exit codes other than 1 (files differ) so real errors aren't swallowed. Use a named, non-runnable label for the output header. Adds a mixed tracked+untracked test.
Refs #12060
---------
Co-authored-by: Minhkunn <minh.12072k6@gmail.com>
#12831 removed the import while rewriting checkpoint restore, and #12830
landed on top of it adding a usage that assumed the import was still
there. apps/cli typecheck has failed on main since, which fails the
sdk-test Quality Checks job on every PR touching sdk/**.
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
After a checkpoint restore (/undo or Esc Esc), the rewound user message is
dropped into the input box to edit and re-send. It was prefilled from the
raw stored text, which the runtime wraps in a <user_input mode="...">
envelope, so the input showed '<user_input mode="act">...</user_input>'
instead of what the user typed. Prefill the display form via
formatDisplayUserInput (already used for the picker preview and imported in
this file), which strips the envelope and preserves slash-command display
form.
* fix(core): create checkpoints reliably across hosts, restarts, and compaction
#12691 moved checkpoint run-boundary detection into a beforeRun hook that
recorded snapshot.messages.length, assuming the run's user prompt is
appended afterwards. SessionRuntime (VS Code + CLI) instead seeds the
prompt into initialMessages and calls run(""), so the beforeRun delta is
always empty and no checkpoints were ever created in either surface.
Gate checkpoint creation on two signals instead of the fragile in-memory
delta alone:
- introducedUserRun: the beforeRun delta contains a new user turn. Covers
hosts that pass the prompt as run input and refreshes the entry on
edit-and-regenerate.
- alreadyCheckpointed: the run count already exists in the DURABLE session
checkpoint history. Covers the seeded-prompt path and, unlike an
in-memory counter, still holds after a process restart.
Skip only when neither applies (a continuation/resumption re-running an
already-checkpointed run), so a reopened session can't overwrite a good
pre-run snapshot with the mutated workspace. Run numbering uses the
span-aware countUserRunMessages so it survives compaction folding turns
into one summary message.
Adds regression tests for the seeded-prompt creation, the reopen-without-
new-turn overwrite case, and the first-turn-after-compaction case.
* fix(cli): number /undo checkpoints span-aware so restore can map them
The interactive /undo picker counted every role="user" message when
assigning run numbers to checkpoints. Tool-result messages also carry
role "user", so any turn that used tools got an inflated run number; the
picker then handed that number to the core, whose span-aware
findUserRunMessage could not map it and aborted with 'Could not find user
message for run N'. Restore was effectively unusable whenever the agent
called a tool.
Count runs with the core's getUserRunSpan (tool results contribute 0, a
compaction summary spans the turns it folded) so the picker's run numbers
match what the core records and resolves. Extracted the item-building into
a pure buildCheckpointPickerItems helper with unit coverage for the
tool-result and compaction cases.
* fix(core): capture untracked files in checkpoints as a third parent
Checkpoint creation used plain `git stash create`, which cannot include
untracked files (no -u support). Restore therefore had no way to bring back
a file Cline created during a task, so a full rewind was impossible.
Synthesize a stash-shaped snapshot commit that also records untracked,
non-ignored files as a third parent - exactly like
`git stash create --include-untracked` - without touching the working tree,
the real index, or the stash list: list `ls-files --others
--exclude-standard`, stage into a temp GIT_INDEX_FILE, write-tree +
commit-tree to get the untracked parent, then rebuild the stash commit with
that extra parent. When the tracked worktree is clean but untracked files
exist, synthesize the stash from HEAD so they are still captured instead of
falling back to a bare HEAD-commit checkpoint. Fully clean worktrees still
use the HEAD-commit fallback.
* fix(core): full workspace rewind on restore for snapshot checkpoints
Restore now rewinds untracked files generation-aware:
- If the checkpoint carries an untracked third parent (a snapshot from
createWorktreeStashCommit), do a full rewind: reset tracked to the base,
`git clean -fd` to drop files created after the checkpoint (and clear the
worktree so `stash apply` cannot hit an "already exists" conflict), then
`git stash apply`, which restores each captured untracked file to its
checkpoint-time content from the third parent. `git clean -fd` (no -x)
leaves .gitignored paths - build output, node_modules, .env - alone. This
is safe because everything removed is either recreated from ^3 or postdates
the checkpoint, and the pre-restore recovery snapshot (stash push
--include-untracked) can roll the whole operation back.
- If the checkpoint has no third parent (legacy 2-parent stashes and
HEAD-commit fallbacks from before capture existed), keep the conservative
behavior: never touch untracked files, since nothing can reconstruct them.
This makes 'Reset Code' / '/undo' a true rewind: a file Cline created in an
early turn and ruined later comes back to the early-turn version.
* fix(telemetry): single classified emitter for provider API errors, gated on terminal failures
* chore: remove explanatory comment block from agent-events.ts
* feat(telemetry): stamp terminal=true on SDK provider failure events
* chore: remove dead notice api_error capture (no producer emits that reason)
* refactor(telemetry): rename provider-failure 'terminal' flag to 'fatal' (terminal is the shell in Cline)
* refactor(telemetry): drop the fatal flag - only user-surfaced failures are reported on both bundles
* fix(cli): don't let the ClinePass promo dialog trap users whose terminal drops Esc
The promo dialog could only be dismissed with Escape, and Esc is the
least reliable key across terminals: it arrives as a bare \x1b that
needs timeout disambiguation, and Bun's Windows console input layer is
known to swallow it (Windows PowerShell users reported being unable to
dismiss the dialog at all). Worse, the 'shown' marker was only written
when the dialog closed, so a user who force-quit saw the promo again on
every launch.
- Any key other than Enter now dismisses the dialog (Enter still opens
the subscription page)
- The shown marker is persisted when the dialog is displayed, not when
it is dismissed, so a force-quit never loops the promo
- Add a tuistory e2e test covering marker timing and any-key dismissal
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): let any key cancel the OAuth waiting screen
Like the ClinePass promo, the OAuth wait screen was dismissible only
with Esc (plus K for the API-key fallback when offered) while blocking
on a browser flow that may never complete — a trap on terminals that
drop Esc. Any key other than K now cancels the pending auth attempt;
K still switches to manual API key entry when available.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): don't let a modifier keypress dismiss link-bearing dialogs
The ClinePass promo and OAuth wait screens both render a URL the user
opens by holding Cmd/Ctrl and clicking. With 'any key closes', that
modifier keystroke could tear the dialog out from under the click. Add
a shared isAnyKeyDismiss() guard so only unmodified keys dismiss; keys
held with ctrl/meta/super/hyper (and bare modifier presses) are ignored.
Enter still opens the promo and K still opens manual API key entry.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert(cli): persist promo shown-marker on dismiss again
Now that any key dismisses the promo, users can reliably close it, so
there's no need to write the shown-marker eagerly on display. Restore
persisting it in the dialog's finally() and update the e2e assertion.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Follow-up to #12782, which replaced the open package with openUrlInBrowser
but missed two call sites and dropped some platform handling the package
provided:
- Migrate the two remaining open users (skills marketplace open in
tui/root.tsx and ACP OAuth in acp/auth.ts) to openUrlInBrowser; the
listenerless-child crash fixed by #12782 was still reachable there.
- Treat containers running on a WSL2 kernel (Docker Desktop for Windows,
devcontainers) as plain Linux: /proc/version says microsoft but there is
no Windows interop, so use xdg-open instead of powershell.exe (matches
the is-inside-container check open@10 performed).
- Try opener candidates in order: on WSL, powershell.exe on PATH, then the
absolute /mnt/c/... path (covers appendWindowsPath=false), then xdg-open
(sandboxed WSL with WSLg); on win32, the %SystemRoot% absolute PowerShell
path first (what open@10 used), then PATH lookup.
- Convert Linux file paths to \\wsl$ UNC paths via wslpath before handing
them to Start-Process, so 'cline doctor log' works on WSL.
- Remove the now-unused open dependency from apps/cli.
captureDiffEditFailure and captureWorkspaceInitError have no callers: SDK core
is the sole emitter of task.diff_edit_failed and workspace.init_error. Keeping
callable host-side capture APIs for core-owned events is how the
task.provider_api_error double-emission happened — a future host caller would
silently double-count these events with no type error or failing test. Also
drops the two event-name constants, which were only referenced by the removed
methods.
* feat(core): resolve display-ready names in fetchClineRecommendedModels
* refactor(cli,vscode): render recommended-model names from the enriched feed
* fix(core): resolve catalog names through vercel/openrouter id aliases
* fix(core): share one timeout budget across the feed and catalog lookups
Greptile flagged that resolveDisplayNames started a fresh timeoutMs window
after the recommendation request finished, so a slow endpoint plus a cold
or hung catalog could keep the picker loading for ~2x the timeout. The
catalog race now gets only the budget remaining from a single deadline;
an already-cached catalog still applies on an exhausted budget because
its promise resolves ahead of the zero-delay timer.
* fix(cli): strip the Slack bot mention from incoming connector messages
Slack delivers an at-mention of the app as `<@U0B8E8H3U1F> hi`, and the chat
SDK deliberately leaves the bot's own mention unresolved so mention detection
keeps working - flattening it to `@U0B8E8H3U1F hi`. The connector forwarded
that verbatim, so the agent saw the raw bot id at the front of every
mention-triggered turn.
Strip the leading self-mention in onNewMention/onSubscribedMessage before the
approval-reply check and handleTurn, resolving the bot id from the adapter
(request-scoped in multi-workspace mode) with a fallback to the event envelope
authorizations. Mentions of other users and inline mentions are preserved, and
a bare mention is left as-is so the turn is not dropped as empty input.
* fix(cli): only strip a complete Slack bot mention, not an id prefix
The `<@ID>` and `<@ID|name>` alternatives in stripSlackBotMention are
terminated by `>`, but the SDK-flattened bare `@ID` alternative had no
trailing boundary, so it also matched the start of a longer id. With bot id
`U123`, a message addressed to a different user - `@U1234 help` - was
rewritten to `4 help`, corrupting both the approval-reply check and the text
handed to the agent.
Require the flattened alternative to be followed by a non-id character with a
`(?![A-Za-z0-9])` lookahead, so it only matches a complete Slack id. A plain
`\b` cannot express this, because Slack ids end in word characters and `\b`
still matches between `U123` and `4`.
Existing behaviour is unchanged: angle-bracket and flattened self-mentions are
still stripped, repeated leading mentions still collapse, trailing `[\s,:]`
separators are still consumed, other users' and inline mentions are preserved,
and a bare mention is still left untouched so the turn is not dropped as empty.
Adds regression tests for the prefix collision, which fail against the previous
regex and pass with this one.
---------
Co-authored-by: cline-test-bot <cline-test-bot@users.noreply.github.com>
* fix: surface upstream provider error from gateway-forwarded stream failures
Vercel AI Gateway streams upstream rejections (e.g. Alibaba Qwen context-
length errors) wrapped in its own parse failure: the top-level message is
just 'Stream error occurred' and the cause is an internal ZodError, while
the real rejection is JSON-encoded in value.error_message. Unwrap it so
users see 'This model's maximum context length is 40960 tokens...' instead
of a raw Zod issue dump.
Also fall back to JSON.stringify for opaque object errors so the UI never
renders '[object Object]'.
* refactor(llms): use shared safe-JSON helpers and a named type guard in extractErrorMessage
* fix(llms): resolve OpenRouter display names for all Cline free models
* fix(vscode): resolve featured model card display names from the provider catalog
* fix(vscode): fall back to endpoint-provided names on featured model cards
* Add tuistory-based TUI e2e harness for the CLI
Evaluates https://github.com/remorses/tuistory as a Playwright-style
driver for the interactive TUI. Adds:
- tuistory devDependency in apps/cli
- test:e2e:tuistory script + vitest.tuistory.e2e.config.ts
- src/cli.tuistory.e2e.test.ts: ports the script(1)-based interactive
smoke tests to reactive waitForText/screen-state assertions against a
real PTY + Ghostty terminal emulator (5 tests, ~11s, no fixed sleeps)
- DEVELOPMENT.md docs for the vitest suite and the tuistory session CLI
agents can use to manually drive the TUI headlessly
* Add tuistory agent skill (.cline/skills, symlinked to .claude/.agents)
Teaches coding agents to drive the Cline TUI headlessly via tuistory
sessions (launch with isolated env, reactive wait, snapshot/screenshot,
observe-act-observe loop) and to write launchTerminal()-based e2e tests,
closing the loop for cloud agents testing apps/cli.
* fix(cli): don't crash on browser-open failure when no opener binary exists
open() with { wait: false } resolves to the detached child process before
the opener binary is known to exist. On hosts without one (e.g. xdg-open
on headless Linux), the failure arrives as an async 'error' event on the
listenerless child, escalating to an uncaughtException that kills the CLI
— bypassing every try/catch and .catch() at the call sites. Hitting
"Sign in with Cline" from the welcome screen reliably crashed the TUI in
containers.
Route all browser opens through a shared openUrlInBrowser() helper that
attaches the error listener and reports failure via its returned promise,
so flows fall back to their existing "visit the URL below" messaging.
* fix(cli): attach opener error listeners in the same tick as spawn
Greptile's review caught that the helper attached its listeners only after
awaiting open()'s promise. Empirically that window is safe under Node 22
(the listener wins) but real under Bun — the runtime the compiled CLI
ships on — where the missing-binary ENOENT 'error' event fires before the
microtask queue drains, reproducing the exact crash this helper exists to
prevent.
macOS and non-WSL Linux now spawn their opener (open / xdg-open) directly
with listeners attached in the same synchronous tick, which both runtimes
guarantee can never miss the event. Windows and WSL keep delegating to the
open package for its shell quoting and interop routing; their openers
(cmd/powershell) always exist, so the post-await path cannot hit ENOENT.
The regression test now emits the error on nextTick — before microtasks —
which fails against the previous implementation.
* fix(cli): drop the open package — same-tick opener spawn on every platform
The win32/WSL delegate path still attached listeners after awaiting
open()'s promise, leaving a narrow uncaught-error window under Bun for
emittable spawn failures (e.g. AV-blocked EPERM). Spawn the opener
directly everywhere instead: open on macOS, xdg-open on Linux, and
powershell -EncodedCommand on Windows/WSL — the base64-encoded
Start-Process command sidesteps cmd/PowerShell quoting of URLs entirely,
so nothing is ever shell-interpolated.
On CLI exit, renderer.destroy() runs root.destroyRecursively() before
React flushes the DialogProvider's passive unmount cleanup, so the
dialog container is already detached when the cleanup calls
renderer.root.remove(container), triggering OpenTUI's 'Renderable with
id dialog-container is not a child of __root__, skipping remove'
warning. Drop the explicit remove from the patched @opentui-ui/dialog
provider cleanup (react + solid): Renderable.destroy() already detaches
from its parent when attached and no-ops when already destroyed.
basename("/") is an empty string, which WorkspaceInfoSchema rejects
(hint is z.string().min(1).optional()), so upsertWorkspaceInfo threw a
ZodError for any session rooted at the filesystem root — e.g. the
desktop app launched from the Dock with cwd "/" — and commands never
ran. Omit the hint instead of storing an empty string.
Launch the hub through CLINE_WRAPPER_PATH after Unix self-updates so npm 12 does not reuse a deleted cached executable. Preserve the in-process fallback for Windows and development builds, and add coverage for success and failure paths.
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
The uid/mcpServerKeys registry existed to encode server names into
native tool-call function names and decode them back at dispatch.
That encode/decode path was removed with the extension host
(c4c126bee): tool names are now built by the SDK's deterministic
defaultMcpToolNameTransform and execution closes over the server
name directly, so getMcpServerByKey has no callers and the keys are
write-only state. Delete the registry, the uid field, and the
deleteServerKey callback plumbing.
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
BannerService tests still used 10ms sleeps for background fetch completion.
On slow CI runners that races mocha timeouts. drainForTesting() already
exists and awaits the in-flight fetch promise deterministically.
Rebased onto monorepo main (apps/vscode path).
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
* ci(vscode): gate the combined A/B package workflow on both bundles' test suites
* docs(skills): add publish-extension skill for VS Code extension releases
* ci(vscode): pin tested revision for next bundle and refuse publishing untested next-refs
* ci(vscode): pin legacy bundle to the revision its test gate ran against
* fix(vscode): show migrated model in settings instead of hardcoded default
After the SDK provider migration a user who never explicitly picked a model
(i.e. took the legacy default) ends up with the model recorded in
providers.json but not in the mode-specific globalState fields the settings
picker reads. The OpenRouter picker and its info card then fell back to the
hardcoded openRouterDefaultModelId (claude-sonnet-4.5) and its pricing, while
the extension actually ran the migrated model (claude-sonnet-5).
- resolveModelInfo: when no model id is requested, honor the provider store's
committed selection (which reads providers.json when the state field is
empty) before substituting a catalog default.
- OpenRouterModelPicker: source the displayed model id/info from the
authoritative resolver as the fallback when the mode fields are empty,
instead of the hardcoded constant. Committed-field users are unaffected.
* fix(vscode): guard picker model info against resolver default substitution
Review hardening: the resolver substitutes its provider default for ids it
cannot resolve, so only trust its info when it answered for the id actually
displayed. Prefer the live catalog entry for the displayed id (synchronous
once fetched, which also removes the transient placeholder while the resolver
is in flight), and never render another model's metadata under the displayed
model's name. Also document why the act-then-plan readSelection order in the
empty-id branch cannot misattribute a mode-specific selection.
* Restore task export to markdown and show download button in all builds
* Render untyped tool outputs and object tool inputs as JSON in task export
* Open the task's SDK session folder from the export button and show it in all builds
* Keep the task header session-folder button dev-only
* Fix queued prompt row alignment and auto-scroll on queue
Center the dot, badges, and cancel button on the first text line of each queued prompt row (the X previously sat ~3px below the text), and re-pin the chat view to the bottom when a prompt is queued so the queue banner doesn't cover the end of the conversation.
* Don't treat task switches as queue growth for auto-scroll
Guard the queued-prompt auto-scroll effect on the displayed task's ts: switching to a task that already has queued prompts grows the count without a send from this webview, and should not hijack the newly opened conversation's scroll position.
* feat(desktop): support message editing & checkpoints
Fork sessions before a selected user run, trim checkpoint history, and restore prior messages so prompts can be edited safely. Update the chat UI and tool activity panels to support the editing flow and preserve horizontal scrolling for long content.
* fix(desktop): restore checkpoints when editing messages
* fix(core): infer kindless checkpoint types
* fix(core): preserve checkpoint run numbering
* fix(desktop): make message edit restores transactional
* fix(desktop): make checkpoint restores workspace-atomic
The confirmation that appears when clicking the compact button in the
task header was a bare unstyled row with a stray bottom margin (my-2)
that stacked on the header card's own bottom padding, leaving a dead
gap under the buttons. It is now a distinct bordered card (editor
background against the header's toolbar surface) with a title, a short
description of what compacting does, and right-aligned Cancel/Compact
buttons, with symmetric spacing above and below.
Also drops the ContextWindow wrapper's bottom margin (my-1.5 -> mt-1.5)
so the row's bottom spacing matches the header padding, and adds a
ContextWindow Storybook story that mirrors the expanded TaskHeader
surface so the confirmation can be previewed in isolation.
Selecting ClinePass in settings awaited a network round-trip (PUT
/active-account + possible token refresh) before postStateToWebview,
so the settings panel stayed on the previous provider until the
request finished. Make the personal-account switch fire-and-forget:
it was already best-effort, and auth state changes propagate to the
webview separately once it completes.
Also convert the helper's test to bun:test so it actually runs (the
mocha version was excluded by both the bun unit runner and the
vscode-test glob) and fix its stale null-vs-undefined assertion from
the SDK migration.
The xAI, Z AI, and Moonshot settings components were never wired to the
catalog's reasoning capability: xAI only offered a legacy low/high
checkbox hardcoded to grok-3-mini model ids, and Z AI / Moonshot had no
reasoning control at all, even though models.dev marks grok-4.5, glm-5,
kimi-k2-thinking, etc. as reasoning models. Every catalog-driven
provider (GenericProviderSettings, OpenRouter/Vercel/Requesty pickers)
already gates ReasoningEffortSelector on supportsReasoning.
Render the shared ReasoningEffortSelector in these three components when
the selected model's catalog info advertises reasoning, persisting the
choice to the provider config the same way GenericProviderSettings does.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(vscode): stop queued-prompt turns from getting stuck on Thinking
When the SDK drains a queued prompt at the end of a turn, the new turn's
pending_prompt_submitted bookkeeping (isRunning=true, phase=streaming) always
runs before the previous turn's send promise unwinds in fireAndForgetSend.
That .then then unconditionally called setRunning(false), so the queued turn
ran with isRunning=false and its own turn-complete was mistaken for a
cancelled-turn straggler - the phase never left "streaming" and the chat
showed an endless Thinking indicator.
Track a monotonic turn epoch on SdkSessionLifecycle: immediate sends and
drained queue prompts bump it, and both the send-settled callbacks and the
event coordinator's turn-end handling skip their bookkeeping when a newer
turn has started since (covers the symmetric interleaving where the done
handler resumes after the drain and would clobber the queued turn's
streaming phase).
* Simplify: preserve only an actual cancel phase in the turn-complete straggler guard
Replaces the turn-epoch machinery with the minimal fix: the straggler
guard's intent is to preserve the cancel-set "resumable" phase, so key it
on the phase itself instead of the isRunning proxy. When the SDK drains a
queued prompt at turn end, the previous turn's send promise settles after
the queued turn already started and flips isRunning back to false
mid-turn; with the old guard the queued turn's real completion was then
mistaken for a cancel straggler and the phase stayed stuck on
"streaming" (endless Thinking). Checking for "resumable" lets that
completion resolve the terminal phase normally while cancel behavior is
unchanged.
The anti-flash grace period (added to stop the loader flashing at turn
end) also fired mid-turn, causing a visible hide/show/hide flicker right
before a tool row appeared:
- When a reasoning tail finalized while the turn kept streaming, the
reasoning shimmer collapsed, the loader stayed hidden for the 500ms
grace, popped in, then hid again when the tool row landed. Reasoning
never ends a turn, so skip the grace for reasoning tails and hand the
shimmer straight to the loader.
- When the loader was already visible below a streaming tool group, the
group tail finalizing blinked it off for the grace period. The grace
now only delays hidden -> visible transitions, never hides an
already-visible loader.
* Show user message immediately when sending to a history-resumed task
Sending a message to a task opened from history routed through the
resume_task/resume_completed_task askResponse branch, which forced the
Thinking loader but never set the optimistic user_feedback bubble. The
extension only echoes the user's message after the full SDK session
resume completes, so the chat showed a Thinking indicator with no user
message until the (slow) resume finished.
Pass showPendingMessage on the resume branch like the other
non-streaming follow-up paths, so the user's message appears in the
chat immediately. The optimistic bubble reconciles with the extension's
say:user_feedback echo once the resume completes (identical raw text).
* Add changeset
* fix(core): add a plugin telemetry bridge
* fix(core): address plugin telemetry bridge review feedback
- Sanitization fallback now covers the whole executeTool IPC payload:
`input` can be rewritten by beforeTool hooks or programmatic callers,
so a non-serializable input degrades gracefully like the context does.
- The sandbox only offers ctx.telemetry when the host actually has a
telemetry service (new PluginSandboxOptions.telemetryAvailable, derived
from options.telemetry in the config loader), so feature-detecting
ctx.telemetry means "someone is listening" in both execution modes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(core): plugin telemetry review round 2 — timer leak and setup-time fallback
- SubprocessSandbox.call: a synchronous child.send() throw (cyclic payload)
left the pending timeout timer armed; it later fired and shut the sandbox
down, killing unrelated in-flight calls. Cancel the pending entry and
reject with the original error so serialization failures stay classifiable.
- plugin_telemetry events emitted during plugin setup() arrive before the
session is registered, so the session-config lookup missed and setup-time
telemetry was silently dropped. Route through a fallback telemetry service
(extensionContext/local config/host default), mirroring handlePluginLog's
fallback logger.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(core): classify BigInt IPC serialization errors for the sandbox fallback
Bun ("cannot serialize BigInt") and Node ("Do not know how to serialize a
BigInt") raise messages that did not match the cyclic/circular predicate, so
a bigint smuggled into tool input or context by a hook or programmatic caller
rethrew instead of retrying with the JSON-safe clone — which already drops
bigint leaves.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Auto-compaction state was silently rejected on every save ("Skipped
stale session compaction state"), forcing a full re-compaction — an
extra summarizer LLM call — on every turn past the trigger, and a
resume-time identity churn could leave a dead sidecar permanently
blocking replacements.
Three changes:
1. Stop hashing volatile transport identity. The source-prefix hash no
longer includes message id/ts, which the codec regenerates on every
wire/storage round-trip (a store's just-appended user turn has none
yet; consolidated parallel tool results are re-split with minted ids
on resume). The fingerprint now covers role, content, and durable
metadata. Hash seed bumped to v2; v1 sidecars fail projection once
and are replaced by the next compaction.
2. Validate persists against the exact source messages the state was
computed over. createCompactionStateAwarePrepareTurn passes
context.messages to saveState, and the local runtime host threads
them into persistActiveSessionCompactionState instead of falling
back to the conversation store's mid-turn shape.
3. Scope the count-based stale-write guard to states that still
project. An unprojectable current state no longer blocks a
newer-timestamped replacement, so invalidated sidecars self-heal
instead of deadlocking the session.
All three regression tests fail on main and pass with this change.
* fix(vscode): mark onboarding complete only after OAuth succeeds
The onboarding webview marked welcomeViewCompleted immediately after the
sign-in URL opened (accountLoginClicked resolves at URL-open time), so
Free/Frontier/ClinePass signups landed in chat signed out when the user
abandoned or failed browser auth, and the flag persisted across reloads.
Restore the classic extension behavior: the host (SdkAuthService) now
sets welcomeViewCompleted after the OAuth token exchange succeeds, in
createAuthRequest, handleAuthCallback, and the E2E mock login. The
webview persists the model selection up front, stays on the 'Almost
there!' step until auth completes, and fires the 'completed' funnel
event via a pending-intent module once clineUser arrives (mirroring the
pendingClinePassSubscribe pattern). This also fixes the legacy
WelcomeView fallback, whose 'Get Started for Free' never completed
onboarding after login.
* refactor(vscode): slim the onboarding-completion fix to its essentials
Drop the pending-telemetry module and App hook (the 'completed' funnel
event keeps its existing main-branch semantics, firing when the flow is
initiated, so no telemetry change in this PR), restore finishOnboarding
to its original shape with just a markCompleted parameter, and reduce
the host helper to a single setGlobalState call.
* Post streaming turn state to webview before session startup
The webview only learns the turn phase through full state posts, and the
first post after initTask happened only after startNewSession settled —
so the chat mounted with a stale idle TurnState and the thinking
indicator popped in noticeably late. Ship a state post right after the
initial task message is emitted, in parallel with session startup.
* Show thinking indicator optimistically on new-task submit
Capture the TurnState seq at the moment the newTask RPC is sent and
force the in-list Thinking loader row until a fresher TurnState arrives
(any phase), so the indicator renders together with the task message
instead of waiting for the streaming TurnState to round-trip. Rolled
back if the RPC fails; legacy (no turnState) hands off to the existing
tail heuristic once the task message lands.
* Paint the initial Thinking loader without waiting for Virtuoso
Frame-by-frame measurement showed the loader decision was true on the
chat view's first paint, but the synthetic in-list row still appeared
~150-200ms later: a cold-mounting virtualized list needs several frames
to measure and paint its first item. When the list has no visible rows
yet (new task just submitted), render the waiting row as a plain
element over the (empty) list instead; once any real row exists the
warm list takes over with the in-list row as before.
* Show thinking indicator immediately for follow-up messages too
Follow-ups had the same delay as new tasks: SdkController.askResponse
moves the phase to streaming but never posted state, so the webview
kept the stale terminal phase (hiding the loader) until the new turn's
first session event posted state. Post right after the phase change,
and generalize the webview's optimistic marker from new-task-only to
any turn-starting send (askResponse outside a streaming phase), with a
guard that never shows the loader while a content row is actively
streaming. Renames pendingNewTaskSeq to pendingTurnStartSeq.
* fix(vscode): render thinking loader synchronously
* fix(vscode): clear chat input immediately when /compact is submitted
* fix(vscode): let the compaction divider label wrap at narrow widths
* fix(vscode): update context-window header even when compaction grows the context
* chore: add changeset for /compact UX fixes
* docs(vscode): align getLastApiReqTotalTokens return doc with unclamped rescale
The desktop chat integration test renders components from @cline/ui, but
it also pulls @cline/shared/browser through the desktop app's own
message-content module. That subpath resolves to dist output no step in
this job produced, so the suite failed to collect.
Build @cline/shared before the test, and install the full workspace: the
two-package filter did not provide enough of the tree for that build.
The ui-publish workflow installs only the @cline/ui and @cline/code
workspaces, so the root devDependencies that previously supplied the
'bun' and 'node' type roots were absent and tsc failed with TS2688.
Declare them on the package that requires them in its tsconfig types.
Also refreshes the stale @cline/code version recorded in bun.lock.
* feat(vscode): enable Auto Compact by default
The SDK-based extension has no fallback context management: with auto
compact off, hitting the model's context window fails the request with a
provider error and retrying keeps failing (the legacy extension truncated
the oldest half of the conversation in this situation). The CLI already
defaults compaction on (agentic); align the extension with it.
* chore: add changeset for Auto Compact default-on
* fix(models): tolerate null contextWindow/maxTokens in SDK catalog shapes
Live LiteLLM proxies report unknown model limits as explicit nulls in
/model/info (e.g. max_tokens: null). adaptSdkModelInfo only tolerated
undefined, so a single such model failed the entire catalog refresh and
left the model picker empty. Treat null like a missing value (matching
the existing pricing handling) and fall back to the safe defaults.
* Update apps/vscode/src/sdk/model-catalog/shape-adapter.ts
Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
* Update apps/vscode/src/sdk/model-catalog/shape-adapter.ts
Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
* fix(models): restore missing limit fallbacks
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
* fix(cli): open history in the existing TUI
* refactor(cli): clarify history TUI startup target
* feat(cli): add history actions to TUI
* fix(cli): avoid empty session when resuming history
* fix(cli): fail history delete without session id
* fix(cli): dispatch resume hook from history picker
* fix(vscode): show per-file diff for multi-file apply_patch
apply_patch edits to multiple files rendered the entire multi-file patch in every per-file diff row. Split the patch into one tool message per file at content_end (mirroring the read_files split) so each row shows only that file's changes.
Closes#9904
* fix(vscode): address review on multi-file apply_patch split
Import the canonical PATCH_MARKERS from @cline/core instead of the local AP_MARKERS duplicate and export it through the core barrel. The cross-world import barrier the old comment claimed does not exist - apps/vscode already imports runtime values from @cline/core.
Route the apply_patch branch in sdkToolToClineSayTool through getApplyPatchString so the streaming and finalized rows derive their content from one source.
Handle the bare-string apply_patch input. ApplyPatchInputUnionSchema accepts { input: string } | string; a bare two-file patch made getApplyPatchString return undefined, so both content_start and content_end produced one empty-path row instead of the per-file split. Return the raw string when the field lookup finds nothing, with a start/end reconciliation test.
Refs #9904
---------
Co-authored-by: Minhkunn <minh.12072k6@gmail.com>
* fix(connectors): recover Slack thread mapping when session is gone
A connector thread binding can outlive its runtime session (hub restart,
session abort, retention cleanup). When that happened the thread stayed
pinned to a dead session id and every subsequent turn failed with
`session_not_found`, so the bot replied "Slack bridge error: session not
found" forever with no way to recover short of editing threads.json.
Drop the stale binding and replay the turn once against a brand new
session. Both the normal turn path and the steering path are covered.
Adds forgetThreadSession() to session-runtime and 3 regression tests.
* fix(connectors): serialize stale session recovery
---------
Co-authored-by: cline-test-bot <cline-test-bot@users.noreply.github.com>
Co-authored-by: abeatrix <beatrix@cline.bot>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* Consolidate per-provider model refresh handlers into the SDK
The VS Code extension resolved model catalogs from two sources: the SDK
catalog (models.dev-backed) used by resolveModelInfo/task header, and
host-side refresh handlers (refreshOpenRouterModels & co.) used by the
settings pickers. This dual-source split produced inconsistencies like
ENG-2345.
SDK (@cline/core):
- New rich live model sources (live-model-sources.ts) ported from the
extension handlers: OpenRouter (pricing incl. cache read/write,
descriptions, image support, thinking config, tiers/global-endpoint
metadata, curated overrides, stealth models), Vercel AI Gateway, and
Hugging Face. Keyed by generated catalog key so cline shares
OpenRouter's live data.
- mergeKnownModels layers rich live entries field-wise on top of the
curated catalog (live fields win, curated fields fill gaps) instead of
the modelsSourceUrl replace semantics.
- New Groq and Requesty private fetchers (API-key gated); Baseten
private fetcher now parses live pricing and reasoning support and is
enriched from the curated catalog.
Extension (apps/vscode):
- refreshOpenRouterModels/Groq/Baseten/VercelAiGateway/HuggingFace/
Hicap/Requesty are now thin delegates over the SDK provider catalog;
all bespoke fetch/parse/disk-cache code is deleted.
- shape-adapter maps the SDK's thinkingConfig, temperature,
global-endpoint capability, and metadata tiers onto the extension
ModelInfo.
- Removed the now-unused StateManager models cache, per-provider disk
cache files, and the dead readOpenRouterModels stub.
Fixes ENG-2381.
* Simplify: rely on the SDK's models.dev catalog, no rich live sources
Drop the ported per-provider live fetchers and curated overrides
(live-model-sources.ts) and all SDK merge changes. The extension now does
exactly what the CLI does: refresh handlers resolve through
resolveProviderConfig, which serves the models.dev-backed catalog
(bundled + runtime live refresh) plus the SDK's pre-existing
authenticated fetchers (Baseten/Hicap/LiteLLM/Poolside). No hardcoded
model info or per-model pricing workarounds remain anywhere.
Also reverts the shape-adapter additions since no SDK catalog source
populates thinkingConfig/temperature/metadata tiers today.
* Replace thinking-budget sliders with catalog-driven reasoning effort selection
Match the CLI's UX: every reasoning-capable model (SDK catalog
'reasoning' capability -> supportsReasoning) gets the Reasoning Effort
selector (none/low/medium/high/xhigh); the legacy 'Enable thinking' +
budget-tokens slider is removed everywhere, along with the hardcoded
per-provider thinking-model id lists (Anthropic, Claude Code, Bedrock,
Qwen) and claude/grok model-id heuristics in the OpenRouter, Vercel,
and Requesty pickers.
Effort changes now dual-write the provider-config reasoning settings
({enabled, effort}) that the session factory actually consumes - the
budget slider wrote legacy plan/act thinkingBudgetTokens state that
sessions already ignored. The utility request path
(buildSdkProviderConfig) drops its budget preference and forwards
effort only; the SDK translates effort into each provider's wire
format (including budget-token mapping where required).
* Gate picker reasoning-effort UI on live catalog entries
The OpenRouter/Vercel/Requesty pickers read the committed legacy
model-info snapshot, which provider-config writes can clear when a
resolution lands on a fallback source - selecting an effort made the
selector disappear. Gate on the live catalog map (with snapshot
fallback) instead; Requesty gates on the catalog only, since its
safe-default fallback over-reports reasoning support.
* Address review: honor legacy thinking budgets, dedupe refresh handlers
- Persisted thinking budgets are honored again (greptile P1 / review
request): normalizeProviderReasoningSettings maps a stored
reasoning.budgetTokens (written by older versions or the SDK's
legacy-state migration) onto the effort scale and treats it as
thinking-on, and buildSdkProviderConfig derives an effort from the
legacy plan/act budget fields when no explicit effort exists. An
explicit 'none' still wins. Shared mapping lives in
reasoningEffortFromThinkingBudget with low/medium/high buckets.
- Extract resolveProviderModelsRecord into providerCatalogShared and
collapse the seven refresh handlers onto it.
- Document the explicit OCA decision: its reasoning control is the
API-driven effort dropdown; the removed budget slider wrote state no
OCA request path consumed.
* Harden OpenRouter picker reasoning gate against placeholder metadata
Gate on the raw committed model-info snapshot instead of the hook's
default-info fallback, so a selected id that is absent from the catalog
can never inherit reasoning support from placeholder metadata (the
fallback carries no supportsReasoning today, but reading the raw field
removes the latent dependency).
Cancelling a task previously only detached Cline's listeners from an
in-flight foreground command (process.continue()); the spawned process
kept running in the user's terminal after cancellation.
Send Ctrl+C (ETX) to the terminal before detaching so the shell delivers
SIGINT to the foreground process group, actually stopping the command.
The terminal is left open for reuse, and cancellation still succeeds even
if the interrupt write throws (e.g. terminal already disposed).
The legacy-provider migration seeded the openai-compatible models.json
entry with hardcoded defaults (contextWindow 128k, no pricing/temperature/
maxTokens/R1 flag). Because later override migrations skip models that
already exist in models.json, the user's legacy planMode/actModeOpenAiModelInfo
overrides were silently discarded on first upgrade: context window,
max output tokens, input/output prices, temperature, supportsImages=false,
and isR1FormatRequired all reset to defaults.
Seed the entry from the mode-appropriate legacy model-info snapshot
instead, treating legacy sentinels (maxTokens -1, temperature 0,
prices 0) as unset.
Matches the legacy extension's OpenRouter default (openRouterDefaultModelId),
so users migrating from the legacy build without an explicitly selected model
keep the same default model instead of being silently moved to
anthropic/claude-sonnet-4.6.
A legacy single-file .clinerules at the workspace root made the config
watcher's scans of .clinerules/skills and .clinerules/workflows throw
ENOTDIR, which aborted the entire user-instruction refresh: workspace
rules, global rules, and the Skills view all silently failed to load.
Treat ENOTDIR like ENOENT in isIgnorableDirectoryError so a file in a
directory position simply yields no candidates. The .clinerules file
itself is still picked up by the file branch of discoverRulesLikeFiles.
* fix(vscode): hide /newrule and /deep-planning until their prompt expansions are ported to the SDK runtime
* feat(vscode): port the /newtask context handoff to the SDK runtime
Expand /newtask into explicit new_task-tool instructions in
SdkController.resolveSlashCommands (ported from legacy
newTaskToolResponse), register a custom new_task AgentTool that captures
the model-generated context summary and completes the run, and emit the
ask:"new_task" message on turn completion so the existing webview
"Start New Task with Context" button (which preloads a fresh task with
the ask text) becomes reachable again. Set the turn phase to
awaiting_followup when emitting the ask, since the completesRun
termination path skips the translator's usual end-of-turn status
handling.
* fix(vscode): hide /reportbug until its prompt expansion is ported to the SDK runtime
Also drop the feature tip promoting /reportbug so the UI doesn't
advertise a command that no longer autocompletes.
* Revert "feat(vscode): port the /newtask context handoff to the SDK runtime"
This reverts commit d9ad153aec.
* feat(vscode): make /newtask an alias of /compact
Condensing achieves /newtask's goal (continue working with a fresh,
summarized context window) without the legacy new_task tool, so the
webview intercepts /newtask alongside /compact and /smol and runs the
condense RPC. Menu description updated to match.
* feat(vscode): port the /deep-planning prompt expansion to the SDK runtime
Expand /deep-planning into the legacy generic-variant instructions
(silent investigation, targeted questions, implementation_plan.md) in
SdkController.resolveSlashCommands, ahead of workflow/skill expansion.
Legacy's STEP 4 created an implementation task via the new_task tool,
which doesn't exist on the SDK runtime; the ported prompt instead has
the agent present the plan and wait for explicit user confirmation.
Re-adds /deep-planning to the slash menu.
* refactor(vscode): simplify the /deep-planning expansion
Drop the custom regex/expander and shell-specific research-command
blocks: the builtin is now a plain AvailableRuntimeCommand appended to
the discovered workflow/skill commands, so the existing
expandSlashCommands machinery handles matching and replacement. The
prompt keeps the four-step protocol and implementation_plan.md
structure with a generic investigation paragraph instead of embedded
OS-specific commands.
* fix(mcp): honor per-server timeout (seconds) across all clients
The per-server timeout field in cline_mcp_settings.json was only read
by the VSCode extension's tools/call path. Everywhere else used
hardcoded constants: the SDK client timed out all requests at 5s and
initialize at 1.5s, and the extension's metadata requests (tools/list,
resources/*, prompts/*) timed out at 5s. Slow servers failed despite a
configured timeout (#7635, #12344).
Resolve the timeout once per client and apply it to every request:
- @cline/shared exports the default (60s) and bounds (1s-3600s) plus a
resolver that clamps out-of-range values, so a milliseconds/seconds
mix-up can no longer become hours.
- The SDK config loader parses timeout into
McpServerRegistration.timeoutSeconds; StdioMcpClient and
SdkUrlMcpClient apply it to initialize, tools/list, and tools/call.
Unconfigured servers keep the fast 1.5s initialize probe so startup
is no slower than before; a configured timeout raises that budget
for slow-starting servers.
- The extension routes every request (including metadata) through one
resolver and drops the hardcoded 5s DEFAULT_REQUEST_TIMEOUT_MS.
- createMcpTools derives the agent tool timeoutMs from the same value,
keeping the wrapper and request timeouts in agreement.
- Timeout errors now name the bound and the field to increase; the
VSCode server row and the CLI server list show the effective timeout
and how to change it.
* fix(mcp): harden timeout lifecycle handling
* fix(mcp): address timeout review feedback
* fix(mcp): bound initialization and reconnect
* fix(mcp): keep timeout snapshots consistent
* fix(mcp): use standard stdio framing
* fix(mcp): bound legacy stdio fallback
* fix(mcp): honor timeout in framed fallback
* test(vscode): use SDK Vitest runner
* fix(mcp): fetch server capabilities in parallel
The four post-connect metadata requests (tools/list, resources/list,
resources/templates/list, prompts/list) ran sequentially, so a server
that hangs after initialize blocked connectToServer for four timeout
bounds. The MCP client correlates concurrent requests by JSON-RPC id
and the stdio transport writes each message atomically, so the fetches
now run in parallel and the worst case is one bound.
Also delete McpHub.readResource and McpHub.getPrompt and their response
types: nothing calls them since the SDK migration removed the
access_mcp_resource tool and prompt expansion.
* fix(mcp): keep failed servers and both framing errors visible
When both stdio framing attempts fail differently during initialize,
name each attempt's error instead of discarding the Content-Length
fallback's diagnostics. When they fail identically (both timed out),
rethrow the newline error unchanged so the timeout hint is the whole
message.
When connectToServer fails before the connection is registered (e.g.
the transport fails to start), register a disconnected entry carrying
the error so the server stays visible in the list instead of silently
disappearing, and notify the webview so the row leaves the connecting
state.
* fix(mcp): reject tool calls on connections without a client
A failed (re)connect registers a disconnected entry with a null client
so the server stays visible in the list. A tool wrapper captured by an
active session can still target that server; callTool now rejects it
with a controlled error naming the server and its last connection
error, instead of dereferencing the null client and throwing a
TypeError.
parseKeyPairsIntoRecord wrapped the whole forEach in one try/catch, so a single entry that broke decodeURIComponent (e.g. a stray % in OTEL_EXPORTER_OTLP_HEADERS) aborted the loop and silently dropped every remaining header. Move the try/catch inside the loop to skip only the malformed entry. Adds regression tests.
* fix(desktop): disable timeout for chat send commands
Add per-invocation timeout options to the desktop client and disable the deadline for long-running chat send requests. Extract the shared command response type and verify send commands use the timeout override.
* fix(desktop): clean up failed websocket sends
useDebouncedInput scheduled its debounced onChange on mount and on every
external initialValue resync, not just user edits. Settings fields mount
with a placeholder value while their backing provider config is still
loading asynchronously, so the mount-fire echoed that placeholder back
to the backend ~100ms later.
For DebouncedTextField-backed secret fields (e.g. the OpenRouter API key,
which renders a masked value derived from the async readProviderConfig
response), losing that race meant writing apiKey: "" — silently deleting
the stored key from both providers.json and the legacy secrets store,
and leaving the field rendering empty despite a previously persisted key.
Non-secret fields similarly re-saved stale placeholder values on every
mount.
Gate the debounced save on an actual user edit: only values set through
the returned setter fire onChange; mount and external resyncs never do.
* fix(openai-compatible): keep user model metadata when only the model id changes
Changing the OpenAI Compatible model id committed the new id without
overrides, so an id unknown to the catalog resolved to safe defaults
(inputPrice/outputPrice 0, supportsPromptCache false) and paid requests
billed as $0.0000. The legacy extension kept this user-authored metadata
in a single id-independent blob, so custom prices survived id edits.
Recommit the currently displayed overrides under the new id when the
model id changes, and let edits made while that commit is round-tripping
target the pending id instead of the stale read-back id.
Fixes ENG-2341
* fix(openai-compatible): scope pending selection state per mode
Review follow-up: the pending-override accumulator and pending-commit
counter were shared across Plan and Act. Changing the Act model id,
switching to Plan while that commit was round-tripping, then editing an
override committed the Plan edit under the pending Act model id (and the
shared pending count blocked Plan's reseed at the mode boundary).
Record the mode alongside the pending selection and only trust it for
edits in the same mode, keep per-mode pending counts so a mode switch
reseeds from that mode's committed state, and cover the deferred-commit
mode-switch scenario with a component test.
* fix(openai-compatible): give each mode its own pending-selection accumulator
Review follow-up: tagging the single shared accumulator with a mode still
lost state on a mode round trip. With an Act commit pending, visiting
Plan reseeded the shared slot to Plan; returning to Act could not reseed
(Act's read-back was still in flight), so the next Act edit merged onto
an empty set and silently dropped the pending prices/context/capabilities.
Keep one accumulator slot per mode so a round trip through the other
mode never disturbs a mode's pending state, and cover the scenario with
a deferred-commit round-trip test.
The OpenRouter model picker (refreshOpenRouterModels) still applied the
legacy 200k context-window restriction to Anthropic Claude models, while
the task header and auto-compaction resolve model info through the SDK
catalog, which reports the full 1m extended context window. The same
model showed Context: 200K in the picker and 1.0m in the task header.
Per the current product direction the 200k restriction (and its :1m
opt-in variants) is dropped entirely — everyone gets the 1m context
window. Remove the artificial clamps from refreshOpenRouterModels
(keeping the prompt-cache pricing overrides) and update the
openRouterDefaultModelInfo fallback to match, so the picker, the task
header, and compaction thresholds all agree on 1m.
Closes ENG-2345.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): migrate legacy API keys for all secret-backed providers
collectCandidateProviderIds only nominated 11 provider ids while
buildLegacyProviderSettings can copy keys for 34, so stored keys for the
other 25 providers (deepseek, mistral, xai, groq, ...) were silently
dropped during migration unless the provider was the active plan/act
provider. Add the missing candidate checks so any stored key makes its
provider a migration candidate.
Also pick the legacy mode per candidate: a split plan/act config applied
the single globalState.mode to every provider, so the non-current mode's
configured model was replaced by the catalog default.
Migration re-runs on manager construction and never overwrites existing
entries, so users who already ran the buggy migration get dropped keys
backfilled from the still-present legacy secrets.json on next launch.
Fixes ENG-2337
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): normalize legacy provider-id aliases during migration
Address review: the mode selection and model fallback compared raw
legacy provider ids, so a declared alias (togetherai -> together,
sap-ai-core -> sapaicore) in globalState would miss its canonical
secret-derived candidate, read the wrong mode, and could write duplicate
alias/canonical entries. Route candidate collection, mode comparison,
and the generic model fallback through the existing normalizeProviderId
boundary. resolveMigratedProviderId now delegates to normalizeProviderId
(identical for the openai -> openai-compatible case it already handled).
Legacy ApiProvider never actually stored alias forms, so this is
hardening for hand-edited state rather than a live regression.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): reconcile the two provider state stores (ENG-2332)
- createStorageContext now honors CLINE_DATA_DIR with the same priority as
the SDK's resolveClineDataDir and the legacy reader's resolveDataDir
(explicit option > CLINE_DATA_DIR > CLINE_DIR/data > ~/.cline/data), so
globalState.json/secrets.json live in the same data dir as providers.json
and legacy task state instead of silently splitting across directories.
- Add setLastUsedProvider and call it on active provider switches
(SdkProviderChangeCoordinator) and when a session resolves its provider
from StateManager (buildSessionConfig), so providers.json's
lastUsedProvider no longer goes stale across provider switches.
- Trim env vars in legacy-state-reader's resolveDataDir to match the SDK's
resolution exactly.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: trim ENG-2332 fix to the minimal change set
Revert the cosmetic legacy-state-reader trim, restore the original CLINE_DIR
line in createStorageContext, and tighten comments. No behavior change to
the two core fixes.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop lastUsedProvider sync, keep only the data-dir alignment fix
Scope ENG-2332 to the root-cause fix: createStorageContext honoring
CLINE_DATA_DIR like the SDK resolvers. The providers.json lastUsedProvider
staleness is deferred to a follow-up.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: trim CLINE_DATA_DIR in resolveDataDir to match createStorageContext
Addresses Greptile P1: a whitespace-padded CLINE_DATA_DIR was trimmed by
createStorageContext but used verbatim by the legacy reader, which could
resolve the two stores to different directories again.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: retrigger CI (windows e2e flake in chat.test.ts)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: share one data-dir resolver between storage context and legacy reader
Per review feedback: extract resolveDataDirFromEnv in storage-context.ts and
have legacy-state-reader's resolveDataDir delegate to it, so the two stores
structurally cannot drift apart again.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: trim CLINE_DIR in the shared data-dir resolver to match the SDK
The SDK's resolveClineDir trims CLINE_DIR; a whitespace-padded value would
otherwise still resolve VS Code state and providers.json to different
directories.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(vscode): show working-directory badge when a task runs outside the open workspace
Tasks resumed from the CLI or another workspace keep their original cwd,
so Cline reads, edits, and runs commands in a directory that is not the
one visible in the window - previously with no indication anywhere.
- Add TaskWorkingDirectoryBadge: a persistent warning chip in the task
header (folder icon + cwd basename, full path + explanation in the
tooltip) shown only when the task cwd is neither an open workspace
root nor inside one. Hidden when roots or cwd are unknown to avoid
false positives.
- Fix SdkController.getStateToPostToWebview to pass its workspace
manager into the shared state builder; the SDK path previously always
sent workspaceRoots: [] to the webview.
- Unit tests for the outside-workspace predicate (case, separators,
multi-root, prefix collisions) and badge render states.
* fix(vscode): platform-aware path comparison in working-directory badge
Address PR #12637 review findings:
- Case folding is now platform-aware (win32/darwin insensitive, linux
and unknown strict), so case-only path differences on Linux are no
longer hidden; mirrors arePathsEqual in src/utils/path.ts.
- Backslashes are treated as separators only on win32; on POSIX a
backslash is an ordinary filename character.
- Containment prefix no longer doubles the separator when a workspace
root already ends with one, fixing false warnings for '/' and drive
roots.
- Tests cover case-only pairs under win32/darwin/linux/unknown,
POSIX-backslash filenames, '/' and 'C:\' workspace roots.
* fix(vscode): make darwin path comparison strict in working-directory badge
Follow-up to PR #12637 review: darwin volumes can be case-sensitive, and
the host's canonical arePathsEqual (src/utils/path.ts) already treats
only win32 as case-insensitive. Align the badge predicate with that
convention: case folding and backslash separators apply on win32 only;
darwin, linux, and unknown compare strictly. For a warning badge a rare
spurious warning beats silently hiding a real mismatch.
* fix(vscode): restore legacy workflow invocation and management UI
- Expand /workflow slash commands typed with the legacy .md filename
spelling (what the autocomplete menu inserts) and mid-message, and
honor the user's workflow enable/disable toggles, instead of only
expanding a leading extension-less /name via the SDK resolver.
- Restore the Workflows tab in the rules modal (view, toggle, create,
edit, delete; enterprise section) that was dropped in the SDK-backed
extension while all its gRPC handlers remained wired.
* chore: add changeset for workflow fixes
* fix(vscode): refresh workflow toggles on webview launch
The slash command menu is driven by workflowToggles state, but nothing
refreshed it at startup in the SDK-backed extension (only opening the
rules modal or creating a rule file did), so workflows never appeared in
the chat autocomplete until the user opened the modal. Legacy refreshed
toggles on task init.
* feat(vscode): move Workflows tab last and add deprecation warning
Workflows tab now appears after Rules/Hooks/Skills, and its view leads
with a warning banner: workflows are being deprecated in favor of
skills, with a docs link.
* chore: update changeset for workflow deprecation notice
* fix(vscode): address review findings on workflow expansion
- Honor remoteWorkflowToggles (and locked alwaysEnabled remote
workflows) when building the disabled set, so disabled enterprise
workflows no longer expand.
- Treat a workflow as disabled only when no scope has it enabled, so a
disabled workspace file no longer shadows a same-named enabled global
one (legacy expanded the enabled scope).
- Strip all workflow extensions the SDK discovers (.md/.markdown/.txt)
when matching typed commands, not just .md.
- Re-read toggle state after the async directory scan in
refreshWorkflowToggles so a toggle flipped mid-scan is not overwritten
by the stale snapshot.
* fix(vscode): map workflow toggles to records so frontmatter names are governed
Compute the disabled set from the discovered workflow records
(listRecords) instead of toggle-path basenames alone: a file's toggle is
matched by its basename and disables the record's actual command name,
so a frontmatter 'name' that differs from the filename is still governed
by the Workflows toggle. Remote-config-materialized records are governed
by the name-keyed remote toggles (locked alwaysEnabled remain on).
* fix(vscode): harden workflow toggle-name mapping for expansion
- A command name shared by several records now counts as enabled when
any record is enabled, so a disabled local workflow can no longer
suppress an enabled or locked (alwaysEnabled) enterprise workflow.
- Remote toggles/locks are matched via a sanitizeSegment-compatible key,
so config names that get rewritten during materialization (e.g. 'Org
Standards' -> org-standards.md) still govern expansion.
- Typed filenames (e.g. /my-workflow.md from autocomplete) now resolve
to workflows whose frontmatter renames the command, via the record's
file basename.
* fix(vscode): govern each workflow command by its own record's toggle
Key the disabled set by exact command name and decide each record
independently instead of OR-aggregating by canonical name: distinct
commands whose names only differ by case or extension (e.g. a local
'Release' and a remote 'release') no longer influence each other, so an
enabled local workflow cannot keep a disabled enterprise workflow
expandable, and a disabled one cannot suppress a locked enterprise
workflow.
* fix(vscode): exact remote-name sanitization and keep mid-scan toggle additions
- Port @cline/shared's sanitizeSegment verbatim (incl. the 80-char cap)
for remote workflow name comparison, so long enterprise workflow names
cannot bypass a disabled toggle after filename truncation.
- The post-scan toggle merge now also keeps entries added while the scan
was running (e.g. a workflow created via the modal), instead of
pruning them with the deleted files.
* fix(vscode): handle mid-scan deletions and sanitized remote-name collisions
- The post-scan toggle merge now also drops entries that were removed
from state while the scan ran, so a workflow deleted mid-refresh is
not restored by the stale scan result.
- Remote toggle names that sanitize to the same materialized name merge
as enabled-if-any-enabled instead of last-write-wins.
* fix(vscode): serialize workflow toggle refreshes
Queue refreshWorkflowToggles runs on a promise chain so overlapping
refreshes (webview launch, modal open, file create/delete) cannot
interleave scans and writes. Combined with the post-scan merge for
direct toggle flips, this closes the remaining stale-refresh races.
* fix(vscode): key remote workflow toggles off the materialized filename
The materializer names remote workflow files from the config name, so
derive the remote toggle key from the file basename instead of the
parsed command name; a frontmatter alias can no longer bypass a
disabled remote toggle.
* fix(vscode): make interrupted tasks findable in History and restore Resume button
Interrupted/cancelled sessions were presented as gone (ENG-2336):
- History fuzzy search used location-based Fuse scoring (ignoreLocation:
false, threshold 0.6), so any match more than ~60 characters into the
task title scored above the threshold and the task silently vanished
from search results even though it was in the list. Search now matches
anywhere in the title.
- Opening a task from History never updated the authoritative TurnState,
so the footer kept the previous context's phase (usually idle) and the
Resume Task button never appeared for interrupted/failed sessions.
showTaskWithId now derives the phase from the reopened conversation:
resumable for interrupted tasks, completed for completed ones.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): decide Resume vs Start New Task from persisted session status
SDK conversations do not record a completion tool call in the transcript
(a completed turn and one interrupted mid-stream both end with plain
assistant text), and history rendering appends a synthetic trailing
ask:"completion_result" either way, so the message tail always looked
"completed". Reopening a task from History now reads the persisted
session status: "completed" gets the Start New Task affordance, while
cancelled/failed (interrupted) sessions get Resume Task. When reopening
the currently-active task, the stop is awaited first so the status read
reflects how the last turn actually ended.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): fence concurrent history opens and default unknown status to Resume
Address review feedback:
- showTaskWithId now takes a generation fence: a request that loses the
race to a newer showTaskWithId or clearTask abandons installation after
its awaited reads, so a slow older request can never clobber the user's
latest selection (task proxy, messages, or turn phase). clearTask bumps
the generation too so New Task wins over an in-flight history open.
- The resume affordance no longer falls back to the message tail when the
persisted session status is unavailable: the tail always ends with the
synthetic ask:"completion_result" that history rendering appends, which
misclassified interrupted tasks as completed on a failed status read.
Only an explicit "completed" status gets Start New Task; anything else
(including unknown) gets Resume Task, the safe direction.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): allocate history-open generation before the lookup and fence before session stop
Address review feedback: the latest-selection-wins fence started too late.
SdkController.showTaskWithId awaited findHistoryItem() before entering the
coordinator, so a stalled preflight for an older selection could re-enter
with a NEWER generation than a later selection and replace it — and since
the first fence check sat after endActiveSession, a superseded request
could also stop a session the newer selection had just installed.
The history lookup now lives inside the coordinator (skipHistoryLookup is
gone), the generation is allocated synchronously before all asynchronous
work, and a fence check runs before endActiveSession so a superseded open
never stops the newer selection's session. The coordinator returns the
HistoryItem so SdkController keeps its TaskResponse contract. Regression
test covers the exact reported sequence: stalled lookup for task A, task B
selected and loaded, A resolves last — B stays installed and A stops
nothing.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Show completion feedback box for inferred turn-final responses in SDK path
The SDK agent usually ends a turn with a plain text response instead of an
attempt_completion / plan_mode_respond tool call, so the legacy green 'Task
Completed' box (act) and 'Plan Created' box (plan) never rendered in the new
extension — making a finished turn look stuck or frozen.
Now, when a turn ends cleanly (done reason 'completed', no completion tool
used) and its last content is a text response, that text row is retagged in
place to say:'completion_result' (act, green box) or the new
say:'plan_completion_result' (plan, yellow-accented 'Plan Created' box).
- Track the turn-final text candidate in MessageTranslatorState; cleared on
tool activity, errors, aborts, and new user turns
- Replay the same inference during history rehydration, recovering each
turn's plan/act mode from the persisted <user_input mode="..."> wrapper
- Add plan_completion_result ClineSay type (+ proto enum) rendered via
PlanCompletionOutputRow, restyled with the plan-yellow accent to match
the plan/act toggle and the CLI's plan color
- Turn phase semantics unchanged: footer buttons still come from TurnState
* Remove attempt_completion tool and strip completion box headers
- Drop the attempt_completion extra tool (and its shell-command executor)
from VS Code SDK sessions; the SDK's built-in submit_and_exit is already
disabled for act/plan presets, so the agent now always ends its turn with
a plain text response and the turn-end inference styles it.
- Translator keeps recognizing attempt_completion/submit_and_exit for
replaying persisted transcripts from older sessions.
- Remove the 'Task Completed' header, check icon, and copy button from the
green completion box, and the 'Plan Created' header, notepad icon, and
copy button from the yellow plan box. The final text of a turn may be a
question rather than an actual completion or plan, so the boxes are now
quiet color cues that make no claim.
* Skip completion retag for terminal text of failed/cancelled sessions
The trailing text of a session whose last run failed or was cancelled is a
dangling partial response, not a completion. Gate the history converter's
final synthesized turn end on the session record's status so reopening a
broken task keeps its terminal text as a plain row instead of an inferred
completion box. Mid-transcript turns are unaffected: the user continued
after them and history carries no per-turn outcome.
* Require clean at-rest session status before retagging terminal text
Tighten the negative failed/cancelled check into an allowlist: the history
converter now only retags the transcript's terminal text when the session
record is 'completed' (formally stopped clean run) or 'idle' (the normal
at-rest state between interactive turns). 'running'/'pending' at rest means
the process died mid-turn, so its dangling partial response stays plain.
* Restrict history completion retag to the transcript's final turn
Persisted SDK transcripts carry no per-turn outcome, so a mid-conversation
turn the user cancelled mid-response (then followed up on) is
indistinguishable from one that ended cleanly. Retagging those presented
interrupted responses as deliberate turn ends. History rehydration now only
retags the final turn's terminal text, gated on the session record's
at-rest status; earlier turns always render as plain text. Live sessions
are unaffected — their per-turn boxes come from real done events.
* Trust only status 'completed' for the history completion retag
'idle' is written by markTurnIdle for every interactive finish reason,
including aborted turns, so an at-rest idle record cannot prove the last
turn ended cleanly. Terminal statuses are reliably written when sessions
are released (task switch, clear, dispose), so requiring 'completed' keeps
the box on normal reopened tasks while never styling an interrupted
response as a deliberate turn end.
* Treat missing session records as unknown outcome in history retag
A transcript with no session record has no recorded outcome, so its
terminal text stays a plain row instead of getting completion styling.
migrateWelcomeViewCompleted derived the flag solely from VS Code's
per-profile stores, which are empty for users upgrading from the live
4.x extension (file-backed config under ~/.cline/data). The flag landed
as false and fully configured users were pushed back through onboarding.
Purely additive: the existing VS Code checks are untouched; the same
signals (completed flag, provider secrets, keyless provider configs) are
now also read from the file-backed globalState.json/secrets.json and
OR-ed into the result.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Models sometimes emit numeric tool arguments as JSON strings. `insert_line`
and the `read_files` line bounds were plain `z.number()`, so an
`insert_line: "3"` rejected the whole tool call before it ran:
1 tool call(s) failed: [editor] {"error":"✖ Invalid input: expected number,
received string\n → at insert_line"}
The model is handed that error and burns a round trip re-deriving the argument.
`z.coerce` leaves the JSON Schema advertised to the model untouched (still
`integer`), and `.int()` / `.positive()` still reject "abc", "3.5" and 3.5.
* fix(webview): stop unbounded polling of local model endpoints (ENG-2344)
The Ollama provider form polled /api/tags every 2s from two places at once
(OllamaProvider and a dead duplicate poll in ApiOptions whose result was
never read), producing ~1 req/s for as long as the settings pane was open.
Since the base URL is user-configurable, this could hammer a remote or
metered endpoint. VSCodeLmProvider and LMStudioProvider had the same
interval pattern.
- Remove all useInterval model polling; fetch on mount and when the
base URL changes instead
- Refresh the Ollama model list when the picker field gains focus so a
server started after the pane opened is still discovered
- Delete the dead _ollamaModels poll in ApiOptions
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(webview): add on-demand model refresh for LM Studio and VS Code LM
Greptile review follow-up: removing the polling intervals left these two
pickers pinned to their mount-time snapshot. Mirror the Ollama picker's
interaction-driven refresh:
- LM Studio: refetch models when the model dropdown or the manual model
id field gains focus
- VS Code LM: refetch when the dropdown gains focus, and add an explicit
'Refresh the model list' link to the empty state
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
An `editor` tool call with `insert_line` (e.g. a prepend) targets an
existing file — the SDK editor executor requires the file to already exist
for inserts — but sdkToolToClineSayTool only treated `old_text`/`replace_in_file`
as edits, so inserts were classified as newFileCreated and the approval card
read "Cline wants to create a new file:" for an existing file.
Treat insert_line as an edit so the card reads "Cline wants to edit this file:".
The webview-ui-toolkit VSCodeDropdown fires a spurious change event with
the wrong option (index 2, Portuguese - Brasil) while its slotted options
initialize after a window reload, and the handler persisted that value
unconditionally. Any saved language not at the top of the list could be
silently rewritten to Portuguese just by opening the General settings tab.
Replace the toolkit dropdown with the ui/select component already used by
the other settings dropdowns (Auto Compact Strategy, MCP Display Mode),
which only emits onValueChange for real user selections, and render the
options from the shared languageOptions list instead of a hardcoded copy.
At turn end the final message is finalized (partial: false) via the fast
partial-message stream a moment before the done event flips turnState out
of "streaming" via a full state post. During that gap the in-list
"Thinking..." loader row appeared and immediately disappeared, flashing
on every turn completion.
- Extract the loader show/hide logic from MessagesArea into a testable
useThinkingLoaderRow hook.
- Debounce the loader when its trigger is the tail message finishing
streaming: mid-turn a real wait outlives the grace period, while the
turn-end phase change cancels it before it ever shows.
- Add the legacy path's say("completion_result") anti-flicker guard to
the turnState path so attempt_completion turns never flash regardless
of timing.
* fix(vscode): restore Retry/Start New Task buttons after API failure
A provider stream error emits ask:'api_req_failed', but the session-event
coordinator resolved the turn-end phase to 'awaiting_followup', clobbering
the error state — so the footer never showed the error-recovery buttons and
the error surface offered no way to recover (ENG-2339).
Record the error outcome in MessageTranslatorState when the error event is
translated, and resolve turn end to the 'error' phase so the existing
api_req_failed button config (Retry / Start New Task) is reachable again,
matching legacy behavior.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): also record error outcome for done(reason:'error') terminations
A turn can terminate with done(reason:'error') without a separate 'error'
event; record the error outcome there too so turn end still resolves to the
'error' phase and the Retry / Start New Task buttons appear.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix ignored China/international API line toggles for Qwen, Moonshot, Z AI (ENG-2340)
The regional apiLine setting was persisted through both storage layers but
never consulted when resolving the request endpoint, silently sending
regional users to the wrong host.
- @cline/llms: record china/international base URLs on the builtin specs
for qwen, qwen-code, moonshot, zai, zai-coding-plan, and minimax; expose
resolveProviderApiLineBaseUrl; resolve options.apiLine against the
registered apiLineBaseUrls in GatewayRegistry.createProvider (explicit
base URLs still win).
- @cline/core: toProviderConfig now resolves the base URL from apiLine
between the explicit setting and the static provider default.
- VS Code: buildSessionConfig resolves the API line from legacy state
(qwenApiLine/moonshotApiLine/zaiApiLine/minimaxApiLine) with a
providers.json fallback and forwards it on the provider config so the
gateway can route regionally.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Share the base provider's legacy API line with qwen-code and zai-coding-plan
The coding variants have regional endpoints in the SDK but no legacy
state field of their own, so a China-line user selecting them from the
VS Code UI would silently fall back to the international default. The
variant's own providers.json apiLine still wins over the shared legacy
field.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Session rebuilds seed the replacement session from readMessages, but the
persisted transcript only catches up at assistant-message/turn boundaries
and abort() does not flush. Toggling plan/act mode while a task's first
turn is mid-flight (e.g. a command approval pending) therefore rebuilt the
session with no history at all and the new mode's model lost the task.
Add RuntimeHost.readLiveSessionMessages (optional) which prefers the
resident session's agent.getMessages() and falls back to the persisted
transcript, expose it as ClineCore.readLiveMessages, and use it in the
VS Code history loader that feeds session rebuilds. readSessionMessages
keeps its persisted-transcript semantics for existing callers (compaction
validation, session snapshots, history).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add a built-in cline-settings skill and broaden the legacy resume
warning
Models diagnosing configuration problems have no authoritative source
for where Cline stores settings: the SDK migration removed the old MCP
documentation tool, and resumed legacy conversations can carry stale
paths and instructions from older runtimes (CLINE-2570).
Add a core-owned virtual skill, cline-settings, whose instructions are
generated at invocation from the shared storage path resolvers. It is
listed and invoked through the existing skills registry on both the
local and Hub session paths, is reserved against shadowing by
file-backed skills (case-insensitive), honors session skill allowlists
(an explicit empty allowlist disables all skills including built-ins),
and never appears in editable listRecords.
Broaden LEGACY_RESUME_MODEL_WARNING to cover stale configuration
paths, file formats, and product instructions, not just tool names.
Anchor the persisted history boundary on a stable marker; recognize
and upgrade the historical warning in place so previously resumed
tasks get the new wording without duplicate warnings, and preserve
resumed user text that shares a message with the warning.
* Fix Windows MCP stdio spawn for paths with spaces; add settings-skill
rule
The runtime-builder MCP test failed on Windows because the stdio
client spawns with shell: true there, and cmd.exe split the unquoted
executable path at the space in "C:\Program Files\nodejs\node.exe".
Quote the command and arguments for cmd.exe so any server whose
command or arguments contain spaces can start. Also raise the connect
timeout to match the request timeout: connect covers process spawn
plus the first initialize round-trip, and 1.5s is tight for cold
starts on loaded machines.
Add a brief .clinerule noting that settings/storage-path changes may
require updating the cline-settings built-in skill.
* Quote empty MCP arguments for cmd.exe
An empty-string argument passed through unquoted disappears when
cmd.exe re-parses the concatenated command line, silently shifting the
server's argument list. Quote empty values so they survive as "".
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(desktop): order sessions by last activity and unify status dot colors
* feat(desktop): add pagination to session history view
Display sessions in ten-item pages and fetch older history only after reaching the final local page. Add coverage for pagination, session opening, and compact token formatting.
* page numbers
* Support adding a session to favorite list
* apply feedback
* fix
* feat(chat): refine tool and reasoning message disclosures
Add tool-specific icons with a fallback, display elapsed reasoning time, and restyle reasoning disclosures. Position hidden message actions outside the layout and update attachment sizing to valid Tailwind utilities.
* fix(desktop): improve chat message actions and scrolling
Refine action positioning, sizing, timestamps, and visibility for chat messages. Remove nested overflow constraints so scrolling remains controlled by the conversation viewport, and tighten tool disclosure spacing.
* tools icon mapping
* fix(desktop): align chat timestamps and tool icons
* feat(desktop): expose session agent execution history
Add a list_session_agents sidecar command to retrieve agent and team run details from child sessions and tool messages. Include comprehensive tests for agent discovery, message parsing, status handling, and result normalization.
* apply feedback
* add test
* feedback fix
* fix
* fix p1
* feat(desktop): add system tray session status support
Enable Tauri tray icon and PNG image features for desktop tray integration. Expose the running session count in process context so the tray can reflect active work, with test coverage for running and idle sessions.
* fix(desktop): buffer tray actions and show app status
* fix(vscode): compact tasks opened from history
The compact button only worked while a session was actively running.
Opening a task from history and clicking compact errored with "There is
no active task to compact."
Compaction is defined over a session transcript, so rather than grow a
second implementation for displayed tasks, resume a displayed history
task on an isolated session host and compact it through the existing
path. The coordinator owns and disposes that host, so task navigation
cannot make cleanup stop a replacement active session.
Follow-up resume and both compaction paths (idle active session and
displayed task) acquire the same session-rebuild boundary around
transcript read, session start, and persistence. Task and session
object identity are rechecked across awaits; cleanup targets only the
exact host and session started by the operation. A follow-up abandoned
by task navigation settles the streaming turn phase it pre-set, so the
newly displayed task never shows a stuck Thinking/Cancel footer.
The resume-start preparation shared by follow-up and compaction is
extracted into prepareTaskResumeStartInput, including legacy task
conversion, so the two callers cannot drift apart.
The compaction divider UX and context-meter shrink remain owned by the
already-merged webview compaction change.
* fix(vscode): deliver follow-ups across a same-task proxy reload
Follow-up targeting checks compared the displayed TaskProxy by object
identity, but showTaskWithId allocates a fresh proxy for the same task
id, so reloading the task mid-resume silently dropped the message.
Compare targeting by taskId; cleanup keeps object identity.
* feat(core): persist plan/act mode, tool auto-approve, and compaction mode in global settings
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): restore /settings general toggles across restarts
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): make global settings updates cross-process safe
Targeted setters previously did unlocked read-modify-write cycles over the
shared global-settings.json, so concurrent hosts (two CLIs, or CLI + VS Code)
could silently discard each other's changes. Route all setters through a new
updateGlobalSettings(mutate) helper that re-reads the latest on-disk state
under a short-lived lock file (with stale-lock reclaim and a bounded wait)
and replaces the file atomically via temp-file rename so readers never see
torn writes.
* Revert "fix(core): make global settings updates cross-process safe"
This reverts commit 198c1c831b.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Introduce the concept of free models that have the cline-free ID
* Add (free) to explicitly free models
* Render (free) in free models name
* Add pricing to the free model info
* Fix free model pricing
* fix tests
* SEt cline-free model pricing to 0
* revert pricing changes
* Add free limit error handling
* Include the reset time in the message
* Add button to switch model in VSCode
* add model not found error
* remove problematic tests
* fix review messages
* Fix model promotion ended
* Revert "revert pricing changes"
This reverts commit 7e5b2a34fd.
* Introduce the concept of free models that have the cline-free ID
* Add (free) to explicitly free models
* Render (free) in free models name
* Add pricing to the free model info
* Fix free model pricing
* fix tests
* SEt cline-free model pricing to 0
* perf(desktop): make the app feel snappy end-to-end
Fixes several compounding sources of UI jank that made every click and
keystroke feel seconds-slow:
- Aurora background: drop per-frame 46-64px CSS blur re-rasterization;
bake softness into gradients + a static mask and animate only
opacity/transform (compositor-only). Onboarding/home idle went from
~10fps to a locked 60fps under 4x CPU throttling.
- Hide the app shell while the opaque onboarding overlay is up so a
second aurora + hero animations are not composited underneath.
- Hero verb animation: opacity/transform only (no text blur filter).
- Composer: keystroke state now lives inside ChatInputBar (versioned
promptDraft injections for quick actions/undo/resets), and mention/
slash detection is derived instead of effect-synced; typing went from
245/246 keystrokes over 50ms to 3/240.
- Chat streaming: coalesce per-token text/reasoning deltas into ~48ms
flushes; memoize MessageBubble/ToolMessageBlock with stable callbacks
so finished messages skip re-rendering during streams.
- Session history: only surface isLoadingHistory before the first load;
background refreshes no longer re-render the whole app twice each.
- Provider catalog (~700KB): dedupe concurrent fetches with a short TTL
so app boot issues one round-trip instead of three.
- Sidecar: session-log appends are now ordered async writes instead of
writeFileSync per streamed token; git/folder-picker/editor discovery
use async execFile so the native picker no longer freezes every
pending command; editor discovery results cached for 60s.
* fix(desktop): address Bugbot review findings
- Invalidate the shared provider-catalog cache after any provider
mutation (onboarding connect paths, account sign-in/out, settings
save, add provider) so post-save reloads never see a pre-save copy.
- Clear the injected composer draft on send so a composer remount
cannot repopulate the previous prompt.
- Mark the hidden app shell inert + aria-hidden while the onboarding
overlay covers it, keeping covered controls out of the tab order.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
read_files rendered file rows keyed by raw path, so the same path listed twice (e.g. find-skills reading a SKILL.md repeatedly) produced duplicate React keys and the two-children-with-the-same-key warning. Build index-namespaced keys instead, at both render sites.
Fixes#9784
Signed-off-by: Minhkunn <minh.12072k6@gmail.com>
Co-authored-by: Minhkunn <minh.12072k6@gmail.com>
* docs: fix typos and incorrect slash command reference
- Fix double period in MiniMax provider description
- Remove duplicate 'through' in kanban install description
- Fix /new -> /newtask (correct slash command name)
* docs(hooks): fix description to reference SDK Plugins, not SDK Hooks
The description said 'SDK Hooks page' but the content links to the
SDK Plugins page (/sdk/plugins). Align the description with the
actual destination.
* fix(desktop): clear busy status when queued turns finish; add Cline API key onboarding path
* feat(desktop): allow cancelling a pending Cline browser sign-in during onboarding
* fix(desktop): address review findings on OAuth cancel, API key verification, and queued-turn status
* fix(desktop): cancel pending OAuth logins when the initiating transport connection closes
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(vscode): resolve base URL and knownModels for compaction summarizer
The agentic compaction summarizer creates its LLM handler from the
session's ProviderConfig alone. For the OpenAI Compatible provider
stored under its SDK spelling (openai-compatible), resolveBaseUrl had
no mapping, so ProviderConfig was built without a baseUrl and the
summarizer silently hit the provider default endpoint (api.openai.com),
failed auth, and fell back to basic compaction - the UI still showed
'Context compacted' with no hint that agentic summarization never ran.
- resolveBaseUrl: accept the SDK spelling of the OpenAI Compatible
provider, and fall back to the providers.json base URL (mirroring
resolveApiKey) when legacy state has none.
- buildSessionConfig: expose knownModels at the top level of
CoreSessionConfig, so manual compaction (sdk-compaction.ts) budgets
against the real model context window instead of the 64k fallback.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): project compaction sidecar even when auto-compaction is disabled
Manual /compact persists a compaction sidecar and promises the next turn
will use the compacted working context, but the runtime host only wired
the compaction-state-aware prepareTurn when compaction was enabled. With
Auto Compact off (the VS Code extension default), a manual /compact was
a silent no-op for the model: the sidecar was saved and the UI showed
'Context compacted', yet every subsequent request still sent the full
canonical transcript.
createCompactionStateAwarePrepareTurn already supports an undefined
compact fn (project existing state, never re-compact), so wire it
unconditionally; sessions without a sidecar are unaffected. Also keep a
resumed/initial sidecar instead of dropping it when compaction is
disabled.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): stop the task at the mistake limit like the CLI, drop the max-mistakes setting
When the SDK's consecutive-mistake limit is hit, the extension used to
block on an ask (Proceed Anyways / Start New Task) while the agent loop
kept running against the provider — reproduced 2,100+ consecutive API
requests behind the unanswered prompt.
Replicate the CLI's non-interactive resolver instead: show an error row
and resolve the decision as an immediate stop. The run aborts cleanly at
the turn boundary, the turn phase becomes awaiting_followup, and the
user continues whenever they want by sending a new message (which also
resets the SDK's mistake tracking on the next productive turn).
Also remove the extension's maxConsecutiveMistakes setting (state key,
settings RPC, webview state, proto fields now reserved). It was never
wired into the SDK session config — the SDK's own default governs — so
the setting was dead weight. Legacy mistake_limit_reached asks from
persisted conversations still render via the existing webview paths.
* fix(proto): reserve retired Settings field 139 (max_consecutive_mistakes)
The original removal added 'reserved 139' but the proto generator at the
branch base had no reserved-statement support and silently dropped it on
regeneration. Main (b4c640733) taught generate-state-proto.mjs to
preserve reserved statements, so after the merge the reservation now
survives. Also reserve the field name, mirroring the custom_prompt
removal pattern.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(core): resolve relative read_files paths against the session cwd
The built-in FileReadExecutor resolved relative paths against process.cwd(),
which in a VS Code extension host is typically '/' rather than the workspace.
Every relative-path read failed with ENOENT, so models fell back to reading
files through the terminal. Resolve relative paths against the tool's
configured cwd in createReadFilesTool before invoking the executor.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): never let a stalled diff preview open fail or delay an edit
Thrown errors from opening the edit diff preview were already swallowed, but
a hung vscode.diff call was unbounded: on auto-approve it burned the editor
tool's 30s execution timeout (failing the whole edit), and on manual approval
it delayed the approval ask indefinitely. Bound the preview open with a 5s
timeout; on timeout the edit proceeds without a preview and the late-opening
tab is closed once the open settles.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style(core): order node:path import first for biome
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): flatten the preview-open timeout into a plain race
Replace the custom timeout error class, the race helper with timer
bookkeeping, and the two-branch cleanup with a single Promise.race and one
settle-then-close line. Same behavior: a rejected or stalled preview open
never blocks the approval ask or fails the edit, and any late-appearing tab
is closed once the open settles.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): move the read_files cwd fix out of the SDK into the host
Revert the SDK change and instead override the read_files executor in the
extension, alongside the existing editor/apply_patch/askQuestion overrides.
The override resolves relative paths against the workspace root before
delegating to the SDK's built-in reader, since the extension host's
process.cwd() is usually '/' and every relative-path read failed with ENOENT,
pushing the model into terminal fallbacks. The SDK is left untouched.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): consistent installed cards and single uninstall in marketplace views
* fix(desktop): surface marketplace setup guidance on matched installed cards
* fix(desktop): show setup guidance for all matched marketplace entries, not just first match
* fix(desktop): unambiguous entry-to-item matching and no stale installed card flash
* fix(desktop): drop orphaned installed keys optimistically instead of hiding cards during recheck
* fix(desktop): guard recheck races and avoid duplicate uninstall for ambiguous matches
* fix(desktop): keep uninstall action on ambiguous fallback marketplace cards
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* Remove dead 'Use compact prompt' toggle from LM Studio settings
The compact system prompt option was never wired up in the SDK-based
extension: the customPrompt value was stored in state and echoed back
to the webview, but nothing in the session factory or SDK ever read it
to alter the system prompt. Remove the checkbox (only shown for the
LM Studio provider) and all the dead state/proto plumbing behind it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add changeset for compact prompt toggle removal
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reserve removed custom_prompt field number/name in Settings proto
Teach generate-state-proto.mjs to preserve reserved statements in the
generated Secrets/Settings messages so removed fields keep their wire
identity reserved across regenerations, and reserve field 150 and the
custom_prompt name (plus the name in UpdateSettingsRequest).
Addresses Greptile review feedback on #12551.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Never assign reserved proto field numbers to new Settings fields
If the highest-numbered field was removed and reserved, the generator
would hand that same number to the next new field, emitting both a
reserved statement and a live field at the same number. Parse reserved
numbers (including ranges) from the existing message, skip them when
assigning new numbers, and fail fast if an active field collides with
a reservation.
Addresses Bugbot review feedback on #12551.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Format generate-state-proto.mjs
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): fold SDK openai-compatible provider id to legacy openai spelling
The settings provider dropdown sources ids from the SDK catalog, so picking
OpenAI Compatible stored 'openai-compatible' into plan/actModeApiProvider.
Every provider-keyed code path (webview model label, planModeOpenAiModelId
slots, session factory) expects the legacy 'openai' spelling, so the model
id under the chat field went stale after Done and fell back to the catalog
default (gpt-4o).
- parseProviderId + toLegacyApiProvider now alias openai-compatible -> openai
- state-keys load transform migrates already-stored SDK spellings
- convertProtoToApiProvider normalizes provider ids written from the webview
- commitModelSelection writes the legacy spelling and posts state to the
webview so model-only commits refresh the chat model label immediately
- session factory normalizes provider ids from state and providers.json
* fix(vscode): make toLegacyApiProvider alias lookup case-insensitive
parseProviderId lowercases before its alias lookup, but toLegacyApiProvider
(used directly by convertProtoToApiProvider and the state-keys load
transform) matched aliases case-sensitively, so a mixed-case
'OpenAI-Compatible' would not fold. Fall back to a lowercased lookup while
preserving original casing for unknown ids.
* fix(vscode): treat spelling-only provider differences as the same provider
Addresses the Bugbot finding on PR #12552: stale snapshots can still hold
the SDK spelling (openai-compatible) while new writes use the legacy
spelling (openai). Normalize both sides of the provider comparisons in
SdkProviderChangeCoordinator.providerForMode and
SdkController.isSelectionForActiveModeProvider so a spelling-only
difference neither restarts the active session nor skips the lightweight
in-session model update.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
The pre-SDK extension auto-retried failed API requests and surfaced
'Auto-retrying in X seconds' rows (say:'error_retry') plus a retryStatus
header on api_req_started. The SDK-based extension never emits either:
errors map straight to an api_req_failed ask with a manual Retry button,
and retrying is handled silently by the AI SDK / auth-refresh retry.
Remove the orphaned webview rendering (ChatRow error_retry case,
ErrorBlockTitle, combineErrorRetryMessages, isRequestInProgress chain,
stories), the unused say types and proto enum values (reserved), the
retryStatus field, and the never-invoked onRetryAttempt callback from
ApiHandlerOptions, sdk-api-handler, and @cline/llms provider config.
Legacy transcripts may still contain error_retry / api_req_retried rows;
readUiMessages now drops them so old tasks don't render raw JSON.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): keep webview alive when moved between sidebars
Moving the Cline view between the primary and secondary sidebars made the
view go blank with 'this.unsubscribeHostTelemetrySettings is not a function'.
Two fixes:
- The vscode host bridge streaming client returned the async IIFE's Promise
instead of the cancel function its contract declares, so callers invoking
the stored unsubscribe function threw a TypeError. It now returns a
synchronous wrapper that resolves the real cancel function in the background.
- VscodeWebviewProvider disposed the whole Controller on WebviewView
onDidDispose. VS Code destroys and re-resolves the view when it is moved
between sidebars, so the re-resolved view was served by a dead controller
(postStateToWebview no-ops after dispose) and rendered blank. onDidDispose
now only releases view-scoped resources; the controller is disposed on
extension deactivation via WebviewProvider.disposeAllInstances.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): address review — don't clear active task on re-resolve, guard stale view dispose
- resolveWebviewView no longer calls clearTask on re-resolves (moving the
view between sidebars must not terminate a running task); it only clears
stale task state on the first resolve after activation.
- onDidDispose now only tears down view resources if the disposed view is
still the active one, so a stale dispose event arriving after a newer view
resolved cannot clobber the active view's listeners. resolveWebviewView
also releases the previous view's resources up front.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
The 'Enter this code in your browser' box shown after clicking
'Sign in to Cline' was left-aligned while the surrounding logged-out
message and button are centered. Center the label and the code/copy row.
Fixes#12531
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: normalize trailing slash in OpenAI Compatible base URL for model list fetch
* fix: construct OpenAiModelsRequest via proto create in refreshOpenAiModels test
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
@opentui-ui/dialog@0.1.2 is built against @opentui/core ^0.1.69, whose
Renderable.remove(id) took a string id. Core 0.4.x renamed it to
remove(child) and throws when handed anything but a renderable, so the
dialog package's removeDialog()/provider teardown aborted before
detaching the panel: the React portal content unmounted but the
imperative grey box stayed on screen over the chat after every dialog
close (model picker, help, command palette, ...).
The upstream package is abandoned at 0.1.2, so pin the fix with a bun
patch that passes the renderable object on all three bindings (react,
solid, core container). A tui-test opens and dismisses the help dialog
and asserts the panel's #262626 background is fully gone, not just its
text.
Fixes#12506
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
* feat(desktop): use the shared Cline Hub runtime
* fix(hub): group code-sidecar-observer clients under Code App
The desktop observer client type was renamed from code-sidecar-approvals
to code-sidecar-observer, but the Code App grouping matchers in the hub
dashboard and menubar sidecar still only matched the old type. Since the
observer now registers on the shared Hub, it showed up as a separate
ungrouped client. Keep the old type matched for older desktop builds.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* feat(core): support pathless sessions with temporary workspaces
* fix(desktop): mark editor icons as decorative
* fix(core): omit absent auth request IDs
* test(sdk): restore request_id auth telemetry param in core-events test
The branch's drive-by request_id -> requestId rename was dropped while
resolving the merge conflict with #12444 (which added requestIdDetails on
main), so the public captureAuthLoggedOut/captureAuthRefreshSoftFailure
API keeps its original parameter name.
* refactor(sdk): root pathless session workspaces under the cline data dir
Move the workspace created for pathless session starts from
<os.tmpdir()>/cline/sessions/<id>-temp/project to
<cline-data-dir>/workspaces/<id>/project (default
~/.cline/data/workspaces/<id>/project), per PR review:
- OS tmp reapers (macOS ~3-day purge, systemd-tmpfiles, reboot cleanup)
silently delete user work created in 'New Project' sessions
- /tmp is a shared namespace on Linux: the first user to create /tmp/cline
owns it (EACCES for everyone else), and guessable session IDs let a local
attacker pre-create the workspace directory
- under the data dir the workspace shares the session store's lifecycle and
the existing CLINE_DATA_DIR / CLINE_DIR overrides for tests and sandboxes
isTemporaryWorkspacePath now matches the .cline/data/workspaces/<id>/project
segment shape, and the -temp suffix is gone since the id-scoped directory no
longer needs to mark itself as reapable.
* feat(sdk): open pathless sessions in one shared chat workspace
Instead of minting a workspace directory per session
(<data>/workspaces/<session-id>/project), all sessions started without a
cwd/workspaceRoot now share <cline-data-dir>/workspaces/chat (default
~/.cline/data/workspaces/chat). Starting a pathless session seeds the
directory with an AGENTS.md rules file (only when missing, so users can
edit it) that tells the agent to treat the session as a chat: don't create
or edit files unprompted, ask where a project should live when the user
wants one built, and default to a new named folder inside the chat
directory that later sessions can reference.
This avoids unbounded per-session directory sprawl, gives chat sessions a
stable home the user can revisit, and groups them naturally in the desktop
sidebar. The desktop app now labels the shared workspace "Chat" (menu
action "Just chat") instead of "New Project", and isChatWorkspacePath
matches only the chat directory itself, so project folders created inside
it behave as regular workspaces.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* feat(desktop): add replay new-user-experience setting
Adds an onboarding state module (localStorage-backed, with a reset event
the app shell can subscribe to) and a 'New user experience' row in
Settings -> General with a Replay button so beta testers and designers
can re-run the first-run flow. The flow itself ships in the stacked
follow-up PR.
* feat(desktop): first-run onboarding flow
Full-screen first-run experience shown until completed once: a welcome
step (3D glass logo over the aurora background), a connect step offering
Cline sign-in (recommended) or bring-your-own API key against the
provider catalog, and a done step that drops the user into a fresh
thread. Completion is tracked by the onboarding state module from the
previous PR; the Settings replay row now re-enters the flow in place via
the reset event, so its toast is gone. Skipping is always available so
nobody gets trapped; the connected provider (and its default model when
known) is remembered so the chat composer opens pointed at it.
* fix(desktop): address greptile review on onboarding flow
- Filter the bring-your-own-key picker to providers a lone API key can
fully configure: providers with structured config fields (Vertex gcp.*,
Bedrock aws.*) or no API-key field at all (Claude Code) no longer appear,
since connecting them here would report success without working.
- Record Cline as the active provider when a signed-in user hits Continue,
so replaying onboarding doesn't leave the chat pointed at a previously
selected provider.
* feat(desktop): accent color themes and switchable app icon (#12496)
* feat(desktop): accent color themes and switchable app icon
Settings -> General grows an appearance cluster next to Dark mode:
- Accent color: six palettes from the Figma exploration (violet default,
graphite, cyan, pink, espresso, ember). Non-default accents re-anchor
--primary/--primary-foreground/--primary-emphasis/--ring per light and
dark mode via html[data-cline-accent] overrides in globals.css, tuned in
OKLCH to mirror the brand token relationships; chart and sidebar tokens
alias var(--primary) so they follow. Persisted in localStorage and
applied at boot alongside the dark-mode sync.
- App icon: the four Figma variants (Classic, Sunrise, Steel, Midnight).
The webview persists the choice, swaps the favicon in browser mode, and
in the Tauri shell calls the new set_app_icon native command, which
loads the matching bundled resource (icons/dock/*.png) and applies it
via NSApplication.applicationIconImage on the main thread. macOS resets
the dock icon every launch, so the shell re-applies the stored choice at
boot; classic is also loaded from a resource because the objc2 binding
warns against passing nil to restore the bundled icon. Other platforms
no-op (Ok(false)).
* fix(desktop): don't let a stale app-icon failure roll back a newer selection
* polish(desktop): cleaner chat markdown + external links that actually open
Links in chat markdown never opened in the packaged app: the confirm
dialog's window.open(_blank) is silently dropped by the Tauri shell.
Route opens through openExternalUrl (open_external_url sidecar command)
and only keep the confirmation dialog for deceptive links whose visible
text reads as a URL on a different host than the real destination —
ordinary external links now open directly in the default browser.
Visual pass on Streamdown output for the chat pane: collapse the
double-boxed code block card and drop the language header row, reveal
the copy button on hover only, turn off line numbers, single-box tables,
chat-scale the heading ramp (h1 was text-3xl next to 14px body), outside
list markers, and tighter block rhythm.
* fix(desktop): harden deceptive-link detection per review
Recurse into element children when extracting link label text so inline
formatting (e.g. a bolded hostname) can't dodge the deception check, and
compare port and (when the label states one) scheme in addition to
hostname so same-host links to an unexpected scheme or port still get
the confirmation dialog. An unparseable destination behind URL-shaped
label text is now treated as deceptive rather than waved through.
* fix(desktop): treat trailing-dot FQDN labels like their plain hostname
Browsers resolve 'github.com.' identically to 'github.com', but the
URL-shaped-label pattern rejected the trailing dot, so a deceptive label
like [github.com.](https://evil.example) skipped the deception check and
opened directly. Accept one trailing dot in the pattern and strip
trailing dots during hostname normalization on both sides, so the FQDN
form is deceptive exactly when the plain form is.
* fix(desktop): treat protocol-relative labels like their https form
A label spelled '//github.com' reads as a URL but failed the URL-shaped
pattern (which only tolerated an https?:// prefix), so it skipped the
deception check and opened an unrelated destination directly. Accept a
protocol-relative prefix in the pattern, and parse '//'-prefixed values
as https-relative in parseLinkParts — prepending 'https://' to them
produced an empty hostname and made the comparison a no-op.
* feat(desktop): drag and drop files to attach them to the chat
The Tauri webview swallows OS file drags by default (dragDropEnabled),
so HTML5 drop events never fire. Disable it on the main window per the
Tauri v2 docs, then handle standard dragenter/dragover/dragleave/drop on
the chat pane: dropped files feed the same dedupe-and-append pipeline as
the paperclip picker, with a depth-counted 'Drop to attach' overlay while
files are dragged over. Image drops become data-URL images via the
existing serializeAttachments path.
* feat(desktop): display image attachments in chat
* 225x225
* fix(desktop): preserve queued attachments
* fix(desktop): distinguish queued image turns
* fix pending
* fixed
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* feat(desktop): add custom overlay title bar navigation
Configure Tauri to use a hidden overlay title bar and host back/forward navigation in the draggable sidebar header. Preserve agent title width during editing to prevent layout shifts, with tests covering both behaviors.
* fix(desktop): reconcile deleted navigation entries
* fix(desktop): dedupe session deletion events
* fix(desktop): serialize session deletion state
* fix(desktop): use exported DesktopAppView type in page.tsx
AppView is a non-exported type local to agent-sidebar.tsx, so referencing
it in page.tsx was a TS2304 error hidden by the typecheck script's webview
exclusion and next's ignoreBuildErrors.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Adds an onboarding state module (localStorage-backed, with a reset event
the app shell can subscribe to) and a 'New user experience' row in
Settings -> General with a Replay button so beta testers and designers
can re-run the first-run flow. The flow itself ships in the stacked
follow-up PR.
* fix(schedules): default headless routines to yolo
Centralize the Cline default model ID in @cline/shared while preserving the @cline/llms export. Keep explicit modes stable and disable ask_question for unattended scheduled runs.
* autoapprove
* fix unit test
* fix(schedules): harden headless routine execution
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* feat(vscode): show compaction progress and results in the webview
Port the CLI's compaction UX to the VS Code extension:
- Translate the SDK's compaction status notices into a say:'compaction'
divider row with a spinner while running, updated in place (same ts) to
'Context compacted · x → y tokens · n → m messages' when done, matching
the CLI's divider. Dangling dividers finalize as failed/cancelled when
the turn errors or ends mid-compaction.
- Manual /compact (button or slash command) drives the same divider from
the compaction coordinator instead of plain info lines, capturing token
counters from the SDK's status notices.
- Drop raw status-notice slugs ('auto-compacting') that previously
rendered as info rows.
- Context window bar now reads the compacted size (tokensAfter) from a
compaction row newer than the last API request, so it drops immediately
after compaction instead of waiting for the next turn.
- Fix the Auto Compact Strategy selector showing 'basic' when unset; the
effective default is agentic (core defaults strategy ?? 'agentic').
* fix(vscode): apply compaction shrink as a ratio to the context meter
Address review feedback from #12487:
- getLastApiReqTotalTokens: instead of substituting the compaction
notice's tokensAfter (an SDK estimate on a different scale than
provider-reported usage, which made the bar re-snap when the next
request's real usage landed), scale the last provider-reported request
total by the compaction's tokensAfter/tokensBefore ratio. Both
counters come from the same estimator, so the ratio is scale-free.
Multiple compactions since the last request compound. A completed
divider without token counters leaves the total unscaled.
- Suppress only the known-internal status notices explicitly
(compaction-budget-adjusted); an unrecognized status notice now falls
through to an info row so future notices surface instead of silently
vanishing.
- Cross-reference the two compaction-divider finalization paths (auto:
translator finalizeDanglingCompaction; manual: coordinator catch) so
terminal-state rule changes touch both.
- Post state to the webview before re-throwing in the coordinator's
failure path, consistent with the other terminal branches.
The Tauri webview swallows OS file drags by default (dragDropEnabled),
so HTML5 drop events never fire. Disable it on the main window per the
Tauri v2 docs, then handle standard dragenter/dragover/dragleave/drop on
the chat pane: dropped files feed the same dedupe-and-append pipeline as
the paperclip picker, with a depth-counted 'Drop to attach' overlay while
files are dragged over. Image drops become data-URL images via the
existing serializeAttachments path.
* fix(telemetry): report host identity on SDK-pipeline events
On JetBrains standalone cline-core, SDK-pipeline events (task lifecycle,
token usage, tool usage, provider failures) reported the hardcoded
cline_type "VSCode Extension", platform "VS Code", and
platform_version "unknown", unlike the classic TelemetryService which
resolves these from HostProvider.env.getHostVersion().
Extend the host_plugin_version resolution in VscodeTelemetryPolicyService
to apply the full host identity (cline_type, platform, platform_version)
with the same mapping the classic pipeline uses, before the telemetry
gate opens. Fields the host does not report keep the construction-time
fallbacks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(telemetry): fall back to unknown host identity, not VSCode labels
A failed getHostVersion lookup previously left the hardcoded VSCode
identity in place, hiding the failure as a plausible-looking row.
"unknown" makes the failure visible and matches the classic
TelemetryService's || "unknown" semantics.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(telemetry): defer provider_created until host identity is applied
telemetry.provider_created was captured synchronously inside the core
factory, before VscodeTelemetryPolicyService resolves getHostVersion —
so that one event always carried the construction-time fallback identity
(pre-existing: it reported the hardcoded VSCode identity on JetBrains
and never had host_plugin_version).
Add an opt-in deferProviderCreatedEvent to the core telemetry factories
that skips the construction-time capture and exposes it as
ConfiguredTelemetryHandle.emitProviderCreated; the policy service emits
it right after applying the resolved host metadata. Other handle
consumers (CLI, hub daemon, examples) keep immediate emission.
Also close the subscription race on the same guarantee: a host setting
flip arriving while getHostVersion is still resolving now waits for the
metadata to be applied before opening the gate, and a slow initial
settings fetch no longer overwrites a newer subscription update.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(telemetry): emit deferred provider_created on early dispose
If the policy service is disposed while the host-version lookup is
still pending, the deferred provider_created would never be captured —
the undeferred event was always emitted (with construction-time
identity) and exported by the shutdown flush. Emit-once semantics:
dispose fires the event with the fallback identity before shutting the
handle down, and the late metadata continuation cannot double-emit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: auto generate built-in provider list
- Generate `providers.generated.ts` and `provider-ids.generated.ts` from `models.dev/api.json` alongside the model catalog
- Merge generated provider specs with handwritten built-in overrides for Cline, Codex, local/OAuth providers, routing metadata, and product defaults
- Include additional `models.dev` providers only when they are OpenAI-compatible for now
- Keep lightweight provider ID utilities from importing the full generated provider spec catalog
* Removed redundant handwritten definitions for providers that are fully described by generated metadata
* update unit test
* feat(telemetry): emit host_plugin_version metadata on all events
The host already reports its Cline distribution version over the
hostbridge (getHostVersion.clineVersion — the JetBrains plugin version
on JetBrains, the extension version on VSCode), but telemetry never
attached it: extension_version is always the cline-core bundle version,
so JetBrains events could not be tied to a plugin release.
Attach it as a new optional host_plugin_version metadata field, omitted
when the host does not report one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(telemetry): loop over host version cases instead of interleaving stubs
Review feedback: the two host_plugin_version cases were interleaved via
onFirstCall/onSecondCall stubs across two service instances. Run one
mock-assert-reset cycle per case so the only differences between them —
the host version response and the expected reported value — are visible
in the case table.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(telemetry): guarantee stub cleanup in host_plugin_version test
Review feedback: the loop installed process-global stubs and only
restored them on the happy path — a rejected create() or failed
assertion would leak exhausted stubs into subsequent tests and leave
the service undisposed. Use a sinon sandbox restored in finally, and
dispose the service there too.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(telemetry): carry host_plugin_version on SDK-pipeline events too
Review feedback: main's production controller emits task lifecycle,
token usage, tool usage, and provider-failure events through a separate
SDK telemetry service whose metadata is built independently, so those
events still omitted the plugin version.
Add the optional host_plugin_version field to the shared SDK
TelemetryMetadata contract and resolve it from the authoritative
getHostVersion response during the policy service's init. The metadata
update is sequenced before the host telemetry setting is applied, and
events stay gated until that setting lands, so no event can be emitted
without the field in place. A failed host-version lookup degrades to
the previous behavior (field absent, telemetry still enabled).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Classify unobservable terminal outcomes so cleanup and reporting share one source of truth. Reclaim managed sendText fallbacks at the disclosed next-acquisition boundary while preserving markerless, continued, detached, and uncertain-error terminals. Make cleanup, CWD reservations, process listeners, and detached logs failure-safe.
Register every foreground command before terminal acquisition so a parallel batch observes one Proceed While Running decision. If a command is still acquiring a terminal, settle its tool result immediately and transfer the approved command to an owned detached lifecycle that logs acquisition, output, completion, and failure. Abort before startup unregisters the handle and prevents the command from starting later.
* fix(desktop): route external link opens through sidecar so they work in Tauri
The markdown 'Open external link?' dialog confirmed via window.open, which
the Tauri webview silently drops (no window opener configured), so clicking
'Open link' did nothing (ENG-2302). Route confirmation through
openExternalUrl, which invokes the open_external_url sidecar command inside
the Tauri shell and falls back to window.open in plain web mode.
Also fixes the marketplace 'Get value' env-var link, which relied on the
same dead target=_blank behavior.
* fix(desktop): open mailto/tel links and middle-clicked marketplace links
Greptile review fixes:
- open_external_url now allows mailto: and tel: alongside http(s) — the
platform openers already dispatch any scheme to the OS protocol handler,
the gate is just the allowlist. Streamdown's harden step blocks every
other scheme before it reaches SafeMarkdownLink (test added to guard
that assumption, since the sidecar allowlist relies on it).
- Protocol-relative URLs pass streamdown but fail the sidecar's new URL()
parse; pin them to https before handing off.
- The marketplace 'Get value' link now intercepts middle clicks (auxclick)
too, which bypassed the onClick handler and fell into the dead
target=_blank path.
* docs: update ClinePass wording from '2-5x API rate limits' to '2-5x the usage on popular open coding models compared to standard API rate'
* docs: update ClinePass wording in cline-provider.mdx for consistency
* nit
* fix(desktop): resolve login shell PATH so agent can find gh and other CLI tools
When the Tauri app is launched from Finder/the Dock on macOS it inherits
launchd's minimal PATH (/usr/bin:/bin:/usr/sbin:/sbin), so the sidecar and
every process it spawns for the agent (bash tool, MCP servers) can't find
tools installed via shell profiles, e.g. Homebrew's gh in /opt/homebrew/bin.
The same task works from the CLI because a terminal runs with the full
login-shell PATH.
At sidecar startup, ask the user's login+interactive shell for its PATH
(sentinel markers isolate it from profile noise, 5s timeout, kill on hang)
and merge it into process.env.PATH: shell entries first, current-only
entries preserved. No-op on Windows; CLINE_SIDECAR_SKIP_SHELL_PATH=1 is the
escape hatch. Failures never block startup.
Fixes CLINE-2740
* fix(desktop): address greptile review on shell PATH resolution
- Don't let shell resolution eat the Tauri endpoint-readiness window: kick
it off first so it overlaps sidecar startup (awaited before the session
manager exists, which is what spawns children), drop the shell timeout
5s -> 2s, and give the fallback attempt half the budget so the combined
worst case (3s) stays inside the 5s readiness poll.
- Handle non-POSIX login shells: run the marker printf inside /bin/sh so
$PATH expansion never depends on the outer shell's rules (fish would
space-join it), pass -i/-l/-c as separate flags, give csh/tcsh only -c
(their -l is valid only as the sole flag), and retry with the platform
default shell when $SHELL can't produce a PATH.
- Don't log the resolved PATH: the applied result now carries an entry
count instead of the merged PATH string.
* fix(desktop): read login shell from the account database, document PATH resolution
$SHELL is set by a parent shell, so a GUI-launched process may not have it.
Use os.userInfo().shell (getpwuid — DirectoryServices on macOS, same source
as dscl UserShell; NSS/etc/passwd on Linux) as the authoritative source,
with $SHELL and the platform default as fallbacks. Also documents the whole
mechanism in the app README.
* fix(desktop): widen endpoint readiness poll, source csh login profile
- The 5s get_desktop_backend_endpoint poll was already tight for
session-manager init on slow machines; shell PATH resolution (bounded 3s
worst case) made it tighter. Poll 15s instead — it returns as soon as the
ready line arrives, so only genuine failure waits longer.
- csh/tcsh can't take -l alongside -c, so mark them as login shells via the
argv[0] dash convention (argv0: "-tcsh") to get ~/.login sourced on top
of the always-read rc file.
* fix(desktop): never spawn a second sidecar while one is alive
ensure_desktop_backend_started treated a live child with a pending
endpoint as absent and fell through to spawn a duplicate, orphaning the
first process. Hold the process lock across the whole check-and-spawn
(concurrent callers serialize), return early for any live child, fail
the endpoint poll fast when the child exits instead of respawning, and
stop a stale stdout-reader from wiping a successor's endpoint. The spawn
is injectable so regression tests cover repeated and concurrent startup
checks (exactly one spawn while pending) and dead-child replacement.
* style(desktop): tighten mergePaths and csh comment per review
* docs(desktop): codify backend state lock ordering
* feat(core): default to agentic compaction
Use agentic compaction when no valid strategy is configured while preserving explicit basic selection. Add a session compaction CLI and package script for testing and comparing compaction strategies.
* createHandlerMock
* fix(core): let the agentic compaction cut land on assistant boundaries
Agentic auto-compaction only accepted typed user messages (turn starts)
as cut boundaries. The canonical host transcript — one typed task
followed by a long assistant tool_use / user tool_result loop — has no
turn start past index 0, so findCutIndex snapped to 0 and
runAgenticCompaction returned undefined: the UI showed "auto-compacting"
then "auto-compaction-skipped" on every turn while the context kept
growing. Re-compaction had the same failure permanently, because the
projected transcript starts with a compaction summary message, which is
excluded from turn starts.
Assistant messages are equally safe boundaries: an assistant's tool_use
keeps its result in the user message that follows it, so a cut there
never orphans half of a tool pair. Typed-user protection is preserved —
when a typed turn exists past index 0 the cut still stays at or before
it, so the latest typed prompt is never folded into the summary.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* add compaction fixtures for testing
* basic compaction improvement
* feat: attach metadata to the merged compaction message
* fix(core): address review comments on compact-session script
- add cline provider to the API key env defaults (CLINE_API_KEY)
- accept legacy string-content messages in readMessages
- print usage instead of a stack trace when --provider/--model are missing
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(core): preserve basic compaction across restores
## Summary
- keep tool-result message IDs stable across restore/persist round-trips
- preserve concluding assistant responses as real messages during basic compaction
- freeze prior compaction output so later passes only fold newly added history
- accumulate removed-message and usage metadata across repeated compactions
- update the basic compaction fixture and regression coverage
## Problem
Tool-result IDs were re-suffixed every time persisted messages were converted
back into agent messages. Because compaction state hashes the source message
prefix, restoring a session changed that hash and invalidated an otherwise
successful compaction, causing the full transcript to be sent again.
Basic compaction also reprocessed its own output on subsequent passes. This
could stack duplicate system notices, discard assistant conclusions retained by
the previous pass, and replace cumulative compaction statistics with values
from only the latest pass.
## Solution
Only add tool-result suffixes when splitting a mixed message, leaving already
split and single-result message IDs unchanged. Mark non-user compaction
survivors as preserved, carry those messages through future passes verbatim,
and budget older turns' final assistant answers as first-class messages.
Compaction metadata now adds prior removed-message and usage totals to the work
performed by the current pass.
## Validation
- 66 focused codec and compaction tests pass
- @cline/core typecheck and smoke typecheck pass
- Biome checks pass for all changed TypeScript files
- git diff --check passes
* fix unit test
* fix compaction defaults and fallback
* fix basic compaction credential lookup
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The collapsed reasoning block header showed 'Thought process · Complete'
with a brain icon; PM feedback is that the status text and icon read as
noise. The trigger is now just the label + disclosure chevron, with
'Thinking' as the default label in both streaming and complete states.
Removes the now-unused cline-chat-reasoning-status style and BrainIcon.
* fix(desktop): make scheduler row actions work and add tooltips (CLINE-2745)
- Replace the view icon's window.alert (a no-op inside the Tauri webview)
with a proper schedule-details dialog
- Trigger schedule.trigger with wait: false so 'run now' queues the run
and returns immediately instead of blocking until the whole agent run
finishes (which outlived the webview's 120s request timeout)
- Add hover tooltips to all schedule row actions (view, edit, run now,
pause/resume, delete, enable switch)
- Show a spinner on the run-now button while triggering and toast on
success/failure
- Return lastExecutions from list_routine_schedules so 'Last result'
actually populates (it was always '-')
- Mount <Toaster /> in the root layout; toast() calls app-wide were
previously rendered nowhere
* fix(desktop): per-schedule last executions and concurrent row actions
- list_routine_schedules backfills the latest execution for schedules
whose runs fell outside the 50-newest global window (skipping
schedules that have never run), so every row can show a last result
- busy/triggering row state is now a set keyed by schedule id, so one
action finishing no longer clears another row's in-flight spinner
* fix(desktop): reject same-row schedule actions synchronously
Two rapid clicks on the same row action could both fire before React
rendered the disabled state; the first completion then cleared the
shared busy id while the second request was still pending (and run-now
would enqueue two runs). Guard entry through a ref that mirrors
busyScheduleIds so the duplicate click is rejected before any request
is sent.
* feat(desktop): auto-update via Tauri updater with restart prompt
The Rust shell now checks the desktop-latest GitHub release feed on launch
and every 2 hours, downloads and stages updates in the background, and
exposes get_update_status/restart_to_apply_update commands. The webview
polls the status and shows a persistent toast with a one-click restart once
an update is staged; ignored updates apply on next launch. Updater
artifacts are only produced with the CI config overlay
(tauri.release.conf.json) so local packaging keeps working without the
updater signing key. Also mounts the previously-unmounted Toaster so
existing toast() calls render.
* ci(desktop): add desktop-publish release workflow and publish-desktop skill
desktop-publish.yml mirrors cli-publish: dispatch with a desktop-vX.Y.Z
tag + confirm gate, validates the tag against package.json and
tauri.conf.json, builds signed+notarized DMGs for aarch64 (native) and
x86_64 (cross-compiled sidecar via bun --target), generates the updater
manifest, publishes the versioned GitHub release, refreshes the rolling
desktop-latest auto-update feed, and posts to Slack. Adds the release
skill, changelog, and README docs for the required GitHub secrets.
* fix(desktop): address review — outlast sidecar shutdown window, dedupe update toast across remounts
stop() now polls for 7s before escalating to kill, past the sidecar's own
5s SHUTDOWN_TIMEOUT_MS graceful-shutdown budget, so clicking Restart now
(or quitting) during session persistence can't SIGKILL the sidecar
mid-write. notifiedVersion moves to module scope so a page remount doesn't
re-toast an update the user already dismissed.
* docs(desktop): move publish-desktop skill to .cline/skills, slim README release section
Match the publish-cli convention: the skill lives in .cline/skills/ and is
symlinked from both .agents/skills/ and .claude/skills/ so all agents pick
it up. The README's release section shrinks to a pointer + the two
never-lose invariants (desktop-latest feed, updater private key); the repo
secrets table moves into the skill, which also fixes its dangling reference
to a 'Release automation' README section and escapes the pipe that broke
the GFM table cell.
* feat(desktop): align settings with hub dashboard (ENG-2286)
- Break out Customizations into its own sidebar nav group (Plugins,
Skills, MCP, Hooks, Rules, Agents, Tools), mirroring the hub
dashboard's customizations break-out, replacing the single
Customizations entry (Rules-only) and the MCP Marketplace entry
- Port the hub's account view: signed-out state with working Sign
in/Sign out (the old Sign Out button had no handler), auth-error
detection, disabled tabs when signed out, PageFrame/PageHeader layout
- Port the hub's add-provider view for consistent PageFrame layout
* fix(desktop): merge the two MCP sidebar entries into one
The sidebar showed MCP twice: 'MCP Servers' under Settings (full
management: add/edit/toggle/delete) and 'MCP' under Customizations
(marketplace browse with uninstall-only cards). Keep the single 'MCP'
entry under Customizations to match the hub sidebar, and route it to
McpServersContent with the marketplace embedded: the management cards
now render as the marketplace view's Installed section, so one page
covers add/edit/toggle/delete plus catalog install.
* fix(desktop): stop long marketplace taglines forcing page-wide overflow
line-clamp (webkit-box) paragraphs report their full unwrapped text
width as intrinsic min-content, and grid/flex items default to
min-width:auto, so long MCP taglines pushed the whole marketplace grid
(and the page) wider than the viewport. Add min-w-0 at each grid-item
level so cards clamp to the container and the tag row scrolls within
itself.
* feat(desktop): add open-in-editor and copy-path actions to diff view
Adds per-file actions to the session diff view (CLINE-2738):
- copy the file path (resolved to an absolute path against the session cwd)
- open the file in a code editor via a new open_file_in_editor sidecar
command that prefers editor CLIs (code/cursor/windsurf/zed/subl) and
falls back to macOS app bundles, then the OS default opener
* fix(desktop): handle Windows editor shims and mount Toaster for failure feedback
Address greptile review on #12434:
- route .cmd/.bat editor shims through cmd.exe (spawn can't launch them
directly) and attach spawn error listeners so async launch failures
fall back to the OS opener instead of crashing the sidecar
- mount the app-wide Toaster (same lines as #12428) so copy/open failure
toasts are actually visible
* fix(desktop): guard Windows shell launches against cmd metacharacters
cmd.exe re-parses metacharacters inside arguments even when Node quotes
them (the reason spawning .cmd files without a shell is banned), so a
file path like 'report & evil.cmd' handed to the cmd /c shim launch
could execute a second command. Reject such paths with a clear error on
win32 and skip shim executables containing metacharacters (CodeQL
js/shell-command-injection-from-environment on #12434).
* feat(desktop): editor picker dropdown + copy button next to path in diff view
Review feedback on #12434:
- Renee: open-in-editor is now a dropdown listing the editors actually
installed on the machine (new list_available_editors sidecar command;
PATH CLIs + macOS app bundles), plus a system-default entry.
open_file_in_editor accepts an optional editor id; omitted keeps the
old auto-cascade, so older sidecars and existing callers still work.
- Beatrix: copy-path button now sits right after the filename (GitHub
style) instead of grouped at the right edge; an invisible flex spacer
keeps the dead space clickable as a collapse toggle.
* feat(desktop): brand icons + kanban editor set in diff-view editor picker
Match the kanban open-in dropdown: monochrome brand glyphs (VS Code,
Cursor, Windsurf, Zed, Xcode, IntelliJ IDEA) rendered inline with
currentColor so they follow the theme, an 'Open in' menu header, and a
system-default entry with a generic icon. Catalog grows to the kanban
editor list (adds VS Code Insiders via code-insiders, IntelliJ via
idea, Xcode via xed; macApps is now a list so IntelliJ CE is found).
Sublime Text keeps a generic file-code glyph (kanban has no sublime
icon).
* fix(desktop): preserve oauth and metadata when upserting MCP servers
upsert_mcp_server rebuilt the settings record from scratch, so editing a
remote server through the dialog silently wiped its oauth block (tokens)
and any plugin-ownership metadata. Merge machine-managed fields from the
existing record (following previousName across renames) into the upserted
entry.
* fix(desktop): drop MCP server oauth tokens when transport or URL changes
Editing a remote server's URL or transport previously carried the old
server's OAuth tokens onto the new registration, sending credentials
issued for one endpoint to a different one. Preserve oauth only when
the effective transport type + URL are unchanged (rename-safe).
* fix(desktop): treat legacy "http" MCP transport as streamableHttp alias
Core config-loader maps transportType "http" to streamableHttp, so a
legacy record resaved through the dialog is the same endpoint; without
normalizing, mcpTransportIdentity saw it as changed and dropped oauth.
* fix(desktop): default typeless URL-based legacy MCP records to sse
Core config-loader resolves a legacy flat record with a url but no
type/transportType as sse, while the sidecar defaulted to stdio. That
skewed mcpTransportIdentity (dropping oauth on a no-op edit) and made
list_mcp_servers report such records as stdio to the dialog.
* Add session and user id to auth telemetry events
* Add the auth metadata
* Address comments
* Add metadata to successful events
* remove user ids from the types
* fix tests
* address comments
* replace startedAtMs with sessionDurationMs
* fix tests
* update based on latest main
* fix imports
* fix(desktop): rebuild sessions when switching providers
Recreate active sessions with their existing transcript and compaction state before sending to a different provider. Preserve provider-specific connection settings and distinguish provider changes from model-only updates.
Add coverage to verify provider switches rebuild the session before sending.
Currently SendSessionInput has no provider/model configuration, so the desktop client must perform that lifecycle transition before sending. The cleaner long-term API would make provider selection part of an atomic turn request—something like send({ sessionId, prompt, providerId, modelId })—and let Core decide whether rebootstrap is necessary.
* fix(desktop): harden provider session transitions
* fix(desktop): make provider rebuilds transactional
* fix(desktop): make account page functional
The account page rendered data but every interaction was dead:
- Sign Out button had no click handler at all. Wire it to clear the
cline provider auth (same flow as cline-hub), show a signed-out card
with a working Sign In (browser OAuth) instead of a raw error + Retry,
and refresh the shared account context so the sidebar identity updates.
- Organization rows were static divs. Make them switchable (including a
Personal row) via the existing cline_account switchAccount operation,
with a pending spinner and overview + context reload after switching.
- External links (+ Credit, + Create org, open dashboard) used
target=_blank anchors, which are silently dropped inside the Tauri
shell (no window opener configured). Route them through a new
open_external_url sidecar command that opens the host default browser
(http/https only); plain web mode falls back to window.open.
- + Credit pointed at the organization credits page even for personal
accounts; use dashboard/account?tab=credits when no org is active.
- Guard the browser-open spawn with an error listener so a missing
opener binary can't crash the sidecar with an unhandled error event.
- Disable Usage/Billing tabs while signed out (they can only error).
Closes CLINE-2737
* fix(desktop): harden external URL opener and auth error classification
- open URLs on Windows via rundll32 instead of cmd /c start so URL
metacharacters cannot be parsed as shell operators
- surface opener spawn failures instead of always reporting opened: true
- classify only definitive signals (missing token, re-auth required,
status 401) as signed-out; transient refresh/permission errors keep
the retryable error UI
* fix(desktop): reject external URL open when the launcher exits non-zero
The opener promise resolved on the spawn event, so a launcher that
started but failed to hand off (xdg-open exits 3 when no handler is
available) still reported opened: true. Reject on a fast non-zero exit;
if the launcher is still running after a 2s grace window, assume the
handoff worked rather than blocking on a launcher that lingers.
rundll32 exits 0 even on failure, so Windows stays best-effort.
Replaces the raw stdio/sse/streamableHttp transport dropdown with a
plain-language Local vs Remote choice (CLINE-2748). Local (stdio) stays
the default per the MCP spec's "Clients SHOULD support stdio whenever
possible"; picking Remote defaults to Streamable HTTP with SSE offered
as a legacy option. Working directory and Metadata JSON move behind an
Advanced collapsible (auto-expanded when editing a server that uses
them), and the server list badge now shows friendly transport labels.
* fix(desktop): keep thinking indicator visible until first model output
The webview only rendered the Thinking indicator while the chat status
was 'starting', but Core reports 'running' as soon as the turn is
dispatched -- well before the first streamed token arrives. The spinner
flashed for the RPC roundtrip and then disappeared, leaving ~1s of dead
air (model time-to-first-token) before the assistant bubble appeared.
Keep the indicator up while the session is running and the model has
not produced output yet: no streaming assistant message, last visible
message is the user's prompt, and no approvals/questions pending.
Closes CLINE-2739
* test(desktop): tighten thinking indicator test formatting
* feat(cli): upgrade opentui 0.1.102 -> 0.4.3
Brings the TUI stack up from April's 0.1.102 to the current 0.4.x line
(0.4.4/0.4.5 are <7 days old and blocked by the registry release-age
gate; bump again once they age out).
- @opentui/core + @opentui/react 0.1.102 -> 0.4.3
- opentui-spinner ^0.0.6 -> ^0.0.7 (0.0.7 peers on @opentui/core ^0.3.4)
- react-reconciler pin 0.32.0 -> 0.33.0 to match @opentui/react 0.4.x
@opentui-ui/dialog stays at 0.1.2 (abandoned upstream, peers ^0.1.69 so
bun warns on install) but its runtime surface (DialogProvider,
useDialog, useDialogKeyboard) works against core 0.4.3 - the tui-test
command-palette spec renders a real dialog in a pty and passes.
Validation: tsc clean, unit 889/890 (the one failure repros on an
untouched main checkout - stale bun pm pack guard expectation), tui-test
62/62 across repeated runs.
* fix(cli): force single opentui generation via root overrides
The previous commit left @opentui-ui/dialog's ^0.1.69 peer range
unsatisfied by core/react 0.4.3, so bun recorded nested
@opentui/core@0.1.102 + @opentui/react@0.1.102 copies under the dialog
package in bun.lock. Local installs happened to link the dialog against
the hoisted 0.4.3 store variant (which is why tui-test passed), but a
fresh install from the lockfile - CI, release builds - would follow the
nested entries and run two renderer generations in one process: dialog
components extending 0.1.102 Renderable classes inside a 0.4.3 renderer
tree.
Pinning @opentui/core and @opentui/react in the root overrides block
forces every consumer, dialog included, onto 0.4.3. The nested lockfile
entries are gone and a runtime identity check confirms
DialogContainerRenderable's prototype chain reaches the same class
objects as the 0.4.3 core the app imports.
Side effect: changing overrides makes bun fully re-resolve the
lockfile. The only drift is ~108 @radix-ui entries nested under the
vscode webview-ui workspace moving to newer patch versions (~1.1.15 ->
~1.1.19); webview-ui's full build (tsc -b && vite build) passes with
them. This drift would land at the next release anyway since bun run
version deletes and re-resolves bun.lock.
Re-validated: tsc clean, tui-test 62/62, unit 889/890 (same single
pre-existing bun pm pack guard failure that repros on untouched main).
Address mermaid CVEs (CVE-2026-41148/41149/41150/41159) and
protobufjs CVEs (CVE-2026-54269, CVE-2026-48712) by pinning
patched versions via package deps and workspace overrides.
* fix(sdk): preserve file line endings in editor tool executor
The native editor executor split and joined file content on "\n" only.
On CRLF files (common on Windows), insertInFile left existing lines with
trailing "\r" while inserted lines were LF-only, producing mixed line
endings. Because reads go through readline with crlfDelay (which strips
"\r"), the model always emits LF-only old_text, so subsequent exact-match
replaceInFile calls failed; multi-line replace on pure-CRLF files was
broken the same way.
Detect the file's dominant EOL and normalize: insertInFile now splits
content and new_text on /\r\n|\n/ and joins with the detected EOL, and
replaceInFile normalizes old_text/new_text to the file's EOL before
matching. The str_replace diff output also splits on /\r\n|\n/ so it no
longer embeds stray "\r" in diff lines sent back to the model.
Reported via JetBrains marketplace review #141234 (DeepSeek + CLion on
Windows).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(sdk): address review — accurate EOL doc, literal $-sequences in replace
Reword the detectLineEnding JSDoc: it is a presence check for CRLF, not a
majority vote, so say so instead of claiming "dominant" EOL.
Use a replacer function in replaceInFile so "$"-sequences in new_text
($&, $', $`, $$, $n) are inserted literally instead of being expanded by
String.prototype.replace. Pre-existing bug surfaced during review; adds a
regression test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(sdk): clarify why EOL detection is a CRLF presence check
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* docs: mark .clineignore as deprecated soon
Add a deprecation notice to the .clineignore page and update pages that recommend it. Enforcement of ignore rules is extremely difficult (agents can get around them via @ mentions or shell commands), and the feature is orphaned in the VS Code/JetBrains extension (ClineIgnoreController), not part of the Cline SDK or CLI.
* docs: update clineignore deprecation wording
* wording changes
* update clineignore docs with plugin reference
* fix plugin example url
* edit clineignore docs file
* update formatting for clineignore doc
* clean up clineignore docs file
---------
Co-authored-by: Cline <bot@cline.bot>
Co-authored-by: TheRealSpencer <spencer@cline.bot>
* fix(desktop): filter project paths
Best effort to remove desktop and user's home directory from showing up in project list in the desktop app.
* feat(desktop-app): add account context and window title utilities
- Add AccountContext provider and hooks for managing Cline account identity
- Add account-context.tsx and account-context.test.tsx
- Add desktop-window-title.ts and desktop-window-title.test.ts
- Update workspace-paths.ts with new utility functions
- Update agent-sidebar.tsx and agent-sidebar.test.tsx to use account context
- Update page.tsx to integrate account context
- Update sidecar/commands.ts to support account operations
- Update core SDK exports
This adds proper account identity management and window title utilities for the desktop app.
* dedup normalizeWorkspacePath
* home page update
ai-sdk-provider-claude-code and ai-sdk-provider-codex-cli were hard
dependencies of @cline/llms, so every npm install of the cline CLI
pulled their native binaries (~250MB claude-agent-sdk platform binary,
~105MB @openai/codex) even for users who never select those providers.
Move both to optional peerDependencies (kept as devDependencies so
monorepo builds still bundle the JS) and load them via literal dynamic
imports in community.ts, mirroring the existing opencode-sdk pattern.
The Claude Code provider now resolves the claude executable explicitly:
bundled platform package when present, otherwise a user-installed
claude from PATH, passed via defaultSettings.pathToClaudeCodeExecutable.
The agent SDK's own resolution cannot be used from Bun-compiled
binaries because it anchors on the virtual bunfs where node_modules
lookups never see packages on disk. Codex already degrades gracefully
(npx -y @openai/codex, then codex on PATH).
* fix(sdk): retry runs once after refreshing expired OAuth credentials
Teammate and subagent sessions inherit the OAuth access token as a
snapshot at spawn time and had no refresh path: when the token expired
while the lead was blocked (e.g. in team_await_runs), their next model
call died with the provider's raw 401 body. Only the lead's turn-start
sync and runWithAuthRetry could refresh, and neither runs mid-turn.
Add an onAuthError hook to AgentConfig, wired once per session by
LocalRuntimeHost: it refreshes credentials through the shared
single-flight RuntimeOAuthTokenManager and propagates the new key to
the lead, delegated defaults, and all teammates via the existing
updateConnection channels. SessionRuntime retries a run once when it
failed with an auth-like error and the refresh succeeded, continuing
from the persisted trail so completed iterations aren't replayed.
Also fix isLikelyAuthError to lowercase string inputs; the server's
'Unauthorized: ...' message only matched when wrapped in an Error.
* fix(sdk): report errored teammate runs as failed instead of completed
Model-stream failures return results with finishReason 'error' rather
than throwing, so executeQueuedRun marked such runs 'completed' with
the error buried in resultSummary. Throw into the existing failure
path so the run reports status 'failed' (with run.error set and a
RunFailed event) and the retry machinery engages when maxRetries
allows.
* fix(sdk): stop exposing the team spawn tool to teammates
Spawning is lead-only, enforced at execution time, so teammates that
saw team_spawn_teammate in their toolset burned turns on 'Only the
lead agent can manage teammates.' rejections before falling back to
doing the work themselves.
* fix(sdk): retry runs once after refreshing expired OAuth credentials
Teammate and subagent sessions inherit the OAuth access token as a
snapshot at spawn time and had no refresh path: when the token expired
while the lead was blocked (e.g. in team_await_runs), their next model
call died with the provider's raw 401 body. Only the lead's turn-start
sync and runWithAuthRetry could refresh, and neither runs mid-turn.
Add an onAuthError hook to AgentConfig, wired once per session by
LocalRuntimeHost: it refreshes credentials through the shared
single-flight RuntimeOAuthTokenManager and propagates the new key to
the lead, delegated defaults, and all teammates via the existing
updateConnection channels. SessionRuntime retries a run once when it
failed with an auth-like error and the refresh succeeded, continuing
from the persisted trail so completed iterations aren't replayed.
Also fix isLikelyAuthError to lowercase string inputs; the server's
'Unauthorized: ...' message only matched when wrapped in an Error.
* fix(sdk): report errored teammate runs as failed instead of completed
Model-stream failures return results with finishReason 'error' rather
than throwing, so executeQueuedRun marked such runs 'completed' with
the error buried in resultSummary. Throw into the existing failure
path so the run reports status 'failed' (with run.error set and a
RunFailed event) and the retry machinery engages when maxRetries
allows.
* fix(sdk): retry runs once after refreshing expired OAuth credentials
Teammate and subagent sessions inherit the OAuth access token as a
snapshot at spawn time and had no refresh path: when the token expired
while the lead was blocked (e.g. in team_await_runs), their next model
call died with the provider's raw 401 body. Only the lead's turn-start
sync and runWithAuthRetry could refresh, and neither runs mid-turn.
Add an onAuthError hook to AgentConfig, wired once per session by
LocalRuntimeHost: it refreshes credentials through the shared
single-flight RuntimeOAuthTokenManager and propagates the new key to
the lead, delegated defaults, and all teammates via the existing
updateConnection channels. SessionRuntime retries a run once when it
failed with an auth-like error and the refresh succeeded, continuing
from the persisted trail so completed iterations aren't replayed.
Also fix isLikelyAuthError to lowercase string inputs; the server's
'Unauthorized: ...' message only matched when wrapped in an Error.
* feat(telemetry): emit user.auth_run_retry when a run is retried after credential refresh
Addresses Greptile review on the auth-retry PR: the refresh itself was
already instrumented (auth_refresh_soft_failure / auth_logged_out fire
inside getValidClineCredentials), but the new retry transition was not.
The recovered flag counts runs that would previously have died with the
raw provider 401 — the direct production measure of this fix working.
* fix(llms): add cline-pass/kimi-k3 to bundled model catalog fallback
* fix(llms): derive cline-pass default model from catalog authored order
Adding kimi-k3 (newest releaseDate) to the bundled cline-pass catalog
would have flipped firstGeneratedModelId — which sorts by release date —
to cline-pass/kimi-k3, silently changing the default model for new
ClinePass setups. Use the catalog's authored order instead, which mirrors
the recommended-models endpoint's curated order (intended default first,
subscription models before free ones).
* Rationalize shell identification and prompting, especially on Windows.
* Probe all pwsh install locations for the Windows default shell.
The default-shell fallback only checked the Program Files pwsh path,
so Microsoft Store installs of PowerShell 7 fell back to Windows
PowerShell while VS Code's own terminal launched pwsh. Share one
candidate list between the sync default-shell check and the async
PowerShell prober. Also drop an 'as string' cast that hid the
setting's type from the checker.
* Address shell resolution review feedback
* Resolve array-valued terminal profile paths on macOS and Linux too
VS Code permits terminal profile 'path' to be string | string[] on every
platform, not just Windows. The resolver (env expansion, first-existing
selection, PATH lookup) is now platform-generic: it uses the host path
module's separators and delimiter, probes PATHEXT only on Windows, and
treats env var names case-insensitively only on Windows. The macOS and
Linux getters route through it instead of returning the raw config value,
which crashed getShellKind() for array values.
* Apply terminal profile changes at the model-request boundary
A terminal profile change previously triggered a deferred session rebuild
to refresh the run_commands tool description. While a task was running the
rebuild waited, so the description could name one shell while commands
executed in another for the rest of the turn.
Instead of rebuilding, createShellTool now accepts a shell provider
function and re-derives the description each time the runtime reads it,
which happens exactly when a model request is built. The VS Code tool
snapshots {profileId, shell} in that provider; both execution paths (the
background spawn and the foreground terminal, via a new profile parameter
on getOrCreateTerminal) consume the snapshot. Commands produced by an
in-flight inference therefore run with the shell the model was told about,
and a mid-turn profile change takes effect when the tool results are sent
back: the next request names and uses the new shell.
The profile-change session rebuild path (handleTerminalProfileChanged) is
removed along with its deferred-rebuild window.
* Use the real createShellTool in the vitest @cline/core stub
The stub's hand-rolled createShellTool duplicated the 'shell must be a
string' invariant instead of exercising the code that enforces it
(getShellKind via description building), so the array-valued-profile
regression test proved only that the stub threw, not that the real tool
survives. Re-export the real implementation from SDK source — the same
pattern the stub already uses for the apply-patch and editor executors —
and assert on the actual generated descriptions, including that a profile
change is reflected at the next description read.
* Harden shell profile path resolution edge cases
- Warn and skip profile paths containing variable references beyond
\ (e.g. \) instead of silently probing a
literal path that can never exist; later candidates and the platform
default still apply.
- Document that an overriding bash executor in createBuiltinTools bypasses
the resolved canonical shell and must honor it to keep the run_commands
description truthful.
* fix: max output token handling
* shared
* max reasoning budgetTokens
* fix unit test
* fix: address review feedback on max output token handling
- OpenRouter effort branch sends only reasoning.effort (OpenRouter rejects
effort combined with reasoning.max_tokens)
- OpenAI Responses forwards explicit caller maxTokens for API-key usage;
ChatGPT OAuth and synthesized gateway defaults are still omitted
- Gateway lifts the synthesized default output cap above explicit Anthropic
reasoning budgets so max_tokens > thinking.budget_tokens holds
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: address second-round review feedback on max output token handling
- Replace the gateway-only requestedMaxTokens field with a defaultedMaxTokens
flag set when the gateway synthesizes a cap, so explicit maxTokens from
direct provider callers is forwarded by default (greptile P1)
- Check the parsed hostname instead of a URL substring when detecting the
ChatGPT OAuth backend (CodeQL)
- Drop the empty else-if branch in toAiSdkMessages in favor of an explicit
emptiedByDroppedReasoning condition (greptile P2; biome rejects the
suggested bare continue)
- Dedupe isPositiveFiniteNumber by exporting it from gateway.ts (greptile P2)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: extract isPositiveFiniteNumber into providers/utils.ts
Move the shared helper to its own module as suggested in review instead
of exporting it from gateway.ts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: remove unrelated VS Code changes
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(vscode): recognize SKILL.md frontmatter with a leading UTF-8 BOM
SKILL.md files saved with a UTF-8 BOM (e.g. by Windows Notepad's 'UTF-8 with BOM' encoding) were silently skipped and not recognized as skills, because gray-matter/regex-style frontmatter parsers require '---' at byte offset 0 and never accounted for the leading BOM byte sequence Node's utf-8 decoder does not strip.
Fixes the shared parseYamlFrontmatter() helper (used by skills, rules, workflows, and remote skill entries in the VS Code extension) and every duplicated ad-hoc frontmatter regex across the SDK/CLI/hub/desktop-app/example-plugin code paths to strip a leading BOM before matching.
Adds regression tests exercising the exact reported scenario (BOM-prefixed SKILL.md silently missing name/description) in frontmatter.test.ts, skills.test.ts, skill-frontmatter-toggle.test.ts, user-instruction-config-loader.test.ts, and configured-agent-config.test.ts.
Fixes https://github.com/cline/cline/issues/12151
* refactor(shared): centralize UTF-8 BOM stripping
* refactor(shared): add UTF-8 file readers
* docs: guide UTF-8 configuration reads
Moves the task.mistake_limit_reached capture (#12354) from the VS Code
SdkController wrapper into @cline/core so every host (CLI, VS Code,
hub daemon) emits it via its session telemetry service.
The MistakeTracker gains an onLimitTelemetry hook fired exactly once
per limit hit, before the limit decision is resolved — including when
no onConsecutiveMistakeLimitReached callback is configured (the
default-stop path, which the extension-side capture missed). The
orchestrator wires the hook to captureMistakeLimitReached using its
reserved telemetry field, reading sessionId/modelId/providerId at fire
time so mid-session connection updates are reflected.
The now-redundant extension wrapper and TelemetryService method are
removed to avoid double-counting in VS Code.
The harness rotted after the npm-to-bun migration: the 'ws' package it imported is no longer in the dependency tree, and Playwright's _electron.launch() times out under bun (the debugee Electron starts but Playwright never finishes attaching; the same launch attaches in under a second under node). Use the runtime's built-in WebSocket for the CDP client and document that the harness must be run with node.
* fix: auto-discover OS trust anchors in the CLI wrapper
The 3.x CLI ships as a Bun-compiled binary. Bun does not read the OS
trust store unless NODE_USE_SYSTEM_CA is set, and even with the flag its
Windows enumeration covers only the `Root` store, not `CA`/Intermediate
(verified empirically across the CLINE-2353 Windows repro rounds). So a
corporate MITM root is not trusted out of the box and inference fails
with "unable to get local issuer certificate". The pre-3.0 (Node) CLI
had no app-level CA handling either; users only succeeded by setting
NODE_EXTRA_CA_CERTS manually. The reporter's ask: have it just work
without the env var.
This follows the CLINE-2353 SDK fetch-threading change. That made the
inference client honor a host-provided proxy/CA-aware fetch, but on the
CLI Bun's global fetch is already proxy-aware and a fetch function
cannot cross the hub-daemon process boundary, so the CLI's missing piece
is trust material, not the fetch. Env vars do inherit across spawns.
The npm `bin/cline` wrapper runs on Node (not Bun), so it can read the
full OS store via tls.getCACertificates("system") (Node >= 22, no flag
required) — including the Windows `CA` store Bun skips — and hand the
certs to the Bun child via NODE_EXTRA_CA_CERTS, which both runtimes
honor. This mirrors the JetBrains plugin's configureCertificates(),
replacing "harvest from the IDE trust store" with "harvest from the OS".
The merge logic lives in a dependency-free, injectable-module CommonJS
helper (bin/ca-certs.cjs) so it is unit-testable and ships verbatim in
the generated wrapper package (publish copies bin/ wholesale). A
user-set NODE_EXTRA_CA_CERTS is merged ahead of the system certs; a
self-reference to the managed bundle is detected to avoid re-appending
every launch; when no system certs are available the user's setting is
left untouched. Writes are atomic (temp + rename) and owner-only.
Adds ca-certs.test.ts (13 cases) covering harvest filtering, user-bundle
PEM/DER/missing handling, newline-separated merge, managed-path
self-reference, and the no-system-certs no-op.
* fix: harden CLI auto-CA harvesting (review follow-ups)
Follow-ups from the CLINE-2353 review of the CLI auto-CA wrapper.
- H1: a legacy NODE_EXTRA_CA_CERTS set to an OS-path-delimited list
("a.pem;b.pem", the CLINE-2324 footgun Node never split) was stat'd as
one file, failed, and silently dropped the user's certs. readUserCerts
now tries the whole value as one file first, then splits on the OS path
delimiter and reads each existing PEM, merging them all.
- M1: skip the rewrite when the managed bundle is already current, instead
of re-harvesting and rewriting on every launch (mirrors the JetBrains
hash-and-skip). configureNodeExtraCaCerts now returns a typed outcome
(unchanged | written | write-failed-reused | write-failed |
no-system-certs) with cert counts.
- M2: tolerate rename-over-existing failures (Windows EPERM/EBUSY when a
concurrent child holds the file open) by removing the target and
retrying, then falling back to a previously-written bundle. Combined
with M1 the steady state no longer rewrites at all.
- M3: the wrapper prints a one-line diagnostic under CLINE_DEBUG=1
(cert counts + managed path, or a warning when no OS certs were found
or the write failed). Runs once per startup.
- M4: corrected the now-stale CLI guidance in shared/net.ts (the CLI no
longer requires users to set NODE_EXTRA_CA_CERTS manually).
- L1: documented the auto-trust behavior, the managed ~/.cline bundle,
the merge-not-replace override semantics, and CLINE_DEBUG in the CLI
README.
- L4: trimmed the helper's file header; DI is still injectable for tests.
ca-certs.test.ts grows to 20 cases: adds readUserCerts (single path,
delimited split, missing-segment skip, managed-bundle exclusion, empty),
the unchanged/second-run skip, and a write-failure outcome via an
fs that throws.
* fix: address CLI auto-CA review issues (temp cleanup, cert count, test)
- writeBundle now hoists the temp path so the outer catch removes a
partially-written temp file (e.g. ENOSPC / ACL failure mid-write).
Previously only the inner double-rename failure cleaned up, so repeated
disk-full/permission failures left a stale .tmp per launch in ~/.cline.
The inner Windows-rename fallback now lets its failure fall through to
the single cleanup path instead of duplicating rmSync.
- userCertCount now counts individual certificates (via countCerts, which
tallies BEGIN CERTIFICATE markers) rather than the number of PEM files,
so a user bundle with N intermediates reports N and is comparable to
systemCertCount. countCerts is exported for testing.
- Adds tests for the write-failed-reused branch (stale bundle reused when
the rewrite fails but the old file is still readable) and for countCerts
(one file holding two certs reports 2).
* fix: warn when the CLI wrapper's Node cannot read the OS trust store
tls.getCACertificates("system") needs Node >= 22.15; on older hosts the
auto-CA harvest silently did nothing, which is indistinguishable from a
broken corporate proxy. Distinguish the missing-API case as its own
outcome (api-unavailable) and print a non-debug warning when the user
has no NODE_EXTRA_CA_CERTS of their own. Found in round-5 Windows
validation (wrapper under Node 22.1.0).
* fix: copy only certificate blocks into the managed CA bundle
Combined cert+key PEMs (nginx/haproxy-style server.pem) passed the
old contains-a-certificate check, so a user NODE_EXTRA_CA_CERTS
pointing at one duplicated the private key into the managed bundle,
where it outlives rotation of the original and gets no permission
tightening on Windows. Extract complete BEGIN/END CERTIFICATE blocks
instead; files with none are treated as not PEM, and certificates-only
files pass through byte-identical so the unchanged-skip stays stable.
Raised in PR review.
* fix: show the old-Node trust warning once per Node version
The api-unavailable warning printed on every CLI invocation, turning
an actionable nudge into stderr noise for users pinned to an old Node.
Stamp the warning per Node version under the cline dir: it shows once,
re-arms when the Node version changes, and a bookkeeping failure never
suppresses the diagnostic. Raised in PR review.
* First cut of 'proceed while running' for foreground tasks.
* Address review: flush partial line on detach; cap log before write; freeze partial output at detach.
* fix(vscode): cap detached command log replay
* Send the Feature Flag Event when rolling out
* Update apps/vscode-rollout/scripts/smoke-loader.mjs
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
The 4.0.0 SDK migration routed Ollama through the generic OpenAI-compatible
vendor (/v1/chat/completions), which cannot express Ollama's options.num_ctx.
Every model loaded at Ollama's 4096-token server default, truncating Cline's
prompt and breaking most features (CLINE-2603, CLINE-2566, CLINE-2572).
- Add a native Ollama vendor backed by ai-sdk-ollama (wraps the official
ollama client); num_ctx derives from the resolved gateway model's
contextWindow at the adapter boundary, defaulting to 32768
- Persist the Model Context Window setting in providers.json via the
pre-existing provider-neutral contextWindow field (legacy
ollamaApiOptionsCtxNum state key kept as read fallback / write mirror),
and surface it as the selected model's contextWindow so the chat
indicator, compaction budgets, and num_ctx all agree
- Project ProviderConfig.maxInputTokens (where ProviderSettings.contextWindow
lands) onto the selected gateway model in both gateway builders so
CLI/Core hosts honor the configured value too
- Stop falling back to the bundled Ollama-Cloud catalog when /api/tags is
empty; local-model-source providers keep the user's committed model
instead of silently selecting a cloud model (nemotron)
- Wire Request Timeout (ms) with the legacy semantics (response must start
within requestTimeoutMs || 30000; streaming never cut off mid-generation)
- Settings UI: gate the context-window field until provider config loads,
skip unchanged writes, drop the custom prompt checkbox
Fixes CLINE-2603, CLINE-2566, CLINE-2572
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(vscode-rollout): align bundle versions in the stable AB workflow
Found by Max in local testing: the union manifest's version (what the
Marketplace and auto-update see) is the stitch input, but each bundle's
About tab and telemetry extension_version read that bundle's OWN
package.json — so the stable combined VSIX reported three different
versions (dispatch input / main's 4.0.0 / legacy's 4.0.8) depending on
where you looked. The nightly channel doesn't have this problem
(nightlify.mjs stamps one version into everything); this gives the stable
channel the identity-preserving equivalent: scripts/set-version.mjs stamps
the dispatch version into each checkout after install, before its build.
Also fixes a latent ab-package bug while restructuring the steps: the
next-bundle build never ran build:sdk, so the @cline/* workspace deps had
no dist and esbuild would fail on a fresh CI checkout (the workflow has
never run end-to-end — the publish environment gate blocked pre-merge
dispatches). Split install/build:sdk/align/build into separate steps,
mirroring the nightly workflow.
* fix(vscode-rollout): assert bundle sub-manifest versions in identity guardrails
Greptile round on #12321: the stable guardrail didn't assert version at
all. Went one further than the suggestion — both workflows' guardrails now
also assert each bundle sub-manifest's version (and name, for nightly)
matches the expected version, which is the check that actually regression-
guards the set-version.mjs/nightlify.mjs stamping (About tab + telemetry
extension_version read the sub-manifests, not the union). Expected version
routed through env rather than interpolated into the script body. Adds the
conventional paired test for set-version.mjs.
* fix(vscode-rollout): don't fail the nightly run when the tag push is rejected
First real combined publish (run 29454994164) published to both registries
successfully but the run went red at the last step: the default
GITHUB_TOKEN cannot create a ref whose commit modifies workflow files, and
HEAD was the #12253 squash merge which rewrote this very workflow. There
is no workflows permission grantable to the token, so this recurs any
night HEAD touched .github/workflows. The tag is bookkeeping — mark the
step continue-on-error so a successful publish isn't reported as a
failure. (Today's missing tag was pushed manually.)
* feat(vscode-rollout): A/B loader and packaging for staged SDK extension rollout
Ship one marketplace VSIX containing a tiny loader plus two complete
extension bundles: next/ (SDK-based apps/vscode from main) and legacy/
(the legacy-extension branch). The loader picks one bundle per window
from a PostHog-flag-driven, sticky, one-way cohort assignment, activates
it with a Proxy-scoped ExtensionContext so each bundle resolves its
resources from its own subdirectory, and falls back to legacy (with
partial-registration cleanup and version pinning) if the next bundle
crashes during activation.
Includes the union-manifest generator with per-cohort when-clause
gating, the VSIX stitcher, a node-level loader smoke test, and the
ext-vscode-ab-package workflow that builds both refs and packages
(optionally publishes) the combined VSIX.
* fix(vscode-rollout): address rollout review feedback
* feat(vscode-rollout): versioned kill-switch, user-setting override, launch-cadence telemetry
Review follow-ups from #12253:
- Kill-switch is now scoped by version instead of boolean: the PostHog flag's
payload carries {"maxKilledVersion": "x.y.z"} and the loader demotes only
combined VSIXes <= that version, so killing a broken release never blocks
the release that fixes it. Arming with no payload still demotes everything,
and the old boolean memento format is normalized on read.
- cline.rollout.bundleOverride user setting (auto | next | legacy) as a
manual escape hatch editable straight from settings.json: beats flags and
the kill-switch in both directions, applies on window reload, reported as
'override' on the activation event. Injected into the union manifest by
gen-manifest so neither bundle has to know about it.
- parseRolloutFlags hardens flag typing: only a literal boolean true promotes
(multivariate variants, numbers, junk fail safe), kill payloads are parsed
defensively from /decide's JSON-string encoding.
- Activation events now carry ms_since_last_activation so the real window-
reload cadence bounds how fast the rollout percentage gets dialed up.
- Walkthrough manifest invariant relaxed from byte-equality to structural
equality (ids/media/completionEvents): the branches already diverge on one
MCP step description, and since walkthrough markdown at the VSIX root comes
from next regardless, hard-failing on copy tweaks bricked the release
pipeline while protecting nothing. Copy divergence now warns and ships
next's text.
* feat(vscode-rollout): identity-aware namespace, authoritative activation telemetry, nightly indicator
- Derive the setting section and sdkBundle context key from the packaged
manifest name (cline.* for stable claude-dev, cline-nightly.* for the
nightly identity, whose packaging rewrites the whole ID namespace);
gen-manifest derives the same prefix for gates and the injected
bundleOverride setting.
- Call the activated bundle's reportRolloutActivation export (merged on
both branches) with attempted/actual/fallback — the authoritative
extension.rollout.bundle_activated event, attributed via the bundle's
variant-built telemetry. On crash fallback the LEGACY bundle reports it.
- Rename the loader's direct PostHog event to
extension.rollout.loader_decision: it collided byte-for-byte with the
bundles' event name under a different schema. It keeps the loader-side
metadata (override, launch cadence, loader_version, extension_name) and
gains double_failure for the both-bundles-dead case.
- Fix duplicate activation events on crash fallback: the recursive legacy
activation no longer emits a second, contradictory fallback:false event.
- Nightly-only status bar indicator (Cline: Next / Cline: Legacy) so
dogfooders can see which bundle a window is running.
- Union diverged engines to the newer requirement instead of hard-failing:
main's VS Code engine (^1.101.0) has legitimately moved ahead of
legacy-extension's (^1.84.0), which bricked every combined build.
- Smoke scenarios for all of the above.
* feat(vscode-rollout): publish the nightly as the combined A/B VSIX
Convert ext-vscode-publish-nightly.yml (cron + dispatch) from the
standalone SDK build to the combined loader + next + legacy package,
published as saoudrizwan.cline-nightly at <major>.<minor>.<unix-seconds>:
- scripts/nightlify.mjs reproduces publish-nightly.mjs's identity mutation
(claude-dev -> cline-nightly, "cline. -> "cline-nightly., displayName,
activity bar title) with the version as an explicit argument so ONE
version reaches both bundle manifests and the union manifest. Runs after
dependency install and before each bundle build.
- Both bundle builds get CLINE_ROLLOUT_VARIANT (next/legacy) in the nightly
AND stable workflows — without it the merged rollout telemetry
(extension_variant common prop + the authoritative bundle_activated
capture) silently no-ops.
- dry-run dispatch input builds and uploads the installable .vsix without
publishing or tagging; publish/tag steps are additionally gated to main,
so the PR branch can be dispatched for pre-merge verification.
- Identity guardrails before packaging: nightly workflow asserts
cline-nightly, the stable ab-package workflow asserts claude-dev.
- The nightly tag now records the legacy bundle sha in its message.
- README: nightly channel section (identity mapping, the two telemetry
events and their owners, dry-run verification), and a note that the
PostHog flags govern nightly only until the stable combined VSIX ships.
The single-bundle publish-nightly.mjs path remains for manual
feature-branch pre-release publishes; CI no longer invokes it.
* chore(vscode-rollout): harden nightly workflow gating
- Restore a job-level branch allowlist on the publish job (main + the
rehearsal branch). Advisory defense-in-depth: the enforced gate is the
PublishNightly environment's deployment-branch policy in repo settings,
which must list the same branches; a dispatched branch runs its own copy
of this file.
- Route the legacy-ref dispatch input through env instead of interpolating
it into the run script body (script-injection hygiene; dispatch already
requires write access).
* add otel vars to rollout build (#12316)
- Extension will not emit otel metrics to otel without these vars, so
adding those into the slow-rollout build workflow
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
* fix(vscode-rollout): pass OTel env to the nightly legacy bundle build
Legacy's esbuild inlines OTEL_* at build time and its standalone publish
workflow passes them, so the combined nightly's legacy bundle was being
built with the OTel logs/metrics pipeline dead. Companion to #12316,
which fixes the same gap in ext-vscode-ab-package.yml (both bundles
there).
* feat(vscode-rollout): make the rollout two-way, remove the kill-switch
The one-way cohort + versioned kill-switch existed to avoid demoting users
whose SDK-bundle tasks aren't listed by legacy and whose rotated creds may
need a re-login. Decision: those are acceptable, temporary UX costs on an
emergency-only path — not worth a second flag and permanent mechanism
complexity (payload parsing, version scoping, killed-up-to cache format).
Now there is ONE knob: each background refresh caches exactly what
ext-sdk-bundle-rollout says for the next window. Dialing the percentage
down demotes; 0% pulls everyone back to legacy on their next reload.
Fail-safe direction preserved: only a literal boolean true promotes —
variant strings / numbers / a deleted flag all resolve to legacy; malformed
/decide responses leave the cache untouched. Local crash pinning (next
threw -> pin this version to legacy on this machine) is unchanged and
independent of the flag.
Removes KILLSWITCH_FLAG/KILLSWITCH_STATE_KEY/isVersionKilled/
normalizeKilledUpTo/compareVersions/nextCachedBundle; parseRolloutFlags
becomes parseRolloutAssignment returning the bundle to cache. Smoke
scenarios replaced with two-way promote/demote coverage.
---------
Co-authored-by: Max <maxpaulus43@gmail.com>
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
* fix: update broken ACP editor integrations redirect to point to CLI reference
* feat: add ACP Editor Integrations page under CLI section
- Create cli/acp-editor-integrations.mdx with ACP overview, supported editors, quick start, and usage guide
- Add page to CLI navigation group in docs.json
- Restore redirect from /cline-cli/acp-editor-integrations to /cli/acp-editor-integrations (page now exists)
* Revert "feat: add ACP Editor Integrations page under CLI section"
This reverts commit 2728b9c2ad.
* fix(telemetry): attach organization context to cached-credential identity
CLI cached credentials only stored the account id, so telemetry identity
resolved from them (headless runs via #11581, the hub daemon via #12177)
carried user_id but no organization_id - making CLI/hub usage invisible
to organization-scoped dashboards even where per-user attribution works.
- AuthSettingsSchema gains optional organizationId/organizationName/
memberId
- loadClineAccountSnapshot persists the active organization into the
cached cline provider settings after fetching /me (cleared when the
user is on their personal account), so the context survives across
processes without a network call
- the CLI runtime identify and the hub daemon identity refresh read the
persisted fields and pass them to identifyAccount; the daemon re-keys
its refresh on account+organization so an org switch re-identifies a
long-lived daemon
* fix(telemetry): strip stray NUL byte, drop needless reshaping of daemon identity resolve
* fix(core): normalize read file request path aliases
Accept `file_path` and `filePath` in read file requests and normalize them to the canonical `path` field. Apply alias handling to direct, array, and nested inputs to prevent model-generated variants from failing validation.
Clarify path descriptions by removing redundant wording.
* update test
* fix(cli): prevent use-after-free when setting terminal title during TUI teardown
* fix(cli): re-check renderer destruction before title reset in teardown microtask
* test(cli): cover terminal title teardown lifecycle
* fix(core): stop reporting benign git states as workspace init errors [ENG-2244]
A freshly initialized repo with no commits makes 'git rev-parse HEAD'
fail, which generateWorkspaceInfoWithDiagnostics recorded as a workspace
init error and surfaced as workspace.init_error telemetry on every
session bootstrap. Filter out git failures that reflect normal
repository states; genuine failures (missing directory, real git
breakage) are still reported.
* fix(core): drop 'bad revision' from benign git error filter
Review feedback: 'fatal: bad revision HEAD' can also indicate a corrupt
.git/HEAD (checkIsRepo still succeeds), which is a genuinely broken
workspace that should keep reporting. The remaining patterns cover the
empty-repo message variants.
* fix(vscode): restore multi-root mention resolution and validate stored task cwd [ENG-2245][ENG-2244]
The SDK adapter's ensureWorkspaceManager() was a stub returning
undefined, which silently disabled multi-root file mention resolution:
parseMentions only searched the primary cwd, so @-mentions of files in
secondary workspace roots failed with not_found. Build a real
WorkspaceRootManager from the host's workspace folders (cached until
the folder set changes) via a new WorkspaceRootManager.fromPaths().
Also validate that a resumed task's stored cwdOnTaskInitialization
still exists before using it — stale paths (deleted/moved dirs) fed
git-based workspace init and produced init-error telemetry.
* fix(vscode): use JSON.stringify for workspace manager cache key
Review feedback: a delimiter-joined key is ambiguous for paths
containing the delimiter (and the previous separator was an embedded
NUL byte). JSON.stringify is unambiguous and order-preserving.
* test(vscode): cover stored task cwd validation
* fix(vscode): use the requested provider's stored credentials when listing OpenAI-compatible models
The OpenAI-compatible settings pane already fetches GET <baseUrl>/models to
suggest model IDs, but the host handler always read the built-in "openai"
provider's stored settings. Custom OpenAI-compatible providers only expose a
masked API key to the webview, so their model-list requests went out
unauthenticated and the suggestion dropdown stayed empty.
Add provider_id to OpenAiModelsRequest and read that provider's stored API
key and custom headers in refreshOpenAiModels. Old clients omit the field,
which defaults to "openai" and preserves the previous behavior.
* feat(cli): suggest model ids from OpenAI-compatible endpoints in the model picker
The CLI showed a bare free-text input for openai-compatible providers and
never asked the endpoint what it serves. Fetch GET <baseUrl>/models with the
provider's stored API key/headers when opening the picker; when the endpoint
answers, show the standard fuzzy list (which keeps the "Create custom model
ID" row for manual entry). Any failure or empty answer falls back to the
existing free-text input.
* fix: resolve OpenAI-compatible model discovery config
* feat(shared): move plan/act mode prompt instructions into the shared prompt builder
The CLI's #12057 fixes (mode-tag explanation, plan-mode contract,
mode-switch notice tracker) were CLI-only wiring, so the VSCode extension
never told the model what the <user_input mode> attribute means and plan
mode kept mutating files (CLINE-2576, CLINE-2607, CLINE-2579). Promote
the pieces every host needs into @cline/shared:
- buildClineSystemPrompt now appends MODE_TAG_INSTRUCTIONS for every mode
and PLAN_MODE_INSTRUCTIONS for plan sessions, composed into the rules
slot in the exact order the CLI historically built by hand, so CLI
output is byte-identical after the refactor.
- The plan-mode contract gains an explicit run_commands paragraph:
the tool intentionally stays available in plan mode (essential for
read-only investigation) but is inspection-only there -- no file
mutations, no state-changing commands. The mitigation for plan-mode
mutations is prompting plus mode-switch notices, not tool removal.
- createModeSwitchNoticeTracker moves from apps/cli/runtime/interactive
to @cline/shared next to formatModeSwitchNotice; the CLI re-exports it
so its import surface and tests stay unchanged.
- deriveTitleFromPrompt gets a regression test pinning that titles never
pick up mode-notice text.
* fix(vscode): teach the model about plan/act modes and surface mode switches
Port the CLI's #12057/#12058 plan-mode fixes to the extension:
- The session factory drops its local PLAN_MODE_INSTRUCTIONS copy; the
shared prompt builder now emits both the mode-tag explanation and the
plan-mode contract (including the read-only run_commands rule), so the
extension's system prompt finally explains the <user_input mode>
wrapper its own messages have carried all along.
- Manual Plan/Act toggles record a mode-switch notice in
SdkModeCoordinator (shared round-trip-cancelling tracker, scoped to
the rebuilt session so it never leaks across tasks), recorded only
after the session replacement actually commits. The model-initiated
switch_to_act_mode path passes source: "tool" and records nothing,
matching the CLI: its tool result and continuation prompt already
announce the switch.
- SdkSessionLifecycle.fireAndForgetSend -- the single funnel for
outbound turn sends -- consumes the notice and prepends
formatModeSwitchNotice() to the next message, exactly like the CLI's
run-interactive stamping.
- Display boundaries never render the raw tag: the queued-prompt echo
in the message translator now goes through formatDisplayUserInput,
and isSyntheticUserPrompt strips notices before matching so a stamped
continuation prompt cannot shift edit/regenerate ordinals.
* feat(sdk): expose edit-executor internals for host diff previews
Extract computePatchChanges() from createApplyPatchExecutor so hosts can
compute a patch's per-file proposed content without writing to disk
(behavior-identical refactor; the executor now calls the helper), and
widen the @cline/core root exports with createEditorExecutor,
createApplyPatchExecutor, computePatchChanges, PatchActionType and the
related types. Needed by the VS Code adapter to restore the editor diff
view for SDK edit tools.
* fix(vscode): restore editor diff view for SDK edit tools
Adds SdkDiffEditCoordinator, which owns per-toolCallId diff sessions over
the legacy DiffViewProvider abstraction (HostProvider factory, so the
external/JetBrains gRPC DiffService path keeps working):
- the diff editor opens populated before the approval ask renders (the
SDK surfaces tool input only after the model stream completes, so the
approval callback is the only pre-execution point with full input)
- an overridden editor executor saves through the diff document:
user edits in the editable right pane and post-save auto-formatting
flow back to the model via formatResponse.fileEditWithUserChanges,
plus 'new problems' diagnostics
- Reject/abort reverts (new files: file + created dirs removed)
- auto-approved edits open the diff during execution with the legacy
3.5s diagnostics settle; Background Edit keeps the headless disk path
- apply_patch gets a preview-only diff of its first changed file; on
approve the preview is reverted and the untouched SDK executor applies
the whole patch
- any diff-pipeline failure reverts and falls back to the SDK disk
executor, preserving canonical error strings
Fixes#11934 (CLINE-2580).
* refactor(vscode): make edit diff preview a read-only virtual-document diff
Reworks the diff view restoration after EDH testing showed the editable
real-document design breaking on same-file multi-edits (tab reuse opened
the actual file instead of a diff; sibling saves closed other sessions'
tabs; right-pane edits misbehaved).
New design per review:
- EditPreview abstraction (mirrors CommentReviewController pattern):
VscodeEditPreview renders vscode.diff with BOTH sides as virtual
cline-diff documents (unique fragment per preview, so same-file edits
get distinct tabs and close is an exact tab match, never the real
file); ExternalEditPreview uses the existing openMultiFileDiff/
closeAllDiffs host-bridge RPCs. New createEditPreview factory on
HostProvider.
- The preview never touches disk: executors close the preview and
delegate to the SDK's default disk executors, whose results and error
strings reach the model unchanged. Reject/abort just closes a tab.
- Dropped by design decision: editing in the diff view, user-edit
feedback to the model, and diagnostics passback (the SDK already
prompts the model to check).
- Auto-approved edits show a brief preview that lingers ~1.5s after the
write; an abort cuts the linger short without failing the applied edit.
- A newer same-file preview supersedes an older pending one (approvals
resolve sequentially), eliminating cross-session interference.
- Legacy DiffViewProvider stack returns to untouched dead code.
* fix(vscode): state that denied edits did not modify the file
Repro: ask Cline to edit a file, then answer the approval with feedback
instead of Approve/Reject. The denial reached the model as just
{"error":"make them bigger"} — nothing said the edit was NOT applied —
so the model treated the feedback as iteration on an applied change and
built its next old_text against content that never landed on disk. From
then on old_text no longer matched the real file and the diff preview
silently stopped appearing (and the eventual executor run would fail the
same way).
Denial reasons now come from buildToolApprovalDenialReason(): edit tools
get 'The user denied this edit. The file was NOT modified and still
contains its original content.' (legacy parity), and all tools get user
feedback wrapped in <feedback> tags instead of the bare prompt as the
whole reason. isKnownToolApprovalDenial also matches the new edit-denial
marker so translator suppression keeps working.
* feat(vscode): simulated streaming animation for edit previews
Brings back the legacy 'yellow sweep' feel on the virtual diff preview.
The SDK only surfaces complete tool input, so this is a deliberate
simulation of the legacy streaming look (which legacy also showed when
it already had the full content in memory).
The sweep covers the whole file like legacy did, with diff-aware pacing:
- Park at the top: whole document under the faded-yellow overlay, cursor
highlight on line 0, viewport pinned to the top, ~400ms hold so the
animation unambiguously starts from the top.
- Zip through unchanged spans in small fast steps (~8 lines per 16ms
frame, capped per span) so they read as continuous motion.
- Slow down through each change: one line per 45ms frame with a ~350ms
minimum dwell per hunk so even a one-line change visibly pauses.
- Changed runs come from a real line diff (diffLines), so multi-hunk
edits slow at EACH hunk and the gaps between hunks zip; pure deletions
pause at the deletion point.
- Zip frames chase the cursor (InCenter) for continuous scroll; typing
frames scroll only when leaving the viewport (no per-frame judder).
- After the sweep reaches the bottom: short beat, then settle centered
on the first changed line for review.
Mechanics: edit previews move from base64-query cline-diff URIs to a new
mutable cline-edit-preview content provider (content set programmatically,
re-rendered via onDidChange) so the virtual right side can update in
place. DecorationController is reused as-is. The approval ask renders
while the animation plays (legacy simultaneity); close() cancels
mid-animation; files >3000 lines render the final diff immediately.
External hosts keep the static openMultiFileDiff preview.
* chore(vscode): remove test artifact comment from memory-monitor
* fix(vscode): address review nits — skip diff computation for large files, close partially-opened previews
- buildEditPreviewAnimation (which runs a full line diff) now runs after
the MAX_ANIMATED_LINES guard; oversized files use a cheap prefix scan
just to aim the viewport.
- If preview.open() throws after partially opening, the tab is closed
directly — the session was never registered, so discardPreview could
not have reached it.
* fix(vscode): keep tsconfig valid JSON for test setup
* fix(vscode): bound diff preview animation
* Store startedAt in auth metadata when starting a Cline session
* Inject the sessionStartedAt when creating the auth credentials
* Remove injecting sessionStartedAt when it's not stored already
* Address review
* fix merge inconsistencies
The custom MarkdownCode node type used a narrow { metastring?: string }
shape that is not assignable from the hast Element passed by
react-markdown/streamdown, so a clean rebuild (fresh dependency resolve,
as done by the release version.ts) fails the `satisfies Components`
check. Widen node.properties to Record<string, unknown> and validate the
metastring value at read time.
* feat(cli): manual API key escape hatch for Cline OAuth providers
Add a way to configure the cline / cline-pass providers with a dashboard
API key from the /settings provider flow, for users where OAuth login
isn't working:
- "Enter API key manually" option in the already-configured dialog
- K keybinding in the OAuth login dialog to switch to key entry
- Saving clears stored OAuth tokens (on both the shared cline storage
entry and any direct cline-pass entry) since the auth handler prefers
auth.accessToken over apiKey — a stale token would otherwise keep
winning over the manual key
- isProviderConfigured now counts a persisted API key for OAuth
providers so escape-hatch users aren't forced back into OAuth on
every provider switch
* fix(cli): move API key fallback to OAuth dialog
* fix(sdk): stop misclassifying transient refresh failures as invalid_grant
getValidClineCredentials returned null for BOTH a rejected refresh token and
any transient error (network down, timeout, 5xx) that happened to land after
the access token expired. Callers treat null as 'session dead' — the
extension wipes providers.json over it, logging out every Cline process on
the machine, which is what CLI users then hit as 'Unauthorized: please
re-authenticate'. A laptop waking from sleep past the ~1h token expiry with
a background job (balance/banners/remote-config) refreshing before the
network is up was enough to trigger it — no refresh-token rotation involved.
Now: null means the refresh token was REJECTED (re-auth required); transient
failures throw so callers keep stored credentials and retry later. The
extension's refreshAccessToken catch and the CLI's error surface already
handle the throw correctly with no changes.
* fix(sdk): write providers.json atomically
providers.json was written with a bare writeFileSync while being read
concurrently by every other Cline process (CLI, extension, hub). A reader
catching a partial write parses garbage, which read() silently treats as
EMPTY settings — indistinguishable from being logged out — and any
subsequent save from that process persists the empty state, erasing every
configured provider.
Stage to a pid-unique temp file and rename into place; rename is atomic on
POSIX and replaces on Windows, so readers only ever see a complete file.
* feat(telemetry): track auth refresh outcomes to measure the hard-logout fix
Adds the observability needed to verify in production that the
transient-vs-invalid_grant fix is working, and to diagnose any logouts that
remain:
- user.auth_refresh_soft_failure — fires when a refresh fails for a reason
that does NOT invalidate the session (network error, timeout, 5xx) and
stored credentials were kept. Instances with tokenExpired=true were hard
logouts before the fix, so this is the 'prevented logout' counter. Emitted
from the SDK (CLI path) and from the extension's refresh/restore catches
under the same event name so dashboards aggregate both clients.
- user.auth_logged_out now carries the HTTP status and errorCode that caused
it, and the extension emits it (with a distinct reason) at every site that
clears providers.json: refresh_rejected, restore_refresh_rejected, and
handleDeauth's LogoutReason (user_initiated / cross_window_sync / …), which
was previously accepted and ignored. Extension-triggered logouts were
completely invisible before — including the legacy-extension cross-window
cascade, which this now measures directly.
Success looks like: auth_logged_out volume drops after release while
auth_refresh_soft_failure appears in its place, and any remaining logouts
carry a reason/status we can act on.
* fix(telemetry): route auth refresh events through SDK
* fix(sdk): stop misclassifying transient refresh failures as invalid_grant
getValidClineCredentials returned null for BOTH a rejected refresh token and
any transient error (network down, timeout, 5xx) that happened to land after
the access token expired. Callers treat null as 'session dead' — the
extension wipes providers.json over it, logging out every Cline process on
the machine, which is what CLI users then hit as 'Unauthorized: please
re-authenticate'. A laptop waking from sleep past the ~1h token expiry with
a background job (balance/banners/remote-config) refreshing before the
network is up was enough to trigger it — no refresh-token rotation involved.
Now: null means the refresh token was REJECTED (re-auth required); transient
failures throw so callers keep stored credentials and retry later. The
extension's refreshAccessToken catch and the CLI's error surface already
handle the throw correctly with no changes.
* fix(sdk): write providers.json atomically
providers.json was written with a bare writeFileSync while being read
concurrently by every other Cline process (CLI, extension, hub). A reader
catching a partial write parses garbage, which read() silently treats as
EMPTY settings — indistinguishable from being logged out — and any
subsequent save from that process persists the empty state, erasing every
configured provider.
Stage to a pid-unique temp file and rename into place; rename is atomic on
POSIX and replaces on Windows, so readers only ever see a complete file.
Next 16 blocks dev-resource requests (/_next/webpack-hmr, dev fonts) from
origins that don't match the dev server's own hostname. Browsing the web
dev mode via 127.0.0.1 left the page hanging with 'Blocked cross-origin
request to Next.js dev resource' warnings. allowedDevOrigins is dev-only,
so production/Tauri builds are unaffected.
* feat(desktop-app): env-configurable sidecar bind host, trusted origins, and webview WS endpoint
Allows running the desktop app's web dev mode (dev:web + dev:sidecar) inside
a Docker container with published ports:
- CLINE_SIDECAR_HOST: sidecar bind hostname (default remains 127.0.0.1)
- CLINE_SIDECAR_TRUSTED_ORIGINS: comma-separated extra browser origins for
the sidecar's origin allowlist (validation itself stays on)
- NEXT_PUBLIC_SIDECAR_WS_ENDPOINT: overrides the webview's hardcoded
ws://127.0.0.1:3126/transport fallback so a browser on the Docker host can
dial the published port
All defaults are unchanged, so local/Tauri behavior is unaffected when the
env vars are absent. When bound to 0.0.0.0 the printed ready endpoint
advertises 127.0.0.1 since a wildcard bind is not dialable.
* chore(desktop-app): untrack next-env.d.ts
It was added to .gitignore previously but never removed from the index, so
it kept showing as modified: Next.js rewrites the routes.d.ts import path
depending on whether 'next dev' or 'next build' ran last. The file is
regenerated by Next on every dev/build run, and the app's typecheck
(tsconfig.dev.json) excludes webview/, so nothing needs it tracked.
* style(desktop-app): format SIDECAR_HOST declaration
* Add the ClinePass limit error to the CLI
* Update apps/cli/src/runtime/run-agent.test.ts
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
* format code and improve instructions
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
* fix(auth): add early SDK debug logging for Cline credential lifecycle (ENG-2213)
Adds targeted debug-level logging at key points in the Cline/Cline Pass
credential lifecycle to diagnose intermittent logout issues. Credentials
are never logged in cleartext; an 8-hex-digit SHA-256 hash is used instead.
The SDK has two logger layers:
1. ClineCore.logger — session-scoped, threaded from ClineCore.create({logger})
into session config and the agent event bridge.
2. setSdkLogger() — early/module-level, for components that operate before
or outside of ClineCore sessions: ProviderSettingsManager (constructed
at startup), RuntimeOAuthTokenManager, and cline.ts auth functions
(token refresh). These can't be reached by the session-scoped logger.
Both VS Code (common.ts) and CLI (main.ts) call setSdkLogger() once at
startup. When no logger is registered (or the host filters out debug),
every call is a no-op — logging is never collected in normal use.
Instrumentation points (SDK core, shared by both surfaces):
- ProviderSettingsManager.read(): logs provider IDs, last-used, and whether
Cline auth is present (with hashed access/refresh token fingerprints)
- ProviderSettingsManager.saveProviderSettings(): logs the provider being
saved, tokenSource, whether Cline auth was present before/after, and
flags authDropped when a previously-present Cline auth block disappears
- RuntimeOAuthTokenManager.resolveProviderApiKeyInternal(): logs each
decision point (no_settings, no_credentials, refresh_start, refresh_null,
refreshed+saved, not_refreshed) with hashed token fingerprints
- cline.ts refreshClineToken(): logs the refresh request URL, response
status/errorCode on failure, and new token hashes on success
- cline.ts getValidClineCredentials(): logs the outcome at each branch
(no_current_credentials, still_valid, needs_refresh, invalid_grant,
transient_failure_kept_current, transient_failure_expired)
VS Code extension (auth-service.ts):
- readClineCredentials/writeClineCredentials/clearClineCredentials: logs
credential presence and hashes at each disk I/O point
- refreshAccessToken: logs refresh start, null result (cleared), changed
(written), or unchanged outcomes
- fetchUserInfoFromApi: logs the GET /api/v1/users/me request and response
status
What to collect when investigating:
VS Code extension:
- Open the "Cline" output channel (View -> Output -> select "Cline")
- Look for lines containing: [SdkAuthService], providers.read,
providers.save, oauth.resolve, cline.refresh, cline.getCredentials
- Debug logging is emitted at the DEBUG level; it appears in the output
channel when IS_DEV=true or in development builds
CLI:
- Set CLINE_LOG_LEVEL=debug environment variable before running cline
- Collect the log file at ~/.cline/data/logs/cline.cli.log (or the path
set by CLINE_LOG_PATH)
- Look for the same event names as above
Files changed:
- sdk/packages/core/src/auth/auth-debug.ts (NEW): hashSecret,
setSdkLogger, getSdkLogger, sdkDebug
- sdk/packages/core/src/auth/cline.ts: refresh/getCredentials logging
- sdk/packages/core/src/services/storage/provider-settings-manager.ts:
read/save logging
- sdk/packages/core/src/runtime/orchestration/runtime-oauth-token-manager.ts:
resolve logging
- sdk/packages/core/src/index.ts: export early logger utilities
- apps/vscode/src/sdk/auth-service.ts: credential lifecycle logging
- apps/vscode/src/common.ts: register SDK early logger
- apps/cli/src/main.ts: register SDK early logger
* fix(vscode): inline SDK debug metadata into log message string (ENG-2213)
* fix(auth): gate debug logging on CLINE_LOG_LEVEL at runtime (ENG-2213)
* fix(auth): use interpolated debug strings, remove log-level gating (ENG-2213)
* refactor: move early logger to sdk/packages/core/src/logging/early-logger.ts
* fix: address review feedback — early logger registration, log after write, remove getSdkLogger from public API
* fix(vscode): add ISO timestamps to all log lines
* fix core import
* fix import
* fix tests
---------
Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
Co-authored-by: BarreiroT <tomasmbarreiroi@gmail.com>
The webview already knew how to render readLineStart/readLineEnd on
readFile tool rows, but the SDK message translator never populated
them, so successive ranged reads of the same file all rendered as
identical bare paths. Extract start_line/end_line from read_files
input (per-file and single-path forms) and render open-ended reads
(start_line only) as "start+".
* fix(sdk): remove regex from zod schema for agent-squad plugin example
The `HandoffPathInput` schema used negative lookaheads to reject absolute paths and `..` traversal segments. When converted to JSON Schema, this regex caused consumers without lookaround support to fail with `invalid JSON schema: regex lookaround is not supported`.
This change removes the lookaround-based regex from the published schema and moves those checks to runtime validation. It preserves validation for allowed characters, absolute paths, traversal segments, and maximum length while strengthening cross-platform directory containment checks using Node’s path utilities.
* add back logger examples
* feat(sdk): emit telemetry from the hub daemon process
The detached hub daemon hosts the LocalRuntimeHost that emits
task.conversation_turn and task.tokens for every hub-backed session
(CLI in prefer-hub mode, desktop app, connectors), but the daemon
entrypoint never created a telemetry handle - startHubWebSocketServer
received telemetry: undefined and every capture in the daemon-side
runtime was a no-op. Sessions billed normally on the backend while
reporting nothing to OTel.
- create a ConfiguredTelemetryHandle in the daemon entry and pass it to
the websocket server and schedule runtime handlers
- identify from the cached cline account at startup and re-resolve
periodically, since the long-lived daemon often starts before login
or outlives an account switch
- flush and dispose the handle on graceful and fatal shutdown
* fix(sdk): flush daemon telemetry when server startup fails
If startHubWebSocketServer throws, dispose the telemetry handle before
rethrowing so failed daemon starts are visible in telemetry instead of
dying silently.
* fix(sdk): bound daemon telemetry flush and reuse settings manager
- Race dispose's flush against a 5s deadline so a hung exporter can't
keep a crashed daemon alive holding the hub port (before this PR the
daemon exited immediately on fatal errors; the flush must not change
that materially).
- Construct ProviderSettingsManager once instead of every identity
refresh; its constructor runs legacy-migration and provider
registration side effects, and getProviderSettings re-reads the file
per call anyway.
- Test the dispose-on-startup-failure path and the cline-hub-daemon
platform metadata.
* fix(sdk): label daemon telemetry cline_type as hub
Review feedback from @abeatrix: daemon-hosted sessions can be triggered
by the CLI, desktop app, or connectors, so daemon-emitted events should
not share the CLI process's cline_type. Existing values are "cli" and
"VSCode Extension"; the daemon now reports "hub" (with the finer
platform=cline-hub-daemon kept as-is).
* fix(sdk): set versioned Cline client-identity headers for Cline provider
* address feedback
* feat: add platform metadata to client context
Include platform, platformVersion, and isMultiRoot in extension client
context for CLI, ACP, and VS Code sessions. This provides downstream
core/session logic with richer runtime information and distinguishes ACP
clients from the standard CLI client.
* lint
* clean up
* fix: resolve client host identity via HostProvider for standalone compatibility
cline-session-factory.ts is also bundled into the standalone cline-core
(JetBrains), where the 'vscode' module resolves to the generated Proxy-stub
module: vscode.env.appName and vscode.version return Proxy objects, which
would flow into X-PLATFORM/X-PLATFORM-VERSION header values and fail at
request serialization.
Resolve the identity through HostProvider.env.getHostVersion() instead —
the VS Code hostbridge returns the identical values (vscode.env.appName,
vscode.version, ClineClient.VSCode, extension version), and JetBrains'
hostbridge returns its real host values, so the standalone stops reporting
itself as the VS Code extension as a bonus. Multi-root detection goes
through HostProvider.workspace.getWorkspacePaths() for the same reason.
Both resolvers degrade gracefully (undefined/false) if the host bridge is
unavailable, in which case the header builder falls back to source-derived
values.
* Add unit test as proof
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
The .greptile config was written for the pre-merge standalone cline/sdk
repo and never updated after the monorepo merge:
- the sdk-telemetry-doc-update rule enforced an Event Catalog in DOC.md,
a file that does not exist in this repo (it now emits a false P2 on
every PR touching core-events.ts, e.g. #12177)
- rules.md cited PR #357, apps/vscode/src/hub-daemon.ts, and
apps/vscode/src/telemetry.ts - none of which exist here
- the 'Hub Daemon Metadata Forwarding' section described an argv-based
metadata payload that was never implemented in this repo; replaced
with the actual daemon-owned telemetry pattern from #12177
- the opted-out-test rule now describes the real convention: assert the
event flows through capture (no-op for OptedOutTelemetryService), not
captureRequired
* feat(llms): include Cline free models in the cline-pass catalog
* feat(vscode): show Subscribed/Free model tabs on the ClinePass provider
* feat(cli): show Subscribed/Free sections in the ClinePass model picker
* fix(cli): drop redundant browse-all entry from ClinePass picker
* fix(cli): show only subscribed models in ClinePass onboarding picker
* feat(cli): include free models and quota explainer in ClinePass onboarding picker
* fix: shorten ClinePass free section copy
* fix(cli): strip redundant free markers from sectioned picker names
* fix: drop free from ClinePass free section copy
* fix: tighten ClinePass free section copy
* refactor: address review feedback on ClinePass free models
- single buildFeaturedModelEntries(providerId) dispatcher, builders private
- rename isClineProvider to isClineManagedProvider (includes cline-pass)
- use isClineManagedProvider in the free-model cost check
- themed tab border, pretty names on free model cards
- clearer cline-pass cost test name
* fix: address ClinePass free-model review blockers
- Stop re-sorting the cline-pass live catalog by release date in
mergeKnownModels: free models carry OpenRouter release dates, so the
sort could put a free model first and make it the fallback default
when the bundled default id rotates out of the live clinePass bucket.
Preserve the normalize-time order (pass models first) and pin it with
an end-to-end resolveProviderConfig test.
- Add the browse-all escape to the CLI ClinePass picker when the
clinePass bucket is empty (bundled fallback after a fetch failure),
so a subscriber isn't left with a free-models-only picker.
- Rename ErrorRow's local isClineManagedProvider to
isClineUsageBillingProvider: it only matches the cline provider,
unlike the shared util of the same name that also matches cline-pass.
* fix(core): use no-emit TypeScript config for checks
Update the core package TypeScript config to run checks without emitting files,
allowing broader workspace sources via the package parent rootDir. Simplify the
dev config so it only extends the main package config and avoids duplicated
compiler overrides.
* feedback
* remove dead code
* fix vscode f5 settings
- fixed the hot module reloading issue while debugging the extension.
- also fixed issue where deb:webview task wasn't showing as complete
* fix vscode webview dev cleanup
---------
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
Step 4 in the IDE setup flow says 'Choose your desired Claude model'
but applies to all providers (OpenAI, Gemini, DeepSeek, local, etc.).
Drop 'Claude' to keep it provider-agnostic.
* perf(sdk): stop listSessions hot loop from hanging the extension host
getStateToPostToWebview rebuilt the full task history on nearly every
streaming/session event, and each rebuild ran persistence-service.listSessions,
which synchronously read + Zod-parsed every session manifest. The 10s metadata
cache meant to absorb this was wiped on every per-turn updateTaskUsage, so each
state post paid the full synchronous scan, saturating the extension-host event
loop (observed as a tight listSessions/readFileUtf8 loop in CPU profiles).
- Debounce/coalesce postStateToWebview: trailing 50ms debounce plus a single
queued follow-up so bursts collapse into one rebuild; dispose() tears it down.
- Add an async, title-only manifest reader (readSessionManifestTitle) and use it
in listSessions to resolve titles concurrently off-thread, instead of a
synchronous readFileSync + full SessionManifestSchema (Zod) parse per row. The
existing sync manifest methods are left intact.
- On single-session updates, patch just the changed record in the merged-history
cache in place instead of invalidating it, so frequent per-turn usage updates
no longer force the next state post to re-enumerate and re-merge every session.
* refactor(sdk): strengthen session history cache patching
Replace patchMetadataHistoryCacheRecord (boolean-returning, metadata-only,
no re-sort) with updateCachedSessionRecord (void, updates prompt +
metadata + updatedAt, re-sorts via shared comparator).
- Void return eliminates the ignorable fallback contract.
- Mirrors all fields the persistence layer writes (prompt, metadata,
updatedAt) so cache and disk stay consistent.
- Re-sorts after patching so the updated record bubbles to the correct
position, using a shared compareSessionHistoryRecordsByRecencyDesc
comparator also used by listHistory.
- Derives updatedAt from the HistoryItem timestamp instead of constructing
a second clock value.
- Self-invalidates on cache miss so callers never manage the fallback.
Adds tests for in-place patching, re-sorting, per-turn usage hot path,
and cache-miss invalidation.
* fix(sdk): await in-flight state post during dispose
Greptile feedback: dispose() did not await a concurrently-running
runDebouncedStatePost, so an in-flight flushStateToWebview could access
torn-down resources after disposal.
Track the runDebouncedStatePost promise in statePostInFlightPromise.
In dispose(), after setting isDisposed and clearing the timer, await
the in-flight promise (swallowing errors) before tearing down downstream
resources. The !this.isDisposed guard in the loop prevents further
iterations after disposal.
* fix(sdk): address review feedback on state-post debounce and cache patch
Three issues from code review of the listSessions hot-loop fix:
1. dispose() could await the wrong promise. A second debounced timer
firing while a flush was already running overwrote
statePostInFlightPromise with a throwaway resolved promise from the
join path, so dispose() could return while the original flush was
still executing. Extract the debounce/coalesce state machine into
StatePostDebouncer, and only track the promise from the call that
actually starts a new flush loop.
2. postStateToWebview() swallowed flush errors, resolving every pending
caller even when flushStateToWebview() threw. Callers awaiting
postStateToWebview() now see the rejection, matching pre-debounce
behavior.
3. Cache patching derived the cached updatedAt from HistoryItem.ts,
but the persistence adapter always stamps updatedAt with the
wall-clock write time. Callers like toggleTaskFavorite() reuse an
old HistoryItem whose ts predates the write, which let the cached
ordering diverge from disk until the 10s TTL expired. Stamp the
cache patch with the write time instead.
Adds unit tests for StatePostDebouncer covering the dispose race and
error-propagation regressions, and a sdk-task-history test for the
stale-updatedAt cache-ordering regression.
* fix(sdk): don't patch cache when session update write didn't land
Beatrix's review feedback: updateSession() ignored the { updated:
boolean } result from host.update() and unconditionally patched the
metadata cache. When persistence returns updated: false (session
deleted/missing, or an optimistic-concurrency retry exhausted by a
racing writer), the webview could show a fake updated record until the
cache TTL expired.
Check the write result: only patch the cache when updated === true,
otherwise invalidate it so the next read re-enumerates from disk.
---------
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
* fix(sdk/cli): emit user_id in telemetry identity attributes
Per CLINE-2406, downstream analytics expects an explicit user_id field
in authenticated SDK/CLI OpenTelemetry log attributes.
Changes:
- sdk/packages/core/src/services/telemetry/core-events.ts: add
user_id: account.id alongside the existing account_id in
identifyAccount() updateCommonProperties call.
- sdk/packages/core/src/services/telemetry/core-events.test.ts: new
identifyAccount suite verifying user_id, account_id, distinct_id, and
org context fields for authenticated user without org, with active org,
absent/blank id handling, and no-op when telemetry is undefined.
- apps/cli/src/main.ts: after loading Cline provider settings in the
runtime path, read auth.accountId and call identifyTelemetryAccount so
subsequent task.* and workspace.* events carry user_id. Document
user.extension_activated as pre-auth by design for subcommand flows.
- apps/cli/src/main.test.ts: three new tests covering saved accountId
triggers identity, missing accountId skips identity, non-Cline
provider skips identity.
* fix(sdk/cli): address review feedback on telemetry identity
- Use trimmed distinctId for user_id in identifyAccount() to keep
user_id and distinct_id consistent when IDs have whitespace
- Remove fragile type cast in CLI main.ts; ProviderSettings already
exposes auth.accountId via AuthSettingsSchema
* wip: Cline Code Desktop App
Add Bun/Tauri desktop packaging commands for macOS, Windows, and Linux, including output to dist/desktop. Enforce macOS signing and notarization requirements for shareable builds while allowing an explicit unsigned local test path.
Document desktop packaging prerequisites, ignore generated build artifacts, and wire runtime session connection updates needed by the desktop app.
Clean up and update sidecar functions.
Safe to merge as this is not a published app.
* fixes
* chat
* apply
* ClinePass support
* add build instructions and use system theme
* fix: diff status
* update tool calls display
* connection updates
* lint fix
* fix keydown
* fix(llms): OpenAI Codex model metadata for GPT Subscription provider
* add unit tests
* Update stale unit tests
* clarify doc string
* Update docs format
* update old test
Commit 9a9300846 ("restyle chat input…", which folded in PR #12075 "replace cyan accent with new plan/act palette") deliberately rebranded the TUI accents in palette.ts:
Dark act: ANSI "cyan" → #79b8ff (and plan "yellow" → #ffea7f, success "brightGreen" → #99e89b)
Light act: #0969da → #0f72cb (and plan #9a6700 → #867100), re-derived in OKLCH to keep the same hue as the new dark accents with ≥4.5:1 contrast on white
But palette.test.ts:27-35 still asserts the old values ("preserves the existing named ANSI colors" — a test description that's now literally obsolete). So getModeAccent("act", "dark") correctly returns #79b8ff, and the test expecting "cyan" fails.
The fix is to update the two tests to the new palette values (and rename the first test, since the colors are no longer named ANSI colors).
* feat(cli): tint assistant markdown accents by the mode they were produced in
Markdown's prominent elements (headings, bold, list markers, links) were
hardcoded to the act accent. getSyntaxStyle now takes the entry's mode
and colors those elements with the matching accent -- plan segments
render yellow-tinted markdown, act segments blue -- completing the
per-mode transcript coloring. Code token colors stay constant across
modes; styles are cached per theme+mode pair. Unstamped entries follow
the current mode, same fallback as the glyph accent.
* fix(cli): resolve entry mode once for glyph and markdown accents
Address review: the accent and mode props used parallel fallback chains
that could drift; both now derive from a single resolved entryMode. Also
cover the light-theme plan/act markdown accents in tests.
* feat(cli): restyle chat input with horizontal rules and slim user bubbles
Replace the tinted-background input field with a minimal frame: full-width
horizontal rules above and below the textarea and a bold accent-colored
prompt glyph, with no background fill. User message bubbles in the
transcript match the new look: a slim neutral-gray bar with the same
glyph, no vertical padding, and no mode-colored tint.
Palette gains getInputRuleColor (neutral adaptive mid-gray for the rules)
and getUserMessageBackground (neutral bubble tint), both built on a shared
OKLAB lift helper extracted from getModeInputBackground. Home view's
robot cursor-tracking offset is adjusted for the input's new left edge,
and the spacer line between the chat input and status bar is removed.
* feat(cli): replace cyan accent with new plan/act palette
Swap the TUI accent colors: act mode goes from ANSI cyan to #79b8ff and
plan mode from ANSI yellow to #ffea7f on dark themes. Light themes get
counterparts derived in OKLCH with the same hue but darkened to hold
>=4.5:1 contrast on white (#0f72cb act, #867100 plan), selected via the
existing getModeAccent theme switch.
All hardcoded "cyan" fg literals across dialogs, model selector, config
view, and onboarding now reference palette.act so the accent is a single
source of truth. The selection highlight follows the act color. The
subliminal OKLAB chroma nudge on input backgrounds/foregrounds now leans
blue (-a, -b) instead of cyan (-a, +b) to match the new accent hue.
* feat(cli): soften success green and use act accent in markdown
Swap the success/diff green from ANSI brightGreen and #22c55e to a muted
sage #87af87 on dark themes (auto-approve banner, git diff +stats, diff
view added-sign color); light themes keep the darker #116329 for contrast
on white. Markdown prominent elements (headings, bold, list markers,
links, table headers) now use the act accent via themePalette instead of
hardcoded one-dark cyan #56b6c2.
* feat(cli): harmonize dark syntax colors with brand accent palette
Rebuild the dark syntax highlighting family around the brand anchors:
functions use the act blue, strings and inline code use the success sage,
types/italics use a dimmed plan yellow, and the remaining hues (keyword
purple, variable coral, number orange, operator ice-blue) are regenerated
in OKLCH at the same pastel lightness/chroma weight (~L 0.78, C 0.11) so
code blocks read as part of the same palette. Light theme keeps its
GitHub-light set.
* fix(cli): brighten success green to match accent palette weight
#87af87 sat at roughly half the OKLCH chroma of the act/plan anchors and
read as gray next to them; #8bd28d (L 0.80, C 0.12) matches their weight.
* fix(cli): brighten success green a step further (#99e89b)
* feat(cli): polish status bar usage display and ClinePass model name
- Cost always renders with two decimals ($0.00) instead of switching to
four decimals under a cent; the turn summary line drops its three-decimal
format for the same reason.
- Token count next to the context bar is now just the number; the word
'tokens' was redundant with the bar right beside it.
- Context window bar shrinks from 8 to 6 cells.
- ClinePass models resolve their friendly models.dev name like every other
provider and get a (ClinePass) suffix: 'GLM 5.2 (ClinePass)' instead of
'ClinePass/glm-5.2'.
- ClinePass no longer shows '$0.00 (included with subscription)' -- cost is
simply hidden for subscription providers.
* fix(cli): place ClinePass suffix after reasoning effort in model name
* fix(cli): format ClinePass model name as 'ClinePass: <model>' prefix
* feat(cli): color transcript entries by the mode they were produced in (#12083)
* feat(cli): color transcript entries by the mode they were produced in
Previously the whole transcript retinted to the current mode's accent on
every plan/act toggle. Entries now record the agent mode active when
they were produced and keep that accent permanently, so a session reads
as a visible history of plan (yellow) and act (blue) segments.
How the mode is captured:
- Live sessions: appendEntry in SessionProvider stamps entries from a
uiMode ref, covering every creation site including mid-run
switch_to_act_mode flips (which already call setUiMode through the
runtime dialog bridge).
- Resumed sessions: hydrateSessionMessages recovers the mode from the
persisted <user_input mode="..."> wrappers via a new shared
parseUserInputMode helper, and flips to act at switch_to_act_mode tool
calls. Transcripts without wrappers stay unstamped and keep the
current-mode fallback accent, matching the old behavior.
- Restores: the /history resume and checkpoint-restore paths insert
hydrated history via replaceEntries instead of appendEntry loops, so
live-entry stamping cannot overwrite hydration's stamps (which would
lock resumed transcripts to the resume-time accent).
The load-bearing core fix: readPersistedMessagesFile stripped the
user_input wrappers and mode notices from user text on every read
('display sanitization'). That read path also feeds session restarts
(mode toggle, compaction-mode change, model change, fork, recovery),
which re-persist what they read -- so every restart laundered the mode
markers off disk and out of the model's seeded context, leaving nothing
for hydration to recover. Reads now return persisted messages verbatim
and formatting is the display surface's job: the CLI TUI, history
titles, and the VS Code SDK history loader already formatted at their
boundaries; the cline-hub webview history mapping and the CLI HTML
export (which used normalizeUserInput and leaked mode_notice text) now
do too. Connectors only surface assistant text, and the remaining
readMessages consumers are programmatic (usage math, re-seeding,
compaction input) where raw is correct.
* fix(shared): match parseUserInputMode exactly to what the writer emits
Drop the case-insensitive flag and the 'zen' value from the wrapper
regex: formatUserInputBlock only ever writes lowercase act/plan/yolo, so
anything else the parser accepted (uppercase look-alikes in adversarial
content, a zen value with no writer) could never be real persisted data.
* feat(cli): restyle chat input with horizontal rules and slim user bubbles
Replace the tinted-background input field with a minimal frame: full-width
horizontal rules above and below the textarea and a bold accent-colored
prompt glyph, with no background fill. User message bubbles in the
transcript match the new look: a slim neutral-gray bar with the same
glyph, no vertical padding, and no mode-colored tint.
Palette gains getInputRuleColor (neutral adaptive mid-gray for the rules)
and getUserMessageBackground (neutral bubble tint), both built on a shared
OKLAB lift helper extracted from getModeInputBackground. Home view's
robot cursor-tracking offset is adjusted for the input's new left edge,
and the spacer line between the chat input and status bar is removed.
* feat(cli): replace cyan accent with new plan/act palette (#12075)
* feat(cli): replace cyan accent with new plan/act palette
Swap the TUI accent colors: act mode goes from ANSI cyan to #79b8ff and
plan mode from ANSI yellow to #ffea7f on dark themes. Light themes get
counterparts derived in OKLCH with the same hue but darkened to hold
>=4.5:1 contrast on white (#0f72cb act, #867100 plan), selected via the
existing getModeAccent theme switch.
All hardcoded "cyan" fg literals across dialogs, model selector, config
view, and onboarding now reference palette.act so the accent is a single
source of truth. The selection highlight follows the act color. The
subliminal OKLAB chroma nudge on input backgrounds/foregrounds now leans
blue (-a, -b) instead of cyan (-a, +b) to match the new accent hue.
* feat(cli): soften success green and use act accent in markdown
Swap the success/diff green from ANSI brightGreen and #22c55e to a muted
sage #87af87 on dark themes (auto-approve banner, git diff +stats, diff
view added-sign color); light themes keep the darker #116329 for contrast
on white. Markdown prominent elements (headings, bold, list markers,
links, table headers) now use the act accent via themePalette instead of
hardcoded one-dark cyan #56b6c2.
* feat(cli): harmonize dark syntax colors with brand accent palette
Rebuild the dark syntax highlighting family around the brand anchors:
functions use the act blue, strings and inline code use the success sage,
types/italics use a dimmed plan yellow, and the remaining hues (keyword
purple, variable coral, number orange, operator ice-blue) are regenerated
in OKLCH at the same pastel lightness/chroma weight (~L 0.78, C 0.11) so
code blocks read as part of the same palette. Light theme keeps its
GitHub-light set.
* fix(cli): brighten success green to match accent palette weight
#87af87 sat at roughly half the OKLCH chroma of the act/plan anchors and
read as gray next to them; #8bd28d (L 0.80, C 0.12) matches their weight.
* fix(cli): brighten success green a step further (#99e89b)
* fix(llms): stop rejecting malformed tool calls before tools can handle them
Weak models emit tool calls with schema mismatches (bare string for a
string[] arg) or unparsable JSON. The AI SDK adapter rejected both
before execution, so the lenient union schemas in the core tool
executors never ran. Drop the strict validate callback (tools own input
validation) and add experimental_repairToolCall backed by the shared
jsonrepair parser for arguments that fail JSON parsing.
* docs(llms): fix stale comment referencing removed validate callback
* fix(shared): stop deleting mode_notice from outbound prompts
The mode-switch notice from #12057 never reached the model:
prepareTurnInput sanitizes every outbound prompt with normalizeUserInput
before wrapping it, and #12057 put the mode_notice strip inside
normalizeUserInput -- so the host deleted the notice on every send. The
transcript confirms it: messages sent after a toggle carry the
user_input wrapper but no notice, and models asked about it confabulate
having seen one because the system prompt describes the tag.
Move the strip into a dedicated stripModeNotices() applied only at
display boundaries: formatDisplayUserInput (TUI hydration, title
inference), deriveTitleFromPrompt (session titles), and the TUI queued
prompt echo. normalizeUserInput now preserves notices, with a
regression test pinning the outbound behavior. Side benefit: notices
survive the message-builder history normalization and the pending
prompt queue, so queued sends deliver them too.
* docs(shared): correct formatModeSwitchNotice JSDoc after strip relocation
* refactor(shared): generalize notice stripping to stripTagElements
stripModeNotices becomes a thin policy wrapper (DISPLAY_HIDDEN_TAGS owns
the what-to-hide list in one place) over a generic stripTagElements that
removes whole elements for any tag list -- the remove-element counterpart
to xmlTagsRemoval. Call sites at display boundaries now carry comments
explaining why stripping happens there and not in normalizeUserInput,
which also sanitizes model-bound prompts.
* revert(shared): drop stripTagElements generalization, keep simple stripModeNotices
The generic tag stripper added API surface without a second use case;
stripModeNotices goes back to the direct implementation. The display-vs-
model call-site comments from the same commit stay.
* feat(cli): make plan/act mode switches visible to the model
The mode signal already rides on every user message via the
<user_input mode="..."> wrapper, but nothing ever told the model what
the attribute means, and a manual plan/act toggle produced no inline
signal at all -- only an invisible system prompt swap the model cannot
diff. Two additions:
- The CLI system prompt now explains the mode attribute (both modes,
since after a switch the transcript still contains messages tagged
with the other mode) and that the newest message's mode governs.
- A user-initiated toggle stamps the next user message with a
<mode_notice> block marking the switch, e.g. "The user switched from
act mode to plan mode before sending this message." Round trips that
return to the mode the model last saw cancel out. The model-initiated
switch_to_act_mode path is excluded: its continuation prompt already
announces the switch.
The notice vocabulary lives in @cline/shared next to the user_input
wrapper it extends, and normalizeUserInput hides the whole element from
transcript display the same structural way it strips the wrapper tags.
* fix(shared): strip mode_notice elements without polynomial regex
CodeQL flagged the lazy dot-all pattern (js/polynomial-redos): with the
global flag, every unmatched opening tag re-scans to the end of the
string, which is quadratic on adversarial transcript content. Replace
it with an indexOf-based splice that removes matched elements in linear
time and leaves unclosed tags intact, with a regression test on 50k
repeated open tags.
restartWithMessages cleared startupPromise and tore down the active
session before the replacement registered, leaving a window with no
active session and no startup in flight. A message submitted in that
window (e.g. typed right after a plan/act Tab toggle) made ensureReady
boot a blank fresh session, which then won the active slot over the
restarted session carrying the conversation history -- the model
responded as if the conversation had just started.
Publish the restart itself as the in-flight startupPromise so any
concurrent ensureReady waits for the restart instead of booting an
empty session. The barrier is cleared once the restart settles,
keeping failed restarts retryable by the next ensureReady.
* fix(cli): end plan-mode run on switch_to_act_mode and auto-continue with act tools
The CLI's switch_to_act_mode tool only queued the mode change; it was
applied after the whole turn finished. The model kept running the rest
of the turn with plan-mode tools (no editor) despite the tool result
claiming it now had edit access, so it fell back to editing files via
run_commands (sed/heredocs).
Mirror the VS Code extension's approach: the switch tool now completes
the run (lifecycle.completesRun), the pending mode change rebuilds the
session with act-mode tools, and a canned continuation prompt resumes
the approved plan automatically. Pending mode changes are tagged with
their source (tool vs UI toggle) so a Tab toggle racing a natural turn
completion can never auto-start plan execution the user did not
approve. The synthetic continuation prompt is hidden from transcript
hydration, and the plan-mode prompt/tool description now warn that
switching immediately starts execution.
* refactor(cli): show act-mode continuation prompt on resume instead of filtering it
Displaying the synthetic user message honestly beats exact-string
matching at the display layer, which was brittle and did not cover
other transcript consumers anyway. The live TUI still never echoes it;
it only appears as a user bubble when resuming a session. A
synthetic-message marker plumbed through SendSessionInput is the
principled follow-up if hiding it becomes worth the SDK surface
change.
* Revert "refactor(cli): show act-mode continuation prompt on resume instead of filtering it"
This reverts commit 969a24f9c9.
* fix(hub): hydrate tool results in session message mapping
Map historical tool call/use and tool result blocks into webview tool events, including same-message results and following user result messages. Add tests to verify hydrated outputs and block ordering so restored sessions render completed tool interactions correctly.
* feedback
* Fix basic compaction first-prompt truncation
Issue: shallow sessions on high-output models such as OpenRouter MiniMax M3 could auto-compact immediately and reduce the initial task prompt to only the leading <user_input> wrapper. Harbor still passed the full task into Cline and session metadata retained it, but the model conversation could receive a truncated first message and respond that the request was empty or cut off.
Root cause: the output-runway target used maxInputTokens - maxTokens for every basic compaction. For MiniMax M3, maxTokens is nearly maxInputTokens, producing a tiny target. Basic compaction then used its last-resort first-user truncation path, and raw messages still contain the user_input envelope, so prefix truncation preserved the wrapper instead of the actionable task.
Fix: only use the output-runway target after the transcript has at least five user-assistant pairs, so early/shallow tasks use the normal trigger-based target. Also prevent first-user truncation unless that first user message alone exceeds the trigger budget, preserving normal first-turn prompts while still allowing genuinely oversized prompts to be reduced. Added regression tests for the MiniMax-style shallow prompt case and the oversized-first-prompt escape hatch.
* Fix compaction budget for huge-output models
Avoid collapsing context-derived input budgets when a model reports an output limit nearly equal to its context window, such as MiniMax M3. In those cases, treating context-output as the input budget causes auto-compaction to trigger on normal-sized prompts.
Only use contextWindow - maxTokens when the derived value remains at least half of the context window. Also simplify long-conversation basic compaction targeting to maxInputTokens * 0.5 instead of applying the default target ratio to maxInputTokens - maxTokens.
Adds regression coverage for MiniMax-style context-only metadata so an 18k-token prompt does not compact against an incorrectly collapsed 12k input budget.
* Guard compaction estimator against cumulative metrics
* Address basic compaction target review comments
#11986 (Forcefully enable ClinePass on the CLI) hardcoded isClinePassEnabled: true in session-runtime.ts, provider-catalog.ts, and main.ts and removed the ext-cline-pass feature-flag check, but left the corresponding tests asserting the old flag-driven / disabled behavior. They fail on main (and every branch that merges it).
- session-runtime.test.ts: expect getLastUsedProviderSettings called with isClinePassEnabled: true.
- provider-catalog.test.ts: drop the obsolete getBooleanFlagEnabled('ext-cline-pass') assertion (source no longer reads the flag) and its now-unused mock; keep the isClinePassEnabled: true expectation.
- main.test.ts: the ClinePass flag is no longer read during startup, so getBooleanFlagEnabled is never called. Re-target the 'seed identity before flags' ordering assertion at refreshCliFeatureFlagsInBackground (which is still invoked after seeding), and wire that mock through featureFlagMocks.
The vscode test (Extension Integration Tests) job is red on main: the updateAutoApprovalSettings suite (added in #11929) is vitest-native but was being collected and run by the Mocha @vscode/test-cli runner, where vitest-only matchers (toHaveBeenCalledWith / toHaveBeenCalledOnce / not.toHaveBeenCalled) are not registered, failing with 'is not a function'.
build-tests.js already excludes bun:test-owned tests from the Node-based out/ tree (single source of truth for the runner split). Extend that same mechanism to also exclude vitest-owned tests (files importing from 'vitest'), so neither bun nor vitest suites are ever compiled into the mocha out/ tree. Verified locally: detection catches the state suite (123 non-mocha test files total) while preserving the existing 60 bun __tests__ exclusions. No coverage lost — these suites run under test:vitest / bun.
* docs: add MiniMax M3, Qwen3.7 Max, Qwen3.7 Plus models to ClinePass page
* more updates
* explicitly direct to personal org
* making the clinepass page more detailed
* updates to cline provider wording
* docs: document ClinePass API usage
* chore: discard McpHub change from PR
* docs: simplify ClinePass model slug table
* updates
* nit
* docs: add MiniMax M3, Qwen3.7 Max, Qwen3.7 Plus models to ClinePass page
* more updates
* explicitly direct to personal org
* making the clinepass page more detailed
* updates to cline provider wording
Align resolved workspace versions in bun.lock with the v0.0.54 package
bumps. bun pm pack substitutes workspace:* deps using the version
recorded in bun.lock, so a stale lock made packed inter-package deps
resolve to 0.0.53, failing check-publish and the node smoke test (which
then pulled the old published @cline/shared from npm).
* Add an intermediate step before going to model selection
* fix type issues
* use allSettled
* fix(cli): serialize ClinePass subscription checks
* fix(cli): handle missing ClinePass plan as unsubscribed
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
PR #11928 added an `instanceof Error` branch to extractErrorMessage to
preserve transport-error wrappers (e.g. "fetch failed: SocketError: ...
(UND_ERR_SOCKET)"), but that branch regressed two cases:
- Generic SDK wrappers like "No output generated. Check the stream for
errors." were prepended to the real cause instead of being dropped.
- Errors carrying structured detail on responseBody/detail/error fields
surfaced the bland top-level .message ("Bad Request") instead of the
detail ("Instructions are required").
The Error branch now drops known generic wrappers in favor of the cause
and extracts structured detail from the error's own fields, while keeping
the transport-wrapper concat behavior #11928 intended.
* Improve compaction token budgeting
Use MessageWithMetadata.metrics input/output token counts when estimating message size for compaction, falling back to the existing chars/3 heuristic only when metrics are unavailable. This makes trigger decisions and post-compaction accounting use provider-reported token usage instead of relying only on serialized character estimates.
When an explicit compaction maxInputTokens budget is configured and the model exposes maxTokens, reserve half of the model output budget before triggering compaction. This gives the next provider request room for completion tokens and reduces edge cases where the local context estimate passes but the provider rejects the prompt as exceeding its limit.
Keep explicit reserveTokens and thresholdRatio overrides intact, and add regression coverage for metric-based token estimation, fallback estimation, and output-token-aware trigger budgeting.
* new target tokens and trigger tokens value
* fixes
* use imports and add unit test
* resolveMaxInputTokens
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
The standalone AgentRuntime({ providerId, modelId }) constructor built the gateway model in resolveRuntimeConfig and returned { ...rest, model } without deriving messageModelInfo. The prebuilt-model path preserves it, and core sessions populate it via buildMessageModelInfo, but standalone SDK callers lost it -- so assistant-message modelInfo and model-tagged telemetry (reasoning tokens, action-follow-through) emitted without provider/model dimensions.
Derive messageModelInfo as { id: modelId, provider: providerId } in that path (family omitted; it is optional and unavailable here). An explicit caller-provided messageModelInfo still wins. Adds provider-form tests covering both the derived and explicit-override cases.
The bun run version catalog regen dropped the xiaomi mimo-v2-omni,
mimo-v2-pro, and mimo-v2-flash models from the live data. mimo-v2-omni
is the xiaomi provider's defaultModelId in builtins.ts, so shipping the
refreshed catalog would point the default at a missing model and broke
the provider-ids test. Revert the catalog to the pre-release state and
ship v0.0.53 as a pure SDK code release; the catalog will refresh in a
later release once upstream data is stable.
* refactor(vscode): use string type for api provider in proto
Replace the ApiProvider proto enum with plain string fields across\nmodels.proto and state.proto, and drop the enum<->string conversion\nmappings in api-configuration-conversion.ts. Updates ApiOptions and\nOpenAICompatible settings components accordingly.
* fix custom provider render
* format
* id
* remove extension providers file
* uses includes
* fix(ci): harden ext-vscode stable release workflow
- Resolve previous tag to the latest vX.Y.Z ancestor instead of the most
recent reachable tag. The nightly workflow now pushes a nightly-main-*
annotated tag on every main commit, so git describe was resolving the
release notes' Full Changelog compare link to a nightly tag rather than
the prior release tag.
- Extract the changelog section by exact version heading (and fail if it
is missing) instead of always taking the first ## [ block, so a stale
top entry can no longer ship as the release notes for a different
version.
- Add a pre-publish Verify Changelog Entry gate so a release cannot be
published unless CHANGELOG.md leads with the version being released.
- Add a Verify Marketplace Tokens gate so a missing VSCE_PAT/OVSX_PAT
fails fast before packaging rather than mid-publish.
- In existing-tag mode, require the tag to point at the tested SHA so the
published artifact always matches what CI verified.
- Add a concurrency group keyed on the tag to prevent duplicate
concurrent publishes of the same release.
* fix(ci): validate stable release metadata before publish
Fix auto-approve checkboxes freezing after "New Task": clear the task-scoped settings overlay when the task view is cleared or switched, so stale task settings no longer shadow global settings
fix: restore workflow support regressions — expand `/workflow.md` slash commands (the legacy filename spelling the autocomplete inserts) and mid-message commands, honor workflow enable/disable toggles during expansion, refresh the slash menu's workflow list on webview launch, and bring back the Workflows management tab in the rules modal (now last in the tab list, with a deprecation notice pointing to Skills)
fix: strip trailing slashes from the OpenAI Compatible base URL when fetching the model list, so `/models` is queried correctly and the model dropdown populates
Enable Auto Compact by default so long chats automatically compress conversation history instead of failing at the model context limit. It can be disabled in Settings → Features → "Auto Compact".
Fix /compact UX: clear the chat input as soon as the command is submitted, wrap the compaction divider row at narrow sidebar widths, and update the context-window header even when compacting a small conversation grows the estimated context
Hide the "View Changes" button on completion rows until there are actually changes to show, instead of rendering it faded and disabled. Turns that changed nothing, non-git workspaces, and repos without commits no longer show a dead button with a misleading tooltip.
Show the user's message in chat immediately when sending to a task opened from history, instead of only a thinking indicator until the session resume finishes
@@ -7,7 +7,7 @@ description: Use when preparing, tagging, and publishing an apps/cli npm release
Use this skill when the user asks to release the CLI, publish `cline`, bump the CLI version, draft release notes, create a `cli-vX.Y.Z` tag, or trigger the CLI publish workflow.
The CLI is npm-only. Do not add alternate distribution or signing steps.
The CLI is npm-only. Do not add alternate distribution channels. Windows binaries are Authenticode-signed automatically by the publish workflow via Azure Trusted Signing (see the `.github/actions/sign-windows-cli` composite action and "Windows code signing" in `apps/cli/DISTRIBUTION.md`); if the signing secrets are not configured the workflow warns and publishes unsigned binaries. Local publishes (`bun release cli`) do not sign — prefer the GitHub Actions publish path for releases users run on Windows.
> Working directory: run every command below from the repository root. Paths and scripts (e.g. `apps/cli/package.json`, `sdk/packages/`, `bun release cli`, `bun run version`) are written relative to the repo root.
description: Use when preparing, tagging, and publishing a Cline desktop app (apps/examples/desktop-app) release — stable (desktop-vX.Y.Z from main) or beta (desktop-vX.Y.Z-beta.N from desktop-experimental, shipped as the side-by-side "Cline Beta" app). Guides changelog drafting, version bumps in package.json + tauri.conf.json, tagging, and the desktop-publish GitHub workflow that builds, signs, notarizes, and updates the per-channel auto-update feed.
---
# Desktop App Release
Use this skill when the user asks to release the desktop app, publish the Cline desktop app, cut a desktop beta, bump the desktop version, create a `desktop-vX.Y.Z` (or `desktop-vX.Y.Z-beta.N`) tag, or trigger the desktop publish workflow.
> Working directory: run every command below from the repository root.
Desktop releases ship two platforms, built entirely in GitHub Actions — there is no local publish path. macOS: a single signed + notarized universal DMG that runs natively on both Apple Silicon and Intel. Windows: an Authenticode-signed NSIS installer (`<Product>_<version>_x64-setup.exe`), signed via Azure Trusted Signing in the `build-windows` job (jsign through Tauri's `signCommand`, see `apps/examples/desktop-app/scripts/tauri-sign-windows.ps1`; requires the repo-level `AZURE_*` secrets including `AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_DESKTOP`, plus a `PublishDesktop`-environment federated credential on the `cline-cli-signing` Entra app). Installed apps discover new releases automatically through the Tauri updater, so publishing a release is what ships the update to every existing user **on that channel**.
## Release contract
- Two channels, one workflow (`channel` input on `desktop-publish.yml`):
- **stable** — tag `desktop-vX.Y.Z` (no suffix; the workflow rejects prerelease suffixes on this channel), cut from `main`, feeds the rolling `desktop-latest` release, ships as "Cline".
- **beta** — tag `desktop-vX.Y.Z-beta.N`, cut from `desktop-experimental`, feeds the rolling `desktop-beta` release, ships as "Cline Beta" (separate bundle identifier `bot.cline.app.beta`; installs side by side with stable). Built with the extra `src-tauri/tauri.beta.conf.json` overlay. Process background: `apps/examples/desktop-app/EXPERIMENTAL.md`.
- Version sources (must match each other and the tag): `apps/examples/desktop-app/package.json` and `apps/examples/desktop-app/src-tauri/tauri.conf.json`. (`src-tauri/Cargo.toml` has its own version but `tauri.conf.json` overrides it; no need to touch it.)
- Beta versions are prereleases of the **next** stable: stable `0.0.13` → betas `0.0.14-beta.1`, `-beta.2`, … Once a stable ≥ the beta base ships, the next beta bumps its base (`0.0.15-beta.1`).
- Release prep includes approved release notes, the version bumps, and an `apps/examples/desktop-app/CHANGELOG.md` update — committed on `main` for stable, on `desktop-experimental` for beta.
- Publish path: `.github/workflows/desktop-publish.yml` (workflow_dispatch, requires the tag to exist, point at the checked-out commit, and be reachable from the channel's branch — `origin/main` for stable, `origin/desktop-experimental` for beta).
- **Both channels dispatch from `main`.** This is a security invariant, not a convenience: the run executes `main`'s workflow copy and only the checkout points at the tag, so the signing-secret gates (the `github.ref == main` check and the PublishDesktop environment's main-only deployment-branch policy) hold for beta too. Never add `desktop-experimental` to the PublishDesktop deployment-branch policy.
- The workflow creates the tag's GitHub release (universal DMG + macOS updater artifact + Windows NSIS installer with its updater signature + `latest.json`; marked prerelease for beta) and refreshes the channel's rolling feed release, which is the static auto-update feed every installed app on that channel polls. Never delete the `desktop-latest` or `desktop-beta` release or tag.
- The changelog's `## <version>` section (exact-match, not "topmost") is extracted verbatim into the GitHub release body, the Slack announcement, and the updater manifest notes.
- Always ask before pushing commits or tags.
## Workflow
0. Ask which channel this release is for — **stable or beta** — if the user has not said. Everything below branches on it; never guess.
1. Gather context.
```sh
git status --short --branch
git fetch origin --tags
git tag --list 'desktop-v*' --sort=-v:refname | head -10
If there is no `desktop-v*` tag yet, this is the first release; use the desktop app's first commit as the baseline and say the baseline is inferred.
For a **beta** release, work on `desktop-experimental` (check out `origin/desktop-experimental`; merge `origin/main` into it first if it is behind — see EXPERIMENTAL.md for the conflict policy) and read the version files from that branch. The last-tag baseline is the newest `desktop-v*` tag of either channel that is an ancestor of the branch.
The sidecar bundles `@cline/core` and friends from the monorepo, so SDK changes ship inside the desktop app too. Fold user-visible SDK changes (providers, models, behavior fixes) into the notes; skip purely internal ones.
3. Draft user-facing release notes.
Flat bullet list, user-facing language. Present the draft and wait for approval before editing files.
4. Decide the version bump.
Stable: ask whether this is patch, minor, major, or an explicit version. Do not guess if the user has not made it clear.
Beta: apply the versioning rule — base = next stable version, increment `N` (`0.0.14-beta.1` → `0.0.14-beta.2`; after stable `0.0.14` ships, next is `0.0.15-beta.1`). Confirm the computed version with the user.
5. Update release files (on `main` for stable, on `desktop-experimental` for beta).
-`apps/examples/desktop-app/package.json` → new version
-`apps/examples/desktop-app/src-tauri/tauri.conf.json` → same version
- Prepend `## X.Y.Z` (no date; `## X.Y.Z-beta.N` for beta) to `apps/examples/desktop-app/CHANGELOG.md` with the approved notes.
6. Verify before committing.
```sh
bun -F @cline/code typecheck
bun test apps/examples/desktop-app/scripts/generate-update-manifest.test.ts
```
The full desktop bundle can only be built on macOS; the workflow's build job is the real verification. For extra local confidence on a Mac checkout, `bun run package:desktop:mac --allow-unsigned-mac` from the app directory.
Ask before pushing the release commit, then before creating and pushing the tag:
```sh
git push origin HEAD
git tag -a desktop-vX.Y.Z -m "Desktop vX.Y.Z"# beta: desktop-vX.Y.Z-beta.N / "Desktop vX.Y.Z-beta.N"
git push origin refs/tags/desktop-vX.Y.Z
```
8. Publish.
The release commit must be on the channel's branch (`main` for stable, `desktop-experimental` for beta) and the tag pushed first. Dispatch from `main` for **both** channels (see the release contract for why).
```sh
# stable:
gh workflow run desktop-publish.yml --ref main -f git_tag=desktop-vX.Y.Z -f channel=stable -f confirm_publish=publish
# beta:
gh workflow run desktop-publish.yml --ref main -f git_tag=desktop-vX.Y.Z-beta.N -f channel=beta -f confirm_publish=publish
gh run list --workflow=desktop-publish.yml --limit=1 --json url,status,conclusion,createdAt --jq '.[0]'
```
**The run pauses for approval.**`validate` runs immediately, then the `build`
job waits on the `PublishDesktop` environment until a required reviewer approves
it — the run sits in `waiting`, which is expected, not a hang. Approve it in the
run's web UI ("Review deployments"), or:
```sh
gh api repos/cline/cline/actions/runs/<run-id>/pending_deployments \
--method POST -f state=approved -f comment="desktop vX.Y.Z"\
Nothing after `validate` runs — and no signing key is readable — until then.
The workflow builds one universal macOS bundle (`tauri build --target universal-apple-darwin` lipos the aarch64 + x86_64 Rust binaries; the Bun sidecar is lipo'd by `build-sidecar-bin.ts`; beta adds the `tauri.beta.conf.json` overlay), verifies every Mach-O in the bundle carries both slices and that the compiled binary embeds exactly its own channel's feed URL, signs with the Developer ID certificate, notarizes with the App Store Connect API key, and signs the updater artifact with the Tauri updater key. In parallel, `build-windows` builds the x64 NSIS installer on a Windows runner, Authenticode-signs every binary via Azure Trusted Signing (Tauri `signCommand` -> `scripts/tauri-sign-windows.ps1`), runs the same feed-endpoint and telemetry guardrails, and verifies the shipped installer with `Get-AuthenticodeSignature`. The release job then creates the GitHub release (prerelease for beta), refreshes the channel's feed (`desktop-latest/latest.json` or `desktop-beta/latest.json`), and posts to Slack. Notarization typically adds 2–10 minutes.
If the workflow fails on missing credentials, see "Publish secrets (one-time setup)" below.
9. Verify the update feed after the run succeeds.
```sh
curl -sL https://github.com/cline/cline/releases/download/desktop-latest/latest.json | head -30 # stable
curl -sL https://github.com/cline/cline/releases/download/desktop-beta/latest.json | head -30 # beta
```
The `version` field must be the new release; both `darwin-aarch64` and `darwin-x86_64` entries must point at the same new universal `.app.tar.gz` asset under the release tag (each slice of the fat binary requests its own arch key at runtime, so both keys serve the one artifact), and the `windows-x86_64` entry must point at the new `*_x64-setup.exe` asset. Installed apps on that channel — including older per-arch installs — pick the update up on next launch or within 2 hours.
After a **beta** publish, also confirm the stable feed was not touched: `desktop-latest/latest.json` must still serve the previous stable version. (The workflow guards this fail-closed, but it is cheap to verify and catastrophic to miss — the updater comparator is a plain semver "newer than", so a beta manifest on `desktop-latest` would auto-update every stable install onto the beta.)
10. Final response.
Report: channel, version, tag, changelog updated, commit hash, what was pushed, workflow URL, and the feed verification result.
## Publish secrets (one-time setup)
These live on the **`PublishDesktop` environment**, not at repository level, so
only the `build` job can read them and only after an approval. Set them under
Settings → Environments → PublishDesktop → Environment secrets. The environment
also restricts deployments to `main` and requires a reviewer.
Adding one of these as a *repository* secret is the common mistake. The build
would still succeed — an environment-gated job resolves repository secrets too,
with environment values simply taking precedence — so the credential would sit
repo-wide while everything looked fine. `validate` therefore fails the run if any
of them resolves in a job with no environment. If you hit that, delete the
repository-level copy rather than duplicating it.
If a secret is missing everywhere, the preflight in `build` fails the run naming
the missing entries. The Apple values come from the same Apple Developer account
used for manual signing (see the app README's "macOS signing & notarization"
section for how to obtain them):
| Secret | Value |
| --- | --- |
| `APPLE_CERTIFICATE` | Base64 of the **Developer ID Application** identity exported from Keychain Access as `.p12` (must include the private key): `base64 -i certificate.p12 \| pbcopy` |
| `APPLE_CERTIFICATE_PASSWORD` | The password chosen when exporting the `.p12` |
| `APPLE_SIGNING_IDENTITY` | `Developer ID Application: <Team Name> (<TEAMID>)` — from `security find-identity -v -p codesigning` |
| `APPLE_API_KEY` | App Store Connect API **Key ID** (notarization) |
| `APPLE_API_KEY_CONTENT` | Contents of the `AuthKey_<KEYID>.p8` file |
| `APPLE_API_ISSUER` | App Store Connect **Issuer ID** (UUID from Users and Access → Integrations) |
| `TAURI_SIGNING_PRIVATE_KEY` | Contents of the Tauri updater private key (`tauri signer generate`). If this key is ever lost, shipped apps can no longer verify updates — guard it. |
| `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | Password for that key |
The Slack + telemetry secrets (`SLACK_RELEASE_BOT_TOKEN`, `TELEMETRY_SERVICE_API_KEY`,
`ERROR_SERVICE_API_KEY`, OTEL settings) are shared with the CLI, SDK, and extension
publish workflows and already configured. **Do not move these into
`PublishDesktop`** — scoping them to this environment empties them in every other
publish workflow, silently, with no error beyond missing telemetry and a failed
description: Use when releasing the Cline VS Code extension — stable (currently the combined legacy+next A/B VSIX via ext-vscode-ab-package), nightly (ext-vscode-publish-nightly), or a legacy-branch hotfix (ext-vscode-publish-legacy). Guides version selection, changelog, PostHog rollout-flag coordination, workflow dispatch, environment approvals, tagging, and post-publish verification, plus the eventual cutover to publishing the SDK extension standalone.
---
# VS Code Extension Release
Use this skill when the user asks to release, publish, or ship the VS Code extension — stable, nightly, or a legacy hotfix — or to dial the rollout, or to cut over to the SDK extension permanently.
> Working directory: repo root. All workflows are dispatched from `main` (GitHub requires the workflow file on the default branch; each workflow checks out the refs it actually builds).
## The current era: combined A/B rollout
We are mid-migration from the legacy (npm, pre-SDK) extension to the next (SDK-based, bun) extension. Until the cutover is complete, **the stable and nightly listings ship a combined VSIX**: a small loader + two complete extensions (`next/` built from `main`, `legacy/` built from the `legacy-extension` branch). The loader picks one per window based on the PostHog flag `ext-sdk-bundle-rollout`. Deep-dive docs: `apps/vscode-rollout/README.md` (authoritative) and PR #12253 (design + runbook comments).
Endgame (see "Cutover" at the bottom): once the next bundle is trusted at 100%, stable goes back to a plain build of `main` via `ext-vscode-publish-stable.yml` and all the legacy/rollout machinery is retired.
### The listings and the workflows
| Channel | Marketplace ID | Workflow | Trigger | Version |
| Nightly (combined) | `saoudrizwan.cline-nightly` | `ext-vscode-publish-nightly.yml` | cron 12:00 UTC + dispatch | auto `<major>.<minor>.<unix-ts>` from main's `apps/vscode/package.json` |
| Legacy hotfix (standalone) | `saoudrizwan.claude-dev` | `ext-vscode-publish-legacy.yml` | dispatch | from `apps/vscode/package.json` on `legacy-extension` |
| Stable standalone (post-cutover) | `saoudrizwan.claude-dev` | `ext-vscode-publish-stable.yml` | dispatch | from `apps/vscode/package.json` on `main` |
All three publish paths gate on tests before publishing: nightly and ab-package run the reusable bun suite (`ext-vscode-test.yml`, tests `main`) — ab-package additionally runs the legacy branch's npm suite — and the legacy workflow inlines the npm suite. Environment gates: stable paths use `publish` → `Publish` environment (required reviewers approve in the Actions UI); nightly uses `PublishNightly` (branch policy only, no reviewers — a reviewer requirement would block the cron).
## Golden rules (read before any release)
1.**One listing, one version line.**`claude-dev` is published from multiple workflows/branches. Every stable publish must use a version **strictly above the highest version ever published to the listing from any branch** — marketplace versions are monotonic and cannot be unpublished (supersede, never delete). Check what's live first:
```bash
curl -s -X POST "https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery" \
`ext-vscode-ab-package` also enforces this automatically for `publish=true` runs: a preflight job validates the version format (plain `X.Y.Z`) and hard-fails unless it exceeds the live Marketplace version, and the publish job re-checks right before publishing (the approval wait can last days — a legacy hotfix landing in between is caught). Still run the query yourself when *choosing* the version.
2. **Check the flag BEFORE any stable combined publish.** `ext-sdk-bundle-rollout` is **shared between nightly and stable** — the loader sends only a machine id to `/decide`, no channel property, so there is no per-channel targeting. If the flag is high (nightly dogfooding) and you publish stable, stable users get the next bundle at that same percentage. Verify the effective percentage empirically (no PostHog admin needed — sample `/decide` with random ids using the key inlined in any shipped loader):
```bash
node -e '
const KEY = process.argv[1]; // phc_... extracted from a shipped VSIX loader
Flag changes are made in the PostHog UI (Cline project). **0% is the kill switch** — the flag is two-way; there is no separate killswitch flag. Dialing down demotes machines back to legacy on their next window reload.
3. **Ask before pushing** commits or tags. Environment approvals are the maintainer's to give.
4. **Changelog lives at the repo ROOT** (`CHANGELOG.md`), on the branch being released — not `apps/vscode/CHANGELOG.md` (doesn't exist). The legacy and stable workflows hard-fail unless the first heading is exactly `## [<version>]`.
5. **Stuck concurrency groups**: `ext-vscode-ab-package` groups on the version with `cancel-in-progress: false`. Only `publish=true` runs wait on environment approval (build-only rehearsals run ungated to completion), but a publish run left `waiting` still blocks every later dispatch of the same version — cancel it (`gh run cancel <id>`) before re-dispatching.
## Stable release (combined A/B VSIX) — the current stable path
### Pre-flight
```bash
# 1. What's live, and what version comes next (must exceed it — rule 1)
# 2. Flag percentage (rule 2) — decide where it should be for this release
# 3. Legacy tip = what the non-promoted cohort will run; confirm it's the shipped hotfix line
git fetch origin main legacy-extension
git log --oneline -3 origin/legacy-extension
# 4. Cheap local rehearsal of the most likely build failure: the union manifest
# hard-fails if views/viewsContainers/configuration diverged between branches.
git show origin/main:apps/vscode/package.json > /tmp/next.json
git show origin/legacy-extension:apps/vscode/package.json > /tmp/legacy.json
- Add `## [<VERSION>]` entry at the top of root `CHANGELOG.md`.
- Bump `apps/vscode/package.json` to `<VERSION>` so the repo reflects the published line. Side effect: nightly versions become `<major>.<minor>.<unix-ts>` of the new base — harmless (separate listing, still monotonic).
### Dispatch
```bash
gh workflow run ext-vscode-ab-package.yml --ref main \
# (the legacy bundle always builds from the protected legacy-extension branch;
# it is deliberately not an input)
# publish=false builds an installable .vsix artifact without publishing and
# needs NO environment approval — the ungated build job uploads the artifact
# and the run completes.
gh run list --workflow=ext-vscode-ab-package.yml --limit 1
```
Preflight (version format + monotonicity) and both test suites run first, then the ungated `build` job packages and uploads the VSIX; for `publish=true` the `publish` job then **waits for `Publish` environment approval** (Actions → run → "Review deployments"). Both bundles build the exact revisions their test gates ran against (branch names are resolved once — commits landing on either branch mid-run or during the approval wait are not picked up); `publish=true` is additionally refused for any `next-ref` other than `main` (the bun gate only tests main — non-main next-refs are for build-only artifact rehearsals). Check what a run is waiting on:
```bash
gh api repos/cline/cline/actions/runs/<run-id>/pending_deployments
```
### Post-publish
1. Verify the marketplace serves the new version (query from rule 1) — expect minutes-to-an-hour of validation lag after "Published" appears in the logs. Also verify Open VSX:
2. Tag, GitHub Release (with the .vsix attached), and the Slack release-bot post happen **automatically** after a real publish (all `continue-on-error` — the publish itself already succeeded, so bookkeeping failures leave the run green). Verify they landed; the known failure is the tag push when the built commit touches `.github/workflows/**` (default token cannot create such refs — no grantable permission fixes it). Manual fallback:
```bash
git tag v<VERSION> <main-sha-built> # ask before pushing
A real publish also **hard-fails early** if root `CHANGELOG.md` on the built main revision doesn't start with `## [<VERSION>]` — the release prep PR must be merged before dispatching.
3. Thorough artifact check (`gh run download <run-id>`): union `package.json` is `saoudrizwan.claude-dev@<VERSION>`, `next/package.json` and `legacy/package.json` carry the SAME version, `grep -c 'phc_' extension/extension.js` ≥ 1 (loader key inlined), no leftover `process.env.TELEMETRY_SERVICE_API_KEY` / `process.env.CLINE_ROLLOUT_VARIANT` literals in either bundle's dist (leftovers = a build ran without its env and telemetry is silently dead).
4. Monitor: `extension.rollout.bundle_activated` in `otel.otel_logs` filtered to `extension_version = '<VERSION>'` (stable cohort is cleanly separable — nightly versions are timestamps). Watch the next/legacy ratio and the crash-fallback rate; Metabase dashboards 17 (rollout + task error rate) and 19 (error deep dive). `extension.rollout.loader_decision` (incl. `double_failure`) is PostHog-only, not in ClickHouse.
5. Dial the flag per the rollout plan (e.g. 0% at publish → 1% → up), verifying each change with the probe from rule 2. Announce demotions ahead of time — dialing down also demotes nightly dogfooders unless they set `"cline-nightly.rollout.bundleOverride": "next"`.
### Known caveats of this path
- **`engines.vscode` unions upward** (main's floor wins, e.g. `^1.101.0` vs legacy's `^1.84.0`): users on older VS Code are never offered the combined VSIX. Fail-safe during rollout; must be resolved before 100%.
- A red run can still mean a successful publish on paths that tag (see Gotchas).
gh workflow run ext-vscode-publish-nightly.yml --ref main # real publish
gh workflow run ext-vscode-publish-nightly.yml --ref main -f dry-run=true # artifact only
gh run watch <run-id> --exit-status --interval 60
```
No changelog/version prep — the version is computed. Verify with the marketplace query against `saoudrizwan.cline-nightly`.
**Red run ≠ failed publish**: the final tag-push step fails whenever main's HEAD touches `.github/workflows/**` (default token cannot create such refs). If "Published" appears in the logs, the release went out; push the `nightly-main-<UTC ts>-<sha12>` tag manually with user credentials.
## Legacy hotfix release (and emergency full rollback)
For shipping a fix on the `legacy-extension` branch — or as the **structural rollback** from a bad combined stable VSIX: a standalone legacy publish at a higher version supersedes the combined VSIX entirely (loader and all) for every user. (For "next bundle misbehaving" you don't need this — dial the flag to 0% instead.)
```bash
# On legacy-extension: commit the fix, bump apps/vscode/package.json ABOVE the
# highest version ever published to the listing (rule 1 — including combined
# versions, e.g. combined 4.1.0 live -> hotfix is 4.1.1, not 4.0.13),
# add the matching `## [x.y.z]` entry to root CHANGELOG.md, push.
gh workflow run ext-vscode-publish-legacy.yml --ref main \
-f release-type=release
# (the branch is hardcoded to legacy-extension in the workflow; it is
# deliberately not an input)
```
npm test suite runs ungated; the publish job waits on the `Publish` environment. This workflow derives + pushes the `v<version>` tag itself and creates the GitHub release — no manual tagging. Publishes to Marketplace **and** Open VSX. The branch is the npm codebase: use `npm`, never `bun`, and expect the old monolith layout (`apps/vscode/src/core/...`).
## Cutover: retiring the A/B machinery (the endgame)
When the next bundle has held at 100% long enough to trust:
1. **Resolve the engines floor**: decide whether stranding VS Code < main's `engines.vscode` on the last combined version is acceptable, or lower main's floor first.
2. Bump `apps/vscode/package.json` on `main` above everything ever published; root `CHANGELOG.md` entry to match (both are enforced by the workflow).
3. Ship standalone from main: `gh workflow run ext-vscode-publish-stable.yml --ref main` — tests main, tags `v<version>` itself, creates the GitHub release, publishes Marketplace + Open VSX.
4. Watch the same rollout telemetry through the transition — `extension_variant` disappears from events as users leave combined builds, which is itself the adoption signal.
5. Only after the standalone version dominates: retire `legacy-extension` (keep for history), delete `ext-vscode-publish-legacy.yml` and `ext-vscode-ab-package.yml`, convert the nightly workflow back to a plain build of main, remove `apps/vscode-rollout/`, and archive the `ext-sdk-bundle-rollout` flag in PostHog (harmless to machines still on a combined VSIX: absent flag fails safe to... nothing changing until they update, but their loader treats a deleted flag as legacy — leave the flag at 100% until combined-VSIX activations flatline, then archive).
6. Update this skill: delete the combined-era sections and keep the standalone flow.
## Gotchas index
- `inputs.*` are empty strings on `schedule` events — preserve `|| 'default'` fallbacks when editing the nightly workflow.
- `bun run package` in `apps/vscode` does not build `@cline/*` workspace deps — fresh checkouts need `bun run build:sdk` first (workflows handle this).
- Job-level `if:` ref checks in workflow YAML are advisory (a dispatched branch runs its own copy of the file); the enforced boundary is each environment's deployment-branch policy in repo settings.
- Marketplace PATs (`VSCE_PAT`/`OVSX_PAT`) are only mounted into publish steps; neither publish workflow has an untrusted trigger surface.
- Environment-approval runs left waiting don't time out quickly — they sit for days and (for ab-package publish runs) block their version's concurrency group.
- Local forcing for manual testing: `CLINE_BUNDLE_OVERRIDE=next|legacy` env (launch VS Code fresh from a terminal) or the `<prefix>.rollout.bundleOverride` setting + reload; both report as `override` in telemetry so they don't pollute cohort data.
description: Prepare, validate, and publish standalone @cline/ui npm releases. Use when bumping the UI package version, publishing latest or next through ui-publish.yml, checking UI release readiness, or completing the one-time npm trusted-publishing bootstrap.
---
# Publish UI
Release `@cline/ui` independently from the Cline SDK runtime packages.
## Release contract
- Version source: `sdk/packages/ui/package.json`.
- Workflow: `.github/workflows/ui-publish.yml`.
- The package keeps `internal: true` only to stay out of the SDK's shared
version/publish scripts. It is still a public npm package because
`private: false` and `publishConfig.access: public` control npm publication.
-`latest` is the production channel. `next` is an opt-in preview channel.
- Use prerelease versions such as `0.2.0-next.0` for `next`; do not publish a
version intended for `latest` under the preview tag because npm versions
cannot be republished.
- There is no UI Git tag, GitHub release, schedule, or Slack announcement.
- The workflow runs only by manual dispatch. Every release attempt runs the UI
quality checks before publishing and requires `confirm_publish=publish` from
`main`.
- The publish job and npm trust relationship use the protected `Publish`
environment.
- Every npm publication needs a new semver version; npm versions are immutable.
- Always ask before pushing commits, triggering the publish workflow, changing
npm trust settings, or running a local publish command.
## Normal release
1. Inspect the branch, current version, npm state, and UI changes.
Drive and test terminal apps (especially the Cline CLI TUI in apps/cli) through tuistory — named background PTY sessions that agents can read, wait on, snapshot, screenshot, and type into. Like Playwright/tmux for terminals, with reactive waiting instead of blind `sleep`.
Use this skill when you need to:
- Manually test or reproduce bugs in the interactive Cline TUI (`bun run cli -i`) from a headless environment
- Run a dev server or any long-lived/interactive process in the background without hanging your tool call
- Write or extend Playwright-style e2e tests for the TUI (`bun run test:e2e:tuistory` in apps/cli)
- Capture text snapshots or styled PNG screenshots of a TUI screen as evidence
---
# tuistory
[tuistory](https://github.com/remorses/tuistory) wraps any terminal command in a named background PTY session backed by a Ghostty terminal emulator. Agents interact with the session via short CLI calls that return instantly; humans can `tuistory attach` to the same session to watch or intervene. No real terminal or display (`DISPLAY`) is needed — it works fully headless, which makes it the preferred way for cloud agents to exercise the Cline TUI.
It is installed as a devDependency of `@cline/cli`, so the pinned binary resolves when you run from `apps/cli`:
```bash
cd apps/cli
bunx tuistory --help # source of truth for commands, options, and syntax
```
For full upstream docs: `curl -s https://raw.githubusercontent.com/remorses/tuistory/refs/heads/main/README.md`
## Driving the Cline TUI headlessly
Launch the TUI in an isolated environment so you don't touch real user config (`~/.cline`):
-- bun src/index.ts --provider anthropic -m claude-sonnet-4-6 -k test-key
```
The dummy `-k test-key` renders the full chat UI; only an actual agent turn would fail. For recorded LLM turns, use the VCR cassettes described in `apps/cli/src/tests/helpers/env.ts` (`CLINE_VCR=playback` + `CLINE_VCR_CASSETTE`). Real turns need a provider credential (e.g. `ANTHROPIC_API_KEY`, `CLINE_API_KEY`).
Then use an **observe → act → observe** loop:
```bash
# Wait reactively for the chat view — never use sleep
bunx tuistory -s cline wait"What can I do for you?" --timeout 30000
# Act, then always observe the resulting screen state
bunx tuistory -s cline type"/settings"
bunx tuistory -s cline snapshot --trim
bunx tuistory -s cline press enter
bunx tuistory -s cline snapshot --trim
# Styled PNG of the current screen (prints the file path) — good for artifacts
bunx tuistory -s cline screenshot
# Full raw output stream (snapshot shows only the visible screen)
bunx tuistory read -s cline --all
# Tear down a session YOU started (double Ctrl+C exits the TUI cleanly)
bunx tuistory -s cline press ctrl c
bunx tuistory -s cline press ctrl c
bunx tuistory -s cline close
```
## Background processes (instead of tmux)
```bash
bunx tuistory -s my-server -- bun run dev:sidecar # returns immediately
bunx tuistory read -s my-server # new output since last read
bunx tuistory -s my-server restart # after code changes
```
## Key rules
- **Options before `--`, command after.** Everything after the first `--` is passed verbatim to the child: `tuistory -s name --cols 150 -- bun src/index.ts` is correct.
- **Snapshot after every action.** TUIs are stateful; dialogs and errors can render over the view you expect. `snapshot` reflects what the user actually sees (occluded text does not count), unlike grepping the raw stream.
- **Wait, never sleep.** `wait "text"` / `wait "/regex/i"` (case-sensitive by default) reacts as fast as the terminal updates; `wait-idle` when you don't know what to expect. Always pass `--timeout`.
- **Keys land instantly.** Unlike sleep-based scripts, a queued second keypress can leak into the next view (e.g. one Enter both accepts a slash completion and submits it).
- **Never close a session you didn't start.** Sessions are shared with humans (`tuistory attach -s name`) and other agents. Default to leaving sessions running; use `read`/`wait`/`snapshot` to inspect without disrupting.
env: isolatedEnv,// see createCliEnv() in the reference test
cols: 120,
rows: 36,
waitForDataTimeout: 30_000,// CLI cold start compiles a large TS graph
});
awaitsession.waitForText("What can I do for you?",{timeout: 30_000});
constscreen=awaitsession.text({trimEnd: true});// emulated screen state
awaitsession.type("/settings");
awaitsession.press("enter");
session.close();// always close in test teardown
```
Screen-state assertions can check that stale UI is *gone* (`expect(screen).not.toContain(...)`), which stream-grepping harnesses cannot. `session.text({ only: { bold: true } })` filters by style; `session.read()` returns the raw stream since the last read.
@@ -16,6 +16,7 @@ This file is the secret sauce for working effectively in this codebase. It captu
- The whole repo (including `apps/vscode`) uses **bun** for package management and task running. Emit `bun run X` / `bun install` / `bunx <bin>` / `bun file.ts`, never npm/npx. Node remains the *runtime* (VS Code's extension host and the standalone cline-core are Node), so Node-runtime tokens are legitimate and must not be "fixed" to bun — see @.clinerules/bun-and-node.md for the keep-list vs rewrite-list.
- Avoid provider-specific string matching / hardcoded provider branches when fixing provider/config plumbing. Prefer provider metadata, shared catalog/defaults, explicit protocol/client capabilities, or centralized normalization utilities that apply by data shape rather than `providerId === "..."`. If a provider exception seems necessary, stop and explain why instead of adding ad-hoc string matching.
- This is a VS Code extension—check `package.json` for available scripts before trying to verify builds (e.g., `bun run compile`, not `bun run build`).
- When reading a configuration files that users may edit, use `readFileStrippingUtf8Bom`, `readFileSyncStrippingUtf8Bom`, or `stripUtf8Bom` from `@cline/shared/node`. DON'T strip byte order marks of user files handled by tools/passed to models.
- When creating PRs, contributors should not create changelog-entry files. Maintainers handle release versioning and changelog curation during the release process.
- When adding new feature flags, see this PR as a reference https://github.com/cline/cline/pull/7566
- Additional instructions about making requests: @.clinerules/network.md
Paste the "About" diagnostics for your Cline surface. This captures the IDE build, runtime, and host details we need.
Paste the diagnostics for your Cline surface. This captures the build, runtime, and host details we need.
- VSCode Extension: open `Help → About` (Windows/Linux) or `Code → About Visual Studio Code` (macOS), then copy the info.
- JetBrains Plugin: open `Help → About` (Windows/Linux) or `<IDE name> → About` (macOS), then click `Copy` to grab build, runtime, OS, memory, and cores.
- CLI: there is no About dialog. Run `cline --version` and paste the output.
placeholder:Paste the copied About info or `cline --version` output here.
- Desktop App: paste the app version from the Settings view.
- Cloud Platform: paste your browser name and version, plus the page URL where the issue occurred.
placeholder:Paste the copied About info, `cline --version` output, or browser/app details here.
for var in AZURE_CLIENT_ID AZURE_TENANT_ID AZURE_SUBSCRIPTION_ID SIGNING_ENDPOINT SIGNING_ACCOUNT SIGNING_PROFILE; do
if [ -z "${!var}" ]; then
missing+=("$var")
else
set_count=$((set_count + 1))
fi
done
if [ "${#missing[@]}" -eq 0 ]; then
echo "Azure Trusted Signing is configured; Windows binaries will be signed."
echo "enabled=true" >> "$GITHUB_OUTPUT"
elif [ "$set_count" -eq 0 ]; then
echo "::warning::Azure Trusted Signing is not configured; publishing UNSIGNED Windows binaries. Set the AZURE_* and AZURE_TRUSTED_SIGNING_* repository secrets to enable signing."
echo "enabled=false" >> "$GITHUB_OUTPUT"
else
# Partial configuration is almost certainly a typo'd or renamed
# secret. Fail loudly instead of silently publishing unsigned.
echo "::error::Azure Trusted Signing is PARTIALLY configured; refusing to publish. Missing: ${missing[*]}"
if ! gh release view "$FEED" >/dev/null 2>&1; then
if [ "$CHANNEL" = "beta" ]; then
gh release create "$FEED" \
--title "Cline desktop beta (auto-update feed)" \
--notes "Rolling release backing the beta desktop app auto-updater. The latest.json asset points at the newest desktop-vX.Y.Z-beta.N release. Only beta installs poll this feed; stable installs use desktop-latest. Do not delete." \
--latest=false \
--prerelease \
--target "$(git rev-parse HEAD)"
else
gh release create "$FEED" \
--title "Cline desktop (auto-update feed)" \
--notes "Rolling release backing the desktop app auto-updater. The latest.json asset points at the newest desktop-vX.Y.Z release. Do not delete." \
text: "<https://github.com/${{ github.repository }}/releases/tag/${{ needs.validate.outputs.tag }}|Download DMG> — ${{ needs.validate.outputs.channel == 'beta' && 'beta channel: installs side by side with the stable app and only beta installs auto-update; stable users are unaffected' || 'installed apps auto-update on next launch' }}${{ steps.prev_tag.outputs.prev_tag != '' && format(' | Full Changelog: https://github.com/{0}/compare/{1}...{2}', github.repository, steps.prev_tag.outputs.prev_tag, needs.validate.outputs.tag) || '' }}"
"rule":"Any PR that adds new event constants to CORE_TELEMETRY_EVENTS in packages/core/src/services/telemetry/core-events.ts, adds new capture* helper functions, or changes the payload shape of an existing event must update the Event Catalog section in DOC.md. Flag PRs that modify core-events.ts without a corresponding change to DOC.md.",
"description":"OpenTelemetry-backed provider that wires logs/metrics/traces exporters. Contains createConfiguredTelemetryService and createConfiguredTelemetryHandle, the canonical factories every host should use."
},
{
"path":"DOC.md",
"description":"Public API and event documentation. The Event Catalog and 'Activation funnel' sections must be kept in sync with core-events.ts. Host integration rules (CLI dir ordering, hub daemon metadata forwarding) are documented here."
},
{
"path":"sdk/ARCHITECTURE.md",
"description":"Architecture reference. Telemetry design decisions, completion semantics (submit_and_exit anchoring), and hub-daemon telemetry forwarding are documented here. Use as ground truth for design intent."
"description":"Architecture reference. Telemetry design decisions and completion semantics (submit_and_exit anchoring) are documented here. Use as ground truth for design intent."
This is the **Cline** monorepo. Toolchain is **Bun 1.3.13** (package manager + task runner) with **Node >=22** as the runtime. Do not use npm/yarn/pnpm.
## Cloud Agent Instructions
### Cline CLI
- Run from source: `bun run cli` (interactive: `bun run cli -i`; one-shot: append a prompt). This resolves to `apps/cli` and **auto-spawns the `@cline/cline-hub` daemon** — you do not start the hub separately.
- Inspect local health with `bun run cli doctor`; `bun run cli version` prints the version.
- An actual agent turn requires an **LLM provider credential**. With no credentials the default `cline` provider fails fast with an `Unauthorized` error and the interactive TUI shows a provider sign-in screen. Configure via `cline auth` or provider env vars (e.g. `ANTHROPIC_API_KEY`, `CLINE_API_KEY`, `OPENROUTER_API_KEY`); see `apps/cli/README.md`.
### Build / Lint / test
- SDK packages (`@cline/shared|llms|agents|core|sdk`) resolve each other through compiled `dist/` (their `exports` point only at `dist/`, with no `development` source condition). You **must** run `bun run build:sdk` after changing SDK dependencies/source before running the CLI or SDK tests, otherwise imports fail with missing `@cline/*` / missing `dist/` errors. Running processes do **not** hot-reload SDK source changes — rebuild and restart.\
- Known cloud-env test artifact: `@cline/core` test `src/services/workspace/workspace-manifest.test.ts > readGitWorkspaceState > prefers origin and returns the current branch` fails because cloud VMs configure git `insteadOf` rules that rewrite GitHub remotes to `https://x-access-token:...@github.com/...`. This is an environment artifact, not a code bug.
- Some `@cline/cli` e2e assertions (`bun -F @cline/cli test:e2e`) may fail on exact tool-listing string formats; treat as pre-existing test drift, not an environment problem.
### GUI display
- A virtual X display is live at **`DISPLAY=:1`** (the same desktop used for screenshots). GUI apps (VS Code, the Tauri desktop window) launched with `DISPLAY=:1` render there and can be screenshotted — no need to start your own `xvfb`. Prefer starting long-running GUI/dev processes in a `tmux` session (see the tmux guidance) so they survive.
### VS Code extension (`apps/vscode`, package `claude-dev`)
Toolchain is pre-installed and persisted in the VM: generated gRPC/proto code, the bundled `ripgrep` binaries (`apps/vscode/bin/`), the built webview (`webview-ui/build`), the esbuild bundle (`dist/extension.js`), VS Code itself (`/usr/bin/code`), and the GUI system libraries its tests need.
- **Codegen prerequisite:** `bun run protos` (from `apps/vscode`) regenerates `src/generated/*` and the webview grpc client. The `dev`, `build:webview`, and `check-types` scripts already run it, so proto changes are picked up by those commands; run it manually only if you edit `.proto` files without a full build.
- **Build:** `bun run build:webview` (webview UI, ~15s) then `bun esbuild.mjs` (extension bundle). `bun run package` does the full production build.
- **Run it (dev host):** `DISPLAY=:1 code --no-sandbox --user-data-dir=/tmp/vscode-userdata --extensionDevelopmentPath=/workspace/apps/vscode <some-folder>`, then click the Cline icon in the Activity Bar to open the webview. (`--no-sandbox` is required in this container.)
- **Test:** `bun run test:unit` (bun-based, ~984 tests, no VS Code host needed). `bun run test:integration` (`@vscode/test-electron`, downloads a VS Code build, runs under the GUI libs) and `bun run test:e2e` (Playwright) exercise a real extension host — heavier, and the GUI libs for them are already installed.
- One-time deps (already installed, listed here in case they must be recreated): ripgrep via `bun run download-ripgrep`; VS Code test GUI libs per `CONTRIBUTING.md` (`libnss3`, `libatk*`, `libgbm1`, `xvfb`, etc.).
A Tauri v2 (Rust) shell + Next.js webview + a Bun "sidecar" backend. Rust and the Tauri Linux system libs are pre-installed and persisted.
- **Headless (no Rust/window):** run the backend and UI separately — `bun run dev:sidecar` (Bun backend on `127.0.0.1:3126`, serves `ws://.../transport`) and `bun run dev:web` (Next.js UI on `http://localhost:3125`).
- **Native window:** `bun run dev` (`tauri dev`) — its `beforeDevCommand` builds the sidecar binary and starts `dev:web` (`:3125`), then Rust `main.rs` spawns the sidecar; so free ports `3125`/`3126` first. Launch with `DISPLAY=:1` to see the window. A `libEGL: DRI3 error` warning is benign (software rendering) — the WebKitGTK window still renders.
- **Rust version caveat:** the crate graph needs Cargo's `edition2024` feature, so **Rust ≥1.85** is required (the VM's base 1.83 fails with "feature `edition2024` is required"). The toolchain here was updated via `rustup default stable` (currently 1.97). First `cargo` build downloads/compiles the full Tauri crate graph (a few minutes); subsequent builds are cached.
Everything here lands through the SDK bundle, so it applies to windows running that bundle.
### Fixed
- Cost estimates are no longer shown for providers billed by a flat-rate subscription (ClinePass, ChatGPT via Codex, and Claude Code). The task header and model pricing rows rendered API-rate dollar figures that read as real charges on top of the subscription, including a flash of them on every chat-view mount while provider listings were loading.
- Signing back in no longer moves your last-used provider off ClinePass on credential refresh.
- Hooks now resolve their workspace from the VS Code window instead of shared global state in `~/.cline`. With a second window open on another project, a workspace's `.clinerules/hooks` scripts were never discovered, and hook cwd and the workspace paths passed to hook scripts resolved against whatever project some other or older Cline instance last recorded.
- New files are now created with your platform's native line endings.
- Fixed the codebase search tool crashing on files containing a single enormous line.
- Credentials embedded in git remote URLs are now redacted from the workspace information sent to the model, which also now carries richer workspace metadata.
- Installing an MCP server from the marketplace no longer misreads the catalog's `--` separator as part of the server command.
- The hub's event log can no longer grow until it fills your disk.
### Changed
- The per-tool MCP auto-approve checkboxes are hidden. MCP auto-approval is governed solely by the global "Use MCP servers" toggle — the per-tool checkboxes were no-ops that implied granularity the approval path does not have.
## [4.1.15]
Everything here lands through the SDK bundle, so it applies to windows running that bundle.
### Fixed
- Auto-approve every MCP tool call while the "Use MCP servers" toggle is on. The toggle only took effect on tools that had also been opted in individually, so turning it on appeared to do nothing; it now governs all MCP tools on its own.
## [4.1.14]
Everything here lands through the SDK bundle, so it applies to windows running that bundle.
### Added
- Refresh the built-in model catalog. New entries include Claude Fable 5, Grok 4.6 on Vertex, several DeepSeek V4 Flash variants (including the vision preview), MiMo v2.5, Qwen3.8 27B, Gemma 4 26B, LongCat 2.0, Nemotron 3.5 Lightning, and Thinking Machines' Inkling models.
### Fixed
- Restore task completion telemetry for interactive sessions. A share of interactive stops routed through a teardown path that never reported completion after 4.1.11 changed how session status is tracked; every session now reports it exactly once.
## [4.1.13]
Everything here lands through the SDK bundle, so it applies to windows running that bundle.
### Fixed
- Restore tool calling for custom OpenAI-Compatible models whose capability list was inferred from convenience flags like `supportsReasoning`. The inferred list read as an authoritative denial and stripped every tool from the request; an explicitly authored capability list still decides.
- Keep Hub-backed sessions intact across a Hub restart or upgrade. Clients replay the events they missed while disconnected, and the same event is no longer delivered twice when the replay and live streams overlap.
- Carry session and client identity into Langfuse traces for Hub-backed and delegated-agent runs, which previously arrived without their session grouping or client version.
## [4.1.12]
Everything here lands through the SDK bundle, so it applies to windows running that bundle.
### Fixed
- Enforce enterprise MCP controls on the Customize marketplace. MCP entries are now hidden when remote config disables the marketplace, and limited to `allowedMCPServers` when an allowlist is configured.
- Restore tool calling for custom OpenAI-Compatible models whose stored capability list was empty.
## [4.1.11]
Everything here lands through the SDK bundle, so it applies to windows running that bundle — except the last section, which is a legacy-bundle fix.
### Added
- Let models that support it generate images during a task. Generated images render inline in the conversation.
### Fixed
- Fix code actions failing with "command not found" on VS Code 1.134.
- Fix `@` file mentions breaking on paths that contain spaces.
- Show the diff edit view for multi-line edits in files with CRLF line endings.
- Continue the surviving session when resuming a task, instead of rebuilding it from the original task text.
- Clear the task-scoped settings overlay when the task view is cleared or switched, so one task's overrides no longer leak into the next.
- Honor the classic truncation range when migrating legacy tasks.
- Preserve LiteLLM input token limits instead of overwriting them with catalog values.
- Restore custom base URLs for Gemini, and normalize legacy host-root values so they keep working.
- Point provider signup links at each provider's API key page instead of a generic landing page.
- Load skill slash commands through the skills tool instead of pasting their instructions into your message, which previously delivered them twice.
- Stop offering image, voice, and other non-chat models in chat model pickers.
- Deliver a `PreToolUse` hook's `contextModification` to the model again, and wait for `PostToolUse` hooks so their output and `cancel` control are honored.
- Show tool activity a provider runs itself — every tool the Claude Code provider executes inside its own session — instead of dropping it from the conversation.
- Fix `run_commands` failing with ENOENT when a structured command carried a full command line with no arguments.
- Run PowerShell commands fail-fast, so a pipeline erroring per item stops at the first error instead of flooding output and still reporting success.
- Keep remote configuration in step with the SDK: coordinated refreshes, session gating, and a fail-closed opt-out.
### Changed
- Show the billed cost for Cline gateway usage.
- Refresh the model catalog, which adds AMD, Arcee, Echo, Jalapeno, Kosmik, LLM Gateway, RunInfra, and SCNet as providers and updates model lists, pricing, and per-provider default models across the board.
### Fixed (legacy bundle)
- Only treat an Anthropic `invalid_request_error` as a context-overflow when its message says so. An unrelated invalid request (bad tool schema, oversized image, unknown model id) no longer triggers context-overflow recovery.
## [4.1.10]
Everything in this release lands through the SDK bundle, so it applies to windows running that bundle and not the legacy one. The legacy bundle is unchanged from 4.1.9.
### Added
- Let models that support it search the web during a task, with a toggle in Feature Settings to turn it on. Search calls and their results appear in the conversation and persist across reloads.
### Fixed
- Stop two Cline installations on different builds from shutting each other's Hub daemon down in a loop, which killed live sessions with an abnormal socket close. Build identity is now compared through a total order, so at most one side of a pair can decide to retire the other.
- Leave a Hub that is still serving sessions in place instead of replacing it mid-handshake; the swap happens once it goes idle.
- Reclaim idle plugin sandbox processes instead of leaving them running for the life of the session.
### Changed
- Refresh the model catalog, which adds Crusoe as a provider and updates model lists and per-provider default models across the board.
## [4.1.9]
### Changed
- Use the editor's foreground color for diff block text, so diffs stay legible in themes where the previous hardcoded color washed them out.
- Switch the interface to Inter and Geist Mono.
### Fixed
- Don't discard a successfully refreshed Cline token when the old one was already past expiry, which made the first request after a long idle period fail despite valid credentials.
- Stop the legacy-task migration backlog from spamming telemetry, and record a migration outcome only once the seeded session actually persists, so a failed migration is no longer reported as a success.
- Report involuntary Cline logouts (a rejected refresh token) instead of clearing credentials silently.
### Fixed (SDK bundle only)
These land through SDK v0.0.74 and therefore apply to windows running the SDK bundle, not the legacy one.
- Fix the Claude Code provider being unusable for agentic work: it now runs its own native tools instead of receiving tool definitions it cannot bridge, anchors the session on your workspace directory, and loads `~/.claude` plus project settings so your permission rules apply.
- Reject truncated tool-call JSON instead of silently "repairing" it into wrong arguments.
- Fix strict providers rejecting a turn with "user message must have content" when a message's content held only empty text parts.
- Fix a mid-turn crash on streamed tool calls with non-zero or non-contiguous indexes, hit through LiteLLM's Anthropic passthrough.
- Report disjoint per-request token buckets instead of re-counting the whole cached conversation on every request, which inflated per-task totals roughly 5x on cache-heavy sessions.
## [4.1.8]
### Added
- Enter any Vertex model ID by hand, including models the catalog doesn't list yet.
- Support Fable 5 on Vertex.
### Changed
- Show the full model catalog for every Vertex region instead of filtering the picker down to a hardcoded list of global-endpoint models, which lagged behind every model launch. Picking a model the region doesn't serve now fails at request time with recovery guidance in the error row.
- Report Fable 5 cost on Vertex as unknown rather than applying Anthropic's list price, which understated what Vertex actually bills — its rates are region-dependent.
- Make the auto-approve menu the single source of truth for unattended runs and remove the Yolo Mode toggle, which was cosmetic: nothing in the approval path read it. Setups that had Yolo Mode (or auto-approve-all) turned on are migrated to auto-approving every action, so they keep running unattended.
### Fixed
- Respect your configured max output tokens when the compaction summarizer requests a summary.
- Remove the stale "Double-Check Completion" feature tip.
## [4.1.7]
### Added
- Restore the "View Changes" button on completion rows, backed by SDK checkpoints, so you can review everything a task touched from the completion card.
- Bring back a copy button on turn-final response rows.
- Support pre-registered OAuth clients for remote MCP servers, for setups where dynamic client registration isn't available.
### Changed
- Fade the "View Changes" button until changes since the last message are confirmed, and hide it entirely when there is nothing to show.
- Centralize plugin settings and contributions, with host-aware snapshots and atomic plugin toggles.
- Carry execution context in scheduled run reports — readable headers, schedule metadata, durations, and lifecycle error details.
### Fixed
- Preserve prompts queued during a turn when that turn is interrupted: they survive aborts, are drained after a turn aborts itself, and the stop is surfaced instead of the queue being silently dropped.
- Keep session context durable across aborts and hub restarts, so an interrupted session resumes with the state it had.
- Settle the turn phase when a mode switch aborts a running turn.
- Report queued-turn failures as `run.failed` instead of letting them complete silently.
- Keep a hung MCP server from taking down session creation, and give stdio servers that were never configured a 30-second initialize budget instead of blocking indefinitely.
- Surface OAuth authorization for SSE MCP servers on a 401 instead of failing outright.
- Route LiteLLM through Chat Completions instead of the Responses API, fixing requests against LiteLLM proxies.
- Retry network interruptions that happen mid-stream but before any model output, instead of failing the turn.
- Use the configured fetch for Vertex ADC token refreshes, so they work behind proxies and custom transports.
- Include files that were untracked when a snapshot was taken in checkpoint diffs, and pick up checkpoints when git is initialized part-way through a session.
- Fall back to the session cwd or Desktop for @-mention file search in empty windows.
- Never run a foreign compiled plugin-sandbox bootstrap for a source host.
## [4.1.6]
### Added
- Offer `meta/muse-spark-1.2-contributor` on the Cline provider, alongside a refreshed model catalog.
### Fixed
- Attribute error telemetry to the model actually in use for a run, so failures are no longer reported against the wrong model.
## [4.1.5]
### Added
- Explain when a free model promotion ends. Requests to a retired free model now show a dedicated notice with a button to pick another model, instead of a generic error with nothing but a Retry prompt.
### Changed
- Map reasoning settings onto a shared path across AI SDK providers, so effort levels and enable/disable toggles behave consistently (including on Ollama) instead of relying on per-provider overrides.
## [4.1.4]
### Added
- Recognize Chutes as a provider.
- Show skills alongside workflows in the slash command menu, and disambiguate commands that share a name instead of letting one shadow the other.
### Changed
- Remove model-initiated plan-to-act switching. Switching out of plan mode is now driven by you, not by the model deciding mid-turn.
- Hard-block file-editing shell commands in plan mode instead of relying on prompting alone. Read-only investigation still works, but file manipulation, in-place editors, redirection to files, mutating git subcommands, and package installs are refused.
### Fixed
- Stop treating a turn that completes with a plan as a failed turn when a plan-blocked command was its only tool call. The turn no longer ends in the error state with a Retry footer, and toggling to Act correctly re-runs the presented plan instead of appearing to do nothing.
- Show tool paths relative to the workspace in the chat view instead of absolute paths.
- Reset pending attachments when starting a new task, so images from the previous task no longer carry over.
- Surface a clear error when the selected provider has no API key configured, instead of a generic failure.
- Refresh MCP tool and resource lists when a server sends a `list_changed` notification, instead of only showing a toast.
- Show installed plugins under their real package names instead of all appearing as "index".
- Correct the Linux keybinding label in the Plan/Act mode tooltip.
- Recover from running out of context instead of failing with a raw provider error — the run compacts and retries once, and the cases that genuinely cannot be recovered explain why.
- Retry empty model responses on every provider rather than only Ollama, fixing hard "Model returned empty response" failures on OpenRouter, Cline, and OpenAI-compatible endpoints.
- Stop Claude 4.6+ and 5.x models being rejected with "thinking.type.enabled is not supported" when they resolve from the offline catalog or from a hand-typed model id.
- Restore Bedrock prompt caching, which reported zero cache reads and writes because the provider sent a cache format Bedrock discards, and route Bedrock foundation models through geo inference profiles.
- Send `max_completion_tokens` for reasoning models on OpenAI-compatible endpoints, and substitute image content for models without image support instead of failing the request.
- Inherit the MiniMax default model from models.dev, and refresh the bundled catalog, which adds Infomaniak and SCX.ai.
- Report the same provider failure once instead of twice in error telemetry, and rate-limit repeated failures from unattended retry loops.
## [4.1.3]
### Fixed
- Stop the two bundles of the combined rollout package from invalidating each other's Cline account session. A still-open legacy window that refreshed its token after the machine was promoted to the new extension would consume the shared refresh token, producing spurious "Unauthorized" / re-authenticate prompts and unexpected sign-outs. Promoted legacy windows now keep working on their current session and offer a one-time Reload Window prompt instead.
- Fall back to the default Cline model when migrating a setup that references a model id the new extension doesn't recognize, instead of leaving the provider unconfigured.
- Restore reliable checkpoints: checkpoints are created consistently, and restoring one now rewinds the whole workspace rather than a subset of files.
- Keep settings edits that are made before the provider config finishes loading — base URLs, API keys, and the Qwen/Moonshot API line are no longer silently discarded.
- Stop losing keystrokes in custom base URL fields, and keep the custom URL checkbox state after a failed clear.
- Use the AskSage custom API URL at inference time instead of ignoring it.
- Settle a pending tool approval when an edited message replaces the session, so the task no longer hangs waiting on a prompt that is gone.
- Drop attachments from messages that have been edited.
- Complete terminal commands when the shell execution ends, so tasks no longer stall on commands that already finished.
- Include untracked files when generating commit messages.
- Run Windows Store PowerShell profiles correctly.
- Surface the upstream provider error when a gateway-forwarded stream fails, instead of a generic failure.
- Retry empty Ollama responses at the model boundary, and raise the response-start timeout to 5 minutes so cold model loads no longer error out.
- Show proper display names for Cline free models and recommended models in the model picker.
- Preserve video input capability for models that support it.
- Keep the plan/act input border in sync with the actual textarea focus.
## [4.1.2]
### Added
- Show which extension variant is active — "Legacy" or "Next" — next to the version in the settings About page, in both bundles of the combined rollout package.
## [4.1.1]
### Changed
- Remove vestigial MCP server-key machinery from McpHub — native MCP tool calls now route by server name instead of a random in-memory uid, so routing survives restarts and server list changes.
## [4.1.0]
### Changed
- Convert the stable extension to a combined A/B package: one VSIX containing both the current (legacy) extension and the new SDK-based extension, plus a loader that activates exactly one per window via a staged remote rollout. For nearly all users nothing changes — the loader activates the same extension as 4.0.12; a small percentage (starting at 1%) is gradually opted into the SDK-based extension. If the new extension fails to activate, the loader falls back to the current one in the same window. Settings and credentials are shared between the two.
## [4.0.12]
### Added
- Add support for free Cline models, shown as "(free)" in the model picker, with a dedicated error card that includes the reset time when the free limit is reached.
### Fixed
- Keep Claude Code responses that were already streamed when the CLI exits with a max-turns error, instead of discarding a valid response.
## [4.0.11]
### Added
- Add Claude Opus 5 across the Anthropic, Claude Code, Bedrock, Vertex, Cline, and OpenRouter providers, including 1M context window variants.
- Add Moonshot Kimi K3 support.
- Include the host plugin version in telemetry events.
### Fixed
- Correct pricing for the Claude Opus 1M context variants, which overstated costs for requests above 200k tokens.
- Enable native tool calling for Kimi K3 models, fixing empty responses.
## [4.0.10]
### Added
- Add telemetry to track when Cline reaches the consecutive mistake limit.
## [4.0.9]
### Added
- Add GPT-5.6 ChatGPT subscription models.
### Changed
- Soften and shorten the message shown when Cline hits the consecutive mistake limit.
### Fixed
- Handle cumulative usage snapshots from OpenAI-compatible providers so token counts are no longer over-reported.
- Load skills from files saved as UTF-8 with a byte-order mark (BOM).
## [4.0.8]
### Added
- Add more models to the GCP Vertex provider, plus a free-form entry option in the model dropdown for specifying custom Vertex models.
## [4.0.7]
### Added
- Add a ClinePass limit-reached error with a one-click option to switch to Cline usage-based billing.
- Allow selecting Cline free models on the ClinePass provider, organized into Subscribed and Free tabs with model descriptions.
### Changed
- Refine ClinePass onboarding and provider settings copy, and open the "learn more" link via the in-app URL handler.
- Remove the Cline model picker recommendation copy.
### Removed
- Remove all references to GLM 5.1.
## [4.0.6]
### Fixed
- Generalize the model capability warning so it applies more broadly.
## [4.0.5]
### Added
- Add support for Claude Sonnet 5 across the Anthropic, Bedrock, Vertex, Claude Code, SAP AI Core, OpenRouter, and Vercel AI Gateway providers, including model picker and recommended-model updates.
## [4.0.4]
### Changed
- Fully remove the ClinePass feature flag so ClinePass is available everywhere in the UI — onboarding, settings, the welcome promo banner, and the credit-limit "Switch to ClinePass" action.
## [4.0.3]
### Changed
- Enable the ClinePass provider for all users by removing the feature-flag gate that previously fell back to the standard Cline provider.
## [4.0.2]
### Added
- Add reasoning effort support (including `xhigh`) for DeepSeek thinking models.
- Improve the ClinePass provider experience with clearer reasoning controls and model selection.
### Fixed
- Show reasoning effort controls for ClinePass models and align ClinePass model resolution with the rest of the provider.
- Prefer canonical Cline Z.ai model ids and polish ClinePass and Z.ai model metadata.
- Fix environment variable replacement in the webview.
- Default focus chain settings in webview state so the toggle reflects the correct value on load.
## [4.0.1]
### Changed
- Roll the stable VS Code extension back to the pre-SDK-migration codebase to resolve regressions reported in 4.0.0. This release ships the 3.89.2 extension code under a higher version number so existing 4.0.0 users receive the update. SDK-migration work continues separately on `main`.
## [4.0.0]
### Added
- Add the SDK-backed VS Code extension runtime. Cline now runs tasks through the shared Cline SDK session layer for agent turns, tools, Plan/Act mode coordination, MCP, checkpoints, telemetry, provider changes, compaction, mistake limits, and task history.
- Add ClinePass to the VS Code extension, including onboarding, provider selection, signup and subscription handoff, live model lists, entitlement and organization error states, out-of-credit prompts, and clearer ClinePass auth/error handling.
- Add the Customize marketplace for discovering and managing Skills, MCP servers, and Plugins from the extension, including installed/marketplace tabs, search and filtering, install/uninstall flows, enable/disable controls, and support for plugin-bundled skills.
- Cline Plugins: Plugins let you extend Cline with custom tools, workflows, skills, and MCP-powered capabilities tailored to your team or project. Install them from the new Customize marketplace to add specialized behavior, connect external services, and package reusable automations—so Cline can do more than code: it can adapt to the way you work.
- Add queued prompts in chat. Messages submitted while Cline is already working are now queued, shown while the current turn streams, and can be cancelled before they run.
- Add edit-and-regenerate support for previous user messages, with clearer Reset Chat and Reset Code actions.
- Add generic SDK provider settings and model-catalog support so more providers can share the same model picker, reasoning controls, dynamic model IDs, provider config persistence, and custom model handling.
- Add additional SDK-backed provider exposure and model/provider updates, including ClinePass models, refreshed Cline catalog data, Fireworks GLM 5.2, Kimi K2.6 Fast, Kimi K2.7 Code, Qwen 3.7 Plus, MiniMax M3 updates, SAP AI Core wiring, LiteLLM model fetching, Codex OAuth credentials, and OpenAI-compatible model settings.
- Add MCP support for plugins and shared marketplace install/uninstall plumbing used by the VS Code extension.
### Changed
- Migrate the VS Code extension from the legacy task implementation to the shared Cline SDK and move the extension build/package workflow to Bun.
- Rework Plan/Act mode handling through SDK coordinators, including closer CLI parity and automatic continuation when switching from Plan to Act.
- Rework provider and model configuration around `providers.json`, the model catalog, and SDK session config so settings are preserved consistently across provider switches and active sessions can restart when the selected provider changes.
- Simplify provider settings UI by replacing many provider-specific views with shared generic settings components and consistent reasoning selectors.
- Simplify terminal execution through the SDK run-commands path, including clearer non-interactive command guidance and safer structured command formatting.
- Migrate legacy MCP files and formats into the shared settings file and protect MCP settings writes with safer locking/atomic updates.
- Refresh the MCP hub automatically after marketplace installs so newly installed servers are available without a manual restart.
- Reorganize MCP/Skills/Plugins entry points under Customize, hide workflows from the Customize menu, wrap Customize tabs on narrow screens, and allow the MCP Marketplace tab to be disabled remotely while installed MCP servers remain accessible.
- Simplify auto-approval settings. Command auto-approval is now disabled by default for safer new and reset configurations, and the auto-approval UI has been streamlined.
- Update task history handling for the SDK migration, including legacy task history visibility, metadata preservation on resume, and corrected deletion behavior.
- Route compacting and mistake-limit behavior through the SDK so the Compact button and mistake tracking affect the active SDK session.
- Remove the legacy Explain Changes feature as part of the SDK migration cleanup.
- Temporarily disable subagents in the VS Code extension while the SDK-backed experience is stabilized.
### Fixed
- Fix marketplace edge cases, including refreshing MCP servers after marketplace installs, disabling the MCP Marketplace tab from remote config, hiding workflows from Customize, surfacing plugin-bundled skills, and uninstalling shared marketplace entries.
- Fix chat submission during active turns by queuing user messages instead of dropping or racing them, showing pending/queued states promptly, rendering direct user messages immediately, and removing delayed send behavior.
- Fix editing previous user messages so Escape cancels editing locally and reset action labels are clearer.
- Fix terminal reliability, including standalone Windows output capture, hardened PowerShell command handling, running-state display for in-progress commands, raw structured command preservation, single-quote handling, cwd setup timeouts, failing-command stdout capture, heredoc coalescing, and removal of duplicated command echoes in tool results.
- Fix SDK tool-result and provider-message budgeting by truncating large tool outputs by default, capping assistant text, limiting bash/file-read/search output ingestion, bounding media budgets, batching outdated-read rewrites to preserve provider prefix caches, and normalizing JSON-like tool inputs by schema.
- Fix login and feature-flag resolution by using the correct user/account identity on startup and simplifying the login UX.
@@ -7,7 +7,7 @@ We're thrilled you're interested in contributing to Cline. Whether you're fixing
Bug reports help make Cline better for everyone! Before creating a new issue, please [search existing ones](https://github.com/cline/cline/issues) to avoid duplicates. When you're ready to report a bug, head over to our [issues page](https://github.com/cline/cline/issues/new/choose) where you'll find a template to help you with filling out the relevant information.
<blockquote class='warning-note'>
🔐 <b>Important:</b> If you discover a security vulnerability, please use the <a href="https://github.com/cline/cline/security/advisories/new">Github security tool to report it privately</a>.
🔐 <b>Important:</b> If you discover a security vulnerability, please use the <a href="https://github.com/cline/cline/security/advisories/new">GitHub security tool to report it privately</a>.
@@ -149,7 +149,7 @@ Toggle between Plan mode and Act mode. In Plan mode, Cline explores your codebas
## Rules and Skills
Define project-specific rules in `.clinerules` files that guide how Cline works in your codebase: coding standards, architecture conventions, deployment procedures, testing requirements. Rules are picked up automatically by the CLI, VS Code extension, and JetBrains plugin. Use skills to let the model load specific rules when needed.
Define project-specific rules in `.clinerules` files that guide how Cline works in your codebase: coding standards, architecture conventions, deployment procedures, testing requirements. Rules are picked up automatically by the CLI, VS Code extension, and JetBrains plugin. Use skills to let the model load specific rules when needed.
## Works With Every Model
@@ -158,10 +158,10 @@ Cline is not locked to a single AI provider. Use whichever model fits your workf
| Provider | Models |
|----------|--------|
| Anthropic | Claude Opus, Sonnet, Haiku |
| OpenAI | GPT series model |
| Google | Gemini series model |
| OpenAI | GPT series models |
| Google | Gemini series models |
| OpenRouter | 200+ models from any provider |
| Vercel AI Gateway | Models through Vercel AI Gateway |
| Vercel AI Gateway | Route to many providers through one gateway |
| AWS Bedrock | Claude, Llama, and more |
| Azure / GCP Vertex | All hosted models |
| Cerebras / Groq | Fast inference models |
@@ -226,7 +226,7 @@ Run Cline with zero interaction for scripting and automation. Pipe input, get JS
```bash
cline "Run tests and fix any failures"
git diff origin/main | cline "Review these changes for issues"
git diff origin/main | cline "Review these changes for issues"
cline --json "List all TODO comments"| jq -r 'select(.type == "agent_event" and .event.text) | .event.text'
- Fixed the background hub process ballooning in memory during long sessions — session status updates were broadcasting a full copy of the conversation transcript to every connected client, which on a large task could grow the process to tens of gigabytes. Upgrading retires the running hub so the fix takes effect on the next command
- New files are now created with your platform's native line endings
- Fixed the codebase search tool crashing on files that contain a single enormous line
- Cost estimates are no longer shown for Claude Code. Its usage is typically covered by a Claude Pro/Max subscription, but its models reuse Anthropic API pricing, so Cline was showing charges you were not being billed
- Credentials embedded in git remote URLs are now redacted from the workspace information sent to the model
- Installing an MCP server no longer misreads a `--` separator in the install arguments as part of the server command
- Refreshed the model catalog. Adds seven providers (Agnes AI, Aixy, IteraCompute, LLM Tech, NeoSmith, Pendra, and Standard Compute) and updates model lists and pricing across providers. The resolved default model changes for ClinePass (now GLM 5.3), Z.ai, Hugging Face, evroc, LLM Gateway, NanoGPT, and Weights & Biases, so if you use one of those without pinning a model you will get a different default
## 3.0.58
- The first-launch "Try ClinePass" dialog no longer advertises the $4.99 first-month promo, which is ending
- The hub's event log is now capped at 64 MiB on disk. Events carrying full session snapshots could previously grow the log to tens of gigabytes on a long-running hub, since deleting rows never shrinks the file. Oldest events are dropped first and the space is returned, and pruning runs on volume as well as on a timer
- Refreshed the model catalog. Adds two providers (AgentRouter and Opper) and updates model lists and pricing across providers. The resolved default model changes for Aki.io and NanoGPT, so if you use one of those without pinning a model you will get a different default
## 3.0.57
- Added `cline hub drain`, which stops a hub from accepting new mutating work while it finishes what it is already running, and `cline hub drain --off` to lift it
- Added `cline hub upgrade`, which drains the hub, waits for it to go idle, stops it, and starts a fresh one on the current build. An aborted upgrade lifts the drain again, so the hub is never left refusing work
- Sessions now survive a hub restart. A reconnecting client replays the events it missed while disconnected, deduped by event id so nothing is delivered twice
- Fixed tool calling being silently disabled for custom OpenAI-Compatible models whose capability list was inferred from convenience flags like `supportsReasoning`. The inferred list read as an authoritative denial and stripped every tool from the request
- Langfuse traces now carry session and client identity for hub-backed and delegated-agent runs, instead of arriving without their session grouping or client version
- Refreshed the model catalog, which updates model lists and pricing across providers and changes the resolved default model for several of them (DeepSeek, Crof, CrossModel, Eden AI, Kilo, and NanoGPT)
## 3.0.56
- Models that support image generation can now produce media during a turn. The TUI saves each generated file to a temporary path and prints it so you can open it with your usual tools, HTML session exports embed images inline, and ACP clients receive generated images as image content
- Skill slash commands now load through the skills tool instead of expanding into your message. History and resume show the `/command` you typed instead of the whole skill body, and the instructions reach the model once instead of twice. Workflows still expand, as does zen mode, whose preset has no skills tool
- Image, voice, and other non-chat models are no longer offered in the onboarding and model pickers or ACP model listings, and are rejected for `--model`
- Fixed TUI dialog colors not following theme changes live
- Fixed the account dialog's selection chevron so it matches the other dialogs
- Fixed provider-executed tool activity — every tool the Claude Code provider runs inside its own session — being dropped instead of shown as a tool card
- Fixed `PreToolUse` hook `contextModification` never reaching the model, and `PostToolUse` hooks running fire-and-forget with their output and `cancel` control discarded
- Fixed `run_commands` failing with ENOENT when a structured command carried a full command line with no `args`
- PowerShell commands now fail fast on the first error instead of emitting an error record per enumerated item and still reporting success
- Fixed Gemini custom base URLs configured as a host root
- Fixed `cline schedule` commands against a remote hub, which now register a workspace client so they are authorized under the new workspace-scoped schedule rules
- Usage now displays the billed gateway cost
- Refreshed the model catalog, which adds AMD, Arcee, Echo, Jalapeno, Kosmik, LLM Gateway, RunInfra, and SCNet as providers and updates model lists, pricing, and per-provider default models across the board
## 3.0.55
- Auto-updates no longer install while a CLI is attached to the Hub. The update is recorded at startup and installed on exit, once the Hub confirms nothing else is attached, so a background update can no longer swap the package out from under a live session and kill it with `Hub connection closed (code=1006)`. `cline update` still installs immediately and now tells you the update applies on next start
- Added protections for an update landing under CLI 3.0.54 and earlier, whose updater restarts the Hub mid-session and then rejects every replacement, bricking a running session. The newly installed package defuses that path during install instead of leaving it to fire
- Fixed two Cline installations on different builds shutting each other's Hub daemon down in a loop, which killed every live session with an abnormal socket close. Build identity is now compared through a total order, so at most one side of a pair can ever decide to retire the other (from SDK v0.0.75)
- A newer build no longer replaces a Hub that is still serving sessions — it attaches to it and the swap happens on a later launch, instead of the sessions dying mid-handshake (from SDK v0.0.75)
- Removed the "outdated Hub" notice. It reported a state you cannot act on, and the toast was capped narrower than the message, so it rendered cut off before the reassuring half of the sentence at every terminal width. The prompt for a genuine build mismatch, where there is something to do, is unchanged
- Streaming assistant markdown no longer flashes back to raw text. Settled headings, links, and code stay rendered as new chunks arrive instead of the whole message being rebuilt and re-highlighted on every chunk, which also stops the transcript from jumping vertically mid-stream
- Web search calls and their results from models that run search natively now render in the transcript (from SDK v0.0.75)
- Idle plugin sandbox processes are now reclaimed instead of lingering for the life of the session (from SDK v0.0.75)
-`cline doctor fix` now reports honestly: processes that survived a kill are separated from ones that appeared while the fix ran, a live parent respawning a daemon is named, and a startup lock held by a running process is reported as held rather than leaked (from SDK v0.0.75)
- Refreshed the model catalog, which adds Crusoe as a provider and updates model lists and per-provider default models across the board (from SDK v0.0.75)
## 3.0.54
- Fixed the Claude Code provider being unusable for agentic work: the provider now runs its own native tools instead of receiving tool definitions it cannot bridge, the session is anchored on your workspace directory instead of inheriting the host's cwd, and `~/.claude` plus project settings are loaded so your permission rules apply. File edits under the workspace are auto-approved; command execution stays gated by your own Claude settings (from SDK v0.0.74)
- Fixed truncated tool-call JSON being silently "repaired" into wrong arguments — a payload with an unterminated string is now rejected rather than getting an invented terminator (from SDK v0.0.74)
- Fixed strict providers rejecting a turn with "user message must have content" when a message's content held only empty text parts (from SDK v0.0.74)
- Fixed a mid-turn crash on streamed tool calls with non-zero or non-contiguous indexes, hit through LiteLLM's Anthropic passthrough (from SDK v0.0.74)
- Managed Hub daemons now upgrade directionally: when another Cline install ships a newer Hub build, the CLI attaches to the newer daemon and prompts you to update and restart instead of the two installs repeatedly retiring each other's daemons. Yolo and sandbox sessions, which never attach to the shared Hub, are not interrupted by that prompt (from SDK v0.0.74)
- Fixed the Hub daemon logging an unhandled `hub server close failed` error and exiting non-zero whenever a client was still connected at shutdown (from SDK v0.0.74)
- Fixed per-task token totals being inflated roughly 5x on cache-heavy sessions — token telemetry now reports disjoint uncached-input, cache-read, and cache-write buckets instead of re-counting the whole cached conversation on every request (from SDK v0.0.74)
- Upgrading the CLI now retires an already-running Hub daemon and respawns it on the new code, instead of the upgraded CLI continuing to talk to a daemon executing the previous release
## 3.0.53
- Fixed the CLI reconnecting to a stale Hub daemon after an upgrade. Hub daemons now carry a runtime build fingerprint, so an upgraded CLI retires and respawns a daemon still running older code instead of attaching to it (from SDK v0.0.73)
- Fixed compaction being silently skipped on reasoning models. The summarizer no longer hardcodes a 1024-token output cap — it honors your max output tokens setting, defaults to 4096 (lowered when the model reports less), and logs a diagnostic when a summary comes back empty (from SDK v0.0.73)
- Added Fable 5 (`claude-fable-5`) to the Vertex model catalog. Pricing is intentionally omitted because Vertex bills region-dependently, so cost shows as unknown rather than wrong (from SDK v0.0.73)
- Custom Vertex model IDs are now passed through unchanged, routing Claude-style IDs to the Anthropic-on-Vertex path (from SDK v0.0.73)
## 3.0.52
- Added `cline mcp uninstall` for removing an installed MCP server
- Schedules now reuse your saved provider settings instead of needing provider configuration of their own
- Queued messages are legible on light-theme terminals — they were previously rendered in a color that washed out against a light background
- MCP tool results render as readable text in the TUI instead of escaped JSON, and binary payloads survive being expanded instead of being mangled
- Malformed tool input/output payloads no longer break rendering — the formatters degrade gracefully instead of throwing
- Prompts queued during a turn now survive being interrupted: they are preserved across aborts, drained after a turn aborts itself, and the stop is surfaced instead of leaving the queue silently dropped (from SDK v0.0.72)
- Session context stays durable across aborts and hub restarts, so an interrupted session resumes with the state it had (from SDK v0.0.72)
- A hung MCP server no longer takes down session creation, and stdio servers that were never configured get a 30-second initialize budget instead of blocking indefinitely (from SDK v0.0.72)
- Remote SSE MCP servers surface an OAuth authorization prompt on a 401 instead of failing outright, and pre-registered OAuth clients are supported for setups without dynamic client registration (from SDK v0.0.72)
- LiteLLM requests route through Chat Completions instead of the Responses API, fixing calls against LiteLLM proxies (from SDK v0.0.72)
- Network interruptions that happen mid-stream but before any model output are retried instead of failing the turn (from SDK v0.0.72)
- Vertex ADC token refreshes use the configured fetch, so they work behind proxies and custom transports (from SDK v0.0.72)
- Checkpoint diffs include files that were untracked when the snapshot was taken, and checkpoints are picked up when git is initialized part-way through a session (from SDK v0.0.72)
- Scheduled run reports carry execution context — readable headers, schedule metadata, durations, and lifecycle error details (from SDK v0.0.72)
## 3.0.51
- Reasoning effort now applies consistently across providers instead of going through per-provider thinking overrides, including Ollama, and asking for reasoning to be off is respected everywhere (from SDK v0.0.71)
-`meta/muse-spark-1.2-contributor` is now selectable on the Cline provider, alongside a refreshed model catalog (from SDK v0.0.71)
- Error telemetry now reports the model that was actually in use for the run (from SDK v0.0.71)
## 3.0.50
- Added user-selectable color themes to the interactive TUI. Pick one with `/theme`, the command palette, or the Theme row in `/settings` — the picker previews each theme live. Built-in themes are Auto (terminal-adaptive, the default), Cline Dark, Cline Light, Tokyo Night, Gruvbox Dark, Nord, Dracula, Catppuccin Mocha, One Dark, Solarized Dark, and Solarized Light. Named themes paint the background, foreground, accents, syntax highlighting, and diff colors, and `CLINE_THEME` overrides the persisted choice at startup
- The git branch shown below the prompt now updates when you switch branches from another terminal or your editor, instead of showing whatever was checked out when the TUI started
- Telegram slash commands such as `/clear` now reach the connector command host — the Telegram library was intercepting them and they were silently dropped
- Racing connector launches no longer collide: an instance is claimed before it opens socket mode, the hub supervises connector processes, and `doctor`/`connect` skip connectors that are already starting. Connector tools are also enabled by default, and the Slack greeting is no longer replayed on reconnect
- Auto-approval settings are now honored over ACP
- Plan mode now hard-blocks file-editing shell commands instead of relying on prompting alone — `run_commands` stays available for read-only investigation, but file-manipulation commands, in-place editors (`sed -i`, `perl -i`), redirection to files, mutating git subcommands, package installs, and nested command strings (`sh -c`, `eval`, `sudo`) are rejected, on Windows and PowerShell too (from SDK v0.0.70)
- A turn that ends with a completed plan is no longer rendered as a failed turn when a plan-blocked command was its only tool call
- Running out of context is now recovered from instead of failing with a raw provider error: the run force-compacts and retries once, and the cases that genuinely cannot be recovered report why (from SDK v0.0.70)
- Empty model responses are now retried on every provider, not just Ollama — OpenRouter, Cline, and OpenAI-compatible endpoints previously failed the task outright with "Model returned empty response" (from SDK v0.0.70)
- Claude 4.6+ and 5.x models are no longer rejected with "thinking.type.enabled is not supported" when they resolve from the offline catalog or from a hand-typed model id (from SDK v0.0.70)
- Bedrock prompt caching works again — the provider was sending a cache format Bedrock silently discards, so cache reads and writes were always 0 — and Bedrock foundation models are now routed through geo inference profiles (from SDK v0.0.70)
- Reasoning models on OpenAI-compatible endpoints now receive `max_completion_tokens` instead of the rejected `max_tokens`, and requests to models without image support substitute the image content instead of failing (from SDK v0.0.70)
- MiniMax now inherits its default model from models.dev, and the model catalog picked up two new providers, Infomaniak and SCX.ai (from SDK v0.0.70)
- Upgraded the model layer to AI SDK 7 and switched Ollama to the native AI SDK provider (from SDK v0.0.70)
- Error telemetry no longer reports the same provider failure twice, and repeated failures from unattended retry loops are rate-limited (from SDK v0.0.70)
## 3.0.49
-`/undo` works again once the agent has used tools — the checkpoint picker counted tool results as user turns, so restore aborted with "Could not find user message for run N"
- Checkpoints are actually created again; a run-boundary regression meant none were ever recorded in the CLI (from SDK v0.0.69)
- Checkpoint restore is now a full workspace rewind: files Cline created during the task come back at their checkpoint-time content and files created after the checkpoint are removed, while `.gitignore`d paths (build output, `node_modules`, `.env`) are left alone (from SDK v0.0.69)
- After a restore, the rewound message is prefilled as plain text instead of the raw `<user_input mode="act">` envelope
- Ollama's response-start timeout is now 5 minutes instead of 30 seconds, so cold-loading a large local model no longer errors out mid-load (from SDK v0.0.69)
- Empty Ollama responses are now retried instead of failing the task with "Model returned empty response" (from SDK v0.0.69)
- Migrated users whose stored Cline model id isn't in the catalog now fall back to the default model instead of sending an unknown model id on every request (from SDK v0.0.69)
- The ClinePass promo dialog can be dismissed with any key (Enter still opens the subscription page), and it is marked as shown when it appears, so force-quitting no longer replays it on every launch
- Opening a URL no longer crashes the CLI on hosts without an opener binary (headless Linux without `xdg-open`); WSL2 containers now use `xdg-open`, Windows tries the absolute PowerShell path first, and `cline doctor log` converts Linux paths to `\\wsl$` UNC paths
- The hub now restarts through the installed wrapper after a Unix self-update, so npm cannot reuse a deleted cached executable
- ACP: ClinePass is selectable as a provider, organizations can be selected, session resolution and text rendering on session restart are fixed, and agent errors now describe the actual failure
- Provider errors forwarded through the Vercel AI Gateway now surface the real upstream message instead of a raw Zod dump or `[object Object]` (from SDK v0.0.68)
- Cline free models and recommended models now show their real display names in the model picker (from SDK v0.0.68)
- Sessions rooted at the filesystem root (`/`) no longer fail every command (from SDK v0.0.68)
- On Windows, PowerShell commands now travel over UTF-8 stdin, so non-ASCII commands survive the active code page and long commands are not capped by the command-line limit (from SDK v0.0.68)
- The live model catalog no longer drops the video input capability (from SDK v0.0.68)
- Removed the CLI promo code flow
## 3.0.48
-`cline history` now opens inside the existing TUI, with resume and delete actions, instead of rendering a second view in the same process
- Connector threads (Slack, Discord, Telegram, Linear, Google Chat, WhatsApp) now recover when the session they were bound to is gone — the stale binding is dropped and the turn replays against a new session, instead of failing with "session not found" until `threads.json` is edited by hand
-`cline --help` now reports the real default `--config` and `--data-dir` paths
- The per-server `timeout` in `cline_mcp_settings.json` is now honored for `initialize`, `tools/list`, and `tools/call`, so slow MCP servers no longer fail against a hardcoded 5s limit (from SDK v0.0.67)
- Reasoning controls are now routed from the models.dev catalog across providers, with clamped budgets and correct per-provider encoding (from SDK v0.0.67)
- OpenRouter now defaults to `anthropic/claude-sonnet-5` (from SDK v0.0.67)
- Fixed the China and international endpoint toggles being ignored for Qwen, Moonshot, and Z AI (from SDK v0.0.67)
- Legacy API keys are now migrated for every secret-backed provider (from SDK v0.0.67)
- Legacy OpenAI Compatible model-info overrides now survive into the seeded `models.json` (from SDK v0.0.67)
- Fixed auto-compaction state being rejected as stale, which added a redundant summarizer call on every turn past the compaction trigger (from SDK v0.0.67)
- Fixed checkpoint restores across session resumes (from SDK v0.0.67)
- Tool calls that pass line numbers as strings (`insert_line`, `read_files` bounds) are now accepted instead of erroring (from SDK v0.0.67)
- A legacy single-file `.clinerules` no longer aborts the config scan (from SDK v0.0.67)
- Plugins can now emit telemetry through `ctx.telemetry` (from SDK v0.0.67)
## 3.0.47
- Free Cline models are now supported end to end: free models show as "(free)", and hitting the free limit renders a dedicated card with the reset time (from SDK v0.0.66)
-`/settings` general toggles (plan/act mode, tool auto-approve, compaction mode) now persist across restarts
- Upgraded the TUI stack from opentui 0.1.102 to 0.4.3
- Fixed a grey panel left behind on screen after closing a dialog (model picker, help, command palette) — a leftover from the opentui upgrade
- Fixed a React duplicate-key warning when `read_files` listed the same path more than once
- Aborting a task no longer risks killing the shared hub daemon
- Connector status delivery failures are no longer fatal to the turn
- Agentic compaction is now the default context-compaction strategy, with fixes for it silently falling back to basic compaction and for tool-heavy transcripts that could never find a cut point (from SDK v0.0.66)
- Editor edits preserve a file's existing line endings, fixing failed exact-match edits on CRLF files (from SDK v0.0.66)
- Broader built-in provider coverage, now generated from models.dev (from SDK v0.0.66)
- Updated the bundled model catalog (from SDK v0.0.66)
## 3.0.46
- Fixed out-of-credits detection so the CLI reliably recognizes the Cline API's real `insufficient_credits` (402) error and shows the "add credits" card instead of a generic error
## 3.0.45
- Smaller install: the Claude Code and Codex providers are now optional and loaded on demand, cutting `npm i -g cline` from ~640MB to ~285MB (from SDK v0.0.65)
- Kimi K3 is now available as a ClinePass model (from SDK v0.0.65)
- Runs now retry once after refreshing expired OAuth credentials (from SDK v0.0.65)
- Team runs: the spawn tool is no longer exposed to teammates, and errored teammate runs now report as failed instead of completed (from SDK v0.0.65)
- Hub status output now includes version numbers
- Updated the bundled model catalog (from SDK v0.0.65)
## 3.0.44
- Improved max output token handling across providers (gateway routing, OpenAI vendor, and reasoning models) (from SDK v0.0.64)
- Frontmatter and configuration files that start with a UTF-8 byte order mark (e.g. saved by Windows editors) now parse correctly (from SDK v0.0.64)
## 3.0.43
- The CLI now automatically trusts your operating system's certificate store, so it works behind corporate proxies and TLS-inspecting firewalls without manually setting `NODE_EXTRA_CA_CERTS` (fixes "unable to get local issuer certificate" errors, including Windows intermediate CA stores)
## 3.0.42
- Fixed Ollama native API routing so context window and timeout settings work again
## 3.0.41
- Compaction now shows progress status in the TUI
- Model IDs are now suggested from OpenAI-compatible endpoints when configuring a provider
- Workspace git info (branch/remote) is now persisted and refreshed across sessions
- Compaction no longer runs during an active turn
- Fixed a crash when the terminal title was updated during TUI teardown
- The API key fallback hint is now highlighted for better visibility
- Benign git states are no longer reported as workspace initialization errors
## 3.0.40
- Added a manual API key escape hatch for Cline OAuth providers, so you can enter a key by hand from settings
- Fixed provider config not reloading when switching models
- Fixed auto-update failing to detect Bun global installs after symlink resolution
- Fixed unexpected logouts caused by transient network or server errors during token refresh
- The ClinePass usage-limit error is now surfaced clearly when you hit the limit
- Session id is now preserved when continuing within the same session
- Hardened context compaction budget handling
## 3.0.39
- You can now select Cline free models on the ClinePass provider in the model picker
- Removed the retired ClinePass GLM 5.1 model
- Fixed OpenAI Codex model metadata under the GPT Subscription provider
-`str_replace` edits now report accurate diffs
- Fixed context compaction so canonical session history is preserved
- The detached hub daemon now emits telemetry, and telemetry identity now includes `user_id`
- Cline provider requests now send versioned client-identity headers
## 3.0.38
- New plan/act accent palette: act mode is now blue (`#79b8ff`) and plan mode amber, replacing the old cyan/yellow — applied across dialogs, the model selector, config, onboarding, markdown, and syntax highlighting, with light-theme variants tuned for contrast
- Restyled chat input: a minimal frame with full-width horizontal rules and a bold accent prompt glyph instead of the tinted background, plus slimmer user-message bubbles
- Assistant markdown accents are now tinted by the mode (plan/act) they were produced in
- Polished the status bar usage display and ClinePass model name
- Harmonized the success/diff green and dark syntax-highlighting colors with the new brand palette
- The thinking-level picker now defaults its cursor to Medium instead of Off
-`read_files` now tolerates malformed input from weaker models: line-range entries (`start_line`/`end_line`) sent as separate array items are coalesced back onto the preceding file path instead of being rejected (from SDK v0.0.58)
- Models in the live catalog that don't report a context window now default to a 128K input-token limit, so under-specified models get a usable context budget (from SDK v0.0.57)
## 3.0.37
- Weaker models (e.g. DeepSeek) that emit malformed tool calls — wrong argument types or truncated JSON — are now handled gracefully and run instead of erroring out
- Plan/act mode switches are now visible to the model, so it knows when you change modes mid-session
- Fixed plan/act mode notices being dropped from prompts sent to the model
- Fixed a race where switching modes in an empty session could trigger an unexpected restart
## 3.0.36
- Fixed plan mode's `switch_to_act_mode` tool not taking effect until the end of the turn: the model would keep running with plan-mode tools (no file editor) and fall back to editing files through shell commands. Switching to act mode now ends the plan-mode run and automatically continues with the approved plan using the full act-mode toolset. A Tab mode toggle racing a completing turn can no longer auto-start plan execution you didn't approve.
## 3.0.35
- ClinePass is now enabled for all CLI users
- Recover missing interactive sessions when reading messages
- Format structured commands in history export
- Add the subscription promo code when linking to the dashboard subscription page
- Add Tencent TokenHub as a provider (from SDK v0.0.55)
- Fix first-prompt truncation on high-output models (e.g. MiniMax M3) that could immediately auto-compact and cut the initial task down to just the input wrapper (from SDK v0.0.55)
- Use a curated default when migrating legacy provider settings (from SDK v0.0.55)
- Advertise run commands as shell strings (from SDK v0.0.55)
- Refresh the bundled model catalog with the latest provider models (from SDK v0.0.55)
## 3.0.34
- Fixed the ClinePass upgrade notice appearing immediately after completing onboarding.
- Improved the wording of the ClinePass onboarding step.
- Streamlined the Cline provider picker by merging the subscription and usage/billing options into one and removing the credits link.
## 3.0.33
- Show a ClinePass subscription URL as a fallback during onboarding so you can still subscribe if the subscription screen can't open automatically
- Hide the ClinePass promo for users who already have a ClinePass subscription
- Use an adaptive plan accent color for ClinePass prompts so they fit the active theme
## 3.0.32
- Improved the ClinePass onboarding experience
- Added an intermediate step before going to ClinePass model selection
- Made the ClinePass subscription screen selectable
- Promoted ClinePass in the startup notice
- Used "ClinePass" as one word consistently and refined the provider UI copy
- More accurate context compaction and clearer error messages (from SDK v0.0.54)
## 3.0.31
- Show when request cost is covered by your Cline subscription
- Prompt to switch to ClinePass when you run out of credits, and list ClinePass features in the not-subscribed message
- Added an option to open the subscription page from the ClinePass options
- Added marketplace uninstall support and surfaced plugin-bundled skills
- Require quoted prompts for one-shot mode
- Capped MCP tool names at 64 characters for OpenAI-compatible providers
- Updated coupon code
## 3.0.30
- Added a token count to the status bar, shown alongside cost
@@ -364,6 +367,34 @@ bun run dev -- --interactive --config /tmp/cline-test
Or set `CLINE_FORCE_ONBOARDING=1` to force the onboarding view regardless of existing config.
### Manually testing the TUI (agents / headless environments)
[tuistory](https://github.com/remorses/tuistory) is installed as a devDependency. It wraps the TUI in a named background PTY session that can be scripted from a plain shell — no real terminal or display needed. This is the preferred way for AI agents (or anyone in a headless environment) to poke at the interactive TUI:
# Wait reactively for the chat view (no sleep guessing)
bunx tuistory -s cline wait"What can I do for you?" --timeout 30000
# Interact and inspect
bunx tuistory -s cline type"/settings"
bunx tuistory -s cline press enter
bunx tuistory -s cline snapshot --trim # current screen as text
bunx tuistory -s cline screenshot # current screen as a styled PNG
# A human can watch/drive the same session from another terminal
tuistory attach -s cline
# Tear down
bunx tuistory -s cline close
```
The same engine powers the `test:e2e:tuistory` vitest suite (`src/cli.tuistory.e2e.test.ts`), which uses the programmatic `launchTerminal()` API for assertions against the emulated screen.
@@ -270,6 +270,9 @@ The postinstall script runs in diverse environments (CI, Docker, restricted perm
### Windows
Windows binaries are `.exe` files. The build script appends `.exe` to the output filename on Windows targets. The resolver handles this. npm on Windows generates `.cmd` shims for bin entries automatically.
### Windows code signing
Windows application control (Smart App Control, WDAC, AppLocker) blocks unsigned executables at launch, regardless of how they were installed — npm distribution gets no exemption ([#12934](https://github.com/cline/cline/issues/12934)). The publish workflow Authenticode-signs `cli-windows-x64/bin/cline.exe` and `cli-windows-arm64/bin/cline.exe` with Azure Trusted Signing before publishing, via the `.github/actions/sign-windows-cli` composite action. Signing runs on the Linux publish runner using [jsign](https://ebourg.github.io/jsign/) (`--storetype TRUSTEDSIGNING`) with an OIDC-federated Entra app, then verifies the signature chain with `osslsigncode` against the Microsoft Identity Verification Root CA 2020. If all `AZURE_*` / `AZURE_TRUSTED_SIGNING_*` repository secrets are absent, the action logs a warning and the release ships unsigned rather than failing; if only some resolve (a typo'd or renamed secret), the release fails loudly instead. The certificate profile secret is suffixed `_CLI` because the desktop app will later get its own profile; the other five secrets are shared. Note that signing bun-compiled executables requires Bun >= 1.2.23 (earlier versions located the embedded bundle relative to the end of the file, which signing corrupts).
### File permissions
Compiled binaries need to be executable (`chmod 755`). The build script sets this after copying. The postinstall also sets permissions on the cached binary. Some npm packaging steps can strip permissions, so both handle this defensively.
@@ -221,13 +221,15 @@ In chat surfaces, connector slash commands include `/help`, `/start`, `/new`, `/
Schedule agents on cron-like intervals or external events.
If `--provider` and `--model` are omitted, schedules use the last configured
provider and model. If only `--provider` is given, the schedule uses that
provider's saved model.
```sh
cline schedule create "Daily code review"\
--cron "0 9 * * MON-FRI"\
--prompt "Review PRs opened yesterday and summarize issues."\
--workspace /path/to/repo \
--provider cline \
--model openai/gpt-5.3-codex \
--timeout 3600\
--tags automation,review
@@ -257,10 +259,10 @@ Schedules can route results back to chat surfaces with `--delivery-adapter`, `--
| `--hooks-dir <path>` | Additional hooks directory hint for runtime hook injection |
| `--acp` | ACP (Agent Client Protocol) mode |
| `--thinking [none\|low\|medium\|high\|xhigh]` | Model thinking level when supported. Defaults to `medium` when the flag is provided without a level; thinking is off when the flag is omitted. |
| `--compaction <agentic\|basic\|off>` | Context compaction mode. Defaults to `basic`; use `agentic` for LLM compaction or `off` to disable. |
| `--compaction <agentic\|basic\|off>` | Context compaction mode. Defaults to `agentic`; use `basic` for local truncation or `off` to disable. |
| `--retries <count>` | Maximum consecutive mistakes (retries) before halting (default: `3`) |
| `--json` | Output NDJSON instead of styled text |
| `--data-dir <path>` | Use isolated local state at `<path>` instead of `~/.cline` (enables sandbox mode automatically) |
| `--data-dir <path>` | Use isolated local state at `<path>` instead of `~/.cline/data` (enables sandbox mode automatically) |
| `--auto-approve [true\|false]` | Set tool auto-approval for all tools |
| `--kanban` | Run the external `kanban` app |
| `-y, --yolo` | Skip tool approval prompts, enable `submit_and_exit`, and disable spawn/team tools by default |
@@ -346,9 +348,24 @@ Desktop-integrated approval mode is also supported via env wiring (`CLINE_TOOL_A
-`CLINE_LOG_NAME` - Logger name embedded in runtime log records
-`CLINE_DEBUG` - Set to `1`/`true` to print wrapper diagnostics (e.g. the CA bundle summary)
`--key` takes precedence over environment variables.
## Certificate trust
The CLI automatically trusts your operating system's certificate store, so it
works behind corporate TLS-inspecting proxies and with self-signed/internal
endpoints without any setup. On launch the `cline` wrapper harvests the OS trust
anchors and writes them to `~/.cline/cli-node-extra-ca-certs.pem`, then points
the runtime's `NODE_EXTRA_CA_CERTS` at that bundle. The file is regenerated when
it changes and is safe to delete (it is rebuilt on the next run).
If you set `NODE_EXTRA_CA_CERTS` yourself, your certificates are **merged** into
that bundle alongside the system store rather than replacing it. Run with
`CLINE_DEBUG=1` to see how many OS and user CAs were loaded and where the bundle
was written.
## Contributing
See [DEVELOPMENT.md](./DEVELOPMENT.md) for local development setup, monorepo structure, and TUI architecture. See [DISTRIBUTION.md](./DISTRIBUTION.md) for how the CLI is packaged and distributed.
it("strips the <user_input> wrapper from replayed user text",()=>{
// Persisted user messages keep their runtime-generated wrapper. Replaying
// it verbatim leaked markup to the client, which rendered the unknown
// element as bare text (a one-word prompt showed up as just its content
// with the wrapper swallowed).
expect(
translateHistoricalMessage({
role:"user",
content:'<user_input mode="act">s</user_input>',
}),
).toEqual([
{
sessionUpdate:"user_message_chunk",
content:{type:"text",text:"s"},
},
]);
expect(
translateHistoricalMessage({
role:"user",
content:[
{
type:"text",
text:'<user_input mode="plan">lets do it</user_input>',
},
],
}),
).toEqual([
{
sessionUpdate:"user_message_chunk",
content:{type:"text",text:"lets do it"},
},
]);
});
it("strips mode notices and formats slash commands for display",()=>{
expect(
translateHistoricalMessage({
role:"user",
content:
'<user_input mode="plan"><mode_notice>The user switched from act mode to plan mode before sending this message.</mode_notice>\nare you okay?</user_input>',
}),
).toEqual([
{
sessionUpdate:"user_message_chunk",
content:{type:"text",text:"are you okay?"},
},
]);
expect(
translateHistoricalMessage({
role:"user",
content:
'<user_command slash="team">spawn a team of agents for the following task: inspect rpc startup</user_command>',
"\nSome of these were respawned by a live parent (100); stop the parent process listed above, then re-run. The rest started after the fix began and were not targeted.",
return"\nThese processes started after the fix began, so they were not targeted. Re-run to see whether they persist.";
}
if(respawned.length===pids.length){
return"\nThese processes were respawned by a live parent. Stop the parent process listed above, then re-run.";
}
return`\nSome of these were respawned by a live parent (${respawned.join(", ")}); stop the parent process listed above, then re-run. The rest started after the fix began and were not targeted.`;
"Stdio MCP install requires a command after the server name, for example: cline mcp install fs -- npx -y @modelcontextprotocol/server-filesystem /tmp",
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.