* 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(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(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(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(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
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