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