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.
* 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>
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.
* 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
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): 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
* 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(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): 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>
* 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 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>
* 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>
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.