* fix(vscode): continue the surviving session on resume instead of rebuilding with the original task text (#13175)
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilt the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
The preserved conversation history is the source of truth on resume, so
the fallback prompt now just asks the model to reassess the history and
continue, matching the legacy resume prompt which also never resent the
original task. User-typed text still takes precedence when provided.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): continue the surviving idle session on follow-ups instead of rebuilding
Stopping a turn keeps the session alive, but every idle follow-up (bare
Resume after Stop, and typed follow-ups after a completed turn) tore
that session down and rebuilt it from persisted task history before
sending. Continue the matching idle session in place instead, the same
way the CLI reuses the live session after an abort. Rebuilding from
history now only happens when no live session matches the displayed
task (task opened from history, extension host reload).
A bare resume still needs a prompt to start a turn, so it sends the
neutral [TASK RESUMPTION] prompt (shared with the rebuild fallback and
hidden from the transcript); user-typed content is echoed and sent
as-is. If the send lands while the abort is still settling, the runtime
auto-queues it and drains once the abort completes.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): consolidate follow-up send paths in SdkFollowupCoordinator
Now that idle follow-ups continue the live session in place, the
two-mode sendToActiveSession helper was redundant: its non-queued branch
duplicated continueIdleSession minus the bare-resume prompt. Split it
into a single-purpose queueToActiveSession and fold the idle no-task
send into continueIdleSession, flattening askResponse's decision tree
to: queue onto a running turn, continue a matching live idle session,
rebuild from history, or abandon.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): reuse the existing neutral resumption prompt for bare resumes
Drop the newly invented long resumption wording in favor of the phrase
that already existed as the no-history fallback and that the transcript
hiding logic and test fixtures recognize: '[TASK RESUMPTION] Please
continue where you left off.' The net change to resumeSessionFromTask
against main is now just deleting the branch that resubmitted
historyItem.task as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilds the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
Bare resumes now always use the neutral prompt that already existed as
the no-history fallback; user-typed text still takes precedence. This
matches the legacy resume prompt (responses.taskResumption), which only
ever included user-supplied text as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): hide synthetic prompts from the queued-prompt echo
A send that races a settling abort is auto-queued by the runtime, so a
bare Resume can reach the pending_prompt_submitted echo carrying the
synthetic [TASK RESUMPTION] prompt. Echoing it leaked model-facing text
as a visible user bubble and shifted the visible-user-message ordinals
that edit/regenerate mapping relies on. Filter synthetic prompts with
isSyntheticUserPrompt, keeping user attachments visible (matching
isSyntheticSdkUserMessage semantics).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): preserve LiteLLM input token limits (#13293)
* fix(vscode): preserve LiteLLM input token limits
* fix(vscode): prefer live LiteLLM model metadata
* fix(vscode): generalize private catalog metadata
* test(vscode): preserve llms exports in vscode lm mock
* fix(vscode): point provider signup URLs at their API key pages (#13337)
* fix(vscode): point Mistral signup URL at the general API keys console
The Mistral provider's signup link led to the Codestral console, which
issues Codestral-scoped keys that fail with 401 on api.mistral.ai — the
endpoint the provider actually calls. Point it at the general API keys
page instead.
Fixes#13288
* fix(vscode): deep-link DeepSeek and Fireworks signup URLs to their API key pages
Both pointed at marketing homepages; link straight to the key-creation
pages instead, matching the rest of the registry and the desktop app's
provider-key-urls map.
* fix(ci): always build the legacy bundle from the legacy-extension branch (#13349)
The combined-VSIX workflow took legacy-ref as a free-form dispatch input
with no publish-time validation (next-ref has one: publish requires main).
Any typed ref — a PR merge ref, an unprotected branch — would be built
into the published VSIX by the environment-less build job, and the publish
environment approver only ever sees an opaque prebuilt artifact, so the
approval protected the marketplace PAT but not the shipped bytes.
Remove the input entirely and hardcode the protected legacy-extension
branch, which makes that branch's protection rules load-bearing for
releases. The tested-sha pinning between test-legacy and build is
unchanged. publish-extension skill dispatch command updated to match.
* fix(ci): lock the legacy publish workflow to the legacy-extension branch (#13350)
The branch dispatch input was a free-form string with no validation. Both
jobs checked it out and ran full npm lifecycle scripts from it: the publish
job next to VSCE_PAT/OVSX_PAT (and npm run publish:marketplace executes a
script from that same ref with the PATs in env), and the test job with NO
environment approval at all while inheriting the workflow-level
contents/packages/checks/pull-requests write grants. A dispatch pointing at
e.g. refs/pull/N/head would run outside-contributor code with the
marketplace keys behind one approval, or with a repo-write token behind
none.
Remove the input and hardcode the protected legacy-extension branch, drop
the workflow-level permissions to contents: read, and elevate only the
publish job to contents: write (tag push + GitHub release). The branch
input's default was legacy-extension, so normal publishes are unchanged.
publish-extension skill dispatch command updated to match.
* fix(vscode): SDK remote-config parity — refresh coordination, session gating, and fail-closed opt-out (#13226)
* feat(desktop): native notifications (#13166)
* feat(desktop): native notifications
* macos target
* fix(desktop): isolate macOS dev app identity
* fix(desktop): address notification review feedback
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched (#13310)
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched
Toggling an auto-approve setting while a task is open writes
autoApprovalSettings into the StateManager's task-settings overlay
(updateAutoApprovalSettings -> setTaskSettings). The SDK controller never
cleared that overlay on clearTask/showTaskWithId (the legacy controller
did), so after New Task the stale overlay kept shadowing global settings
in getGlobalSettingsKey(): toggle RPCs were accepted into global state,
but every posted state still carried the overlay's old version, which the
webview rejects as not newer - the auto-approve checkboxes froze forever.
Restore legacy parity in SdkTaskControlCoordinator: drop the overlay
(persisting pending writes first) in clearTask() and before installing a
different task's proxy in showTaskWithId().
Fixes#13260
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* changeset
* test(vscode): add end-to-end regression test for auto-approve freeze after New Task
Wires the real StateManager, the real updateAutoApprovalSettings handler,
and the real SdkTaskControlCoordinator.clearTask() together with the
webview's version gate modeled on ExtensionStateContext, pinning the
end-to-end invariant behind #13260: checkbox toggles must keep reaching
the webview after a mid-task toggle followed by New Task. Verified the
test fails when the clearTaskSettings() call is removed from clearTask().
* fix implicit any in regression test
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): show provider web-search support under the settings toggle (#13328)
* feat(desktop): show provider web-search support under the settings toggle
The global Web search toggle silently does nothing unless the session's
provider offers native web search, which made the setting read as if it
worked with any provider. The desktop General settings row now explains
that only providers with built-in web search honor it, and shows a live
status line: which connected providers are ready to use it (no extra
setup needed), or an amber warning with a link to the Models section
when none of them support it.
Support is resolved in the webview via a new providerOffersModelTool
helper in @cline/llms (browser export), sharing the same builtin-manifest
source of truth as the runtime's supportsModelTool attachment check.
* fix(desktop): address review — refetch web-search status on catalog invalidation, clarify per-model support
Greptile P2: the one-time catalog fetch could race an in-flight provider
save and show stale status; the row now refetches when the provider
catalog cache is invalidated (fired after saves complete).
Greptile P1: the ready line implied every model on the provider works;
Vertex excludes Claude routes, so the copy now scopes the promise to
models that support it.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat(ui/desktop): collapse finished runs into a work summary and remove hover-state dead space (#13315)
* feat(ui): add WorkActivity collapsed-run summary and float message actions as a pill
WorkActivity/WorkActivityTrigger/WorkActivityContent fold a finished agent
run's working rows (tool calls, thinking traces, narration) behind a single
"Worked for 4m 12s · 14 tool calls" disclosure built on the shared animated
disclosure primitives, with formatWorkActivityLabel/formatWorkDuration
exported for consumers.
Message hover actions no longer rely on the transcript reserving blank space
below each message: the action row is now a self-backed pill (border,
blurred background, shadow) that floats over whatever follows, so
conversations can pack rows tightly without hover chrome colliding with the
next message.
* feat(desktop): collapse finished runs into a work summary and tighten chat spacing
collapseCompletedWork post-processes the grouped transcript: once a run ends
on assistant text with no further tool calls, its working rows fold into one
expandable WorkActivity row while the final answer stays visible. Runs are
delimited by user messages; the trailing run only collapses when the session
has stopped running and actually produced an answer, so live streams and
cancelled/failed tails keep their rows. Assistant messages carrying images
or media are treated as deliverables and never collapse.
The conversation list gap drops from gap-8 to gap-4 now that hover actions
are self-backed pills that need no reserved space, and user messages add
their own top margin so turn boundaries stay visually distinct.
* refactor(ui/desktop): work summary label wording, flat expansion, stable in-run rhythm
Feedback round on #13315:
- Label reads "Worked for 4m 12s and made 14 tool calls" instead of joining
with a dot; without a duration it falls back to "Made N tool calls".
- Expanded work rows render at transcript level — no rail or extra indent —
since tool rows and thinking traces already carry their own nesting when
expanded. The work content keeps the tight working-row rhythm.
- Live working rows (thinking traces + tool calls) now group into a 'run'
render item with the same tight 0.25rem rhythm, so there is no oversized
gap under a "Thought for Ns" row and every row keeps its exact position
when the finished run folds into the work summary. A trailing
answer-in-progress stays outside the group at transcript level, and pure
prose spans keep normal spacing.
- The transient "Thinking..." indicator moves inside the transcript column
and mirrors a trigger row's geometry, so the first real row replaces it in
place with no jump.
* style(ui/desktop): hover-pill metrics, right-pointing work chevron, scroll and spacing fixes
Another feedback round on #13315:
- Hover action pill: +2px internal padding, a trailing inset after the
timestamp (it sat flush against the pill border), and more clearance
between the message content and the pill (2px -> 6px; the hover bridge
grows to match).
- The work summary chevron points right while collapsed and continues
counterclockwise to point up when expanded.
- Conversation bottom padding drops pb-20 -> pb-8: the composer sits below
the scroller, so the padding only needs to clear a pinned action pill.
- Sending a message scrolls back to the bottom even if the reader had
scrolled up (new AutoScrollOnSend on the user-message count, which ignores
optimistic-bubble re-keying; @cline/ui now exports useConversation for
this).
- An assistant answer directly under its run's working rows pulls itself
0.5rem closer than the full transcript gap.
* style(desktop): leave a visible gap between a pinned action pill and the composer
pb-8 exactly matched the pill's ~40px footprint, so the last row's hover
actions sat flush against the composer top; pb-12 restores ~8px of daylight.
* style(desktop): widen the gap between the pinned action pill and the composer to ~24px
pb-12 left only ~8px of daylight under the pill; pb-16 reads comfortable
without reverting to pb-20's dead space.
* fix(desktop): keep the thinking indicator at the working-row offset mid-run
The indicator matched a trigger row's geometry but sat a full transcript gap
(1rem) below the last working row, while the tool/thinking row replacing it
joins the tight run group at 0.25rem — a visible upward jump. When the last
transcript item is working rows (or streamed assistant output), the
indicator now pulls up to the same tight offset; only at the start of a run,
under the user message, does it keep the normal gap.
* style(ui): calm the hover actions surface per team feedback
Borderless rectangle instead of the bordered pill: radius drops to
var(--radius), the side padding goes entirely (the icon buttons carry their
own hit areas), and the vertical padding halves. Blurred background and
shadow stay so it remains legible over following content.
* feat(ui/desktop): full-band hover reveal and iOS-style disclosure easing
The hover actions only appeared while the pointer was inside the message
box itself. The invisible bridge under each message now spans the full
height of the band the floating actions occupy (full row width), so
hovering anywhere in that strip reveals them. Sibling row types
(.cline-chat-tool, .cline-chat-work, and the desktop's run/tool groups)
become position: relative so they paint above the bridge — their own
content keeps its hover and clicks, and the bridge only wins in the band's
genuinely empty space.
All expandable rows (work summary, tool panels, thinking) open and close on
a 240ms symmetric ease-in-out cubic-bezier instead of the 60ms snap, with
chevron rotation on the same curve. Reduced-motion still disables both.
* revert(ui/desktop): drop the full-band hover reveal; quicken disclosure easing to 180ms
The full-band hover bridge (and the position: relative changes that made it
safe) is reverted per feedback — back to the narrow bridge that only spans
the gap under the message. The iOS-style ease-in-out on disclosures stays
but speeds up from 240ms to 180ms.
* fix(ui): recover live tool diffs that mount as a blank pierre skeleton
Live-streamed edit rows could show an empty diff for the whole run, with the
diff only appearing after the collapsed work row was expanded (fresh mount).
Root cause, confirmed by driving a live session and inspecting the element:
React StrictMode double-invokes @pierre/diffs' ref callback; the first
instance's async highlight work aborts on its immediate cleanup, and the
second instance adopts the abandoned half-rendered shadow tree as if it were
complete prerendered output — zero height, no code, no theme stylesheet,
permanently. A rendered diff always carries style[data-theme-css] in its
shadow root, so ToolFileDiff now checks for it shortly after mount and
remounts FileDiff (bounded attempts) when missing; the fresh host element
takes the normal render path and recovers within ~400ms. Verified live: the
diff now renders during the run.
* fix(desktop): keep interrupted runs expanded even with partial trailing text
The trailing-run collapse gated on 'ended with assistant text', which
misread a Stop that landed mid-answer as a finished run and folded the tool
calls the user wants to inspect. The gate is now the terminal status itself:
only completed (or restored-idle) sessions collapse the trailing run;
cancelled/failed/error tails keep their rows regardless of partial text.
(Greptile P1 on #13315 — matches the PR's stated rule.)
* feat(ui): share the markdown pipeline, chat polish, and ThinkingBlock across products (#13323)
* feat(ui): share the markdown pipeline, chat polish CSS, and ThinkingBlock
The desktop app and the cloud dashboard both consume @cline/ui yet rendered
assistant output differently, because Markdown policy and the thinking-trace
row lived app-side. This moves the shareable parts into the package:
- components/markdown (new export): the lazy Shiki code highlighter (GitHub
light/dark, pinned language set) and agentMarkdownControls — the standard
Streamdown configuration. streamdown/shiki/@shikijs/* become optional peer
dependencies, mirroring @pierre/diffs.
- components/markdown.css: the desktop's chat polish moves in — chat-scale
headings, outside list markers, single quiet code blocks with a
hover-revealed copy control, table cards. Kept unlayered so it beats
Streamdown's layered Tailwind utilities without !important.
- ThinkingBlock + formatThoughtLabel in agent-chat: the standard thinking
row (brain icon, Thinking/Thought-for-Ns label, streaming shimmer, rail
presentation, capped scrollable body). The shimmer and the
reasoning-hover-suppression rule move into agent-chat.css; triggers gain
the color transition the desktop applied locally.
Version bumps to 0.2.0-next.5 for the dashboard to pick up.
* refactor(desktop): consume shared markdown and thinking primitives from @cline/ui
The local Shiki highlighter, Streamdown controls, chat markdown polish CSS,
streaming-title shimmer, and reasoning hover-suppression rule are deleted in
favor of the @cline/ui versions (the highlighter test moves to the package's
suite). ReasoningBlock becomes a thin wrapper that hands MemoizedMarkdown to
the shared ThinkingBlock, and formatThoughtLabel re-exports from the package
so grouping code and tests keep their import path.
globals.css now imports @cline/ui/components/markdown.css (unlayered, so the
polish keeps beating Streamdown's layered utilities); the app keeps only what
is genuinely app-specific: link/image policy in markdown.tsx, selectability
rules, accent palettes, and the view-enter transition.
* style(ui/desktop): make thinking-trace prose legible
Thinking body text rendered too faint: plain muted-foreground plus the
desktop's font-thin weight. The shared thinking content now leans 75% of the
way back toward the body text color (still slightly de-emphasized), and the
desktop drops the thin font weight.
* ci(ui-publish): build @cline/shared before ui typecheck (#13354)
@cline/ui's generated-media imports @cline/shared/browser, which resolves to
shared's dist output. The build-shared step sat after typecheck/test/build,
so the first ui-publish dispatch since #13025 failed at Typecheck UI with
TS2307. Move the step to right after install.
* fix: run_commands object form without args routes through the shell instead of failing with ENOENT (#13336)
* fix: run_commands object form without args routes through the shell
The structured { command, args? } form of run_commands was always spawned
directly with shell: false. When a model emitted a full command line in
command with no args (e.g. { command: "echo hello" }), spawn failed with
ENOENT for any command containing a space, breaking command execution for
the whole session.
Direct exec now only applies when a non-empty args list is provided; the
object form without args is routed through getShellInvocation like the
string form. Schema descriptions are tightened so models put arguments in
args instead of embedding them in command.
Fixes#13279
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: trim structured-command schema descriptions
The union schema is only used for lenient validation of input the model
already sent; its descriptions never reach a model prompt. Keep them
short instead of restating executor behavior.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: simplify direct-exec comment in shell executor
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert: keep original structured-command schema description
The description never reaches a model prompt and the executor now handles
both shapes, so the wording change was cosmetic noise.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: gate direct exec on args key presence, not array length
Review feedback: an explicit empty args array is intentionally structured
input and stays direct exec; only an object with no args key is treated
as a full shell command line. Matches the key-presence rule already used
by the VS Code host's formatCommandForTerminal. Also replaces the
empty-args shell test (which was PowerShell-incompatible) with a test
pinning the direct-exec contract.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: normalize Gemini custom base URLs for legacy host-root values (#13329)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* docs: add GLM-5.3 to ClinePass models and reference pricing (#13357)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): stream run command output (#13179)
* feat(desktop): stream run command output
* fix(sdk): clean up detached command logs
* fix(sdk): reap detached logs after hub restarts
* fix(sdk): preserve live detached command logs
* fix(desktop): harden live command progress
* fix(sdk): recover detached logs for local hosts
* fix(desktop): reconcile command output tool rows
* fix(sdk): retain logs for surviving commands
* fix(core): prevent PID reuse from retaining detached logs
* fix(core): preserve detached logs on probe failures
* fix(core): retain detached logs during probe outages
* fix(desktop): resolve leftover merge conflict in messages projection test
Combine both sides of the assertion: main's incremented per-block
createdAt projection and this branch's toolCallId/hookEventName meta.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): make TUI dialog colors follow theme changes live (#13355)
* fix(cli): make TUI dialog colors follow theme changes live
Dialog content previously read the static palette constant, so open
dialogs (including the theme picker itself) kept the default dark-blue
accents while scrolling through theme previews. Add getDialogPalette /
useDialogPalette, which resolve dialog colors from the active theme's
dialog accents and re-render on every theme change, and migrate all
dialog-rendered components to it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(cli): derive dialog panel background from the active theme
Dark themes now lift their own background one OKLAB step for the dialog
surface, so panels keep the theme's hue instead of the library's fixed
#262626. DialogThemeSync pushes the surface into the dialog container
for new dialogs and repaints open panels, so the surface also follows
live theme previews. Light themes keep the neutral dark panel to match
the dark accent fallback and the light-on-dark dialog text.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: skill slash commands load via the skills tool instead of expanding into the user message (#13327)
* fix(desktop): show typed slash command instead of expanded skill markdown
The sidecar expands /skill and /workflow tokens into their instructions
before dispatching, so the runtime's persisted transcript only contains the
expanded text. After a turn (and when reopening a session) the webview
re-hydrates from that history and rendered the whole SKILL.md body as the
user's message; queue events echoing the expanded prompt could also add a
second user bubble, and fresh sessions were titled with the markdown's first
line. The CLI never shows this because its TUI keeps the typed text in its
own transcript and only sends the expanded prompt to the model.
Mirror that separation inside the desktop sidecar's display boundaries:
- history projection (readSessionMessages) inverts user text that starts
with a configured command's instructions back to '/name remainder',
which also repairs sessions recorded before this fix
- queue snapshots and chat_queued_prompt_start events echo the typed
prompt recorded at expansion time, so the webview's optimistic-bubble
re-key matches again
- an untitled session sent an expanded prompt gets titled from the typed
command instead of the instructions' first line
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't overwrite a mid-turn rename with the typed-command title
The untitled check ran before dispatch, so renaming a fresh slash-command
session while its first turn was running got clobbered by the post-turn
typed-command title. Re-check at write time and only replace a missing title
or the one the runtime auto-derived from the expanded prompt.
Also documents the inherent prefix-inversion ambiguity flagged in review:
text hand-typed with a command's exact instructions persists byte-identically
to that command's expansion, so stored history alone cannot distinguish them.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop expanding skill commands; let the skills tool load them
Pasting the skill body into the prompt is why the transcript could ever show
it: the desktop webview re-hydrates from the runtime's persisted history, so
whatever the sidecar splices into the user message renders as if the user
typed it. The runtime already registers the skills tool, whose description
requires the model to invoke it whenever the user references a slash command
— so send the typed /skill text through and let the tool deliver the
instructions as a tool result (previously they arrived twice: pasted and via
the tool). The persisted user message, session title, and queue entries are
then simply the typed command, which deletes the typed-prompt registry, the
queue event/snapshot rewriting, and the title machinery from the previous
approach.
Workflows are not served by the skills tool and keep textual expansion, so
the read-time display inverter stays: it collapses expanded workflow prompts
— and skill prompts persisted before this change — back to the typed
/command in the history projection.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): option to keep skill slash commands typed for the skills tool
resolveRuntimeSlashCommandFromWatcher (and the hub snapshot proxy) accept
expandSkillCommands: hosts whose sessions register the skills tool pass
false so the typed /skill goes through and the model loads the instructions
as a tool result, keeping the persisted transcript as what the user typed.
Workflows always expand — the tool does not serve them. isSkillsToolAvailable
exposes the catalog check hosts use to decide (yolo preset and the skills
tool toggle leave textual expansion as the only delivery path).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): skill slash commands load via the skills tool instead of expanding
The TUI user-command wrap and buildUserInputMessage now keep a typed /skill
as-is when the session's mode/toggles register the skills tool, matching the
desktop app; workflows keep expanding, and yolo (zen) keeps expanding skills
because its preset has no skills tool. This also fixes CLI resume/history
surfaces showing the skill body: the persisted user message is now the typed
command.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): keep configured skill slash commands typed for the skills tool
expandSlashCommands no longer splices a configured skill's instructions into
the model text; the SDK session's skills tool delivers them as a tool result
(previously they arrived twice). Builtin pseudo-skills like /deep-planning
are not served by that tool and keep expanding, as do workflows.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): use the shared skill-expansion option in the sidecar
Replaces the sidecar's workflow-detection dance with core's
expandSkillCommands option and gates on isSkillsToolAvailable, restoring
textual expansion where the tool is missing (yolo mode or the skills tool
toggle) — a gap in the previous desktop-only change.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): drop the display inverter for expanded transcripts
Accepted trade-off to keep the change minimal: sessions recorded before
skills switched to the skills tool, workflow sends (deprecated), and
yolo-mode skill sends persist expanded instructions and now render that text
as-is instead of being collapsed back to the typed /command at projection
time.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Use fixed selection chevron in account dialog to match other dialogs (#13364)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): align system prompt with session mode (#13361)
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(desktop): finalize queued turns on chat_done with canonical history reconcile (#13330)
Turns that settle through the event stream (queued prompts, including the
first prompt of a fresh session) resolve their send() RPC early, so nothing
cleared the streaming shimmer or reconciled live-streamed content against
the persisted transcript at turn end. A turn whose deltas were incomplete
stayed visually streaming forever and only healed when a later non-queued
send rehydrated history.
chat_done (and chat_session_ended / the queue-drain double check) now clears
the active assistant streaming id and schedules a short-delayed
read_session_messages + applyCanonicalHistory, guarded by turn epoch,
session id, and in-flight send submissions so it never clobbers a newer
turn or duplicates the blocking send path's own finalization.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(desktop): release v0.0.14
* fix(clients): filter non-chat models from chat pickers (#13317)
* fix(clients): filter non-chat models from chat pickers
* fix(clients): align chat model eligibility
* fix(desktop): strip user_input envelope when copying a user message (#13369)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bee <abeatrix@users.noreply.github.com>
* docs: show DeepSeek V4 peak and off-peak pricing (#13312)
* docs: update DeepSeek V4 average pricing
* docs: show DeepSeek peak and off-peak pricing
* docs: add GLM-5.3 reference pricing (same as GLM-5.2)
* docs: add GLM-5.3 to ClinePass models table
* fix(llms): display billed gateway cost (#13385)
* fix(shared): run PowerShell commands with fail-fast error semantics (#13358)
* fix(shared): run PowerShell commands with fail-fast error semantics
The run_commands PowerShell wrapper never set $ErrorActionPreference, so
the default 'Continue' applied: a pipeline erroring per item (e.g. a
malformed Where-Object over Get-ChildItem -Recurse) emitted one error
record per enumerated file - tens of thousands of stderr records on
large trees, looking like a hang - and could still resolve as SUCCESS
with exit 0.
Prepend $ErrorActionPreference='Stop'; to the script content executed
by the ScriptBlock so the first error terminates the command with a
non-zero exit and a single error message. Concatenated on the same line
as the user command so error line numbers stay unshifted.
Fixes#13285
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(shared): set the fail-fast preference in the bootstrap scope
Setting $ErrorActionPreference='Stop' by string-prepending it into the
scriptblock source displaced a leading param(...) from its mandatory
first-statement position, so scripts beginning with a param block failed
with CommandNotFoundException. Preference variables are dynamically
scoped, so setting Stop in the -Command bootstrap gives the invoked
scriptblock identical fail-fast semantics while keeping the user script
byte-identical (param works, error positions unshifted) and drops the
doubled-quote escaping.
* docs(shared): document the fail-fast tradeoffs in the PowerShell wrapper
Stop promotes every non-terminating error, not only per-item pipeline
floods: partial-result commands (recursive listings over access-denied
junctions) now stop at their first error, and Windows PowerShell 5.1
turns in-script stderr redirection of succeeding native commands fatal.
State this in the wrapper comment as a deliberate tradeoff, with the
GitHub Actions precedent and the per-command opt-outs.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
* ci: stop over-long changelogs from silently dropping release Slack posts (#12955)
Slack section blocks reject text longer than 3000 characters. The Slack
action logs that rejection as ##[error] but does not fail the step, so an
over-long changelog drops the release announcement while the run stays
green — cline@3.0.50 (3272 chars) published to npm, tagged, and cut a
GitHub release with no Slack post and nothing red to notice.
Every publish workflow pasted the changelog section verbatim into one
section block, so all six were exposed; the SDK, desktop, and extension
sections were only 150-350 chars under the ceiling.
Add a slack_content output alongside content: unchanged when the section
fits, otherwise trimmed on a line boundary with a link to the full
release notes. Only the Slack payload uses it — GitHub release bodies and
the desktop updater manifest still get the whole section.
* ci: tidy workflow cache config and job permissions (#13403)
Publish workflows now always do clean npm installs (no dependency
cache in their test gates), the e2e workflow's cache keys are
exact-match only, and the e2e job drops an id-token permission it
never used.
* Rename desktop app from "Cline Code" to "Cline" (#13401)
* Rename desktop app from Cline Code to Cline
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Format touched Rust test assertions
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): surface provider-executed tool activity as observational events (#13300)
* fix(llms): surface provider-executed tool activity as observational events
Provider-executed tool parts (e.g. every tool the Claude Code CLI runs
inside its own session) were dropped by the model-tool guard added for
web search: only declared model tools were re-emitted, everything else
hit continue with nothing yielded. Those sessions modified the workspace
with no tool activity in runtime events, transcripts, or the UI.
Route all providerExecuted parts onto the observational path instead:
emit execution-tagged tool-call-delta and tool-result events, matched by
tool-call ID for providers that omit the flag on the result half. They
stay out of AgentRuntime's execution/approval loop, and the runtime
already persists them as modelToolActivities and projects them for
display.
The AgentModelEvent tool-result variant widens toolName from
ModelToolName to string to carry the provider's own tool names.
* fix(agents): keep turns that are only provider-executed tool activity
A turn consisting solely of observational tool activity has an empty
assistant content array - the activity lives in message metadata, since
projecting it into content would replay tool_use blocks the model never
gets results for. The empty-content guard threw on such turns, erroring
the run and losing the activity from the transcript. Count model-tool
activity as content for the emptiness check (error finishes still
throw); replay stays safe through the codec's empty-content placeholder.
Also drop the trailing text delta from one gateway test so the tool-only
stream shape stays covered end to end.
* feat: allow agents to create scheduled tasks (#13331)
* feat(core, desktop): add durable todo agenda
* fix(desktop): secure todo approvals and track tool usage
* fix(desktop): clean up failed approval delivery
* fix(desktop): authenticate approval connections
* fix(desktop): cancel approvals on broadcast failure
* fix(desktop): authenticate development approvals
* fix(desktop): harden development approvals
* test(core): make task paths cross-platform
* fix(desktop): serialize approval readiness
* refactor(core): unify todo and schedule tools
* feat(core): distinguish user todos from agent suggestions
* fix(core): hide tasks tool in yolo mode
* fix(core): enforce schedule workspace scope
* fix(core): bind schedule scope to hub connection
* fix(core): establish task scope at hub startup
* fix(core): scope task automation by workspace
* test(core): normalize workspace path expectations
* test(core): serialize Windows CI workers
* fix(core): reject unregistered schedule authority
* fix(desktop): guard task execution commands
* fix(core): avoid polynomial regex in mention parsing
* fix(core): address schedule tool review feedback
* fix(core): bind websocket clients to hub workspace
* fix(core): flatten tasks tool input schema
* fix(core): authorize multi-workspace hub clients
* test(core): type hub transport authority mock
* fix(cli): register a workspace client for remote schedule commands (#13398)
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(desktop): treat ClinePass as OAuth-managed in the chat credential gate (#13404)
* fix(desktop): treat ClinePass as OAuth-managed in chat credential gate
ClinePass shares the Cline account OAuth credentials (its auth handler
stores under the "cline" provider), so the webview never sees a plain
API key for it. The chat pre-flight check only exempted cline/oca/
openai-codex, so switching to ClinePass while signed in via OAuth
blocked with "Missing API key" even though the sidecar resolves the
stored access token fine (which is why the CLI worked).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style: format helpers.test.ts with biome
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(ui): stack code block lines when streamdown lineNumbers is off (#13412)
streamdown renders each Shiki token line as a bare inline span with no
newline text between non-empty lines, and only applies its block line
class when lineNumbers is on. With lineNumbers off (the desktop app's
config) every multi-line fenced block collapsed into one run-on line.
Make the direct line spans under code-block-body display: block in the
shared markdown.css; empty lines keep their height via their lone "\n"
child under white-space: pre.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): work summary undercounts wall time when pre-tool thinking attaches to the answer (#13413)
* fix(desktop): anchor work summary duration on the answer row, not attached pre-tool reasoning
The collapsed 'Worked for Xs' row undercounted wall time whenever a turn's
assistant message contained thinking + tool_use with no narration text: the
canonical projection emitted the reasoning-only row after the tool row (both
stamped before the tool executed), the webview attached that row to the final
answer, and collapseCompletedWork used the answer's earliest attached
reasoning timestamp as the end anchor - excluding the entire tool execution
(e.g. 'Worked for 5s' for a turn with an 8s command).
- webview: end the work span at the answer row's own timestamp, clamped to
the last collapsed row so a fallback answer bubble with a synthetic early
timestamp cannot shrink the duration either
- sidecar: flush pending thinking before a tool_use row so rehydrated
transcripts keep the live-stream order (thinking before its tool call) and
pre-tool reasoning no longer rides on the next answer
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep interleaved thinking between the tool calls it separates
Address Greptile review: when one assistant message interleaves thinking
between multiple tool_use blocks, each reasoning segment now projects at its
own position (attached to a text row from its own segment when present,
otherwise as its own row) instead of merging into the first reasoning row,
which displayed later thinking before a tool call it actually followed.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): remove settings gear hover state while Account screen is open (#13408)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't show "No sessions found" while session history is still loading (#13414)
* fix(desktop): don't show 'No sessions found' while session history is still loading
Replace the isLoadingHistory flag with hasLoadedHistory, set only once the
backend has actually answered a list_discovered_sessions request. The sidebar
and Sessions view now keep their loading state until that first definitive
response, so the empty-state copy can no longer appear while history is still
being fetched (or while a failed fetch is being retried).
Also retry a failed initial fetch on the 2s event cadence instead of stranding
the UI until the 12s periodic poll, which is what stretched the misleading
empty state to ~10 seconds after a webview reload when the websocket lost the
race with the page load.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop history fast-retry from re-arming after hook unmount
A failed initial fetch that settles after the hook unmounted could schedule a
new retry timer after cleanup had already cleared the refs, leaving the
abandoned hook polling the backend every 2s. Guard scheduleRefresh with a
disposed ref set by the mount effect's cleanup.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix @ file mentions breaking on paths with spaces (#13391)
* Fix @ file mentions breaking on paths with spaces
Quote mentions generated by getFileMentionFromPath (Add to Cline /
Fix / Explain / Improve commands) when the relative path contains
spaces, so the mention regex no longer truncates the path at the
first space. Also quote the path part of workspace-prefixed mentions
(workspace:/path with spaces) inserted from the @ context menu, which
previously bypassed quoting because the value does not start with '/'.
Fixes#13338
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix import ordering in mentions test (biome organize imports)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reduce fix to minimal scope
Revert the webview quoting refactor and extra tests; keep only the
getFileMentionFromPath quoting fix with a single regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Normalize mention paths to posix separators for Windows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix code actions failing with "command not found" on VS Code 1.134 (#13402)
* Fix @ file mentions breaking on paths with spaces
Quote mentions generated by getFileMentionFromPath (Add to Cline /
Fix / Explain / Improve commands) when the relative path contains
spaces, so the mention regex no longer truncates the path at the
first space. Also quote the path part of workspace-prefixed mentions
(workspace:/path with spaces) inserted from the @ context menu, which
previously bypassed quoting because the value does not start with '/'.
Fixes#13338
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix import ordering in mentions test (biome organize imports)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reduce fix to minimal scope
Revert the webview quoting refactor and extra tests; keep only the
getFileMentionFromPath quoting fix with a single regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Normalize mention paths to posix separators for Windows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix code actions failing with 'command not found' on VS Code 1.134
Code action commands carried arguments (expandedRange, diagnostics),
which routes them through VS Code's CommandsConverter cache. VS Code
1.134 disposes the cached entries before the clicked action executes,
so every lightbulb action failed with 'Actual command not found,
wanted to execute cline.addToChat'.
Drop the arguments so the command id is passed through directly, and
recover the context in the handler instead: getContextForCommand now
expands an empty selection by 3 surrounding lines (matching the old
provider behavior) and gathers document diagnostics intersecting the
range when none are passed explicitly.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Scope gathered diagnostics to the selection/cursor
Match the old CodeActionContext.diagnostics behavior: only include
diagnostics intersecting the range the action was requested for, not
the surrounding lines the text gets expanded to.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Desktop: unify Plugins, MCP, and Skills into one Plugins hub with a dedicated Marketplace page (#13411)
* Unify desktop plugins, apps, MCP, and skills into one Plugins hub with a Browse directory mode
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Open the marketplace directory as a modal over the Plugins hub instead of swapping the page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename directory to Marketplace: Browse Marketplace button, Marketplace modal title with icon, search placeholder
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix search input focus ring clipped by the Marketplace modal scroll container
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Address Greptile review: keep selected tag chip visible when its count drops to zero, and remount installed tab when a marketplace install completes after the modal closed
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Track marketplace modal mutation flag in a ref so a close click racing a queued render cannot skip the inventory remount
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make Marketplace its own settings page under Customizations and restore Channels as a standalone page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Remove icon from Marketplace page header for consistency with other settings pages
* Notify mounted inventory views when the marketplace invalidates the cache so late install completions refresh the Plugins hub
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop/ui): recommended and free model tiers in the composer model selector (#13410)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK (#13415)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): recommended-feed badges and descriptions in provider settings (#13416)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* feat(desktop): recommended-feed badges and descriptions in provider settings
Review suggestion on #13410: the provider settings page has room for
more model detail than the composer's picker. The cline/cline-pass
provider cards now refresh their model list through
list_provider_models (the catalog snapshot deliberately skips the
recommended-feed overlay so the startup catalog fetch never blocks on
the feed) and render Recommended/Free tier badges plus feed tags (NEW)
next to the model name, with the model description underneath. The
refreshed list also surfaces the live entries instead of the bundled
snapshot.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): scope the settings featured model list to its provider and revision
The fetched featured list was unscoped component state: switching
between cline and cline-pass reused the component instance, so the
previous provider's models stayed visible while the new request was
pending (or forever, when it failed), and the retained copy shadowed
later provider.modelList updates — adding a second custom model
submitted the stale list as the complete configuration and dropped the
first addition.
The fetched list now only applies to the provider and modelList
revision it was fetched for (falling back to the catalog snapshot
otherwise and refetching on membership changes), and add-model submits
the union of the displayed and configured ids so an update can never
silently unconfigure existing entries.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(ui): update packed-Tailwind smoke contract for the picker's max-h-64 (#13421)
The ui-publish smoke check pins a set of Tailwind candidates the packed
sources must emit; #13410 grew the SearchCombobox options list from
max-h-56 to max-h-64, so the publish run failed on the stale candidate.
All other pinned candidates verified against the current sources.
* feat(desktop): refresh app icons and branding (#13400)
* ci(vscode): upload E2E failure recordings from the right path (#13427)
The job sets working-directory: apps/vscode, but that default applies to run
steps only, not to `uses:` steps. Since #10961 moved the extension under apps/
and added that default, the artifact path has resolved against the repo root,
matched nothing, and every failing run logged "No files were found with the
provided path: test-results/playwright/" instead of uploading recordings.
Widen to test-results/ so Playwright's error-context snapshots ship alongside
the videos.
* fix(hooks): deliver tool hook contextModification to the model (#13297)
* fix(hooks): deliver tool hook contextModification to the model
On the next engine, a tool_call (PreToolUse) hook's contextModification
was parsed into HookControl.context and then silently dropped: the
runtime beforeTool/afterTool result contract had no channel for
injecting conversation context. Legacy consumed it (ToolExecutor /
ToolHookUtils pushed <hook_context> blocks into the next user turn), so
this was a regression of documented behavior.
- Add appendContext to AgentBeforeToolResult/AgentAfterToolResult.
- AgentRuntime collects appendContext across hooks during an
iteration's tool executions and appends one <hook_context> user
message after the tool results, keeping tool-result parts contiguous.
- Map HookControl.context into appendContext in both subprocess hook
layers (skipped when the hook cancels, matching legacy, where the
message doubled as the error).
- Truncate injected context at 50KB per hook output, matching legacy.
- Concatenate appendContext across merged hook layers.
tool_result (PostToolUse) hooks still run detached with stdout ignored;
making them blocking so their context can be collected is a follow-up.
Ref: https://linear.app/cline-bot/issue/CLINE-2987
* fix(hooks): stamp tool identity on injected hook context blocks
Contexts are batched into one message after the tool results, and
parallel tool execution collects them in completion order, so position
alone cannot attribute a block to its tool call. Add tool_name and
tool_call_id attributes to each <hook_context> block.
* fix(hooks): sanitize hook context block markup
Attribute values (tool_name, tool_call_id) are stripped of quote/angle
characters and embedded </hook_context> closers in hook output are
neutralized, so neither provider-supplied ids nor hook text can corrupt
or spoof a block's stamped identity.
* fix(hooks): neutralize forged opening hook_context tags in hook output
The previous sanitization only neutralized closing tags, so hook output
could still open a forged <hook_context> block claiming another tool's
identity. Escape both opening and closing embedded tags with one rule.
* fix(hooks): hide injected hook context from user-facing transcripts
Stamp the injected hook-context user message with displayRole 'system'
(the compaction-summary convention) so it reaches the model but does
not render as a user bubble in live or replayed transcripts. Without
this, resuming a session showed the raw <hook_context> block as if the
user had typed it.
* fix(hooks): neutralize case-variant embedded hook_context tags
The tag-neutralization regex was case-sensitive, so hook output could
still smuggle a forged tag as <HOOK_CONTEXT>. Match case-insensitively.
* fix(vscode): map PreToolUse contextModification into runtime appendContext
The extension's hooks adapter bridged file hooks into the SDK runtime
but forwarded only cancel/errorMessage, so a PreToolUse hook's
contextModification never reached the model. Map it into the runtime's
appendContext channel; HookFactory already truncates it at 50KB.
* fix(vscode): hide hook-injected context from replayed transcripts
Live sessions never rendered the injected <hook_context> user message,
but session reload replayed it as a user bubble (and post-resume turns
kept doing so). Treat these messages as synthetic in the user-message
mapping: honor the displayRole 'system' stamp the runtime sets, with a
text-prefix guard for paths where metadata is unavailable. This also
keeps edit/regenerate ordinal mapping aligned with visible bubbles.
* fix(hooks): run file hooks through exactly one layer per host
The VS Code extension registered two independent hook execution layers:
its own hooks adapter (config.hooks) and the SDK core's file-hook
extension from the runtime bootstrap. When both discover the same hook
files, every hook executes twice per event — and with context injection
wired, each contextModification would be injected twice.
Add a 'hooks' runtime config extension kind (in the default set, so the
CLI keeps core file hooks unchanged) and gate the bootstrap's file-hook
extension on it. The extension excludes 'hooks' at session start, so
its adapter — which also provides the hook status UI and the
hooksEnabled setting — is its single execution path.
* fix(vscode): discover hooks from the session workspace, not only global state
Hook discovery read workspaceRoots from global state shared across
every Cline instance, so another window repointing it made workspace
hooks silently stop being discovered. With the extension's adapter now
the single hook execution layer, that meant no hooks at all.
HookFactory takes an optional sessionWorkspaceRoot and unions that
root's .clinerules/hooks into discovery (and into cwd resolution), fed
from the session config's cwd. Shared-state discovery still works, so
behavior in the single-window case is unchanged.
* fix(hooks): keep sanitized hook attribute values distinguishable
Replacing every markup delimiter with the same underscore could
collapse two tool call ids that differ only by such a character into
identical stamps. Escape each delimiter with a distinct token instead.
* fix(hooks): make hook attribute sanitization injective
Escaping the underscore itself turns the attribute escaping into a
uniquely decodable code, so no two distinct tool call ids can collapse
to the same sanitized stamp (previously an id containing a literal
escape token could collide with an id containing the delimiter).
* fix(vscode): reconstruct hook status rows when replaying transcripts
hook_status messages are emitted live but never persisted, so reloading
a session dropped every hook row. The injected <hook_context> blocks
carry the hook source and tool name, so the replay translator now
rebuilds a completed hook status row from each block. The injection is
also no longer treated as a user turn boundary, so the final turn's
completion retag is unaffected by it.
* fix(hooks): collect PostToolUse hook output and honor its control (#13298)
* fix(hooks): collect PostToolUse hook output and honor its control
tool_result (PostToolUse) hooks ran fire-and-forget with stdout
ignored, so their entire JSON output — contextModification and cancel —
was discarded. Legacy awaited PostToolUse, injected its
contextModification into the conversation, and honored cancel.
- Run tool_result hook commands blocking (same 120s default timeout as
tool_call) in both the hook-config-file layer and the agent-hook
subprocess layer.
- Map their output: cancel stops the run with the hook's error message
as the reason; otherwise context is injected via afterTool
appendContext.
This restores legacy blocking semantics: tool results now wait for
tool_result hooks, but only in sessions that have one configured.
Ref: https://linear.app/cline-bot/issue/CLINE-2987
* fix(hooks): bound tool_result hook wait and isolate cancel reason
Address review findings:
- The agent-hook subprocess layer forwarded an unset timeoutMs
unchanged, so a tool hook command that never exits would block the
agent indefinitely. Default both tool_call and tool_result to the
120s bound the hook-config-file layer already used.
- A cancelling hook's error message was folded into the same context
field as other hooks' injectable context, so merging controls could
leak unrelated hook context into the cancellation reason. Carry it as
a separate cancelReason, and surface it as the stop reason for
beforeTool cancels too.
* fix(hooks): prefer errorMessage as a cancelling hook's stop reason
When a cancelling hook returns both contextModification and
errorMessage, the context-first parse precedence made the injectable
context the cancel reason and discarded the actual error. Parse the two
fields separately: errorMessage wins as the cancel reason (matching
legacy), and a lone errorMessage still folds into injectable context
for non-cancelling hooks as before.
* fix(vscode): honor PostToolUse hook cancel and contextModification
The adapter awaited PostToolUse hooks but discarded their output
entirely. Map cancel to a stop control (with errorMessage as the
reason) and contextModification into the runtime appendContext channel,
matching the PreToolUse mapping and legacy semantics.
* fix(hooks): whitespace-only errorMessage no longer suppresses the cancel reason
A cancelling hook returning meaningful context alongside a blank
errorMessage lost both: the parsers selected the whitespace as the
reason and the result mappers trimmed it away. Require a non-blank
errorMessage before it wins, so context serves as the fallback reason.
Apply the same fallback in the extension adapter's stop mapping.
* fix(core): stop Windows CI worker crashes from the agenda spec watcher (#13428)
* fix(core): watch agenda task specs via the resolved long path
fs.watch on a path with 8.3 short components (e.g. C:\Users\RUNNER~1
temp dirs) trips a libuv assertion in fs-event.c on Windows and aborts
the whole process. Since the agenda task manager landed, every hub
server test spins up its spec watcher on such a path on hosted Windows
runners, killing the vitest worker and failing the sdk-test Windows job
on every branch. Resolve the specs dir with realpathSync.native before
watching so libuv only ever sees the long form.
* test(ui): stub ResizeObserver for @pierre/diffs in tool-diff tests
jsdom does not implement ResizeObserver, so every ToolFileDiff render
logged a ReferenceError from @pierre/diffs to stderr. Tests still
passed; this just silences the noise the same way the constructable
stylesheet shim does.
* fix(core): skip the agenda spec watcher when the dir does not resolve
Falling back to the raw path on realpath failure would reintroduce the
Windows short-path abort; log and go without the watcher instead.
* fix(vscode): honor the classic truncation range when migrating legacy tasks (#13419)
Classic Cline truncated long conversations by omitting an index range of
api_conversation_history from every API request (keep the first
user-assistant pair, drop everything through the range end, strip
orphaned tool_results from the first kept message). The range was
persisted on the history item while the full history stayed on disk.
legacyApiHistoryToSdkMessages ignored conversationHistoryDeletedRange
and converted the entire file, so resuming a migrated long task handed
the SDK an untruncated working context that could exceed the model's
context window by millions of tokens - every request failed with
'prompt is too long' and every compaction restarted from the full
history (#12996, confirmed by the reporter: the task was migrated from
an older version and broke after a restart, with each compaction
starting from ~3M tokens).
The migration now replays exactly what the classic extension sent:
slice out the deleted range and drop orphaned tool_results, mirroring
ContextManager.getTruncatedMessages (see origin/main). Malformed ranges
fall back to the full history (previous behavior).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): show the diff edit view for multi-line edits in CRLF files (#13417)
The edit preview computed proposed content with an exact old_text match, but
the SDK executor normalizes old/new text to the file's own line endings before
matching (#12305) - reads strip CR, so models emit LF-only text even for CRLF
files. Any multi-line old_text in a CRLF file therefore failed the preview's
match: the diff edit view silently never opened while the executor applied the
edit. Single-line edits (no line break in old_text) were unaffected, which is
why the diff view appeared to trigger inconsistently.
Mirror the executor's EOL normalization (and its literal $-sequence insertion)
in the preview computation.
Fixes#13296
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): report truthful session status so desktop checkpoint restore stops wedging (#13418)
* fix(core): keep hub session status truthful across queue-drained turns
Queue-drained turns settle only through the event stream, but the hub
runtime host mistranslated their lifecycle in two ways:
- session.updated events carrying only a snapshot (persistence updates)
defaulted the projected status to "running". When one trailed the
final idle update after a turn, clients that track busy state from
status events (the desktop sidecar's workspace restore gate) stayed
busy forever. Use the snapshot's real status and emit nothing when
neither source reports one.
- the per-run agent.done dedup was only reset by run.started, which the
daemon-side queue drain never publishes, so a drained turn's done was
swallowed as a duplicate of the previous turn's. Reset the dedup on
session.pending_prompt_submitted, and suppress stale run.completed
events that land inside a drained turn's window so they can neither
emit a phantom done nor consume the drained turn's dedup slot.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(desktop): cover restore unlock after an event-settled queued turn
Exports the sidecar's core-session event handler so the queued-turn
lifecycle (busy via status events, cleared by the done agent event,
restore allowed afterwards) is testable end-to-end.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): start interactive sessions without a prompt as idle
The runtime host reported every new session as "running" until its
first turn ended. Interactive hosts (the desktop app) start sessions
with no prompt and dispatch turns through separate send calls, so a
created-but-never-prompted session stayed "running" forever — wedging
clients that gate workspace operations (checkpoint restore, message
edit) on active turns.
Interactive no-prompt starts now begin idle, start emits the session's
actual status (resumed sessions no longer masquerade as running), and
markTurn* transitions keep tracking in-memory status for lazily
persisted sessions so the first turn still reports running -> idle.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style: format hub-runtime-host test filter
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop the drained-turn done bookkeeping, keep the minimal fix
The stuck restore is fully explained by the two status defects (fabricated
"running" from snapshot-only session.updated events, and never-prompted
interactive sessions reporting "running"). The done-dedup machinery for
queue-drained turns addressed a separate cosmetic gap (queued turns emit no
chat_done, pre-existing) and required fragile run-window heuristics, so it
is removed to keep this change reviewable. Sidecar test now settles the
queued turn through the status event, matching the shipped mechanism.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* docs(sdk): document the truthful session-status contract
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(deps): update Langfuse packages and bump app versions (#13443)
* chore(deps): update Langfuse packages and bump app versions
Update @langfuse/otel to v5.10.1 and add @langfuse/vercel-ai-sdk v5.9.1 for improved observability with Vercel AI SDK.
Bump versions for @cline/code to 0.0.14 and @cline/ui to 0.2.0-next.6, updated via bun.lock.
Other Changes:
Added optional userId to AgentRuntimeConfig.
Propagated userId, sessionId, conversationId, runId, iteration, provider, and model context into AI SDK telemetry.
Added AI SDK 7 runtimeContext with explicit includeRuntimeContext.
Added stable OTEL_SERVICE_NAME=cline-sdk.
Added runtime metadata assertions in agent tests.
* add taskId
* Revert "add taskId"
This reverts commit f20d31d96d.
* docs: simplify Open Cline step in installing guide (#13405)
* docs: remove duplicate GLM-5.3 rows in ClinePass tables (#13449)
Co-authored-by: cline-cloud[bot] <276134852+cline-cloud[bot]@users.noreply.github.com>
* chore(sdk): release v0.0.76
* chore(cli): release v3.0.56
* docs(cli): scope the v3.0.56 release notes to CLI-visible changes
* feat(desktop): interactive welcome hero graphic (#13399)
* feat(desktop): add interactive welcome hero
* feat(desktop): support composable welcome hero variants
* feat(desktop): reskin first-run onboarding (#13441)
* refactor: centralize client tool availability (#13451)
* chore(sdk): release v0.0.77
* docs(cli): drop the tasks tool from the v3.0.56 notes, it is desktop-only
* chore(vscode): prepare 4.1.11 release
* chore(desktop): release v0.0.15
* fix(vscode): remote config MCP settings (#13466)
* fix(vscode): enforce enterprise MCP controls on the Customize marketplace
The unified Customize marketplace replaced the old MCP marketplace
without carrying over enterprise remote-config enforcement: the catalog
RPC returned every MCP entry and installs were never policy-checked,
so orgs with mcpMarketplaceEnabled=false or an allowedMCPServers
allowlist saw (and could install) all marketplace MCP servers.
- Filter MCP entries out of getMarketplaceCatalog when the marketplace
is disabled, and restrict entries to the allowlist when configured
(matching entry id, display name, installed server name, or source
repo URL, mirroring legacy GitHub-URL allowlist ids)
- Reject installMarketplaceEntry requests that violate the policy
- Map the published catalog's repo/homepage fields onto
sourceUrl/homepageUrl so URL-based allowlists can match
- Update the enterprise MCP server controls docs
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: simplify MCP marketplace policy enforcement
Fold the policy check into marketplace-helpers, drop the dedicated
test suite, and trim the docs edit to the strictly necessary line.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Treat an empty preserved capability list as unspecified when seeding tools (#13465)
* Treat an empty preserved capability list as unspecified when seeding tools
toSdkModelInfo guarded the tools seeding with a strict
preservedCapabilities === undefined check, but modelHasCapability —
the runtime's own reader — treats undefined AND length === 0 as
"unspecified". A custom OpenAI-Compatible model whose stored
capabilities field is a defined-but-empty array (a config carried over
from before the field existed, or one round-tripped through a boundary
that defaults it to []) skipped the seeding; the first boolean
projection to run afterwards (e.g. supportsReasoning) then populated
the array, the runtime gate read the non-empty, tool-less list as
authoritative, and every tool definition was silently dropped from the
session (#13463).
The guard now covers the empty array too, matching the reader's
unspecified semantics.
* test: satisfy the store's isModelInfo gate so the empty-capabilities case actually reaches knownModels
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: yzxcj797 <yzxcj797@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): prepare 4.1.12 release
* Add feature flags to the desktop app (#13289)
* Add feature flags to the app
* React to account updates
* Address comments
* use a per-app file
* fix: propagate Langfuse session telemetry (#13473)
* fix telemetry session propagation
* feat telemetry client version metadata
* fix(core): address Langfuse review feedback — hub client identity + delegated agent session grouping (#13475)
* fix(core): rebuild hub session client identity from request headers
Hub-backed sessions do not transport extensionContext (it is local-only),
so the daemon's runtime built traces without the clientName/clientVersion
metadata even though the hub client bakes X-CLIENT-TYPE / X-CLIENT-VERSION
into the session's provider headers. Reconstruct extensionContext.client
from those headers during local runtime bootstrap so hub-backed Langfuse
traces carry the same client identity as local runtimes, and the daemon's
header re-resolution stops clobbering the original X-CLIENT-TYPE.
* fix(core): propagate parent distinctId/sessionId to delegated agents
Delegated agents (spawned sub-agents, configured agents, teammates) were
built without distinctId and sessionId, so their Langfuse traces had no
userId or sessionId and did not group with the parent user or session.
Thread the host-resolved distinctId through RuntimeBuilderInput and the
root sessionId through the delegated-agent config provider, and copy both
onto the delegated AgentConfig.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* ci(vscode): make combined nightly manual-dispatch only
The PublishNightly environment gained required reviewers, so each cron
run parked on approval, held the workflow's concurrency group, and
silently cancelled every scheduled run queued behind it. 20 consecutive
scheduled nightlies died this way between 2026-07-31 and 2026-08-21;
the only nightlies that shipped in that window were manual dispatches.
Drop the cron rather than leave a trigger that cannot succeed unattended.
* feat(hub): add drain and upgrade commands with replay support (#13468)
* feat(hub): add drain and upgrade commands with replay support
* handles disconnection
* feat(hub): wire bot profiles, drain, and durable event/run-queue into the live transport
Completes the wiring the previous commits' primitives needed:
HubServerTransport gains isDraining(), hub.drain/hub.status/profile.get
command handling, and replayEventsAfter() (backed by the durable event
log), plus the sequence/sinceSequence wire types they depend on in
shared/hub.ts. run-queue-handlers.ts reads the active bot profile's
plugin roots when executing durable runs.
Also adds hub/profiles/: profile.json (identity/rules/plugins) ->
system prompt composition, --profile / CLINE_HUB_BOT_PROFILE
resolution, and the bundled cline-dad profile with its
cline_hub_support read-only diagnostics tool.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Revert "feat(hub): wire bot profiles, drain, and durable event/run-queue into the live transport"
This reverts commit 6696d5d202.
* fix(hub): dedupe replayed events by eventId, not just sequence
HubEventLogStore.append() returns a new envelope stamped with a
sequence rather than mutating the input, so a pending approval
re-issued sequence-less by subscribe() (it predates any durable-log
append) and its later sequence-stamped copy from the durable log are
two different objects carrying the same eventId. The replay-then-live
buffer in browser-websocket.ts only deduped by sequence, so the
sequence-less copy's guard never tripped and it was delivered a second
time when the buffer flushed after replay.
Track delivered eventIds alongside the sequence cursor; eventId
survives the append/stamp round-trip unchanged, so this dedupes the
exact-same logical event regardless of which copy arrives first.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): wire drain, durable event log, and run queue into the live transport
CI on this branch failed bun run build:sdk: browser-websocket.ts,
client/index.ts, and hub-websocket-server.ts (already on this branch)
reference sequence/sinceSequence, HubServerTransport.isDraining(), and
the "hub.drain" command — but the commit that reverted bot profiles
out of this branch also reverted this wiring, since it shared a commit
with the profiles work. That wiring is a hub concern, not a
bot-profiles one; split it back out.
- shared/hub.ts: sequence/sinceSequence types, run.enqueue/run.list/
hub.drain/hub.status/stream.replay capability, command, and event
names. profile.get intentionally excluded — stays bot-profiles-only.
- context.ts: isDraining() on HubTransportContext. botProfile field
intentionally excluded.
- hub-server-transport.ts: eventLog/runQueue fields and start/stop
lifecycle, publish() appends to the durable log, handleCommand cases
for run.enqueue/run.list/hub.drain/hub.status, drain-refusal check,
replayEventsAfter()/lastEventSequence(). startBotProfile()/
startHubSupportTool() and the profile.get case intentionally
excluded.
- run-queue-handlers.ts: added without handleProfileGet (needs
ctx.botProfile, which doesn't exist here).
- hub-upgrades.test.ts: added without its two bot-profile-injection
tests (they need a resolved bot profile to assert against).
Verified bun run build:sdk exits 0 (the exact CI command) and
bunx vitest run src/hub passes (311/312; the one failure is the
same pre-existing environment-timing flake already present before
this change).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): export instance-lock, event-log, and run-queue from the hub barrel
These landed as internal modules only; hub-server-transport.ts and
hub-websocket-server.ts import them by direct path, but nothing
re-exported them from the public @cline/core/hub surface the way
sibling discovery/server modules already are.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): wire the instance lock into the daemon entry point
The singleton lock (discovery/instance-lock.ts) and its consumption in
startHubWebSocketServer/ensureHubWebSocketServer were already on this
branch, but the daemon entry point's own half was not: retrying a bind
when a retiring predecessor still holds the lock, and exiting with a
distinct code (3) instead of the generic fatal path when a live Hub
already owns the data directory. Without this, a daemon racing a
retiring predecessor could fail outright instead of waiting the lock
out, and losing the singleton race looked identical to a crash.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): address drain/upgrade review findings (#13478)
- cline hub upgrade: check idleness at least once (--wait 0 works), reject
non-numeric --wait, and un-drain on every abort path so an aborted
upgrade can never leave the hub refusing new work
- add cline hub drain --off and the off query param to requestHubDrain so
POST /drain?off is reachable from shipped code
- HubEventLogStore/HubRunQueue: WAL journal mode + busy_timeout, and stamp
sequences from lastInsertRowid instead of SELECT MAX(sequence)
- HubInstanceLock.acquire: degrade to an unheld lock when SQLite is
unavailable instead of refusing hub startup; only BUSY/LOCKED still
raises HubLockHeldError
- ensureHubWebSocketServer: retire an unusable discovered hub through the
shared retireDiscoveredHub (busy hubs are attached to, drain precedes
shutdown, discovery cleared only when the hub actually retired)
- replay adapter: advance the cursor past eventId-deduped events, cap
replay pages, stop when the cursor stalls, and drop the dedupe set after
the buffered flush so it cannot grow for the socket lifetime
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(hub): derive the singleton e2e challenger cwd portably
The challenger's working directory was derived by round-tripping the
discovery path through a file: URL and stripping the last pathname
segment. On Windows that yields a POSIX-style '/C:/...' path, which is
not a valid spawn cwd, so the spawn fails ENOENT before the singleton
lock is ever contested and the Windows SDK test job goes red.
The data dir is simply the discovery file's parent: use dirname().
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): stop stored capability lists from silently revoking tool calling for custom models (#13476)
* fix(core): seed tools capability when custom model capabilities are synthesized from boolean flags
For a models.json entry with no explicit capabilities list, toStoredModelInfo
synthesized a capability array purely from boolean convenience flags (e.g.
supportsReasoning: true -> ["reasoning"]). modelSupportsToolCalling fails open
only for a missing or empty list, so the synthesized non-empty list read as an
authoritative denial and silently stripped every tool definition from requests
to custom OpenAI-compatible models (#13463).
Seed "tools" whenever the list was not explicitly authored and the boolean
projections made it non-empty, preserving the fail-open contract. Explicitly
authored capability lists remain authoritative and can still disable tools.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(core): cover stale catalog capability overrides
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: treat stored capability lists as non-authoritative for tool calling
The hasExplicitCapabilities guard still let two producers of tool-less
lists through:
- The VS Code legacy-override migration (legacyModelInfoToOverrides)
persists explicit partial lists like ["prompt-cache"] into models.json
for custom OpenAI-compatible models, which then read as an authoritative
"cannot call tools" and drop every tool - same symptom as #13463.
- Any hand- or UI-authored partial list on a non-catalog model.
Stored entries and user-authored provider metadata have no way to declare
"cannot call tools" (there is no supportsTools field, and every writer
that authors a full list includes "tools"), so seed "tools" into any
non-empty list for a language model. Only generated catalog capabilities
remain authoritative - a genuine no-tools catalog model stays that way -
and non-language models (e.g. image generation) never gain a tools claim.
Also make legacyModelInfoToOverrides write "tools" into the arrays it
fabricates, matching the providers.json migration, so models.json stops
being poisoned for older readers.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): prepare 4.1.13 release
* chore(sdk): release v0.0.78
* chore(cli): release v3.0.57
* fix(core): run hub e2e files serially so daemon timing budgets survive CI contention
singleton.e2e.test.ts (added in #13468) spawns real daemons and runs for
~15s. Vitest's default file parallelism let it run alongside
shutdown.e2e.test.ts, whose assertions are wall-clock bound: discovery
within 10s, exit within 5s, and a 2s shutdown watchdog. On the 2-core
windows-latest runner that contention alone broke those budgets, failing
the shutdown test two different ways across runs — once never observing
discovery, once with the daemon forced to exit before its HTTP 202
flushed (socket hang up). The test passed on Windows before #13468 and
has failed every SDK publish run since.
* chore(desktop): release v0.0.16
* test(sdk): give windows-sensitive suites realistic timeouts
Four consecutive SDK publish runs failed on windows-latest, each on a
different test, all of them plain timeouts: two @cline/shared SQLite
tests at the 5s vitest default, core's bash executor at 10s, and the hub
singleton endpoint test at 10s. The 2-core Windows runner spawns forks
and takes SQLite locks slowly enough to blow those budgets under load.
These timeouts guard against hangs; they are not timing assertions (the
one suite that does assert elapsed time, shutdown.e2e, was fixed by
removing file-level parallelism instead). Raise core to 20s and give
@cline/shared an explicit 15s in place of the inherited 5s default.
* fix(telemetry): emit task.completed from every session teardown path (#13489)
The task.completed fallback lived only inside shutdownSession, but
stopSession/dispose route interactive sessions with a terminal reported
status through releaseSessionRuntime, which never emitted. Truthful
session-status reporting (shipped in 4.1.11) re-routed a large share of
interactive stops onto that branch and silently dropped the event.
Route the emission through a single choke point,
emitTaskCompletedOnTeardown, called from both shutdownSession and
releaseSessionRuntime. The completion criterion no longer reads
session.status: interactive sessions use the recorded final-turn
outcome (lastInteractiveTurnFinishReason), non-interactive sessions
keep the existing input.status === "completed" logic. A new
taskCompletedEmitted flag (also set by the submit_and_exit observer)
enforces exactly one task.completed per session. failSession now
records the errored final turn so a stale "completed" from an earlier
turn can never leak into the teardown emission. Telemetry only; no
user-facing behavior changes.
* chore(vscode): release v4.1.14
* fix(vscode): auto-approve all MCP tool calls when the MCP toggle is on (#13498)
* fix(vscode): honor MCP auto-approve settings for SDK tool calls
The SDK extension required both the global 'Use MCP servers' auto-approve
toggle AND each tool's per-tool autoApprove flag before silently approving
an MCP call, while the legacy extension treated them as either/or. Restore
the legacy OR semantics so toggling MCP auto-approve works again.
Also key toolPolicies by the registered SDK tool name (via
defaultMcpToolNameTransform, now exported from @cline/core) instead of raw
server__tool. Servers whose names contain sanitized characters (e.g.
marketplace names like github.com/user/repo) or exceed 64 chars produced
policy keys that never matched the registered tool, so those MCP tools ran
without any approval gate; the live auto-approve lookup now re-applies the
transform instead of string-splitting the name.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Revert "fix(vscode): honor MCP auto-approve settings for SDK tool calls"
This reverts commit 86c568fbba.
* fix(vscode): auto-approve all MCP tool calls when the MCP toggle is on
The SDK extension only auto-approved an MCP call when the global 'Use MCP
servers' auto-approve toggle AND that tool's per-tool autoApprove flag were
both set, so toggling MCP auto-approve appeared to do nothing and users had
to opt in each tool individually. The toggle alone now governs all MCP
tools; the per-tool flag is no longer consulted.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): release v4.1.15
* fix(cli): remove the $4.99 ClinePass promo copy (#13514)
The $4.99 first-month promo is ending, so the CLI's first-launch "Try ClinePass" dialog should no longer advertise it. Also drops the leftover CLI_PROMO_CODE plumbing, which has been an empty string since the promo-code flow was removed.
* fix(vscode): resolve hook workspace identity from the window, not shared global state (#13352)
* fix(vscode): resolve hook workspace identity from the window, not shared global state
Hook discovery, hook cwd selection, and the workspaceRoots metadata passed
to hook scripts all read the workspaceRoots/primaryRootIndex global state
keys. Global state lives in ~/.cline and is shared by every Cline instance
(all VS Code windows, the CLI, the JetBrains plugin), and nothing writes
these keys anymore, so hooks resolved against whatever project some other
or older instance last recorded. With a second window open on another
project, a workspace's .clinerules/hooks scripts were never discovered.
Resolve workspace roots via a single guarded helper backed by
HostProvider.workspace.getWorkspacePaths() (in-process, window-scoped,
same as refreshHooks): blank paths are filtered, a host-bridge failure
degrades to no workspace roots instead of silently disabling global hooks
or skipping blocking PreToolUse guards, and one resolution is threaded
through hooks-dir discovery, cache misses, cwd selection, and hook input
metadata so they can't disagree (previously up to four host lookups per
hook execution — real gRPC round trips in the standalone host). Roots and
hooks dirs are matched on whole path segments with the longest root
winning, so prefix-sharing or nested workspace roots resolve to the right
project. The adapter creates the runner once per event and skips no-op
runners, making creation the single resolution point; the separate
hasHook/getHookInfo checks are removed. The dead workspaceRoots and
primaryRootIndex state keys are dropped, and the four hand-rolled
HostProvider.workspace test stubs are consolidated into one shared
helper.
* test(vscode): add e2e coverage for workspace-scoped hook discovery
Boots real VS Code with the packaged extension against the workspace
fixture, sends a prompt, and asserts the fixture's UserPromptSubmit hook
was discovered from the open window's workspace, executed with that
workspace root as its cwd, and received the same root in its
workspaceRoots input — the end-to-end contract the hook workspace
identity fix establishes.
* test(vscode): isolate the e2e hook fixture from the shared workspace
The UserPromptSubmit fixture hook lived in the shared e2e workspace, so
every prompt-sending spec executed it (hooksEnabled defaults to true) —
and its cold PowerShell spawn on Windows pushed chat.test.ts past the
5s expect timeout. hooks.test.ts now overrides workspaceDir to a
dedicated workspace-hooks fixture, so only the hooks spec pays the hook
spawn.
* fix(hub): cap hub-events db size so it can't fill the disk (#13516)
* fix(hub): cap hub-events db size so it can't fill the disk
Row/time retention alone didn't bound disk usage: envelopes carrying
full session snapshots reach hundreds of KB each, so retained rows
could total tens of GB, sweeps only ran hourly, and DELETE never
shrinks a SQLite file. Enforce a 64 MiB size budget in prune() (oldest
rows first, VACUUM to return the space), and also prune after every
16 MiB appended so bursts can't outrun the hourly timer.
Fixes#13505
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): tolerate VACUUM failure on a full disk
VACUUM needs scratch space and can fail in exactly the state a
ballooned event log causes. The byte-budget deletes already bound live
data, so swallow the error and let the next sweep retry the reclaim
instead of aborting startup pruning and disabling the durable log.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): count the size budget in UTF-8 bytes, not characters
envelopeJson.length (UTF-16 units) and SQLite LENGTH() (characters)
undercount multibyte text by up to 3x, which could leave a CJK-heavy
log settled above budget and re-running VACUUM every sweep. Use
Buffer.byteLength and LENGTH(CAST(... AS BLOB)) instead.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(sdk): carry root overrides into the Node smoke-test sandbox (#13517)
ci-node-smoke.ts installs the packed SDK tarballs with a plain npm
install in a fresh temp dir, where the repo root package.json overrides
do not apply. When @sap-cloud-sdk 4.9.0 shipped (2026-08-24) it broke
@sap-ai-sdk/ai-api 2.14.0 (via @jerome-benoit/sap-ai-provider in
@cline/llms) with ERR_PACKAGE_PATH_NOT_EXPORTED, failing the smoke step
on every PR even though the root already pins @sap-cloud-sdk/* to 4.6.0.
Copy the root overrides block into the generated sandbox package.json
so the smoke install resolves the same pinned versions as the repo and
future third-party releases cannot break it independently.
* chore(sdk): release v0.0.79
* fix(vscode): don't steal last-used provider from ClinePass on credential refresh (#13520)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): flush the /shutdown 202 before daemon teardown
The /shutdown handler queued teardown on a microtask, which runs before
the event loop's write phase, so the daemon could process.exit() before
the accepted 202 was handed to the socket. Unix masked it (uv_try_write
lands small loopback writes synchronously); Windows has no such fast
path and lost the race regularly — the recurring shutdown.e2e.test.ts
'socket hang up' failures on windows-latest. Start teardown from the
response's write callback instead, with an idempotent 1s fallback so a
client that vanishes mid-write cannot strand the daemon, and send
Connection: close so the client gets a FIN rather than an abort.
Since the flakiness this compensated for is fixed at the source, restore
maxWorkers: 2 for the Windows core suite (serializing it cost ~3 min of
CI per run), and raise the e2e daemon discovery hang guard 10s→30s —
it guards against hangs, not runner speed.
* chore(cli): release v3.0.58
* fix(core): prevent search_codebase from crashing the process on giant single-line files (#13525)
* fix(core): prevent search_codebase from crashing the process on giant single-line files
searchWithRipgrep buffered all of rg's --json stdout into one string. Each
JSON event embeds the full text of the matched line (--max-columns is
ignored in JSON mode), so searching a directory of serialized trace dumps
(single-line multi-hundred-MB JSON files) accumulated gigabytes of stdout
until string concatenation threw RangeError: Out of memory inside the
stream data handler. That throw is outside the tool's try/catch, so it
escalated to an uncaughtException and killed the CLI/hub daemon.
Parse rg's JSON events incrementally line by line, drop events larger
than 256KB, truncate matched/context lines to MAX_LINE_CHARS, and stop
reading once maxResults is reached. The fallback regex scan now skips
files larger than 10MB (reporting the skip count) and truncates its
context lines the same way.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* simplify search_codebase crash fix to a minimal diff
Replace the incremental JSON-event parser with three small guards: stop
buffering rg stdout past 10MB, drop the trailing partial event before
parsing, and slice fallback context lines to MAX_LINE_CHARS. Drops the
fallback file-size skip and skip-count reporting.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): hide per-tool MCP auto-approve checkboxes behind a flag (#13522)
* chore(vscode): remove per-tool MCP auto-approve checkboxes from webview
MCP auto-approval is now governed solely by the global 'Use MCP servers'
toggle; the SDK approval path (shared with the CLI and desktop app) has no
per-tool granularity, so the per-tool and 'Auto-approve all tools'
checkboxes were no-ops that implied control that no longer exists. Remove
them from the MCP settings view and chat tool rows. The autoApprove arrays
in cline_mcp_settings.json and the toggleToolAutoApprove RPC are left
intact for the legacy extension.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): hide per-tool MCP auto-approve checkboxes behind a flag
Keep the checkbox components, handlers, and RPC plumbing intact but gate
rendering behind SHOW_MCP_PER_TOOL_AUTO_APPROVE=false: the SDK approval
path (shared with the CLI and desktop app) is all-or-nothing via the
global 'Use MCP servers' toggle, so the per-tool checkboxes were no-ops.
Flip the flag back on if the SDK gains per-tool approval granularity.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(tools): create new files with the platform-native line ending (#13521)
* fix(tools): use platform-native EOL for new files and preserve CRLF in apply_patch updates
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* simplify to the minimal new-file EOL fix
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* extract shared normalizeNewFileLineEndings helper
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add suggested schedule templates to the desktop Schedules page (#13529)
* Add suggested schedule templates to desktop Schedules page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix unreadable selected text in inputs caused by selection utility conflict
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Restyle Suggested section label as small gray uppercase
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Hide suggested schedule cards that match an existing schedule name
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Disable the agent todo tool and hide the Agenda UI in the desktop app (#13530)
* remove todo tool and Agenda UI, keep schedule-only tasks tool
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: biome formatting fixes
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* restore agenda backend; disable todo kind behind a flag instead of deleting
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* keep agenda automation pump idle while the todo tool is disabled
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* remove todo tool and Agenda UI altogether (revert the disable-flag hybrid)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* restore all agenda code to main state
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* disable agent todo tool and hide Agenda UI behind flags
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add Desktop App and Cloud Platform to bug report issue template (#13532)
* Add Desktop App and Cloud Platform to bug report surfaces
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename Surface Diagnostics field to Diagnostics
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: sidebar navigation cleanup with New/Schedule/Customize rows and dialog-based search (#13533)
* desktop: clean up sidebar navigation chrome
- Give New Task its own full-width labeled row below the logo row
instead of an ambiguous icon next to the agenda toggle
- Wire the New Task row to the home action so starting a new task
clearly takes you home (the logo still works as a fallback)
- Swap back/forward chevrons for browser-style arrow icons and
bump their size
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: sidebar New/Schedule/Customize rows and always-visible search
- Stack New (plus icon), Schedule, and Customize as full-width labeled
rows below the logo; whole row highlights on hover via sidebarItem
- New starts a fresh task (home), Schedule opens Settings > Schedules,
Customize opens the Customizations sections (Plugins first)
- Show the session search bar permanently above the sessions list
instead of hiding it behind a search icon toggle
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: move session search into a dialog behind a logo-row icon
- Replace the inline sidebar search bar with a search icon in the
logo row that opens a cmdk command dialog listing sessions
- Selecting a result opens that session and closes the dialog
- Remove the agenda/tasks toggle the icon replaces, along with the
now-unreachable sidebar Agenda panel (the welcome screen still
surfaces agenda tasks)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: load full session history when the search dialog opens
Addresses Greptile review on #13533: the dialog only searched the
currently loaded history batch, so older unloaded sessions could not
be found. Opening search now kicks off loadAllSessions() (the hook's
purpose-built global-search loader), and the empty state reads
'Searching older sessions...' while more history is streaming in.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Hide Channels and Agents sections from desktop app sidebar (#13527)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Desktop app: organize sidebar sessions into Pinned, Scheduled, and Tasks sections (#13528)
* Add Pinned/Scheduled/Tasks categories to desktop app sidebar
Replace the Schedules and Favorites filter-menu options with visible
collapsible category sections in the session sidebar, and rename the
Favorite action to Pin across the sidebar and sessions view.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Grow full history window when Tasks show-more outpaces loaded tasks
loadMoreSessions treats its argument as a limit on all sessions, but the
Tasks show-more count only tracks Task rows, so once pinned/scheduled
rows pushed the loaded total past the requested count the call no-oped
and clicks went dead. Grow the whole history window via
loadOlderSessions instead, and only when the loaded tasks cannot fill
the next page. Addresses Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Auto-fill the Tasks page instead of fetching once per show-more click
A single 50-session window growth can consist entirely of pinned or
scheduled sessions, leaving a show-more click with no visible Tasks
progress. Replace the one-shot fetch with a page-fill effect that keeps
growing the history window until the requested Tasks page fills or
history runs out. Addresses the follow-up Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Halt page-fill retries after a failed history fetch
A failed fetch leaves the task count and has-more state unchanged,
which are exactly the conditions the page-fill effect fires on, so one
failing request would retry and re-toast forever. Halt the effect after
a failure and let the next explicit show-more click retry. Addresses
the third Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Redesign desktop Model Providers page and split voice input into its own settings page (#13531)
* Redesign desktop Model Providers page and split voice input into its own settings page
- Group providers into Connected / Popular / All with auth-kind hints and
connection status instead of per-row enable toggles
- Show browser sign-in (not an API key field) for OAuth providers, with a
collapsed manual-key escape hatch where supported, plus explicit
Connect / Disconnect / Sign out actions
- Move voice input to a dedicated Settings > Voice page that only offers
connected transcription-capable providers, preselects a default model
(streaming preferred), and stays disabled in the sidebar until a
provider is connected
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Show native tooltip on the disabled Voice settings nav item
Disabled buttons drop pointer events, so the 'connect a model provider'
hint moves to a wrapping span for the browser tooltip to render.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Drop letter avatars and gray provider ids from provider rows and voice chips
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Drop model counts from provider list rows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename provider Connected status to Configured and drop the green styling
A settings entry is configuration, not a live connection; neutral gray
text avoids implying an active link, since the user still picks which
configured provider to use per chat.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Resync provider catalog from disk when a settings save fails
Connect/disconnect/credential edits update the list optimistically; a
failed save now reloads the catalog instead of leaving the optimistic
state (and the view's module cache) claiming a configuration that was
never persisted.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename oauthProvider test fixture to dodge CodeQL name heuristic
CodeQL's clear-text-storage query flags any identifier matching 'oauth'
as a credential source and traced the fixture's provider id into the
favorite-models localStorage write, which stores only provider/model id
strings. Renaming the fixture removes the false-positive source.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Guard catalog reloads against races and resync detail drafts on failed saves
Optimistic provider mutations now bump a generation that discards any
in-flight catalog response, so a failed-save recovery reload can't
overwrite a newer edit with an older disk snapshot. The recovery also
remounts the provider detail panel via a reset token so its local field
drafts reflect the reloaded on-disk state instead of unpersisted edits
or an optimistically cleared disconnect.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix failed-save recovery ordering and retry superseded reloads
Remount the provider detail only after the authoritative catalog reload
lands, so its drafts re-seed from disk state rather than the optimistic
values that failed to persist. When a concurrent edit supersedes the
recovery's in-flight response, retry the reload (bounded) instead of
dropping it, since that edit performs no reload of its own.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): Customize hub, sidebar overhaul, and settings polish (#13538)
* feat(desktop): merge customization pages into a Customize hub with inline marketplace
Replaces the Plugins page and the dedicated Marketplace page with a single
Customize hub. Tabs: Skills, MCP, Plugins, Rules, Hooks, Tools, each with
live counts. Tabs backed by a marketplace catalog render the installed
items followed by an inline browsable Browse section (CLI-hub style), so
installing from the catalog immediately reflects in Installed above.
- Installed cards restyled to mirror the browse-card anatomy: bg-card p-4
containers, absolute top-right xs Uninstall matching Install, truncating
semibold titles, primary-tinted icons, real Badge components instead of
ad-hoc bordered spans, un-indented line-clamped descriptions
- Rules/Hooks/Tools rows brought into the same card language; redundant
intro paragraphs (duplicating the page description) removed; Tools group
headers match the Installed header style with counts
- Marketplace section header renamed to Browse; duplicate 'N results' row
removed (the header count is the single source)
- MCP embedded view now shows the full marketplace instead of
installed-only
* feat(desktop): overhaul sidebar sessions and navigation
Sessions list:
- Sort toggle removed; sessions are always grouped by project, with pinned
sessions leading each group (both subsets ordered by recency). The
Pinned/Scheduled/Tasks category sections and their time-mode paging
machinery (page-fill effect included) are deleted
- Scheduled sessions get an inline clock icon next to the pin position;
pin + clock render together when both apply, and the running/unread
status dot now coexists with them
- One font size (text-sm) across the list: titles, timestamps, project
headers, show-more buttons, empty states. sidebarText needed !text-sm
because the default button size's text-base wins the twMerge conflict
- Gradient fade under the Sessions header once the list scrolls, so rows
fade out instead of hard-clipping
- The session-detail hover card is controlled from the sidebar and closes
on scroll (Radix receives no pointer events while scrolling, so it used
to float over moving content)
- Sidebar min resize width raised 224->260px; the per-project show-more
label truncates so its nowrap text can't force rows to overflow and clip
timestamps at narrow widths
Navigation:
- Customize replaces the Plugins/Marketplace/Hooks/Rules/Tools sidebar
entries; Schedules and Customize are hidden from the expanded settings
nav (their top rows cover them) but stay reachable when collapsed
- The settings gear always opens General instead of resuming the last
section; the Account no-op hover special case is gone
- The New row highlights (aria-current) while the fresh not-yet-started
task page is showing and hands off to the session row once the task
starts; hitting New also focuses the prompt input via a window-event
signal (lib/prompt-input-focus.ts) since the sidebar and composer sit in
distant subtrees
- Fixed the xs button size collapsing any icon-bearing button to 12x12
(leftover has-[>svg]:size-3 from when xs was a micro button) — this was
why Uninstall buttons rendered broken next to Install
* feat(desktop): polish settings pages and chat composer
Models page:
- The provider detail panel is always open: no X button, no empty
no-selection state. It defaults to the first connected provider (falling
back to the first in the catalog), which also removes the layout shift
that happened when the page swapped between full-width and panel
variants on selection
- Fixed the list pane becoming unscrollable while the panel was open:
grid items default to min-size auto, so the pane grew past its track
inside the overflow-hidden grid and its ScrollArea had nothing to
scroll; wrapped it in a min-h-0 min-w-0 cell
- Add Provider opens a Dialog instead of swapping the page
(AddProviderContent gained a dialog variant that renders only the form)
- Embedded inputs (provider search, model search, detail fields) share one
EMBEDDED_INPUT_CLASS stripping the Input component's own border/dark bg
tint/shadow/ring, which rendered as a mismatched inner box; the model
search box uses the same h-9/px-3 frame as the provider search
- Model list flows with the page instead of a max-h capped inner scroller
Other pages:
- Account uses the shared PageFrame/PageHeader: left-aligned, text-3xl
title, Sign Out in the header actions slot
- Desktop notifications is one General section: header row plus the
Event/Notify/Sound matrix nested in a card, so its rows no longer read
as top-level peers of Dark mode; 'Available in the desktop app' label
removed
- Schedule page retitled from Schedules with a real description; Customize
description rewritten
Chat composer:
- The voice dictation button only renders once a voice model is
configured (Settings -> Voice); the unconfigured deep-link state is
gone (prop type kept for an easy restore)
* chore(desktop): release v0.0.17
* fix(desktop): unblock sdk-test lint on the voice-input model picker (#13553)
The model picker renders a radiogroup of styled buttons with role=radio
and aria-checked; biome's useSemanticElements flags the role as an
error, which fails the sdk-test Quality Checks lint for every PR
touching sdk/ or apps/ paths. Suppress with a justification — switching
to input type=radio needs a restyle and belongs to the desktop settings
work.
* fix(vscode): include rich workspace metadata in system prompt (#13518)
* capture richer workspace information for vs code extension
* fix(shared): redact credentials from workspace remotes
* fix(shared): avoid regex backtracking in remote redaction
---------
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
* Hide task costs on vscode when ClinePass is selected (#13515)
* fix: stop showing cost estimates for subscription-billed providers (#13552)
* fix(vscode): stop showing cost estimates for subscription-billed providers
Providers whose usage is covered by a flat-rate subscription (ChatGPT
Plus/Pro via openai-codex, ClinePass) are marked with
metadata.usageCostDisplay = "subscription" in the SDK, and the CLI
already suppresses dollar figures for them. The VS Code host collapsed
that value into "show" before it reached the webview, so the task
header and model pricing rows rendered API-rate cost estimates that
users read as real charges on top of their subscription.
Pass all three usageCostDisplay values ("show" | "hide" |
"subscription") through the catalog listing and render cost only when
the value is "show", matching the CLI's shouldShowCliUsageCost
policy.
* feat(llms): mark Claude Code as a subscription-billed provider
Claude Code is typically authenticated with a Claude Pro/Max
subscription, but its models reuse Anthropic API pricing metadata, so
Cline rendered per-token prices and API-rate cost estimates for usage
that is covered by the subscription. Set usageCostDisplay =
"subscription" on the provider (picked up by the CLI and the VS Code
webview) and suppress the price rows in the Claude Code settings card.
The Claude Code CLI can also run on API-key billing, where a real cost
exists; the provider cannot distinguish the two, so we prefer showing
no number over a misleading one.
* fix(vscode): suppress cost display until provider listings load
While the ListProviders request is in flight (or after it fails), the
usage-cost hook had no listing to consult and fell back to "show",
flashing the API-rate estimate at subscription users on every chat-view
mount — the exact display the previous commit removes. Return
"unknown" whenever listings are absent; consumers already render cost
only for "show", so they suppress it during that window with no
changes. Briefly hiding a real cost is harmless, briefly showing a fake
charge is not.
* fix(desktop): reconcile voice settings after main sync
* test(llms): allow experimental ElevenLabs models
* fix(sdk): preserve canonical media model behavior
* feat(desktop): customize macOS DMG install window (#13563)
* feat(desktop): add Retina DMG background tooling
* feat(desktop): customize the macOS DMG layout
* ci(desktop): validate DMG background assets
* fix(desktop): adjust DMG Applications icon position
* ci(desktop): drop redundant DMG artwork validation from publish workflow
Tauri's beforeBuildCommand already runs dmg:background (with its own
validation) at the start of the build/sign/notarize step, and the
release/beta config overlays do not override the build section, so this
step duplicated work the publish job performs anyway. PR-time coverage
lives in desktop-test.yml.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): sidebar time view, Customize/Marketplace split, and schedule page UX (#13570)
* feat(desktop): split Customize into Installed and Marketplace pages
The Customize hub previously embedded a Browse section inside every tab
that had a catalog. That inlining made each tab long and buried the
catalog. Customize is now the installed inventory only (skills, MCP,
plugins, rules, hooks, tools tabs pass marketplaceVariant="installed"
to the embedded MarketplaceView; McpServersContent grew the same prop),
with an outline Marketplace button in the header.
Browsing moved to a dedicated Marketplace settings section that renders
the previously dead "directory" variant of MarketplaceView: one list
across all catalog types with type-filter chips, wrapping tag chips,
and light rules separating the filter tiers from each other and from
the results. The Clear control now renders inline at the end of the tag
row only while a tag is active, the Updated date is gone, and the
header hosts an Installed button mirroring the one on the Customize
page. Directory subheader copy: "A curated set of plugins, MCP
servers, and skills from the Cline community."
Tag and type chips wrap to new lines instead of scrolling
horizontally.
* feat(desktop): sidebar time view with sections, sort toggle, and scheduled detection
Restores the time-sorted session list as the default sidebar view, with
collapsible Pinned / Scheduled / Tasks sections (headers appear only
once something is pinned or scheduled) and the page-fill effect that
grows the fetched history window until a Show-more click makes visible
progress. Project grouping stays as the alternate mode behind a
one-click sort toggle whose icon reflects the active mode — the old
dropdown cost an extra click for a two-option choice.
Scheduled sessions are detected two ways: the hub-schedule origin
trigger in session metadata, plus a fallback that asks the hub which
session ids belong to schedule executions (list_routine_schedules,
fetched on mount and every two minutes, merged into a rolling set).
The fallback matters because locally executed scheduled runs do not
reliably stamp the trigger into session metadata — a real scheduled
session created today carried only {mode:"user"} provenance. The
scheduled clock icon now leads the row, left of the title; pin and
timestamp stay on the right.
The initial visible page grows from 10 to 30 rows so a tall sidebar
fills instead of stranding a stub of rows over empty space (history
fetches already start at 50).
The expanded sidebar's Customize row now hosts indented Installed and
Marketplace sub-tabs while a customize section is open; the active
sub-tab carries the full selected background while the parent keeps a
subtler one so the two simultaneous highlights read differently.
Also fixes the hover-card flash on click (logo card and session-row
cards): Radix HoverCardContent sits on a DismissableLayer, so a click
on the trigger registers as a pointer-down outside the card and
dismisses it, and the trigger's focus event immediately reopens it.
onPointerDownOutside preventDefault suppresses the dismissal; cards
still close on pointer leave.
* feat(desktop): schedule page row, dialog, and details UX polish
Schedule cards are now click targets: clicking anywhere on a card
outside its controls opens the details dialog (guarded via
closest("button,...") since every inline control, including the Radix
switch, renders a button element), with Enter/Space keyboard support.
The redundant eye button is gone. The remaining edit / run / pause /
delete buttons grow from the 12px icon-sm size to 28px targets with
16px icons, sized consistently with the adjacent enable toggle — the
icons use explicit size-4 classes so the Button base svg rule cannot
shrink them back.
The new/edit dialog gains breathing room between field labels and their
inputs (space-y-2 per field wrapper).
The details dialog no longer scrolls as a whole when the schedule JSON
is long: the dialog is a flex column capped at 85vh, the JSON pre
shrinks to the remaining space (min-h-0) and scrolls internally, and
the Runs tab list scrolls inside the tab the same way.
* feat(desktop): scheduled sessions UX — unified details dialog, run-now handoff, hidden steering, stuck-thinking fix (#13573)
* feat(desktop): merge schedule details into one view and open run-now sessions
The schedule details dialog drops its Overview/Runs tabs: one scrollable
column with the meta grid, the configuration JSON (capped at max-h-64
with internal scroll so it cannot crowd out what follows), and a Runs
section beneath it showing the three most recent runs with a ghost
"Show all N runs" expander (collapsed again whenever a different
schedule's details open). The "Full configuration for this schedule"
subtext is gone; the dialog passes aria-describedby={undefined} so
Radix does not warn about the missing description.
Run now hands you into the session it starts. The trigger command
queues the run and returns before the runner attaches a session id, so
after the toast the handler polls the schedule overview once a second
for up to 15 seconds — which doubles as keeping the page's run status
fresh (refreshSchedules now returns the fetched overview to make that
single-stream) — until the triggered execution reports its session id,
then calls onOpenSession. Guarded so it never auto-navigates after the
user left the page.
* feat(desktop): hide runtime steering messages from transcripts
Scheduled/automation runs inject user-role steering messages each
iteration ("[SYSTEM] This run is not complete until you call
submit_and_exit...", plus a team-obligations variant). The chat view
rendered them as user bubbles, as if the person had typed them — in a
scheduled session the transcript was mostly [SYSTEM] noise.
They are machinery talking to the model, not something the person said
or needs to read, so the transcript now hides them entirely:
MessageBubble renders null for any [SYSTEM]-prefixed user message.
Grouping still treats them as working-row machinery via a single
isSystemSteeringMessage predicate — they collapse into the run's work
span, are never a turn boundary, can never be mistaken for a run's
answer, and never advance the run count even when metadata is missing —
so work-block folding and checkpoint/edit run numbering stay correct.
A finished scheduled session now reads as prompt, work summary, answer.
* fix(desktop): poll history while an attached session's event stream is dead
Opening a scheduled session while (or right after) it runs left the
view stuck on the thinking shimmer until the user switched away and
back. Root cause is in core: the hub daemon executes scheduled runs on
a private LocalRuntimeHost inside createLocalHubScheduleRuntimeHandlers,
while the hub server only projects live events from its own session
host — so session.attach succeeds but no assistant/tool/status events
ever flow. And since multiple hub daemons share cron.db, a run claimed
by a different daemon is invisible to this hub regardless. The proper
core rewiring is tracked as ENG-2474.
Client-side heal that covers every case: while an attached history
session reports a busy status and no chat_event chunk has arrived for
five seconds (and no assistant bubble is mid-stream), poll every three
seconds — re-read canonical history, merged through the same dedupe
path hydration uses, and the session record's status — so the
transcript and the thinking indicator settle in place. Locally driven
turns keep chunks flowing, so the quiet-window guard keeps the fallback
inert there.
* chore(desktop): format workspace selector components
Biome formatting drift that landed on main; picked up by a formatter
pass over components/views/chat.
* fix(desktop): keep stale-stream poll inert during locally driven turns
The fallback poll could fire between a local submit and the model's
first chunk (optimistic user bubble added, stream quiet past the
window, no assistant bubble yet). It then replaced the optimistic
bubble — raw prompt text — with its canonical history twin, which is
stored wrapped in a user_input envelope. The rekey handler that runs
when the stream starts looks for a trailing user bubble matching the
raw prompt, finds only the wrapped copy, and appends a second bubble:
duplicated messages in normal interactive chat.
The poll now stays inert while a local turn is in flight
(turnEpoch !== turnSettledEpoch, or outstanding optimistic user
messages), checked both before polling and again after the snapshot
returns. Hydration marks the turn settled — the mount defaults
(epoch 0, settled -1) otherwise read as an open turn and would keep
the fallback inert forever for the scheduled-session case it exists
for. Applying a polled snapshot also rebuilds the live tool routing
keys, same as hydration, so later tool events update canonical rows
in place instead of appending.
* fix(desktop): keep the working indicator alive for narrating scheduled runs
Watching a scheduled run live: the first tool row appeared, then the
thinking indicator vanished with nothing streaming, and the rest of
the run (final answer, submit_and_exit) only showed up seconds later
in one lump.
inferHydratedChatStatus treats a "running" session record whose
transcript ends on an assistant message as a session that died without
a status flip and reports "completed". That heuristic is right for
stale records, but scheduled/automation models narrate between tool
calls, so a polled snapshot can genuinely end on assistant text
mid-run — the completed flip hid the working indicator, folded the
run early, and disarmed the stale-stream poll (status left the busy
set), dead-ending live updates until an in-flight poll happened to
deliver the finished run.
The heuristic now only applies once the transcript has actually gone
quiet (newest message older than two minutes — comfortably past model
latency plus tool runs). A recently active transcript keeps the
record's "running" verdict, so the indicator stays up and polling
stays armed until the record itself settles.
* fix(desktop): stale-stream poll mirrors the session record instead of inferring
Replaces the previous fix for the vanishing working indicator (the
time-window guard added to inferHydratedChatStatus) with a version
that adds no inference at all: the heuristic is restored to exactly
its long-standing form, and the poll now maps the session record's
status verbatim (mapSessionRecordStatus).
The record is the right authority in the poll's context: the sessions
this fallback serves have a live host maintaining their record, and it
flips to a terminal status when the run ends. Transcript-shape
inference belongs only where it has always lived — hydrating sessions
whose records may be orphaned — and would misread a mid-run snapshot
ending on assistant narration as a finished session, hiding the
working indicator and disarming the poll.
* fix(desktop): address review findings on steering detection and run-now matching
Steering detection additionally requires the injected-message marker
(meta.userRunSpan === 0) beside the [SYSTEM] prefix, so a person's
genuine prompt that happens to start with "[SYSTEM]" stays visible
and turn-counted. The failure direction is deliberate: an unstamped
injected reminder would merely show as a user bubble, while the
content-only check could hide a real prompt.
Run-now only follows the execution id the trigger reply itself named;
the newest-execution-for-this-schedule fallback could open a previous
run's session when the trigger failed to enqueue one.
* fix(desktop): report a failed run-now instead of confirming a start
A trigger reply without an execution means no run was enqueued (the
schedule may have been disabled or deleted since the page loaded). The
handler previously toasted "Run started" regardless and then silently
skipped the session-open polling. It now shows a destructive
"Run not started" toast, refreshes the schedule list so the row
reflects reality, and skips the polling entirely.
* fix(desktop): don't block the main thread on quit while stopping the sidecar (#13566)
Quitting the mac app beach-balled for ~5-7s. The shutdown POST was
built from the ws transport URL (appending /shutdown lands inside the
query string), so the sidecar was never told to exit, and stop() then
polled the child for up to 7s on the main thread - on macOS inside
applicationWillTerminate - before SIGKILLing it.
stop() now sends SIGTERM and returns immediately. The sidecar handles
SIGTERM with the same bounded (5s) graceful shutdown as the /shutdown
endpoint and exits itself, finishing session persistence as an orphan.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): hover trash button on sidebar session rows (#13582)
Each session row shows a trash icon on the right while hovered (or
when the button itself is focused), opening the same delete
confirmation dialog the row's context menu uses. The row is a button
and buttons cannot nest, so the trash is an absolutely positioned
sibling inside a group/row wrapper, overlaid where the timestamp sits:
row hover hides the timestamp, shows the trash, and moves the row's
hover background to the wrapper group so it holds while the pointer is
on the trash itself.
* fix(desktop): install marketplace plugins and MCP servers in-process instead of spawning a cline binary (#13585)
* fix: install marketplace plugins and MCP servers in-process instead of spawning a cline binary
The desktop app sidecar and cline-hub shelled out to 'cline plugin install'
and 'cline mcp install' for marketplace installs. Packaged GUI apps inherit
launchd's minimal PATH on macOS and most desktop users have no cline CLI
installed at all, so installs failed with a red
'Executable not found in $PATH: "cline"' error.
Install via @cline/core's installPlugin/installMcpServer in-process instead,
matching what the VS Code extension already does. Also fix
parseMcpInstallArgs in @cline/core to treat the marketplace catalog's '--'
separator as end-of-options; previously the separator itself became the
stdio command.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop test-injection plumbing from marketplace installers
Call @cline/core's installPlugin directly instead of threading an
installer option through the marketplace entry points; tests stub the
core module instead.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert: keep cline-hub marketplace installs CLI-backed
The hub dashboard is launched via 'cline dashboard', so a CLI is always
present and CLINE_WRAPPER_PATH resolves it; the PATH bug only affects
the desktop app, which does not ship a CLI.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(desktop): release v0.0.18
* chore(vscode): release v4.1.16
* chore(sdk): release v0.0.80
* chore(cli): release v3.0.59
* fix(hub): stop shipping full transcripts inside broadcast hub events (#13587)
* fix(hub): stop shipping full transcripts inside broadcast hub events
Every session.updated (and session.created/detached/run.started) event
embedded the session's ENTIRE message transcript via readCoreSessionSnapshot,
even though no consumer reads snapshot.messages off an event — clients fetch
messages with the session.messages command. For a multi-megabyte transcript
this turns every status flip into megabytes per subscriber, floods the
durable event log, and (until the send-queue backpressure fix lands) lets a
slow subscriber balloon the hub process by one full transcript copy per
event — reported as a 25GB cline process on a 16GB Mac.
Strip snapshot.messages centrally in HubServerTransport.publish() so every
current and future event publisher is covered, the event log stores slim
envelopes, and cursor replay stays byte-identical with live fan-out. All
other snapshot fields (status, usage, model, workspace, checkpoint) are kept,
and command replies are untouched.
* fix(hub): never capture the transcript into event/reply snapshots
Replaces the publish-boundary strip with the real fix: don't build
message-bearing snapshots in the first place. emitSessionSnapshot no longer
re-reads the entire transcript from disk on every status flip, and
readCoreSessionSnapshot no longer reads it for any event or reply — a
snapshot is a state notification (status, usage, model, workspace,
checkpoint); the transcript is fetched via the session.messages command.
Checkpoint-restore snapshots (session-versioning-service) are untouched:
restore replies carry messages in their own dedicated field.
* chore(desktop): release v0.0.19
* chore(sdk): release v0.0.81
* chore(cli): release v3.0.60
* fix(vscode): avoid render crash on malformed api_req payloads in combineApiRequests (#13560)
* fix(vscode): stop pinning DeepSeek model count in catalog smoke test (#13600)
* feat(ui): share agent welcome hero (#13567)
* feat(ui): share agent welcome hero
* test(ui): cover welcome hero pointer states
* refactor(ui): keep welcome hero API minimal
* test(ui): verify welcome hero package assets
* fix(ui): inline welcome hero masks
* fix(tools): preserve a file's own CRLF line endings across apply_patch updates (#13512)
* fix(desktop): keep the window title bar draggable across views (#13572)
* fix(desktop): keep window title bar persistent
* fix(desktop): reserve persistent title bar space
* fix(desktop): polish persistent title bar layout
* Sign Windows CLI binaries with Azure Trusted Signing; surface app-control launch errors (#13021)
* feat(cli): sign Windows binaries with Azure Trusted Signing and surface app-control launch errors
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): use _CLI-suffixed signing profile secret, normalize endpoint, fail loud on partial config
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* Tunnel ProtoBus over the existing Host Bridge (#13218)
* feat(core): tunnel ProtoBus over Host Bridge
* fix(core): harden Host Bridge stream lifecycle
* fix(core): serialize concurrent chunked responses per request
Streaming handlers deliver updates fire-and-forget, so two logical
responses for one request_id can be in flight at once. Chunked payloads
made forwarding non-atomic: each chunk write is an await, so concurrent
forwards could interleave their chunk sequences and the receiver --
which reassembles purely by arrival order -- would splice two payloads
into one. Route all forwards for a request through one promise chain; a
failed write rejects every later forward so a torn payload is never
followed by more chunks.
Rename the lock manager's instanceAddress to instanceOwner: it holds an
opaque per-spawn instance ID on the token path and a listener address
only on the CLI-harness path. Delete the caller-less getInstanceByPort
query that interpreted the owner as an address.
Also: document message_json as a legal wire encoding for small
payloads, close the gRPC client when startup fails, note the
intentional discard of the cancellation confirmation, and add the
proto's trailing newline.
---------
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
* Build and Authenticode-sign a Windows x64 desktop installer in desktop releases (#13607)
* feat(desktop): build and Authenticode-sign a Windows x64 NSIS installer in desktop releases
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): pin OIDC-adjacent actions to commit SHAs in the Windows signing job
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): pin checkout and upload-artifact to commit SHAs in the Windows signing job
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): show agent-created schedules on the Schedules page (#13613)
* fix(desktop): show agent-created schedules on the Schedules page
Schedule hub commands are scoped to the workspace registered by the
connection, but the desktop app's hub client registers the app launch
directory while agent-created schedules live under each chat's own
workspace folder - so they never appeared on the Schedules page.
Grant token-authenticated hub connections (which can already bind any
workspace at registration) explicit cross-workspace schedule access via
an allWorkspaces payload flag, and have the desktop sidecar request it
for routine schedule commands. Workspace-bound clients (local browser
origins) and default CLI behavior stay scoped.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(core): strip allWorkspaces flag from schedule inputs and pin it in the sidecar payload
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make suggested routine template prompts prescriptive about their final output (#13611)
* Make bug hunter routine template prescriptive about its final report
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make remaining routine templates prescriptive about their final output
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Desktop: surface scheduled-task final output — auto-expand submit_and_exit and render its summary as markdown (#13612)
* desktop: auto-expand submit_and_exit and render its summary as markdown
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: render submit summary in full foreground color
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: label the submit row 'Scheduled task completed'
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: label errored submit_and_exit rows as failed
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add tooltips explaining Live and After recording badges on voice input models (#13610)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Remove box shadow from chat message actions row (#13630)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): make the Tauri shell work on Windows (#13632)
- Defer updater installation to the user-initiated restart on Windows:
install() launches the NSIS installer and exits the process immediately,
so the background cycle now downloads only and stages the bytes, and
restart_to_apply_update installs them after stopping the sidecar.
- Spawn child processes (sidecar, git, cmd /C start) with CREATE_NO_WINDOW
so the GUI-subsystem app doesn't pop visible console windows.
- Fall back to USERPROFILE when HOME is unset resolving the MCP settings
path, matching the sidecar's homedir().
- Reap the sidecar after the Windows hard-kill so its exe file lock is
released before the NSIS installer replaces it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): stop watching agenda spec dirs while the todo tool is disabled (#13629)
* fix(core): stop watching agenda spec dirs while the todo tool is disabled
Since #13530 disabled the agent todo tool, the Agenda UI, and the
automation pump, the hub still created fs.watch watchers on the global
agenda specs dir and on every workspace root recorded in the task store
(at startup and on scope access). Nothing consumes the watcher-driven
task events while the feature is off, and the task.* hub commands
already reconcile spec files on demand, so the watchers are pure
overhead - one OS watch handle per known workspace.
Wire watchFiles to AGENDA_TODO_TOOL_ENABLED the same way
automationEnabled is, preserving a host's explicit watchFiles opt-out
for when the flag is turned back on. Schedules are unaffected: the
schedule list has no file watcher and updates through hub commands and
published schedule events.
* fix(core): reconcile external spec edits inside updateTask
With the spec watchers off there is no background reconciliation, so a
task spec edited directly on disk made every same-store task.update fail
the signature check with "task spec changed outside the manager" until
an unrelated task.get or task.list happened to reconcile the scope.
Reconcile the task's scope at the start of updateTask (mirroring what
refreshAndVerifyTaskIntent already does for approve/run), skipping it
when the file reconciler itself is the caller to avoid recursing from
reconcileFileStore. An external edit now surfaces as the store's normal
stale-revision conflict, and a re-read-and-retry succeeds. This also
closes the pre-existing watcher debounce race for updates.
* fix(sdk): don't log out Codex/OCA users when token refresh fails transiently (#13565)
* fix(sdk): don't log out Codex/OCA users when token refresh fails transiently
Port the cline-provider refresh semantics to openai-codex and oca:
a transient refresh failure (network error, timeout, server 5xx) with an
already-expired access token now rethrows instead of returning null.
A null return means the refresh token was REJECTED and re-auth is
required; treating an outage blip as a rejection is what turned it into
a forced 'openai-codex requires re-authentication.' task stop while the
settings UI still showed the user as signed in.
Both providers also emit user.auth_refresh_soft_failure telemetry on
transient failures (the 'prevented logout' counter the cline provider
already has) and attach status/errorCode details to the genuine
invalid_grant logout event.
* refactor: collapse duplicate soft-failure telemetry branches and test
Review feedback: compute tokenExpired once and emit the soft-failure
event once in both providers, then return current credentials or
rethrow. Fold the codex soft-failure telemetry assertions into the
existing still-usable-token test instead of a near-duplicate case.
* fix: make OpenAI Codex (ChatGPT subscription) sign-in fail loudly instead of silently dead-ending (#13537)
* fix: make OpenAI Codex sign-in fail loudly instead of silently dead-ending
When callback port 1455 is already in use (e.g. by the Codex CLI or a
previous pending sign-in), startLocalOAuthServer returns a no-op server
and loginOpenAICodex would open the browser anyway, then dead-end:
the callback could never be received, and in the VS Code extension the
user just saw nothing happen after clicking 'Sign in to OpenAI Codex'.
- loginOpenAICodex now fails fast with an actionable 'port in use'
error before opening the browser, unless the host provides manual
code entry (the CLI's paste fallback keeps working)
- surface OAuth redirect errors (e.g. access_denied) instead of
collapsing them into 'Missing authorization code'
- the extension dedupes concurrent sign-in clicks: a re-click re-opens
the auth page of the pending flow instead of spawning a second flow
that would collide with our own callback server
- browser-open failures now show an error message with the URL to
open manually instead of only logging
- abandoned-flow timeouts no longer surface a confusing 'Missing
authorization code' toast
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop host-side codex login dedupe, keep flow identical to CLI
The SDK owns the failure handling now (fail-fast on an unbindable
callback port), so the extension keeps the exact same simple
loginOpenAICodex call the CLI uses. A second click while a flow is
pending gets the SDK's clear port-in-use error, same as running
'cline auth openai-codex' twice would. Keep only the CLI-parallel
onOpenUrlError surfacing (the CLI prints 'open the URL above
manually'; the extension's equivalent is an error toast with the
URL).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(e2e): cover Codex sign-in callback-port failure and redirect errors
Two driven-VS Code tests for the OpenAI Codex (ChatGPT subscription)
sign-in flow:
- with port 1455 occupied on both loopback families, clicking the
sign-in button surfaces the fail-fast port-in-use toast
- with the port free, the callback server binds and an OAuth redirect
error (access_denied) propagates to a visible error toast
The second test opens a real browser tab to the OpenAI auth page as a
side effect of the genuine sign-in click.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
* feat(core): anchor agent-created schedules in the user's .cline schedules home (#13634)
* feat(core): anchor agent-created schedules in the user's .cline schedules home
Agent-created schedules inherited whichever workspace folder the chat
session happened to run in, scattering user-level routines across chat
and project folders. They were invisible to workspace-scoped listings
elsewhere, tied to folders that may be cleaned up, and each chat's
tasks tool saw a different set when checking for duplicates.
Anchor them in ~/.cline/schedules instead: the hub's scheduled-task
session defaults now resolve to that home (created on demand), so
agent-created schedules live and run in one stable user-level scope.
The tasks tool guidance now tells agents that scheduled sessions run in
the schedules home, so prompts must carry absolute paths to any project
they operate on.
Schedules created explicitly with a workspace (CLI --workspace, desktop
routine wizard) are unchanged, and existing rows keep their current
workspaceRoot - they stay visible through the all-workspaces listing
paths (#13613, #13633).
* test(core): restore any pre-existing CLINE_DIR after the agenda hub test
The test's cleanup deleted CLINE_DIR outright, so an environment that
had it configured would leave later tests in the same worker on the
default storage directory. Save the previous value and restore it.
* test(core): restore CLINE_DIR even when hub test setup throws early
Restoring the override in the try/finally missed failures thrown during
transport construction or start(), before the try was entered. Register
the restore with onTestFinished instead, which runs regardless of where
the test fails.
* fix(desktop): don't show providers as configured without real credentials (#13608)
* fix(desktop): don't show providers as configured without real credentials
The desktop settings marked any provider with a persisted settings entry
as Configured, but legacy VS Code migration and empty saves can seed
entries (e.g. qwen-code, sapaicore) holding only a default model and no
credentials. Move the CLI's isProviderSettingsUsable readiness check into
@cline/core, expose it as a computed 'configured' flag on the provider
catalog, and use it in the desktop's isProviderConnected.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): resync catalog after saves so Configured badge updates live
Optimistic provider mutations can't know the sidecar-computed 'configured'
flag, so after connecting a keyless provider or saving cloud credentials
(e.g. a Vertex project id) the row stayed 'Not configured' until remount.
Silently refetch the catalog after each successful save, guarded by the
existing generation counter so newer edits discard stale responses.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): claim a generation in post-save resync so overlapping refreshes can't apply stale snapshots
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): bump catalog generation on OAuth login success
Every other optimistic provider mutation claims a new generation; the
OAuth success path didn't, so a catalog load or resync still in flight
could arrive late and overwrite the just-connected state.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): resync catalog after OAuth login instead of bare generation bump
The resync claims a new generation (discarding any stale in-flight
response) and its own fetch covers both the new OAuth connection and any
provider saved moments earlier, matching the post-save path.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): refuse checkpoint workspace restore when HEAD moved past the checkpoint (#13626)
* fix(core): refuse checkpoint workspace restore when HEAD moved past the checkpoint
Restoring a checkpoint runs git reset --hard, which moves the current
branch pointer. If commits were made after the checkpoint (by the user
or by the agent), the reset silently knocked them off the branch,
leaving them reachable only through the reflog.
Guard the reset: if HEAD no longer matches the commit the checkpoint
was created on, throw a descriptive error (including how many commits
would be dropped) instead of destroying history. Chat-only restore is
unaffected, and users who really want to discard the commits can reset
the branch manually first.
Fixes#13550
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): close the guard-to-reset race with an atomic ref update
The moved-HEAD guard read HEAD, ran further git commands, then reset
unconditionally, so a commit landing in that window could still be
knocked off the branch. Replace the reset's branch move with git's
native compare-and-swap (git update-ref HEAD <new> <old>), which fails
if HEAD no longer points at the verified commit, and follow with a bare
reset --hard to sync the index and worktree to the already-moved HEAD.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: hide history cost estimates for subscription-billed tasks (#13562)
* fix(vscode): hide history cost estimates for subscription-billed tasks
The task-header fix for subscription providers cannot reach history:
history rows render the stored totalCost (an API-rate estimate) and do
not know which provider ran the task, so the history page printed
$X.XXXX on every row and the recent-task chips in an empty chat view
rendered a $ chip even for subscription-billed tasks.
The SDK session records already persist the provider — the CLI's
history view uses it for exactly this — but the VS Code mappers dropped
it. Map it through both transports (HistoryItem.apiProvider for the
state-pushed taskHistory, TaskItem.api_provider for getTaskHistory) and
suppress the dollar figure per row when that provider's
usageCostDisplay is not "show", via a new useUsageCostVisibility
predicate shared by both surfaces.
Rows without a recorded provider (tasks predating the field, legacy
imports) keep showing the stored value — there is nothing to key
suppression on.
* test(vscode): e2e-verify history cost suppression in real VS Code
Seeds SDK session records (one openai-codex subscription task, one
anthropic usage-billed task) into the isolated CLINE_DIR before the
webview loads, then asserts in a real VS Code instance that both the
recent-task chips and the full history page render the dollar figure
only for the usage-billed task. Covers the two boundaries the unit
tests stub: on-disk records reaching getTaskHistory with provider
populated, and the provider listings delivering the subscription mark
to the webview.
* Fix scheduled tasks disappearing after desktop app updates (#13627)
* Fix hub-managed schedules being wiped by cron reconciliation on hub restart
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Require the virtual hub/schedules path when exempting specs from removal reconciliation
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Treat recorded source mtime as proof a spec is file-backed, closing the hub/schedules spoof gap
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(shared): discover global rules at ~/Cline/Rules (#13614)
The VS Code Rules tab resolves the Documents folder via
'xdg-user-dir DOCUMENTS', which prints bare $HOME when no user-dirs
config exists (WSL/headless installs), so it reads and writes global
rules at ~/Cline/Rules. The SDK's rule search paths only covered
~/Documents/Cline/Rules, so those rules never reached the system prompt.
Add the missing path to the search list.
Fixes#13542
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat: add searchable session history (#13420)
* feat: add searchable session history
Rebased onto main and updated to supersede the sidebar search dialog
from #13533: the sidebar search icon now opens the indexed command bar
(Cmd/Ctrl+P) instead of a sidebar-local cmdk dialog that eagerly loaded
the entire session history via loadAllSessions(). CommandDialog gains a
shouldFilter passthrough so server-ranked FTS hits are displayed as-is.
* fix: harden session history search
* fix: evict failed restoration sessions from search
* fix: preserve deletion when search eviction fails
* fix: address session search review feedback
* fix: preserve search suppression during reconciliation
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* Fix CLI crash when a remote MCP server is offline but enabled (#13639)
Remote (SSE/streamable HTTP) MCP connects run on the session.create
critical path, which the hub caps at 30s. Without a connect budget an
unreachable server spent the full 60s default request timeout (with the
SSE transport stuck in a reconnect loop), stalling session.create past
the hub deadline and tearing the whole session down - the interactive
TUI exited and one-shot runs failed. Stdio servers already have a
bounded initialize budget for exactly this reason; give URL clients the
same treatment with a 10s default connect budget that an explicit
timeout overrides in either direction.
Fixes#13597
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(vscode): prevent E2E worker teardown hangs (#13644)
* test(vscode): capture external URLs in E2E runs
* docs(test): clarify browser capture rationale
* Add a GitHub integration step to the onboarding (#13225)
* Add feature flags to the app
* React to account updates
* Address comments
* Add a GitHub integration step to the onboarding
* validate domain and fix errors on auth
* Hide the step behind a feature flag
* update version
---------
Co-authored-by: John Choi <john.choi@cline.bot>
* fix(ci): stop e2e worker teardown timeouts and deflake hub daemon e2e on Windows (#13646)
* fix(e2e): stop VS Code e2e worker teardown from timing out
The ext-vscode-test-e2e job has been failing on main with 'Worker teardown
timeout of 60000ms exceeded' even though every test passes. Playwright only
reports an Electron app as closed once the process exits AND every holder of
its stdio pipes is gone (ChildProcess 'close' waits on the extra fd3/fd4
pipes Playwright creates for Electron). Any VS Code descendant that outlives
the main process (chrome_crashpad_handler, GLib's 'dconf watch' helper,
xdg-open browser handlers, VS Code 1.135's agent host CLI subprocess that
logs 'unable to kill the process') keeps those pipes open, so app.close()
never resolves and the worker teardown hangs on it until its 60s timeout
fails the job.
Harness fixes, each removing one source of that wedge:
- closeAppForTeardown now SIGKILLs the whole process group (taskkill /T on
Windows) when app.close() times out, instead of only the main pid — and
does so even when the main process already exited, which is exactly the
wedged state. Playwright launches Electron detached, so pid == pgid.
- Launch VS Code with --disable-crash-reporter so no crashpad handler
outlives the app holding the harness pipes.
- Seed the fresh user-data-dir with chat.disableAIFeatures: true so VS
Code's own AI features (rolled out via server-side experiments, so CI
breaks without any repo change) never start their agent host process.
- Drop the page.close() teardown: closing VS Code's last window quits the
whole app, and ElectronApplication.close() on an already-exited app
deadlocks; the app fixture's app.close() closes windows itself while the
app is alive.
- Codex sign-in no longer opens a real external browser under E2E_TEST; the
codex-oauth test drives the OAuth callback itself, and the browser was an
orphaned process holding the harness pipes on the runner.
* fix(core): deflake hub daemon e2e tests on Windows runners
sdk-test on windows-latest fails intermittently in the hub daemon e2e
files:
- shutdown.e2e.test.ts dies with a bare 'Error: socket hang up'. That
message is the ws handshake (http.ClientRequest) failing, not the
/shutdown fetch (an undici failure prints 'TypeError: fetch failed'):
a freshly spawned bun daemon on a loaded 2-core Windows runner
occasionally drops its first accepted connection before writing the
upgrade response. Real hub clients reconnect with backoff, and the test
asserts shutdown behavior rather than first-connection reliability, so
openAuthenticatedSocket now retries transient handshake failures within
a 15s budget.
- singleton.e2e.test.ts times out waiting for daemon discovery: it still
used the 10s hang guard that 0cfc90158 already raised to 30s in
shutdown.e2e.test.ts for the same reason. Use the same 30s guard.
- Raise the e2e testTimeout to 60s so a test that legitimately spawns two
daemons back to back can survive slow-runner startups instead of the
discovery hang guard being cut off by the test timeout.
* feat(desktop): render tool output images as attachments (#13643)
* fix(desktop): render tool output images as attachments
Add support for displaying media returned by tool calls (e.g. screenshots)
as rendered images with expand-to-fullscreen capability instead of raw
base64 text. Introduces an `ImageCarousel` component for navigating
multiple images, propagates the expand handler to tool message blocks,
and extracts/validates output media in tool summaries.
* test: cover multi-image and canonical media extraction in tool output (#13645)
extractOutputMedia and the desktop tool-message rendering path were only
ever exercised with exactly one distinct valid image, and
canonicalInlineMedia (MCP-style type: "media" blocks for audio/video/file)
had zero coverage. Add tests for: multiple distinct images in one tool
result (parser + desktop carousel navigation), inline audio via the
mime_type key spelling, canonical video/file media blocks, and rejection
of an invalid canonical image block.
---------
Co-authored-by: Harrison <harrison@cline.bot>
* chore(desktop): release v0.0.20
* feat(sdk): add discovery boundary ahead of Agent Plugins support (#13017)
* ENG-2490: Propagate session aborts to teammates (#13647)
* fix(core): propagate session abort to teammates
* fix(core): persist aborted teammate tasks as cancelled
* fix(core): settle teammate work on session abort
* fix(core): isolate replacement runs from stale aborts
* refactor(core): narrow teammate task status metadata
---------
Co-authored-by: abeatrix <beatrix@cline.bot>
* fix(llms): use AI SDK 7 Langfuse telemetry (#13651)
* fix(llms): use AI SDK 7 Langfuse telemetry
* test(llms): cover Langfuse runtime context
* chore(llms): built-in model list update 1787907289186 (#13663)
* chore(llms): built-in model list update 1787907289186
Result of `bun run build:models`.
Includes updated model list and fixed formatting issues across codebase.
* test(llms): update GLM reasoning toggle expectation
* test: cover session search fallback on hub timeout and rejection (#13642)
* feat: add searchable session history
Rebased onto main and updated to supersede the sidebar search dialog
from #13533: the sidebar search icon now opens the indexed command bar
(Cmd/Ctrl+P) instead of a sidebar-local cmdk dialog that eagerly loaded
the entire session history via loadAllSessions(). CommandDialog gains a
shouldFilter passthrough so server-ranked FTS hits are displayed as-is.
* fix: harden session history search
* fix: evict failed restoration sessions from search
* fix: preserve deletion when search eviction fails
* fix: address session search review feedback
* fix: preserve search suppression during reconciliation
* test: cover sidecar search fallback on hub timeout and rejection
The existing search_sessions tests only exercised the index-hit and
empty-index-fallback paths with an immediately-resolved hub reply.
Add coverage for the two other realistic Hub-connection failure
modes the fallback is meant to tolerate: the hub call rejecting, and
the hub call hanging past the 750ms withSearchDeadline race.
---------
Co-authored-by: abeatrix <beatrix@cline.bot>
Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
* fix(core): refresh Cline models from live catalog (#13670)
* feat(ui): share attachment drop zone (#13672)
* feat(ui): share attachment drop zone
* fix(ui): cancel disabled attachment drops
* chore(ui): simplify drop zone surface
* chore(ui): release v0.2.0-next.8
* Chore/bump undici mermaid (#13675)
* chore(deps): bump mermaid to 11.16.1 and raise undici floor to 7.29.0
* chore(deps): patch js-yaml and body-parser in the npm-managed subprojects
* fix(llms): make Langfuse tracer detection survive minified release builds (#13680)
* fix(llms): recognize direct tracer providers
* fix(llms): make Langfuse tracer detection survive minified release builds
Release binaries are compiled with minify enabled, which renames classes,
so initializeLangfuseTelemetry's constructor-name guard never matched
"ProxyTracerProvider" and silently returned readiness=false in every
production build (hub log: "creating span processor" followed by
"initialized readiness=false" with no branch message in between). Dev runs
execute unminified source, which is why the same env vars worked there.
Replace every constructor-name comparison with checks that survive
minification: detect the proxy structurally via getDelegate, distinguish a
recording provider from the no-op fallback by its lifecycle methods, and
confirm our NodeTracerProvider registration by object identity. When a
foreign provider already owns the global slot, attach the Langfuse span
processor to it when it accepts processors, and otherwise shut down the
orphaned provider and report the rejection instead of bailing silently.
Verified by bundling the module with Bun minify:true against the real
OpenTelemetry packages: the previous code reproduces readiness=false
(provider class name mangles to "H2"), the new code initializes with
readiness=true.
* fix(vscode): prevent hook spawn failures from crashing the core process (#13422)
* fix(vscode): prevent hook spawn failures from crashing the core process
A hook child-process spawn failure emitted "error" on HookProcess with no
listener registered, which Node's EventEmitter turns into an uncaught
exception - killing the entire cline-core process instead of failing the
one hook open. Guard the emit behind listenerCount so the rejection (which
StdioHookRunner handles) is the only propagation path.
The trigger was a workspace root that no longer exists on disk passed as
the spawn cwd: Node reports a nonexistent cwd as a misleading ENOENT on
the launcher binary ("spawn /bin/sh ENOENT"). Validate cwd existence in
HookProcess right before spawning - falling back to no explicit cwd with
a warning that names the missing directory - and when a spawn still fails
ENOENT because the directory vanished in between, name it in the error
message instead of blaming the shell.
* fix(vscode): fail hooks with a missing working directory instead of relocating them
Running a hook whose assigned cwd no longer exists from the host
process's own working directory would let its relative paths read and
write an unrelated location (e.g. the IDE install directory). Reject
before spawning, with an error naming the missing directory; the runner
reports the hook as failed and the task continues. Also carry pre-spawn
failure messages into HookExecutionError details so the cause is not
reduced to a bare "exited with code 1".
* fix(vscode): thread task id into hook runner creation so execution telemetry fires (#13547)
The SDK hooks adapter created every hook runner without a task id, and
StdioHookRunner gates all captureHookExecution calls on one being set —
so the next variant emitted zero hooks.execution events while discovery
telemetry fired normally. Pass the task id (and tool name for the tool
hooks) at all five factory.create call sites, and pin the threading
with a regression test.
* Desktop marketplace redesign: two-pane explorer with full catalog metadata (#13653)
* feat(desktop): add marketplace design exploration prototypes (storefront, explorer, registry)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): render catalog icon tiles without percentage padding
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): drop placeholder icon tiles from explorer marketplace direction
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): make explorer the marketplace view, drop design exploration harness
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): add category tag filters to marketplace explorer
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): collapse marketplace category pills behind a more toggle
* feat(desktop): remove maturity badges and CLI install section from marketplace
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(core): propagate parent aborts to delegated subagents (#13677)
* fix(core): propagate parent aborts to delegated subagents
* docs(core): narrow delegated abort guarantees
* fix(core): release delegated sessions after execution
* fix(core): scope abort listeners to active runs
* fix(core): inherit parent runtime pid for subagents
* fix(desktop): keep Stop available for running child agents (#13678)
* fix(desktop): keep Stop available for running child agents
* fix(desktop): reconcile aborted tool activity
* fix(desktop): guard abort and agent polling races
* fix(desktop): preserve authoritative abort status
* fix(desktop): track queue-verified completion
* test(desktop): trim duplicate abort coverage
* fix(desktop): settle delayed queue verification
* fix: sanitize stored API keys and make provider credential rejections actionable (#13549)
* fix(vscode): sanitize pasted provider API keys at the settings write boundary
Clipboards smuggle control and invisible formatting characters (newlines,
zero-width spaces, BOM) into pasted API keys. The masked key field hides
the corruption and providers reject the key with a 401 indistinguishable
from a genuinely wrong key. Strip those characters and surrounding
whitespace once in the provider config store write path, so both backing
stores (legacy state secrets and providers.json) receive the clean value.
A whitespace-only value now clears the key.
* feat(llms,vscode): classify provider 401/403 as auth errors and surface actionable guidance
Add an "auth" ProviderErrorClass, assigned when the HTTP layer reports
401/403 — status-only on purpose, since provider bodies can quote words
like "unauthorized" without the request being an auth failure. The class
rides the existing errorClass plumbing (finish -> run-failed ->
AgentErrorEvent), so every host receives it with no new wiring.
In the VS Code chat surface, rewrite classified credential rejections
from BYOK providers into actionable text pointing at the API key
configuration, keeping the provider's raw body as a diagnostic tail.
Raw bodies alone are dead ends: Mistral, for example, answers an
identical {"detail":"Invalid API Key"} for a wrong, empty, or
wrong-scope key. Cline-account providers keep the JSON path so the
webview still renders their auth failures as a sign-in card.
* Fix ask-question option text not wrapping (#13718)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(desktop): release v0.0.21
* fix(core): stop an empty capability list from stripping image input (#13583)
`modelHasCapability` documents a missing or empty capability list as
carrying no signal, so each gate declares its own default. Two readers
bypassed it and read `capabilities` directly, where an empty list is not
nullish but `[].includes(x)` is false:
- the session runtime's `modelSupportsImages` metadata used
`capabilities?.includes("images") ?? true`, so the intended fail-open
never fired for an empty list and the file-read tool silently dropped
every image from the request;
- `toProviderModel` projected an empty list onto `false`, telling pickers
a model definitively lacks vision, attachments, and reasoning when
nothing had been declared.
Both now route through the shared helpers, which state their unspecified
default explicitly: `modelSupportsImageInput` fails open for a capability
gate, and `declaredCapability` preserves `undefined` for `ProviderModel`'s
tri-state booleans. A populated list stays authoritative in both.
A thinking config now short-circuits `supportsReasoning` instead of being
OR-ed with the capability read, so its absence no longer collapses the
tri-state to `false`.
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
* fix(llms): translate gateway capabilities in one place (#13584)
* fix(core): stop an empty capability list from stripping image input
`modelHasCapability` documents a missing or empty capability list as
carrying no signal, so each gate declares its own default. Two readers
bypassed it and read `capabilities` directly, where an empty list is not
nullish but `[].includes(x)` is false:
- the session runtime's `modelSupportsImages` metadata used
`capabilities?.includes("images") ?? true`, so the intended fail-open
never fired for an empty list and the file-read tool silently dropped
every image from the request;
- `toProviderModel` projected an empty list onto `false`, telling pickers
a model definitively lacks vision, attachments, and reasoning when
nothing had been declared.
Both now route through the shared helpers, which state their unspecified
default explicitly: `modelSupportsImageInput` fails open for a capability
gate, and `declaredCapability` preserves `undefined` for `ProviderModel`'s
tri-state booleans. A populated list stays authoritative in both.
A thinking config now short-circuits `supportsReasoning` instead of being
OR-ed with the capability read, so its absence no longer collapses the
tri-state to `false`.
* fix(llms): translate gateway capabilities in one place
Three producers built gateway model definitions from catalog `ModelInfo`,
and each carried its own hand-written `switch` over the capability list.
Nothing tied them together, so they drifted:
- builtin providers always emitted a capability list, so a model whose
catalog entry declares no capabilities became `["text"]` where the other
producers emitted `undefined`. `modelSupportsToolCalling` fails open only
for an absent or empty list, so that list read as an authoritative denial
and stripped every tool definition from requests to the affected language
models (dify, sapaicore, opencode, and the Codex CLI);
- the OpenAI-compatible path mapped an `audio` capability that
`ModelCapabilitySchema` does not define, while the other two dropped it;
- the pass-through capabilities (`streaming`, `files`, `temperature`, ...)
were enumerated explicitly in one, folded into `default:` in another,
and ignored in the third.
One exported `toGatewayModelCapabilities` now serves every producer. It is
built on a `Record<ModelCapability, GatewayModelCapability | null>` rather
than a `switch`, so extending `ModelCapabilitySchema` without deciding the
new capability's mapping fails to compile instead of silently falling
through to a default.
The conformance tests walk the capability state space taken from
`ModelCapabilitySchema` itself and assert the real producers agree with the
translator, so a future producer that maps capabilities on its own fails
even when the translator's own unit tests still pass.
---------
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
Co-authored-by: Ara <arafat.da.khan@gmail.com>
* fix(cli): keep markdown streaming prop stable to stop settle flash (#13719)
Flipping the <markdown> streaming prop from true to false when an
assistant text segment settles makes MarkdownRenderable call
updateBlocks(true), which skips every block-reuse path and destroys and
recreates all block renderables. Until tree-sitter re-highlights them
the whole message renders blank/unhighlighted, which users see as the
text flashing at the end of each response. Keep streaming={true} for
the transcript markdown (opencode's TUI does the same); entry.streaming
still drives the spinner glyph.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Clarify model-facing message when user rejects a tool call (#12673)
* Clarify model-facing message when user rejects a tool call
* Include the rejected tool's name in denial reasons
* Move user-rejected tool reason into @cline/shared
* Route new user-rejection approval paths through shared reason builder
Since the original PR, several new approval surfaces landed on main with
their own terse denial strings (CLI connectors, ACP permissions, Cline Hub
webview, desktop webview, example VS Code extension). Route all of them
through buildUserRejectedToolReason so the model sees a consistent,
non-error rejection message.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add buildUserRejectedToolReason to the @cline/shared integration-test stub
The VS Code integration tests run the tsc-built CJS tree and stub the
ESM-only @cline/shared package in test-setup.js; the stub was missing the
new export, so tool-approval-denial.js threw at module load in CI.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Trim scope back to the minimal rejection-copy fix
Restore the connector deniedReason plumbing, ACP permission strings,
desktop webview reason, example extension reason, and hub server fallback
to their main versions. Those surfaces already attribute the denial to a
user and are outside ENG-2329. Keep the Cline Hub webview change since
that path emits its own rejection string the model sees.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Move rejection guidance suffix into agent runtime per review
* Apply review suggestions: neutral fallback reason and -- separator before rejection suffix
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Default web search on for the desktop app (#13725)
* Default web search on for the desktop app
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make desktop web search default seed best-effort
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(desktop): reconcile experimental sync behavior
* chore(desktop): bump beta to 0.0.22-beta.1
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: JasmineLCY <38378321+JasmineLCY@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
Co-authored-by: Max <maxpaulus43@gmail.com>
Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bee <abeatrix@users.noreply.github.com>
Co-authored-by: Renee Huang <100229782+reneehuang1@users.noreply.github.com>
Co-authored-by: Ara <arafat.da.khan@gmail.com>
Co-authored-by: Haley Park <haleypark.design@gmail.com>
Co-authored-by: cline-cloud[bot] <276134852+cline-cloud[bot]@users.noreply.github.com>
Co-authored-by: yzxcj797 <54314860+yzxcj797@users.noreply.github.com>
Co-authored-by: yzxcj797 <yzxcj797@users.noreply.github.com>
Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
Co-authored-by: 𝓜𝓲𝓼𝓼𝓪𝓻𝓲 𝓐𝓱𝓲𝓵 🌿 <143264692+missarii@users.noreply.github.com>
Co-authored-by: Dominic Cooney <dominic.cooney@cline.bot>
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
Co-authored-by: Harrison <harrison@cline.bot>
Co-authored-by: abeatrix <beatrix@cline.bot>
Co-authored-by: TheRealSpencer <32678829+TheRealSpencer@users.noreply.github.com>
* fix(vscode): continue the surviving session on resume instead of rebuilding with the original task text (#13175)
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilt the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
The preserved conversation history is the source of truth on resume, so
the fallback prompt now just asks the model to reassess the history and
continue, matching the legacy resume prompt which also never resent the
original task. User-typed text still takes precedence when provided.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): continue the surviving idle session on follow-ups instead of rebuilding
Stopping a turn keeps the session alive, but every idle follow-up (bare
Resume after Stop, and typed follow-ups after a completed turn) tore
that session down and rebuilt it from persisted task history before
sending. Continue the matching idle session in place instead, the same
way the CLI reuses the live session after an abort. Rebuilding from
history now only happens when no live session matches the displayed
task (task opened from history, extension host reload).
A bare resume still needs a prompt to start a turn, so it sends the
neutral [TASK RESUMPTION] prompt (shared with the rebuild fallback and
hidden from the transcript); user-typed content is echoed and sent
as-is. If the send lands while the abort is still settling, the runtime
auto-queues it and drains once the abort completes.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): consolidate follow-up send paths in SdkFollowupCoordinator
Now that idle follow-ups continue the live session in place, the
two-mode sendToActiveSession helper was redundant: its non-queued branch
duplicated continueIdleSession minus the bare-resume prompt. Split it
into a single-purpose queueToActiveSession and fold the idle no-task
send into continueIdleSession, flattening askResponse's decision tree
to: queue onto a running turn, continue a matching live idle session,
rebuild from history, or abandon.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): reuse the existing neutral resumption prompt for bare resumes
Drop the newly invented long resumption wording in favor of the phrase
that already existed as the no-history fallback and that the transcript
hiding logic and test fixtures recognize: '[TASK RESUMPTION] Please
continue where you left off.' The net change to resumeSessionFromTask
against main is now just deleting the branch that resubmitted
historyItem.task as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilds the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
Bare resumes now always use the neutral prompt that already existed as
the no-history fallback; user-typed text still takes precedence. This
matches the legacy resume prompt (responses.taskResumption), which only
ever included user-supplied text as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): hide synthetic prompts from the queued-prompt echo
A send that races a settling abort is auto-queued by the runtime, so a
bare Resume can reach the pending_prompt_submitted echo carrying the
synthetic [TASK RESUMPTION] prompt. Echoing it leaked model-facing text
as a visible user bubble and shifted the visible-user-message ordinals
that edit/regenerate mapping relies on. Filter synthetic prompts with
isSyntheticUserPrompt, keeping user attachments visible (matching
isSyntheticSdkUserMessage semantics).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): preserve LiteLLM input token limits (#13293)
* fix(vscode): preserve LiteLLM input token limits
* fix(vscode): prefer live LiteLLM model metadata
* fix(vscode): generalize private catalog metadata
* test(vscode): preserve llms exports in vscode lm mock
* fix(vscode): point provider signup URLs at their API key pages (#13337)
* fix(vscode): point Mistral signup URL at the general API keys console
The Mistral provider's signup link led to the Codestral console, which
issues Codestral-scoped keys that fail with 401 on api.mistral.ai — the
endpoint the provider actually calls. Point it at the general API keys
page instead.
Fixes#13288
* fix(vscode): deep-link DeepSeek and Fireworks signup URLs to their API key pages
Both pointed at marketing homepages; link straight to the key-creation
pages instead, matching the rest of the registry and the desktop app's
provider-key-urls map.
* fix(ci): always build the legacy bundle from the legacy-extension branch (#13349)
The combined-VSIX workflow took legacy-ref as a free-form dispatch input
with no publish-time validation (next-ref has one: publish requires main).
Any typed ref — a PR merge ref, an unprotected branch — would be built
into the published VSIX by the environment-less build job, and the publish
environment approver only ever sees an opaque prebuilt artifact, so the
approval protected the marketplace PAT but not the shipped bytes.
Remove the input entirely and hardcode the protected legacy-extension
branch, which makes that branch's protection rules load-bearing for
releases. The tested-sha pinning between test-legacy and build is
unchanged. publish-extension skill dispatch command updated to match.
* fix(ci): lock the legacy publish workflow to the legacy-extension branch (#13350)
The branch dispatch input was a free-form string with no validation. Both
jobs checked it out and ran full npm lifecycle scripts from it: the publish
job next to VSCE_PAT/OVSX_PAT (and npm run publish:marketplace executes a
script from that same ref with the PATs in env), and the test job with NO
environment approval at all while inheriting the workflow-level
contents/packages/checks/pull-requests write grants. A dispatch pointing at
e.g. refs/pull/N/head would run outside-contributor code with the
marketplace keys behind one approval, or with a repo-write token behind
none.
Remove the input and hardcode the protected legacy-extension branch, drop
the workflow-level permissions to contents: read, and elevate only the
publish job to contents: write (tag push + GitHub release). The branch
input's default was legacy-extension, so normal publishes are unchanged.
publish-extension skill dispatch command updated to match.
* fix(vscode): SDK remote-config parity — refresh coordination, session gating, and fail-closed opt-out (#13226)
* feat(desktop): native notifications (#13166)
* feat(desktop): native notifications
* macos target
* fix(desktop): isolate macOS dev app identity
* fix(desktop): address notification review feedback
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched (#13310)
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched
Toggling an auto-approve setting while a task is open writes
autoApprovalSettings into the StateManager's task-settings overlay
(updateAutoApprovalSettings -> setTaskSettings). The SDK controller never
cleared that overlay on clearTask/showTaskWithId (the legacy controller
did), so after New Task the stale overlay kept shadowing global settings
in getGlobalSettingsKey(): toggle RPCs were accepted into global state,
but every posted state still carried the overlay's old version, which the
webview rejects as not newer - the auto-approve checkboxes froze forever.
Restore legacy parity in SdkTaskControlCoordinator: drop the overlay
(persisting pending writes first) in clearTask() and before installing a
different task's proxy in showTaskWithId().
Fixes#13260
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* changeset
* test(vscode): add end-to-end regression test for auto-approve freeze after New Task
Wires the real StateManager, the real updateAutoApprovalSettings handler,
and the real SdkTaskControlCoordinator.clearTask() together with the
webview's version gate modeled on ExtensionStateContext, pinning the
end-to-end invariant behind #13260: checkbox toggles must keep reaching
the webview after a mid-task toggle followed by New Task. Verified the
test fails when the clearTaskSettings() call is removed from clearTask().
* fix implicit any in regression test
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): show provider web-search support under the settings toggle (#13328)
* feat(desktop): show provider web-search support under the settings toggle
The global Web search toggle silently does nothing unless the session's
provider offers native web search, which made the setting read as if it
worked with any provider. The desktop General settings row now explains
that only providers with built-in web search honor it, and shows a live
status line: which connected providers are ready to use it (no extra
setup needed), or an amber warning with a link to the Models section
when none of them support it.
Support is resolved in the webview via a new providerOffersModelTool
helper in @cline/llms (browser export), sharing the same builtin-manifest
source of truth as the runtime's supportsModelTool attachment check.
* fix(desktop): address review — refetch web-search status on catalog invalidation, clarify per-model support
Greptile P2: the one-time catalog fetch could race an in-flight provider
save and show stale status; the row now refetches when the provider
catalog cache is invalidated (fired after saves complete).
Greptile P1: the ready line implied every model on the provider works;
Vertex excludes Claude routes, so the copy now scopes the promise to
models that support it.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat(ui/desktop): collapse finished runs into a work summary and remove hover-state dead space (#13315)
* feat(ui): add WorkActivity collapsed-run summary and float message actions as a pill
WorkActivity/WorkActivityTrigger/WorkActivityContent fold a finished agent
run's working rows (tool calls, thinking traces, narration) behind a single
"Worked for 4m 12s · 14 tool calls" disclosure built on the shared animated
disclosure primitives, with formatWorkActivityLabel/formatWorkDuration
exported for consumers.
Message hover actions no longer rely on the transcript reserving blank space
below each message: the action row is now a self-backed pill (border,
blurred background, shadow) that floats over whatever follows, so
conversations can pack rows tightly without hover chrome colliding with the
next message.
* feat(desktop): collapse finished runs into a work summary and tighten chat spacing
collapseCompletedWork post-processes the grouped transcript: once a run ends
on assistant text with no further tool calls, its working rows fold into one
expandable WorkActivity row while the final answer stays visible. Runs are
delimited by user messages; the trailing run only collapses when the session
has stopped running and actually produced an answer, so live streams and
cancelled/failed tails keep their rows. Assistant messages carrying images
or media are treated as deliverables and never collapse.
The conversation list gap drops from gap-8 to gap-4 now that hover actions
are self-backed pills that need no reserved space, and user messages add
their own top margin so turn boundaries stay visually distinct.
* refactor(ui/desktop): work summary label wording, flat expansion, stable in-run rhythm
Feedback round on #13315:
- Label reads "Worked for 4m 12s and made 14 tool calls" instead of joining
with a dot; without a duration it falls back to "Made N tool calls".
- Expanded work rows render at transcript level — no rail or extra indent —
since tool rows and thinking traces already carry their own nesting when
expanded. The work content keeps the tight working-row rhythm.
- Live working rows (thinking traces + tool calls) now group into a 'run'
render item with the same tight 0.25rem rhythm, so there is no oversized
gap under a "Thought for Ns" row and every row keeps its exact position
when the finished run folds into the work summary. A trailing
answer-in-progress stays outside the group at transcript level, and pure
prose spans keep normal spacing.
- The transient "Thinking..." indicator moves inside the transcript column
and mirrors a trigger row's geometry, so the first real row replaces it in
place with no jump.
* style(ui/desktop): hover-pill metrics, right-pointing work chevron, scroll and spacing fixes
Another feedback round on #13315:
- Hover action pill: +2px internal padding, a trailing inset after the
timestamp (it sat flush against the pill border), and more clearance
between the message content and the pill (2px -> 6px; the hover bridge
grows to match).
- The work summary chevron points right while collapsed and continues
counterclockwise to point up when expanded.
- Conversation bottom padding drops pb-20 -> pb-8: the composer sits below
the scroller, so the padding only needs to clear a pinned action pill.
- Sending a message scrolls back to the bottom even if the reader had
scrolled up (new AutoScrollOnSend on the user-message count, which ignores
optimistic-bubble re-keying; @cline/ui now exports useConversation for
this).
- An assistant answer directly under its run's working rows pulls itself
0.5rem closer than the full transcript gap.
* style(desktop): leave a visible gap between a pinned action pill and the composer
pb-8 exactly matched the pill's ~40px footprint, so the last row's hover
actions sat flush against the composer top; pb-12 restores ~8px of daylight.
* style(desktop): widen the gap between the pinned action pill and the composer to ~24px
pb-12 left only ~8px of daylight under the pill; pb-16 reads comfortable
without reverting to pb-20's dead space.
* fix(desktop): keep the thinking indicator at the working-row offset mid-run
The indicator matched a trigger row's geometry but sat a full transcript gap
(1rem) below the last working row, while the tool/thinking row replacing it
joins the tight run group at 0.25rem — a visible upward jump. When the last
transcript item is working rows (or streamed assistant output), the
indicator now pulls up to the same tight offset; only at the start of a run,
under the user message, does it keep the normal gap.
* style(ui): calm the hover actions surface per team feedback
Borderless rectangle instead of the bordered pill: radius drops to
var(--radius), the side padding goes entirely (the icon buttons carry their
own hit areas), and the vertical padding halves. Blurred background and
shadow stay so it remains legible over following content.
* feat(ui/desktop): full-band hover reveal and iOS-style disclosure easing
The hover actions only appeared while the pointer was inside the message
box itself. The invisible bridge under each message now spans the full
height of the band the floating actions occupy (full row width), so
hovering anywhere in that strip reveals them. Sibling row types
(.cline-chat-tool, .cline-chat-work, and the desktop's run/tool groups)
become position: relative so they paint above the bridge — their own
content keeps its hover and clicks, and the bridge only wins in the band's
genuinely empty space.
All expandable rows (work summary, tool panels, thinking) open and close on
a 240ms symmetric ease-in-out cubic-bezier instead of the 60ms snap, with
chevron rotation on the same curve. Reduced-motion still disables both.
* revert(ui/desktop): drop the full-band hover reveal; quicken disclosure easing to 180ms
The full-band hover bridge (and the position: relative changes that made it
safe) is reverted per feedback — back to the narrow bridge that only spans
the gap under the message. The iOS-style ease-in-out on disclosures stays
but speeds up from 240ms to 180ms.
* fix(ui): recover live tool diffs that mount as a blank pierre skeleton
Live-streamed edit rows could show an empty diff for the whole run, with the
diff only appearing after the collapsed work row was expanded (fresh mount).
Root cause, confirmed by driving a live session and inspecting the element:
React StrictMode double-invokes @pierre/diffs' ref callback; the first
instance's async highlight work aborts on its immediate cleanup, and the
second instance adopts the abandoned half-rendered shadow tree as if it were
complete prerendered output — zero height, no code, no theme stylesheet,
permanently. A rendered diff always carries style[data-theme-css] in its
shadow root, so ToolFileDiff now checks for it shortly after mount and
remounts FileDiff (bounded attempts) when missing; the fresh host element
takes the normal render path and recovers within ~400ms. Verified live: the
diff now renders during the run.
* fix(desktop): keep interrupted runs expanded even with partial trailing text
The trailing-run collapse gated on 'ended with assistant text', which
misread a Stop that landed mid-answer as a finished run and folded the tool
calls the user wants to inspect. The gate is now the terminal status itself:
only completed (or restored-idle) sessions collapse the trailing run;
cancelled/failed/error tails keep their rows regardless of partial text.
(Greptile P1 on #13315 — matches the PR's stated rule.)
* feat(ui): share the markdown pipeline, chat polish, and ThinkingBlock across products (#13323)
* feat(ui): share the markdown pipeline, chat polish CSS, and ThinkingBlock
The desktop app and the cloud dashboard both consume @cline/ui yet rendered
assistant output differently, because Markdown policy and the thinking-trace
row lived app-side. This moves the shareable parts into the package:
- components/markdown (new export): the lazy Shiki code highlighter (GitHub
light/dark, pinned language set) and agentMarkdownControls — the standard
Streamdown configuration. streamdown/shiki/@shikijs/* become optional peer
dependencies, mirroring @pierre/diffs.
- components/markdown.css: the desktop's chat polish moves in — chat-scale
headings, outside list markers, single quiet code blocks with a
hover-revealed copy control, table cards. Kept unlayered so it beats
Streamdown's layered Tailwind utilities without !important.
- ThinkingBlock + formatThoughtLabel in agent-chat: the standard thinking
row (brain icon, Thinking/Thought-for-Ns label, streaming shimmer, rail
presentation, capped scrollable body). The shimmer and the
reasoning-hover-suppression rule move into agent-chat.css; triggers gain
the color transition the desktop applied locally.
Version bumps to 0.2.0-next.5 for the dashboard to pick up.
* refactor(desktop): consume shared markdown and thinking primitives from @cline/ui
The local Shiki highlighter, Streamdown controls, chat markdown polish CSS,
streaming-title shimmer, and reasoning hover-suppression rule are deleted in
favor of the @cline/ui versions (the highlighter test moves to the package's
suite). ReasoningBlock becomes a thin wrapper that hands MemoizedMarkdown to
the shared ThinkingBlock, and formatThoughtLabel re-exports from the package
so grouping code and tests keep their import path.
globals.css now imports @cline/ui/components/markdown.css (unlayered, so the
polish keeps beating Streamdown's layered utilities); the app keeps only what
is genuinely app-specific: link/image policy in markdown.tsx, selectability
rules, accent palettes, and the view-enter transition.
* style(ui/desktop): make thinking-trace prose legible
Thinking body text rendered too faint: plain muted-foreground plus the
desktop's font-thin weight. The shared thinking content now leans 75% of the
way back toward the body text color (still slightly de-emphasized), and the
desktop drops the thin font weight.
* ci(ui-publish): build @cline/shared before ui typecheck (#13354)
@cline/ui's generated-media imports @cline/shared/browser, which resolves to
shared's dist output. The build-shared step sat after typecheck/test/build,
so the first ui-publish dispatch since #13025 failed at Typecheck UI with
TS2307. Move the step to right after install.
* fix: run_commands object form without args routes through the shell instead of failing with ENOENT (#13336)
* fix: run_commands object form without args routes through the shell
The structured { command, args? } form of run_commands was always spawned
directly with shell: false. When a model emitted a full command line in
command with no args (e.g. { command: "echo hello" }), spawn failed with
ENOENT for any command containing a space, breaking command execution for
the whole session.
Direct exec now only applies when a non-empty args list is provided; the
object form without args is routed through getShellInvocation like the
string form. Schema descriptions are tightened so models put arguments in
args instead of embedding them in command.
Fixes#13279
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: trim structured-command schema descriptions
The union schema is only used for lenient validation of input the model
already sent; its descriptions never reach a model prompt. Keep them
short instead of restating executor behavior.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: simplify direct-exec comment in shell executor
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert: keep original structured-command schema description
The description never reaches a model prompt and the executor now handles
both shapes, so the wording change was cosmetic noise.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: gate direct exec on args key presence, not array length
Review feedback: an explicit empty args array is intentionally structured
input and stays direct exec; only an object with no args key is treated
as a full shell command line. Matches the key-presence rule already used
by the VS Code host's formatCommandForTerminal. Also replaces the
empty-args shell test (which was PowerShell-incompatible) with a test
pinning the direct-exec contract.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: normalize Gemini custom base URLs for legacy host-root values (#13329)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* docs: add GLM-5.3 to ClinePass models and reference pricing (#13357)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): stream run command output (#13179)
* feat(desktop): stream run command output
* fix(sdk): clean up detached command logs
* fix(sdk): reap detached logs after hub restarts
* fix(sdk): preserve live detached command logs
* fix(desktop): harden live command progress
* fix(sdk): recover detached logs for local hosts
* fix(desktop): reconcile command output tool rows
* fix(sdk): retain logs for surviving commands
* fix(core): prevent PID reuse from retaining detached logs
* fix(core): preserve detached logs on probe failures
* fix(core): retain detached logs during probe outages
* fix(desktop): resolve leftover merge conflict in messages projection test
Combine both sides of the assertion: main's incremented per-block
createdAt projection and this branch's toolCallId/hookEventName meta.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): make TUI dialog colors follow theme changes live (#13355)
* fix(cli): make TUI dialog colors follow theme changes live
Dialog content previously read the static palette constant, so open
dialogs (including the theme picker itself) kept the default dark-blue
accents while scrolling through theme previews. Add getDialogPalette /
useDialogPalette, which resolve dialog colors from the active theme's
dialog accents and re-render on every theme change, and migrate all
dialog-rendered components to it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(cli): derive dialog panel background from the active theme
Dark themes now lift their own background one OKLAB step for the dialog
surface, so panels keep the theme's hue instead of the library's fixed
#262626. DialogThemeSync pushes the surface into the dialog container
for new dialogs and repaints open panels, so the surface also follows
live theme previews. Light themes keep the neutral dark panel to match
the dark accent fallback and the light-on-dark dialog text.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: skill slash commands load via the skills tool instead of expanding into the user message (#13327)
* fix(desktop): show typed slash command instead of expanded skill markdown
The sidecar expands /skill and /workflow tokens into their instructions
before dispatching, so the runtime's persisted transcript only contains the
expanded text. After a turn (and when reopening a session) the webview
re-hydrates from that history and rendered the whole SKILL.md body as the
user's message; queue events echoing the expanded prompt could also add a
second user bubble, and fresh sessions were titled with the markdown's first
line. The CLI never shows this because its TUI keeps the typed text in its
own transcript and only sends the expanded prompt to the model.
Mirror that separation inside the desktop sidecar's display boundaries:
- history projection (readSessionMessages) inverts user text that starts
with a configured command's instructions back to '/name remainder',
which also repairs sessions recorded before this fix
- queue snapshots and chat_queued_prompt_start events echo the typed
prompt recorded at expansion time, so the webview's optimistic-bubble
re-key matches again
- an untitled session sent an expanded prompt gets titled from the typed
command instead of the instructions' first line
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't overwrite a mid-turn rename with the typed-command title
The untitled check ran before dispatch, so renaming a fresh slash-command
session while its first turn was running got clobbered by the post-turn
typed-command title. Re-check at write time and only replace a missing title
or the one the runtime auto-derived from the expanded prompt.
Also documents the inherent prefix-inversion ambiguity flagged in review:
text hand-typed with a command's exact instructions persists byte-identically
to that command's expansion, so stored history alone cannot distinguish them.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop expanding skill commands; let the skills tool load them
Pasting the skill body into the prompt is why the transcript could ever show
it: the desktop webview re-hydrates from the runtime's persisted history, so
whatever the sidecar splices into the user message renders as if the user
typed it. The runtime already registers the skills tool, whose description
requires the model to invoke it whenever the user references a slash command
— so send the typed /skill text through and let the tool deliver the
instructions as a tool result (previously they arrived twice: pasted and via
the tool). The persisted user message, session title, and queue entries are
then simply the typed command, which deletes the typed-prompt registry, the
queue event/snapshot rewriting, and the title machinery from the previous
approach.
Workflows are not served by the skills tool and keep textual expansion, so
the read-time display inverter stays: it collapses expanded workflow prompts
— and skill prompts persisted before this change — back to the typed
/command in the history projection.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): option to keep skill slash commands typed for the skills tool
resolveRuntimeSlashCommandFromWatcher (and the hub snapshot proxy) accept
expandSkillCommands: hosts whose sessions register the skills tool pass
false so the typed /skill goes through and the model loads the instructions
as a tool result, keeping the persisted transcript as what the user typed.
Workflows always expand — the tool does not serve them. isSkillsToolAvailable
exposes the catalog check hosts use to decide (yolo preset and the skills
tool toggle leave textual expansion as the only delivery path).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): skill slash commands load via the skills tool instead of expanding
The TUI user-command wrap and buildUserInputMessage now keep a typed /skill
as-is when the session's mode/toggles register the skills tool, matching the
desktop app; workflows keep expanding, and yolo (zen) keeps expanding skills
because its preset has no skills tool. This also fixes CLI resume/history
surfaces showing the skill body: the persisted user message is now the typed
command.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): keep configured skill slash commands typed for the skills tool
expandSlashCommands no longer splices a configured skill's instructions into
the model text; the SDK session's skills tool delivers them as a tool result
(previously they arrived twice). Builtin pseudo-skills like /deep-planning
are not served by that tool and keep expanding, as do workflows.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): use the shared skill-expansion option in the sidecar
Replaces the sidecar's workflow-detection dance with core's
expandSkillCommands option and gates on isSkillsToolAvailable, restoring
textual expansion where the tool is missing (yolo mode or the skills tool
toggle) — a gap in the previous desktop-only change.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): drop the display inverter for expanded transcripts
Accepted trade-off to keep the change minimal: sessions recorded before
skills switched to the skills tool, workflow sends (deprecated), and
yolo-mode skill sends persist expanded instructions and now render that text
as-is instead of being collapsed back to the typed /command at projection
time.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Use fixed selection chevron in account dialog to match other dialogs (#13364)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): align system prompt with session mode (#13361)
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(desktop): finalize queued turns on chat_done with canonical history reconcile (#13330)
Turns that settle through the event stream (queued prompts, including the
first prompt of a fresh session) resolve their send() RPC early, so nothing
cleared the streaming shimmer or reconciled live-streamed content against
the persisted transcript at turn end. A turn whose deltas were incomplete
stayed visually streaming forever and only healed when a later non-queued
send rehydrated history.
chat_done (and chat_session_ended / the queue-drain double check) now clears
the active assistant streaming id and schedules a short-delayed
read_session_messages + applyCanonicalHistory, guarded by turn epoch,
session id, and in-flight send submissions so it never clobbers a newer
turn or duplicates the blocking send path's own finalization.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(desktop): release v0.0.14
* fix(clients): filter non-chat models from chat pickers (#13317)
* fix(clients): filter non-chat models from chat pickers
* fix(clients): align chat model eligibility
* fix(desktop): strip user_input envelope when copying a user message (#13369)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bee <abeatrix@users.noreply.github.com>
* docs: show DeepSeek V4 peak and off-peak pricing (#13312)
* docs: update DeepSeek V4 average pricing
* docs: show DeepSeek peak and off-peak pricing
* docs: add GLM-5.3 reference pricing (same as GLM-5.2)
* docs: add GLM-5.3 to ClinePass models table
* fix(llms): display billed gateway cost (#13385)
* fix(shared): run PowerShell commands with fail-fast error semantics (#13358)
* fix(shared): run PowerShell commands with fail-fast error semantics
The run_commands PowerShell wrapper never set $ErrorActionPreference, so
the default 'Continue' applied: a pipeline erroring per item (e.g. a
malformed Where-Object over Get-ChildItem -Recurse) emitted one error
record per enumerated file - tens of thousands of stderr records on
large trees, looking like a hang - and could still resolve as SUCCESS
with exit 0.
Prepend $ErrorActionPreference='Stop'; to the script content executed
by the ScriptBlock so the first error terminates the command with a
non-zero exit and a single error message. Concatenated on the same line
as the user command so error line numbers stay unshifted.
Fixes#13285
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(shared): set the fail-fast preference in the bootstrap scope
Setting $ErrorActionPreference='Stop' by string-prepending it into the
scriptblock source displaced a leading param(...) from its mandatory
first-statement position, so scripts beginning with a param block failed
with CommandNotFoundException. Preference variables are dynamically
scoped, so setting Stop in the -Command bootstrap gives the invoked
scriptblock identical fail-fast semantics while keeping the user script
byte-identical (param works, error positions unshifted) and drops the
doubled-quote escaping.
* docs(shared): document the fail-fast tradeoffs in the PowerShell wrapper
Stop promotes every non-terminating error, not only per-item pipeline
floods: partial-result commands (recursive listings over access-denied
junctions) now stop at their first error, and Windows PowerShell 5.1
turns in-script stderr redirection of succeeding native commands fatal.
State this in the wrapper comment as a deliberate tradeoff, with the
GitHub Actions precedent and the per-command opt-outs.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
* ci: stop over-long changelogs from silently dropping release Slack posts (#12955)
Slack section blocks reject text longer than 3000 characters. The Slack
action logs that rejection as ##[error] but does not fail the step, so an
over-long changelog drops the release announcement while the run stays
green — cline@3.0.50 (3272 chars) published to npm, tagged, and cut a
GitHub release with no Slack post and nothing red to notice.
Every publish workflow pasted the changelog section verbatim into one
section block, so all six were exposed; the SDK, desktop, and extension
sections were only 150-350 chars under the ceiling.
Add a slack_content output alongside content: unchanged when the section
fits, otherwise trimmed on a line boundary with a link to the full
release notes. Only the Slack payload uses it — GitHub release bodies and
the desktop updater manifest still get the whole section.
* ci: tidy workflow cache config and job permissions (#13403)
Publish workflows now always do clean npm installs (no dependency
cache in their test gates), the e2e workflow's cache keys are
exact-match only, and the e2e job drops an id-token permission it
never used.
* Rename desktop app from "Cline Code" to "Cline" (#13401)
* Rename desktop app from Cline Code to Cline
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Format touched Rust test assertions
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): surface provider-executed tool activity as observational events (#13300)
* fix(llms): surface provider-executed tool activity as observational events
Provider-executed tool parts (e.g. every tool the Claude Code CLI runs
inside its own session) were dropped by the model-tool guard added for
web search: only declared model tools were re-emitted, everything else
hit continue with nothing yielded. Those sessions modified the workspace
with no tool activity in runtime events, transcripts, or the UI.
Route all providerExecuted parts onto the observational path instead:
emit execution-tagged tool-call-delta and tool-result events, matched by
tool-call ID for providers that omit the flag on the result half. They
stay out of AgentRuntime's execution/approval loop, and the runtime
already persists them as modelToolActivities and projects them for
display.
The AgentModelEvent tool-result variant widens toolName from
ModelToolName to string to carry the provider's own tool names.
* fix(agents): keep turns that are only provider-executed tool activity
A turn consisting solely of observational tool activity has an empty
assistant content array - the activity lives in message metadata, since
projecting it into content would replay tool_use blocks the model never
gets results for. The empty-content guard threw on such turns, erroring
the run and losing the activity from the transcript. Count model-tool
activity as content for the emptiness check (error finishes still
throw); replay stays safe through the codec's empty-content placeholder.
Also drop the trailing text delta from one gateway test so the tool-only
stream shape stays covered end to end.
* feat: allow agents to create scheduled tasks (#13331)
* feat(core, desktop): add durable todo agenda
* fix(desktop): secure todo approvals and track tool usage
* fix(desktop): clean up failed approval delivery
* fix(desktop): authenticate approval connections
* fix(desktop): cancel approvals on broadcast failure
* fix(desktop): authenticate development approvals
* fix(desktop): harden development approvals
* test(core): make task paths cross-platform
* fix(desktop): serialize approval readiness
* refactor(core): unify todo and schedule tools
* feat(core): distinguish user todos from agent suggestions
* fix(core): hide tasks tool in yolo mode
* fix(core): enforce schedule workspace scope
* fix(core): bind schedule scope to hub connection
* fix(core): establish task scope at hub startup
* fix(core): scope task automation by workspace
* test(core): normalize workspace path expectations
* test(core): serialize Windows CI workers
* fix(core): reject unregistered schedule authority
* fix(desktop): guard task execution commands
* fix(core): avoid polynomial regex in mention parsing
* fix(core): address schedule tool review feedback
* fix(core): bind websocket clients to hub workspace
* fix(core): flatten tasks tool input schema
* fix(core): authorize multi-workspace hub clients
* test(core): type hub transport authority mock
* fix(cli): register a workspace client for remote schedule commands (#13398)
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(desktop): treat ClinePass as OAuth-managed in the chat credential gate (#13404)
* fix(desktop): treat ClinePass as OAuth-managed in chat credential gate
ClinePass shares the Cline account OAuth credentials (its auth handler
stores under the "cline" provider), so the webview never sees a plain
API key for it. The chat pre-flight check only exempted cline/oca/
openai-codex, so switching to ClinePass while signed in via OAuth
blocked with "Missing API key" even though the sidecar resolves the
stored access token fine (which is why the CLI worked).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style: format helpers.test.ts with biome
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(ui): stack code block lines when streamdown lineNumbers is off (#13412)
streamdown renders each Shiki token line as a bare inline span with no
newline text between non-empty lines, and only applies its block line
class when lineNumbers is on. With lineNumbers off (the desktop app's
config) every multi-line fenced block collapsed into one run-on line.
Make the direct line spans under code-block-body display: block in the
shared markdown.css; empty lines keep their height via their lone "\n"
child under white-space: pre.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): work summary undercounts wall time when pre-tool thinking attaches to the answer (#13413)
* fix(desktop): anchor work summary duration on the answer row, not attached pre-tool reasoning
The collapsed 'Worked for Xs' row undercounted wall time whenever a turn's
assistant message contained thinking + tool_use with no narration text: the
canonical projection emitted the reasoning-only row after the tool row (both
stamped before the tool executed), the webview attached that row to the final
answer, and collapseCompletedWork used the answer's earliest attached
reasoning timestamp as the end anchor - excluding the entire tool execution
(e.g. 'Worked for 5s' for a turn with an 8s command).
- webview: end the work span at the answer row's own timestamp, clamped to
the last collapsed row so a fallback answer bubble with a synthetic early
timestamp cannot shrink the duration either
- sidecar: flush pending thinking before a tool_use row so rehydrated
transcripts keep the live-stream order (thinking before its tool call) and
pre-tool reasoning no longer rides on the next answer
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep interleaved thinking between the tool calls it separates
Address Greptile review: when one assistant message interleaves thinking
between multiple tool_use blocks, each reasoning segment now projects at its
own position (attached to a text row from its own segment when present,
otherwise as its own row) instead of merging into the first reasoning row,
which displayed later thinking before a tool call it actually followed.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): remove settings gear hover state while Account screen is open (#13408)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't show "No sessions found" while session history is still loading (#13414)
* fix(desktop): don't show 'No sessions found' while session history is still loading
Replace the isLoadingHistory flag with hasLoadedHistory, set only once the
backend has actually answered a list_discovered_sessions request. The sidebar
and Sessions view now keep their loading state until that first definitive
response, so the empty-state copy can no longer appear while history is still
being fetched (or while a failed fetch is being retried).
Also retry a failed initial fetch on the 2s event cadence instead of stranding
the UI until the 12s periodic poll, which is what stretched the misleading
empty state to ~10 seconds after a webview reload when the websocket lost the
race with the page load.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop history fast-retry from re-arming after hook unmount
A failed initial fetch that settles after the hook unmounted could schedule a
new retry timer after cleanup had already cleared the refs, leaving the
abandoned hook polling the backend every 2s. Guard scheduleRefresh with a
disposed ref set by the mount effect's cleanup.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix @ file mentions breaking on paths with spaces (#13391)
* Fix @ file mentions breaking on paths with spaces
Quote mentions generated by getFileMentionFromPath (Add to Cline /
Fix / Explain / Improve commands) when the relative path contains
spaces, so the mention regex no longer truncates the path at the
first space. Also quote the path part of workspace-prefixed mentions
(workspace:/path with spaces) inserted from the @ context menu, which
previously bypassed quoting because the value does not start with '/'.
Fixes#13338
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix import ordering in mentions test (biome organize imports)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reduce fix to minimal scope
Revert the webview quoting refactor and extra tests; keep only the
getFileMentionFromPath quoting fix with a single regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Normalize mention paths to posix separators for Windows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix code actions failing with "command not found" on VS Code 1.134 (#13402)
* Fix @ file mentions breaking on paths with spaces
Quote mentions generated by getFileMentionFromPath (Add to Cline /
Fix / Explain / Improve commands) when the relative path contains
spaces, so the mention regex no longer truncates the path at the
first space. Also quote the path part of workspace-prefixed mentions
(workspace:/path with spaces) inserted from the @ context menu, which
previously bypassed quoting because the value does not start with '/'.
Fixes#13338
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix import ordering in mentions test (biome organize imports)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reduce fix to minimal scope
Revert the webview quoting refactor and extra tests; keep only the
getFileMentionFromPath quoting fix with a single regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Normalize mention paths to posix separators for Windows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix code actions failing with 'command not found' on VS Code 1.134
Code action commands carried arguments (expandedRange, diagnostics),
which routes them through VS Code's CommandsConverter cache. VS Code
1.134 disposes the cached entries before the clicked action executes,
so every lightbulb action failed with 'Actual command not found,
wanted to execute cline.addToChat'.
Drop the arguments so the command id is passed through directly, and
recover the context in the handler instead: getContextForCommand now
expands an empty selection by 3 surrounding lines (matching the old
provider behavior) and gathers document diagnostics intersecting the
range when none are passed explicitly.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Scope gathered diagnostics to the selection/cursor
Match the old CodeActionContext.diagnostics behavior: only include
diagnostics intersecting the range the action was requested for, not
the surrounding lines the text gets expanded to.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Desktop: unify Plugins, MCP, and Skills into one Plugins hub with a dedicated Marketplace page (#13411)
* Unify desktop plugins, apps, MCP, and skills into one Plugins hub with a Browse directory mode
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Open the marketplace directory as a modal over the Plugins hub instead of swapping the page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename directory to Marketplace: Browse Marketplace button, Marketplace modal title with icon, search placeholder
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix search input focus ring clipped by the Marketplace modal scroll container
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Address Greptile review: keep selected tag chip visible when its count drops to zero, and remount installed tab when a marketplace install completes after the modal closed
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Track marketplace modal mutation flag in a ref so a close click racing a queued render cannot skip the inventory remount
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make Marketplace its own settings page under Customizations and restore Channels as a standalone page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Remove icon from Marketplace page header for consistency with other settings pages
* Notify mounted inventory views when the marketplace invalidates the cache so late install completions refresh the Plugins hub
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop/ui): recommended and free model tiers in the composer model selector (#13410)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK (#13415)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): recommended-feed badges and descriptions in provider settings (#13416)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* feat(desktop): recommended-feed badges and descriptions in provider settings
Review suggestion on #13410: the provider settings page has room for
more model detail than the composer's picker. The cline/cline-pass
provider cards now refresh their model list through
list_provider_models (the catalog snapshot deliberately skips the
recommended-feed overlay so the startup catalog fetch never blocks on
the feed) and render Recommended/Free tier badges plus feed tags (NEW)
next to the model name, with the model description underneath. The
refreshed list also surfaces the live entries instead of the bundled
snapshot.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): scope the settings featured model list to its provider and revision
The fetched featured list was unscoped component state: switching
between cline and cline-pass reused the component instance, so the
previous provider's models stayed visible while the new request was
pending (or forever, when it failed), and the retained copy shadowed
later provider.modelList updates — adding a second custom model
submitted the stale list as the complete configuration and dropped the
first addition.
The fetched list now only applies to the provider and modelList
revision it was fetched for (falling back to the catalog snapshot
otherwise and refetching on membership changes), and add-model submits
the union of the displayed and configured ids so an update can never
silently unconfigure existing entries.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(ui): update packed-Tailwind smoke contract for the picker's max-h-64 (#13421)
The ui-publish smoke check pins a set of Tailwind candidates the packed
sources must emit; #13410 grew the SearchCombobox options list from
max-h-56 to max-h-64, so the publish run failed on the stale candidate.
All other pinned candidates verified against the current sources.
* feat(desktop): refresh app icons and branding (#13400)
* ci(vscode): upload E2E failure recordings from the right path (#13427)
The job sets working-directory: apps/vscode, but that default applies to run
steps only, not to `uses:` steps. Since #10961 moved the extension under apps/
and added that default, the artifact path has resolved against the repo root,
matched nothing, and every failing run logged "No files were found with the
provided path: test-results/playwright/" instead of uploading recordings.
Widen to test-results/ so Playwright's error-context snapshots ship alongside
the videos.
* fix(hooks): deliver tool hook contextModification to the model (#13297)
* fix(hooks): deliver tool hook contextModification to the model
On the next engine, a tool_call (PreToolUse) hook's contextModification
was parsed into HookControl.context and then silently dropped: the
runtime beforeTool/afterTool result contract had no channel for
injecting conversation context. Legacy consumed it (ToolExecutor /
ToolHookUtils pushed <hook_context> blocks into the next user turn), so
this was a regression of documented behavior.
- Add appendContext to AgentBeforeToolResult/AgentAfterToolResult.
- AgentRuntime collects appendContext across hooks during an
iteration's tool executions and appends one <hook_context> user
message after the tool results, keeping tool-result parts contiguous.
- Map HookControl.context into appendContext in both subprocess hook
layers (skipped when the hook cancels, matching legacy, where the
message doubled as the error).
- Truncate injected context at 50KB per hook output, matching legacy.
- Concatenate appendContext across merged hook layers.
tool_result (PostToolUse) hooks still run detached with stdout ignored;
making them blocking so their context can be collected is a follow-up.
Ref: https://linear.app/cline-bot/issue/CLINE-2987
* fix(hooks): stamp tool identity on injected hook context blocks
Contexts are batched into one message after the tool results, and
parallel tool execution collects them in completion order, so position
alone cannot attribute a block to its tool call. Add tool_name and
tool_call_id attributes to each <hook_context> block.
* fix(hooks): sanitize hook context block markup
Attribute values (tool_name, tool_call_id) are stripped of quote/angle
characters and embedded </hook_context> closers in hook output are
neutralized, so neither provider-supplied ids nor hook text can corrupt
or spoof a block's stamped identity.
* fix(hooks): neutralize forged opening hook_context tags in hook output
The previous sanitization only neutralized closing tags, so hook output
could still open a forged <hook_context> block claiming another tool's
identity. Escape both opening and closing embedded tags with one rule.
* fix(hooks): hide injected hook context from user-facing transcripts
Stamp the injected hook-context user message with displayRole 'system'
(the compaction-summary convention) so it reaches the model but does
not render as a user bubble in live or replayed transcripts. Without
this, resuming a session showed the raw <hook_context> block as if the
user had typed it.
* fix(hooks): neutralize case-variant embedded hook_context tags
The tag-neutralization regex was case-sensitive, so hook output could
still smuggle a forged tag as <HOOK_CONTEXT>. Match case-insensitively.
* fix(vscode): map PreToolUse contextModification into runtime appendContext
The extension's hooks adapter bridged file hooks into the SDK runtime
but forwarded only cancel/errorMessage, so a PreToolUse hook's
contextModification never reached the model. Map it into the runtime's
appendContext channel; HookFactory already truncates it at 50KB.
* fix(vscode): hide hook-injected context from replayed transcripts
Live sessions never rendered the injected <hook_context> user message,
but session reload replayed it as a user bubble (and post-resume turns
kept doing so). Treat these messages as synthetic in the user-message
mapping: honor the displayRole 'system' stamp the runtime sets, with a
text-prefix guard for paths where metadata is unavailable. This also
keeps edit/regenerate ordinal mapping aligned with visible bubbles.
* fix(hooks): run file hooks through exactly one layer per host
The VS Code extension registered two independent hook execution layers:
its own hooks adapter (config.hooks) and the SDK core's file-hook
extension from the runtime bootstrap. When both discover the same hook
files, every hook executes twice per event — and with context injection
wired, each contextModification would be injected twice.
Add a 'hooks' runtime config extension kind (in the default set, so the
CLI keeps core file hooks unchanged) and gate the bootstrap's file-hook
extension on it. The extension excludes 'hooks' at session start, so
its adapter — which also provides the hook status UI and the
hooksEnabled setting — is its single execution path.
* fix(vscode): discover hooks from the session workspace, not only global state
Hook discovery read workspaceRoots from global state shared across
every Cline instance, so another window repointing it made workspace
hooks silently stop being discovered. With the extension's adapter now
the single hook execution layer, that meant no hooks at all.
HookFactory takes an optional sessionWorkspaceRoot and unions that
root's .clinerules/hooks into discovery (and into cwd resolution), fed
from the session config's cwd. Shared-state discovery still works, so
behavior in the single-window case is unchanged.
* fix(hooks): keep sanitized hook attribute values distinguishable
Replacing every markup delimiter with the same underscore could
collapse two tool call ids that differ only by such a character into
identical stamps. Escape each delimiter with a distinct token instead.
* fix(hooks): make hook attribute sanitization injective
Escaping the underscore itself turns the attribute escaping into a
uniquely decodable code, so no two distinct tool call ids can collapse
to the same sanitized stamp (previously an id containing a literal
escape token could collide with an id containing the delimiter).
* fix(vscode): reconstruct hook status rows when replaying transcripts
hook_status messages are emitted live but never persisted, so reloading
a session dropped every hook row. The injected <hook_context> blocks
carry the hook source and tool name, so the replay translator now
rebuilds a completed hook status row from each block. The injection is
also no longer treated as a user turn boundary, so the final turn's
completion retag is unaffected by it.
* fix(hooks): collect PostToolUse hook output and honor its control (#13298)
* fix(hooks): collect PostToolUse hook output and honor its control
tool_result (PostToolUse) hooks ran fire-and-forget with stdout
ignored, so their entire JSON output — contextModification and cancel —
was discarded. Legacy awaited PostToolUse, injected its
contextModification into the conversation, and honored cancel.
- Run tool_result hook commands blocking (same 120s default timeout as
tool_call) in both the hook-config-file layer and the agent-hook
subprocess layer.
- Map their output: cancel stops the run with the hook's error message
as the reason; otherwise context is injected via afterTool
appendContext.
This restores legacy blocking semantics: tool results now wait for
tool_result hooks, but only in sessions that have one configured.
Ref: https://linear.app/cline-bot/issue/CLINE-2987
* fix(hooks): bound tool_result hook wait and isolate cancel reason
Address review findings:
- The agent-hook subprocess layer forwarded an unset timeoutMs
unchanged, so a tool hook command that never exits would block the
agent indefinitely. Default both tool_call and tool_result to the
120s bound the hook-config-file layer already used.
- A cancelling hook's error message was folded into the same context
field as other hooks' injectable context, so merging controls could
leak unrelated hook context into the cancellation reason. Carry it as
a separate cancelReason, and surface it as the stop reason for
beforeTool cancels too.
* fix(hooks): prefer errorMessage as a cancelling hook's stop reason
When a cancelling hook returns both contextModification and
errorMessage, the context-first parse precedence made the injectable
context the cancel reason and discarded the actual error. Parse the two
fields separately: errorMessage wins as the cancel reason (matching
legacy), and a lone errorMessage still folds into injectable context
for non-cancelling hooks as before.
* fix(vscode): honor PostToolUse hook cancel and contextModification
The adapter awaited PostToolUse hooks but discarded their output
entirely. Map cancel to a stop control (with errorMessage as the
reason) and contextModification into the runtime appendContext channel,
matching the PreToolUse mapping and legacy semantics.
* fix(hooks): whitespace-only errorMessage no longer suppresses the cancel reason
A cancelling hook returning meaningful context alongside a blank
errorMessage lost both: the parsers selected the whitespace as the
reason and the result mappers trimmed it away. Require a non-blank
errorMessage before it wins, so context serves as the fallback reason.
Apply the same fallback in the extension adapter's stop mapping.
* fix(core): stop Windows CI worker crashes from the agenda spec watcher (#13428)
* fix(core): watch agenda task specs via the resolved long path
fs.watch on a path with 8.3 short components (e.g. C:\Users\RUNNER~1
temp dirs) trips a libuv assertion in fs-event.c on Windows and aborts
the whole process. Since the agenda task manager landed, every hub
server test spins up its spec watcher on such a path on hosted Windows
runners, killing the vitest worker and failing the sdk-test Windows job
on every branch. Resolve the specs dir with realpathSync.native before
watching so libuv only ever sees the long form.
* test(ui): stub ResizeObserver for @pierre/diffs in tool-diff tests
jsdom does not implement ResizeObserver, so every ToolFileDiff render
logged a ReferenceError from @pierre/diffs to stderr. Tests still
passed; this just silences the noise the same way the constructable
stylesheet shim does.
* fix(core): skip the agenda spec watcher when the dir does not resolve
Falling back to the raw path on realpath failure would reintroduce the
Windows short-path abort; log and go without the watcher instead.
* fix(vscode): honor the classic truncation range when migrating legacy tasks (#13419)
Classic Cline truncated long conversations by omitting an index range of
api_conversation_history from every API request (keep the first
user-assistant pair, drop everything through the range end, strip
orphaned tool_results from the first kept message). The range was
persisted on the history item while the full history stayed on disk.
legacyApiHistoryToSdkMessages ignored conversationHistoryDeletedRange
and converted the entire file, so resuming a migrated long task handed
the SDK an untruncated working context that could exceed the model's
context window by millions of tokens - every request failed with
'prompt is too long' and every compaction restarted from the full
history (#12996, confirmed by the reporter: the task was migrated from
an older version and broke after a restart, with each compaction
starting from ~3M tokens).
The migration now replays exactly what the classic extension sent:
slice out the deleted range and drop orphaned tool_results, mirroring
ContextManager.getTruncatedMessages (see origin/main). Malformed ranges
fall back to the full history (previous behavior).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): show the diff edit view for multi-line edits in CRLF files (#13417)
The edit preview computed proposed content with an exact old_text match, but
the SDK executor normalizes old/new text to the file's own line endings before
matching (#12305) - reads strip CR, so models emit LF-only text even for CRLF
files. Any multi-line old_text in a CRLF file therefore failed the preview's
match: the diff edit view silently never opened while the executor applied the
edit. Single-line edits (no line break in old_text) were unaffected, which is
why the diff view appeared to trigger inconsistently.
Mirror the executor's EOL normalization (and its literal $-sequence insertion)
in the preview computation.
Fixes#13296
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): report truthful session status so desktop checkpoint restore stops wedging (#13418)
* fix(core): keep hub session status truthful across queue-drained turns
Queue-drained turns settle only through the event stream, but the hub
runtime host mistranslated their lifecycle in two ways:
- session.updated events carrying only a snapshot (persistence updates)
defaulted the projected status to "running". When one trailed the
final idle update after a turn, clients that track busy state from
status events (the desktop sidecar's workspace restore gate) stayed
busy forever. Use the snapshot's real status and emit nothing when
neither source reports one.
- the per-run agent.done dedup was only reset by run.started, which the
daemon-side queue drain never publishes, so a drained turn's done was
swallowed as a duplicate of the previous turn's. Reset the dedup on
session.pending_prompt_submitted, and suppress stale run.completed
events that land inside a drained turn's window so they can neither
emit a phantom done nor consume the drained turn's dedup slot.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(desktop): cover restore unlock after an event-settled queued turn
Exports the sidecar's core-session event handler so the queued-turn
lifecycle (busy via status events, cleared by the done agent event,
restore allowed afterwards) is testable end-to-end.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): start interactive sessions without a prompt as idle
The runtime host reported every new session as "running" until its
first turn ended. Interactive hosts (the desktop app) start sessions
with no prompt and dispatch turns through separate send calls, so a
created-but-never-prompted session stayed "running" forever — wedging
clients that gate workspace operations (checkpoint restore, message
edit) on active turns.
Interactive no-prompt starts now begin idle, start emits the session's
actual status (resumed sessions no longer masquerade as running), and
markTurn* transitions keep tracking in-memory status for lazily
persisted sessions so the first turn still reports running -> idle.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style: format hub-runtime-host test filter
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop the drained-turn done bookkeeping, keep the minimal fix
The stuck restore is fully explained by the two status defects (fabricated
"running" from snapshot-only session.updated events, and never-prompted
interactive sessions reporting "running"). The done-dedup machinery for
queue-drained turns addressed a separate cosmetic gap (queued turns emit no
chat_done, pre-existing) and required fragile run-window heuristics, so it
is removed to keep this change reviewable. Sidecar test now settles the
queued turn through the status event, matching the shipped mechanism.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* docs(sdk): document the truthful session-status contract
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(deps): update Langfuse packages and bump app versions (#13443)
* chore(deps): update Langfuse packages and bump app versions
Update @langfuse/otel to v5.10.1 and add @langfuse/vercel-ai-sdk v5.9.1 for improved observability with Vercel AI SDK.
Bump versions for @cline/code to 0.0.14 and @cline/ui to 0.2.0-next.6, updated via bun.lock.
Other Changes:
Added optional userId to AgentRuntimeConfig.
Propagated userId, sessionId, conversationId, runId, iteration, provider, and model context into AI SDK telemetry.
Added AI SDK 7 runtimeContext with explicit includeRuntimeContext.
Added stable OTEL_SERVICE_NAME=cline-sdk.
Added runtime metadata assertions in agent tests.
* add taskId
* Revert "add taskId"
This reverts commit f20d31d96d.
* docs: simplify Open Cline step in installing guide (#13405)
* docs: remove duplicate GLM-5.3 rows in ClinePass tables (#13449)
Co-authored-by: cline-cloud[bot] <276134852+cline-cloud[bot]@users.noreply.github.com>
* chore(sdk): release v0.0.76
* chore(cli): release v3.0.56
* docs(cli): scope the v3.0.56 release notes to CLI-visible changes
* feat(desktop): interactive welcome hero graphic (#13399)
* feat(desktop): add interactive welcome hero
* feat(desktop): support composable welcome hero variants
* feat(desktop): reskin first-run onboarding (#13441)
* refactor: centralize client tool availability (#13451)
* chore(sdk): release v0.0.77
* docs(cli): drop the tasks tool from the v3.0.56 notes, it is desktop-only
* chore(vscode): prepare 4.1.11 release
* chore(desktop): release v0.0.15
* fix(vscode): remote config MCP settings (#13466)
* fix(vscode): enforce enterprise MCP controls on the Customize marketplace
The unified Customize marketplace replaced the old MCP marketplace
without carrying over enterprise remote-config enforcement: the catalog
RPC returned every MCP entry and installs were never policy-checked,
so orgs with mcpMarketplaceEnabled=false or an allowedMCPServers
allowlist saw (and could install) all marketplace MCP servers.
- Filter MCP entries out of getMarketplaceCatalog when the marketplace
is disabled, and restrict entries to the allowlist when configured
(matching entry id, display name, installed server name, or source
repo URL, mirroring legacy GitHub-URL allowlist ids)
- Reject installMarketplaceEntry requests that violate the policy
- Map the published catalog's repo/homepage fields onto
sourceUrl/homepageUrl so URL-based allowlists can match
- Update the enterprise MCP server controls docs
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: simplify MCP marketplace policy enforcement
Fold the policy check into marketplace-helpers, drop the dedicated
test suite, and trim the docs edit to the strictly necessary line.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Treat an empty preserved capability list as unspecified when seeding tools (#13465)
* Treat an empty preserved capability list as unspecified when seeding tools
toSdkModelInfo guarded the tools seeding with a strict
preservedCapabilities === undefined check, but modelHasCapability —
the runtime's own reader — treats undefined AND length === 0 as
"unspecified". A custom OpenAI-Compatible model whose stored
capabilities field is a defined-but-empty array (a config carried over
from before the field existed, or one round-tripped through a boundary
that defaults it to []) skipped the seeding; the first boolean
projection to run afterwards (e.g. supportsReasoning) then populated
the array, the runtime gate read the non-empty, tool-less list as
authoritative, and every tool definition was silently dropped from the
session (#13463).
The guard now covers the empty array too, matching the reader's
unspecified semantics.
* test: satisfy the store's isModelInfo gate so the empty-capabilities case actually reaches knownModels
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: yzxcj797 <yzxcj797@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): prepare 4.1.12 release
* Add feature flags to the desktop app (#13289)
* Add feature flags to the app
* React to account updates
* Address comments
* use a per-app file
* fix: propagate Langfuse session telemetry (#13473)
* fix telemetry session propagation
* feat telemetry client version metadata
* fix(core): address Langfuse review feedback — hub client identity + delegated agent session grouping (#13475)
* fix(core): rebuild hub session client identity from request headers
Hub-backed sessions do not transport extensionContext (it is local-only),
so the daemon's runtime built traces without the clientName/clientVersion
metadata even though the hub client bakes X-CLIENT-TYPE / X-CLIENT-VERSION
into the session's provider headers. Reconstruct extensionContext.client
from those headers during local runtime bootstrap so hub-backed Langfuse
traces carry the same client identity as local runtimes, and the daemon's
header re-resolution stops clobbering the original X-CLIENT-TYPE.
* fix(core): propagate parent distinctId/sessionId to delegated agents
Delegated agents (spawned sub-agents, configured agents, teammates) were
built without distinctId and sessionId, so their Langfuse traces had no
userId or sessionId and did not group with the parent user or session.
Thread the host-resolved distinctId through RuntimeBuilderInput and the
root sessionId through the delegated-agent config provider, and copy both
onto the delegated AgentConfig.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* ci(vscode): make combined nightly manual-dispatch only
The PublishNightly environment gained required reviewers, so each cron
run parked on approval, held the workflow's concurrency group, and
silently cancelled every scheduled run queued behind it. 20 consecutive
scheduled nightlies died this way between 2026-07-31 and 2026-08-21;
the only nightlies that shipped in that window were manual dispatches.
Drop the cron rather than leave a trigger that cannot succeed unattended.
* feat(hub): add drain and upgrade commands with replay support (#13468)
* feat(hub): add drain and upgrade commands with replay support
* handles disconnection
* feat(hub): wire bot profiles, drain, and durable event/run-queue into the live transport
Completes the wiring the previous commits' primitives needed:
HubServerTransport gains isDraining(), hub.drain/hub.status/profile.get
command handling, and replayEventsAfter() (backed by the durable event
log), plus the sequence/sinceSequence wire types they depend on in
shared/hub.ts. run-queue-handlers.ts reads the active bot profile's
plugin roots when executing durable runs.
Also adds hub/profiles/: profile.json (identity/rules/plugins) ->
system prompt composition, --profile / CLINE_HUB_BOT_PROFILE
resolution, and the bundled cline-dad profile with its
cline_hub_support read-only diagnostics tool.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Revert "feat(hub): wire bot profiles, drain, and durable event/run-queue into the live transport"
This reverts commit 6696d5d202.
* fix(hub): dedupe replayed events by eventId, not just sequence
HubEventLogStore.append() returns a new envelope stamped with a
sequence rather than mutating the input, so a pending approval
re-issued sequence-less by subscribe() (it predates any durable-log
append) and its later sequence-stamped copy from the durable log are
two different objects carrying the same eventId. The replay-then-live
buffer in browser-websocket.ts only deduped by sequence, so the
sequence-less copy's guard never tripped and it was delivered a second
time when the buffer flushed after replay.
Track delivered eventIds alongside the sequence cursor; eventId
survives the append/stamp round-trip unchanged, so this dedupes the
exact-same logical event regardless of which copy arrives first.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): wire drain, durable event log, and run queue into the live transport
CI on this branch failed bun run build:sdk: browser-websocket.ts,
client/index.ts, and hub-websocket-server.ts (already on this branch)
reference sequence/sinceSequence, HubServerTransport.isDraining(), and
the "hub.drain" command — but the commit that reverted bot profiles
out of this branch also reverted this wiring, since it shared a commit
with the profiles work. That wiring is a hub concern, not a
bot-profiles one; split it back out.
- shared/hub.ts: sequence/sinceSequence types, run.enqueue/run.list/
hub.drain/hub.status/stream.replay capability, command, and event
names. profile.get intentionally excluded — stays bot-profiles-only.
- context.ts: isDraining() on HubTransportContext. botProfile field
intentionally excluded.
- hub-server-transport.ts: eventLog/runQueue fields and start/stop
lifecycle, publish() appends to the durable log, handleCommand cases
for run.enqueue/run.list/hub.drain/hub.status, drain-refusal check,
replayEventsAfter()/lastEventSequence(). startBotProfile()/
startHubSupportTool() and the profile.get case intentionally
excluded.
- run-queue-handlers.ts: added without handleProfileGet (needs
ctx.botProfile, which doesn't exist here).
- hub-upgrades.test.ts: added without its two bot-profile-injection
tests (they need a resolved bot profile to assert against).
Verified bun run build:sdk exits 0 (the exact CI command) and
bunx vitest run src/hub passes (311/312; the one failure is the
same pre-existing environment-timing flake already present before
this change).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): export instance-lock, event-log, and run-queue from the hub barrel
These landed as internal modules only; hub-server-transport.ts and
hub-websocket-server.ts import them by direct path, but nothing
re-exported them from the public @cline/core/hub surface the way
sibling discovery/server modules already are.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): wire the instance lock into the daemon entry point
The singleton lock (discovery/instance-lock.ts) and its consumption in
startHubWebSocketServer/ensureHubWebSocketServer were already on this
branch, but the daemon entry point's own half was not: retrying a bind
when a retiring predecessor still holds the lock, and exiting with a
distinct code (3) instead of the generic fatal path when a live Hub
already owns the data directory. Without this, a daemon racing a
retiring predecessor could fail outright instead of waiting the lock
out, and losing the singleton race looked identical to a crash.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): address drain/upgrade review findings (#13478)
- cline hub upgrade: check idleness at least once (--wait 0 works), reject
non-numeric --wait, and un-drain on every abort path so an aborted
upgrade can never leave the hub refusing new work
- add cline hub drain --off and the off query param to requestHubDrain so
POST /drain?off is reachable from shipped code
- HubEventLogStore/HubRunQueue: WAL journal mode + busy_timeout, and stamp
sequences from lastInsertRowid instead of SELECT MAX(sequence)
- HubInstanceLock.acquire: degrade to an unheld lock when SQLite is
unavailable instead of refusing hub startup; only BUSY/LOCKED still
raises HubLockHeldError
- ensureHubWebSocketServer: retire an unusable discovered hub through the
shared retireDiscoveredHub (busy hubs are attached to, drain precedes
shutdown, discovery cleared only when the hub actually retired)
- replay adapter: advance the cursor past eventId-deduped events, cap
replay pages, stop when the cursor stalls, and drop the dedupe set after
the buffered flush so it cannot grow for the socket lifetime
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(hub): derive the singleton e2e challenger cwd portably
The challenger's working directory was derived by round-tripping the
discovery path through a file: URL and stripping the last pathname
segment. On Windows that yields a POSIX-style '/C:/...' path, which is
not a valid spawn cwd, so the spawn fails ENOENT before the singleton
lock is ever contested and the Windows SDK test job goes red.
The data dir is simply the discovery file's parent: use dirname().
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): stop stored capability lists from silently revoking tool calling for custom models (#13476)
* fix(core): seed tools capability when custom model capabilities are synthesized from boolean flags
For a models.json entry with no explicit capabilities list, toStoredModelInfo
synthesized a capability array purely from boolean convenience flags (e.g.
supportsReasoning: true -> ["reasoning"]). modelSupportsToolCalling fails open
only for a missing or empty list, so the synthesized non-empty list read as an
authoritative denial and silently stripped every tool definition from requests
to custom OpenAI-compatible models (#13463).
Seed "tools" whenever the list was not explicitly authored and the boolean
projections made it non-empty, preserving the fail-open contract. Explicitly
authored capability lists remain authoritative and can still disable tools.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(core): cover stale catalog capability overrides
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: treat stored capability lists as non-authoritative for tool calling
The hasExplicitCapabilities guard still let two producers of tool-less
lists through:
- The VS Code legacy-override migration (legacyModelInfoToOverrides)
persists explicit partial lists like ["prompt-cache"] into models.json
for custom OpenAI-compatible models, which then read as an authoritative
"cannot call tools" and drop every tool - same symptom as #13463.
- Any hand- or UI-authored partial list on a non-catalog model.
Stored entries and user-authored provider metadata have no way to declare
"cannot call tools" (there is no supportsTools field, and every writer
that authors a full list includes "tools"), so seed "tools" into any
non-empty list for a language model. Only generated catalog capabilities
remain authoritative - a genuine no-tools catalog model stays that way -
and non-language models (e.g. image generation) never gain a tools claim.
Also make legacyModelInfoToOverrides write "tools" into the arrays it
fabricates, matching the providers.json migration, so models.json stops
being poisoned for older readers.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): prepare 4.1.13 release
* chore(sdk): release v0.0.78
* chore(cli): release v3.0.57
* fix(core): run hub e2e files serially so daemon timing budgets survive CI contention
singleton.e2e.test.ts (added in #13468) spawns real daemons and runs for
~15s. Vitest's default file parallelism let it run alongside
shutdown.e2e.test.ts, whose assertions are wall-clock bound: discovery
within 10s, exit within 5s, and a 2s shutdown watchdog. On the 2-core
windows-latest runner that contention alone broke those budgets, failing
the shutdown test two different ways across runs — once never observing
discovery, once with the daemon forced to exit before its HTTP 202
flushed (socket hang up). The test passed on Windows before #13468 and
has failed every SDK publish run since.
* chore(desktop): release v0.0.16
* test(sdk): give windows-sensitive suites realistic timeouts
Four consecutive SDK publish runs failed on windows-latest, each on a
different test, all of them plain timeouts: two @cline/shared SQLite
tests at the 5s vitest default, core's bash executor at 10s, and the hub
singleton endpoint test at 10s. The 2-core Windows runner spawns forks
and takes SQLite locks slowly enough to blow those budgets under load.
These timeouts guard against hangs; they are not timing assertions (the
one suite that does assert elapsed time, shutdown.e2e, was fixed by
removing file-level parallelism instead). Raise core to 20s and give
@cline/shared an explicit 15s in place of the inherited 5s default.
* fix(telemetry): emit task.completed from every session teardown path (#13489)
The task.completed fallback lived only inside shutdownSession, but
stopSession/dispose route interactive sessions with a terminal reported
status through releaseSessionRuntime, which never emitted. Truthful
session-status reporting (shipped in 4.1.11) re-routed a large share of
interactive stops onto that branch and silently dropped the event.
Route the emission through a single choke point,
emitTaskCompletedOnTeardown, called from both shutdownSession and
releaseSessionRuntime. The completion criterion no longer reads
session.status: interactive sessions use the recorded final-turn
outcome (lastInteractiveTurnFinishReason), non-interactive sessions
keep the existing input.status === "completed" logic. A new
taskCompletedEmitted flag (also set by the submit_and_exit observer)
enforces exactly one task.completed per session. failSession now
records the errored final turn so a stale "completed" from an earlier
turn can never leak into the teardown emission. Telemetry only; no
user-facing behavior changes.
* chore(vscode): release v4.1.14
* fix(vscode): auto-approve all MCP tool calls when the MCP toggle is on (#13498)
* fix(vscode): honor MCP auto-approve settings for SDK tool calls
The SDK extension required both the global 'Use MCP servers' auto-approve
toggle AND each tool's per-tool autoApprove flag before silently approving
an MCP call, while the legacy extension treated them as either/or. Restore
the legacy OR semantics so toggling MCP auto-approve works again.
Also key toolPolicies by the registered SDK tool name (via
defaultMcpToolNameTransform, now exported from @cline/core) instead of raw
server__tool. Servers whose names contain sanitized characters (e.g.
marketplace names like github.com/user/repo) or exceed 64 chars produced
policy keys that never matched the registered tool, so those MCP tools ran
without any approval gate; the live auto-approve lookup now re-applies the
transform instead of string-splitting the name.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Revert "fix(vscode): honor MCP auto-approve settings for SDK tool calls"
This reverts commit 86c568fbba.
* fix(vscode): auto-approve all MCP tool calls when the MCP toggle is on
The SDK extension only auto-approved an MCP call when the global 'Use MCP
servers' auto-approve toggle AND that tool's per-tool autoApprove flag were
both set, so toggling MCP auto-approve appeared to do nothing and users had
to opt in each tool individually. The toggle alone now governs all MCP
tools; the per-tool flag is no longer consulted.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): release v4.1.15
* fix(cli): remove the $4.99 ClinePass promo copy (#13514)
The $4.99 first-month promo is ending, so the CLI's first-launch "Try ClinePass" dialog should no longer advertise it. Also drops the leftover CLI_PROMO_CODE plumbing, which has been an empty string since the promo-code flow was removed.
* fix(vscode): resolve hook workspace identity from the window, not shared global state (#13352)
* fix(vscode): resolve hook workspace identity from the window, not shared global state
Hook discovery, hook cwd selection, and the workspaceRoots metadata passed
to hook scripts all read the workspaceRoots/primaryRootIndex global state
keys. Global state lives in ~/.cline and is shared by every Cline instance
(all VS Code windows, the CLI, the JetBrains plugin), and nothing writes
these keys anymore, so hooks resolved against whatever project some other
or older instance last recorded. With a second window open on another
project, a workspace's .clinerules/hooks scripts were never discovered.
Resolve workspace roots via a single guarded helper backed by
HostProvider.workspace.getWorkspacePaths() (in-process, window-scoped,
same as refreshHooks): blank paths are filtered, a host-bridge failure
degrades to no workspace roots instead of silently disabling global hooks
or skipping blocking PreToolUse guards, and one resolution is threaded
through hooks-dir discovery, cache misses, cwd selection, and hook input
metadata so they can't disagree (previously up to four host lookups per
hook execution — real gRPC round trips in the standalone host). Roots and
hooks dirs are matched on whole path segments with the longest root
winning, so prefix-sharing or nested workspace roots resolve to the right
project. The adapter creates the runner once per event and skips no-op
runners, making creation the single resolution point; the separate
hasHook/getHookInfo checks are removed. The dead workspaceRoots and
primaryRootIndex state keys are dropped, and the four hand-rolled
HostProvider.workspace test stubs are consolidated into one shared
helper.
* test(vscode): add e2e coverage for workspace-scoped hook discovery
Boots real VS Code with the packaged extension against the workspace
fixture, sends a prompt, and asserts the fixture's UserPromptSubmit hook
was discovered from the open window's workspace, executed with that
workspace root as its cwd, and received the same root in its
workspaceRoots input — the end-to-end contract the hook workspace
identity fix establishes.
* test(vscode): isolate the e2e hook fixture from the shared workspace
The UserPromptSubmit fixture hook lived in the shared e2e workspace, so
every prompt-sending spec executed it (hooksEnabled defaults to true) —
and its cold PowerShell spawn on Windows pushed chat.test.ts past the
5s expect timeout. hooks.test.ts now overrides workspaceDir to a
dedicated workspace-hooks fixture, so only the hooks spec pays the hook
spawn.
* fix(hub): cap hub-events db size so it can't fill the disk (#13516)
* fix(hub): cap hub-events db size so it can't fill the disk
Row/time retention alone didn't bound disk usage: envelopes carrying
full session snapshots reach hundreds of KB each, so retained rows
could total tens of GB, sweeps only ran hourly, and DELETE never
shrinks a SQLite file. Enforce a 64 MiB size budget in prune() (oldest
rows first, VACUUM to return the space), and also prune after every
16 MiB appended so bursts can't outrun the hourly timer.
Fixes#13505
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): tolerate VACUUM failure on a full disk
VACUUM needs scratch space and can fail in exactly the state a
ballooned event log causes. The byte-budget deletes already bound live
data, so swallow the error and let the next sweep retry the reclaim
instead of aborting startup pruning and disabling the durable log.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): count the size budget in UTF-8 bytes, not characters
envelopeJson.length (UTF-16 units) and SQLite LENGTH() (characters)
undercount multibyte text by up to 3x, which could leave a CJK-heavy
log settled above budget and re-running VACUUM every sweep. Use
Buffer.byteLength and LENGTH(CAST(... AS BLOB)) instead.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(sdk): carry root overrides into the Node smoke-test sandbox (#13517)
ci-node-smoke.ts installs the packed SDK tarballs with a plain npm
install in a fresh temp dir, where the repo root package.json overrides
do not apply. When @sap-cloud-sdk 4.9.0 shipped (2026-08-24) it broke
@sap-ai-sdk/ai-api 2.14.0 (via @jerome-benoit/sap-ai-provider in
@cline/llms) with ERR_PACKAGE_PATH_NOT_EXPORTED, failing the smoke step
on every PR even though the root already pins @sap-cloud-sdk/* to 4.6.0.
Copy the root overrides block into the generated sandbox package.json
so the smoke install resolves the same pinned versions as the repo and
future third-party releases cannot break it independently.
* chore(sdk): release v0.0.79
* fix(vscode): don't steal last-used provider from ClinePass on credential refresh (#13520)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): flush the /shutdown 202 before daemon teardown
The /shutdown handler queued teardown on a microtask, which runs before
the event loop's write phase, so the daemon could process.exit() before
the accepted 202 was handed to the socket. Unix masked it (uv_try_write
lands small loopback writes synchronously); Windows has no such fast
path and lost the race regularly — the recurring shutdown.e2e.test.ts
'socket hang up' failures on windows-latest. Start teardown from the
response's write callback instead, with an idempotent 1s fallback so a
client that vanishes mid-write cannot strand the daemon, and send
Connection: close so the client gets a FIN rather than an abort.
Since the flakiness this compensated for is fixed at the source, restore
maxWorkers: 2 for the Windows core suite (serializing it cost ~3 min of
CI per run), and raise the e2e daemon discovery hang guard 10s→30s —
it guards against hangs, not runner speed.
* chore(cli): release v3.0.58
* fix(core): prevent search_codebase from crashing the process on giant single-line files (#13525)
* fix(core): prevent search_codebase from crashing the process on giant single-line files
searchWithRipgrep buffered all of rg's --json stdout into one string. Each
JSON event embeds the full text of the matched line (--max-columns is
ignored in JSON mode), so searching a directory of serialized trace dumps
(single-line multi-hundred-MB JSON files) accumulated gigabytes of stdout
until string concatenation threw RangeError: Out of memory inside the
stream data handler. That throw is outside the tool's try/catch, so it
escalated to an uncaughtException and killed the CLI/hub daemon.
Parse rg's JSON events incrementally line by line, drop events larger
than 256KB, truncate matched/context lines to MAX_LINE_CHARS, and stop
reading once maxResults is reached. The fallback regex scan now skips
files larger than 10MB (reporting the skip count) and truncates its
context lines the same way.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* simplify search_codebase crash fix to a minimal diff
Replace the incremental JSON-event parser with three small guards: stop
buffering rg stdout past 10MB, drop the trailing partial event before
parsing, and slice fallback context lines to MAX_LINE_CHARS. Drops the
fallback file-size skip and skip-count reporting.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): hide per-tool MCP auto-approve checkboxes behind a flag (#13522)
* chore(vscode): remove per-tool MCP auto-approve checkboxes from webview
MCP auto-approval is now governed solely by the global 'Use MCP servers'
toggle; the SDK approval path (shared with the CLI and desktop app) has no
per-tool granularity, so the per-tool and 'Auto-approve all tools'
checkboxes were no-ops that implied control that no longer exists. Remove
them from the MCP settings view and chat tool rows. The autoApprove arrays
in cline_mcp_settings.json and the toggleToolAutoApprove RPC are left
intact for the legacy extension.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): hide per-tool MCP auto-approve checkboxes behind a flag
Keep the checkbox components, handlers, and RPC plumbing intact but gate
rendering behind SHOW_MCP_PER_TOOL_AUTO_APPROVE=false: the SDK approval
path (shared with the CLI and desktop app) is all-or-nothing via the
global 'Use MCP servers' toggle, so the per-tool checkboxes were no-ops.
Flip the flag back on if the SDK gains per-tool approval granularity.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(tools): create new files with the platform-native line ending (#13521)
* fix(tools): use platform-native EOL for new files and preserve CRLF in apply_patch updates
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* simplify to the minimal new-file EOL fix
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* extract shared normalizeNewFileLineEndings helper
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add suggested schedule templates to the desktop Schedules page (#13529)
* Add suggested schedule templates to desktop Schedules page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix unreadable selected text in inputs caused by selection utility conflict
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Restyle Suggested section label as small gray uppercase
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Hide suggested schedule cards that match an existing schedule name
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Disable the agent todo tool and hide the Agenda UI in the desktop app (#13530)
* remove todo tool and Agenda UI, keep schedule-only tasks tool
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: biome formatting fixes
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* restore agenda backend; disable todo kind behind a flag instead of deleting
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* keep agenda automation pump idle while the todo tool is disabled
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* remove todo tool and Agenda UI altogether (revert the disable-flag hybrid)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* restore all agenda code to main state
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* disable agent todo tool and hide Agenda UI behind flags
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add Desktop App and Cloud Platform to bug report issue template (#13532)
* Add Desktop App and Cloud Platform to bug report surfaces
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename Surface Diagnostics field to Diagnostics
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: sidebar navigation cleanup with New/Schedule/Customize rows and dialog-based search (#13533)
* desktop: clean up sidebar navigation chrome
- Give New Task its own full-width labeled row below the logo row
instead of an ambiguous icon next to the agenda toggle
- Wire the New Task row to the home action so starting a new task
clearly takes you home (the logo still works as a fallback)
- Swap back/forward chevrons for browser-style arrow icons and
bump their size
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: sidebar New/Schedule/Customize rows and always-visible search
- Stack New (plus icon), Schedule, and Customize as full-width labeled
rows below the logo; whole row highlights on hover via sidebarItem
- New starts a fresh task (home), Schedule opens Settings > Schedules,
Customize opens the Customizations sections (Plugins first)
- Show the session search bar permanently above the sessions list
instead of hiding it behind a search icon toggle
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: move session search into a dialog behind a logo-row icon
- Replace the inline sidebar search bar with a search icon in the
logo row that opens a cmdk command dialog listing sessions
- Selecting a result opens that session and closes the dialog
- Remove the agenda/tasks toggle the icon replaces, along with the
now-unreachable sidebar Agenda panel (the welcome screen still
surfaces agenda tasks)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: load full session history when the search dialog opens
Addresses Greptile review on #13533: the dialog only searched the
currently loaded history batch, so older unloaded sessions could not
be found. Opening search now kicks off loadAllSessions() (the hook's
purpose-built global-search loader), and the empty state reads
'Searching older sessions...' while more history is streaming in.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Hide Channels and Agents sections from desktop app sidebar (#13527)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Desktop app: organize sidebar sessions into Pinned, Scheduled, and Tasks sections (#13528)
* Add Pinned/Scheduled/Tasks categories to desktop app sidebar
Replace the Schedules and Favorites filter-menu options with visible
collapsible category sections in the session sidebar, and rename the
Favorite action to Pin across the sidebar and sessions view.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Grow full history window when Tasks show-more outpaces loaded tasks
loadMoreSessions treats its argument as a limit on all sessions, but the
Tasks show-more count only tracks Task rows, so once pinned/scheduled
rows pushed the loaded total past the requested count the call no-oped
and clicks went dead. Grow the whole history window via
loadOlderSessions instead, and only when the loaded tasks cannot fill
the next page. Addresses Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Auto-fill the Tasks page instead of fetching once per show-more click
A single 50-session window growth can consist entirely of pinned or
scheduled sessions, leaving a show-more click with no visible Tasks
progress. Replace the one-shot fetch with a page-fill effect that keeps
growing the history window until the requested Tasks page fills or
history runs out. Addresses the follow-up Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Halt page-fill retries after a failed history fetch
A failed fetch leaves the task count and has-more state unchanged,
which are exactly the conditions the page-fill effect fires on, so one
failing request would retry and re-toast forever. Halt the effect after
a failure and let the next explicit show-more click retry. Addresses
the third Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Redesign desktop Model Providers page and split voice input into its own settings page (#13531)
* Redesign desktop Model Providers page and split voice input into its own settings page
- Group providers into Connected / Popular / All with auth-kind hints and
connection status instead of per-row enable toggles
- Show browser sign-in (not an API key field) for OAuth providers, with a
collapsed manual-key escape hatch where supported, plus explicit
Connect / Disconnect / Sign out actions
- Move voice input to a dedicated Settings > Voice page that only offers
connected transcription-capable providers, preselects a default model
(streaming preferred), and stays disabled in the sidebar until a
provider is connected
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Show native tooltip on the disabled Voice settings nav item
Disabled buttons drop pointer events, so the 'connect a model provider'
hint moves to a wrapping span for the browser tooltip to render.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Drop letter avatars and gray provider ids from provider rows and voice chips
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Drop model counts from provider list rows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename provider Connected status to Configured and drop the green styling
A settings entry is configuration, not a live connection; neutral gray
text avoids implying an active link, since the user still picks which
configured provider to use per chat.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Resync provider catalog from disk when a settings save fails
Connect/disconnect/credential edits update the list optimistically; a
failed save now reloads the catalog instead of leaving the optimistic
state (and the view's module cache) claiming a configuration that was
never persisted.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename oauthProvider test fixture to dodge CodeQL name heuristic
CodeQL's clear-text-storage query flags any identifier matching 'oauth'
as a credential source and traced the fixture's provider id into the
favorite-models localStorage write, which stores only provider/model id
strings. Renaming the fixture removes the false-positive source.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Guard catalog reloads against races and resync detail drafts on failed saves
Optimistic provider mutations now bump a generation that discards any
in-flight catalog response, so a failed-save recovery reload can't
overwrite a newer edit with an older disk snapshot. The recovery also
remounts the provider detail panel via a reset token so its local field
drafts reflect the reloaded on-disk state instead of unpersisted edits
or an optimistically cleared disconnect.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix failed-save recovery ordering and retry superseded reloads
Remount the provider detail only after the authoritative catalog reload
lands, so its drafts re-seed from disk state rather than the optimistic
values that failed to persist. When a concurrent edit supersedes the
recovery's in-flight response, retry the reload (bounded) instead of
dropping it, since that edit performs no reload of its own.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): Customize hub, sidebar overhaul, and settings polish (#13538)
* feat(desktop): merge customization pages into a Customize hub with inline marketplace
Replaces the Plugins page and the dedicated Marketplace page with a single
Customize hub. Tabs: Skills, MCP, Plugins, Rules, Hooks, Tools, each with
live counts. Tabs backed by a marketplace catalog render the installed
items followed by an inline browsable Browse section (CLI-hub style), so
installing from the catalog immediately reflects in Installed above.
- Installed cards restyled to mirror the browse-card anatomy: bg-card p-4
containers, absolute top-right xs Uninstall matching Install, truncating
semibold titles, primary-tinted icons, real Badge components instead of
ad-hoc bordered spans, un-indented line-clamped descriptions
- Rules/Hooks/Tools rows brought into the same card language; redundant
intro paragraphs (duplicating the page description) removed; Tools group
headers match the Installed header style with counts
- Marketplace section header renamed to Browse; duplicate 'N results' row
removed (the header count is the single source)
- MCP embedded view now shows the full marketplace instead of
installed-only
* feat(desktop): overhaul sidebar sessions and navigation
Sessions list:
- Sort toggle removed; sessions are always grouped by project, with pinned
sessions leading each group (both subsets ordered by recency). The
Pinned/Scheduled/Tasks category sections and their time-mode paging
machinery (page-fill effect included) are deleted
- Scheduled sessions get an inline clock icon next to the pin position;
pin + clock render together when both apply, and the running/unread
status dot now coexists with them
- One font size (text-sm) across the list: titles, timestamps, project
headers, show-more buttons, empty states. sidebarText needed !text-sm
because the default button size's text-base wins the twMerge conflict
- Gradient fade under the Sessions header once the list scrolls, so rows
fade out instead of hard-clipping
- The session-detail hover card is controlled from the sidebar and closes
on scroll (Radix receives no pointer events while scrolling, so it used
to float over moving content)
- Sidebar min resize width raised 224->260px; the per-project show-more
label truncates so its nowrap text can't force rows to overflow and clip
timestamps at narrow widths
Navigation:
- Customize replaces the Plugins/Marketplace/Hooks/Rules/Tools sidebar
entries; Schedules and Customize are hidden from the expanded settings
nav (their top rows cover them) but stay reachable when collapsed
- The settings gear always opens General instead of resuming the last
section; the Account no-op hover special case is gone
- The New row highlights (aria-current) while the fresh not-yet-started
task page is showing and hands off to the session row once the task
starts; hitting New also focuses the prompt input via a window-event
signal (lib/prompt-input-focus.ts) since the sidebar and composer sit in
distant subtrees
- Fixed the xs button size collapsing any icon-bearing button to 12x12
(leftover has-[>svg]:size-3 from when xs was a micro button) — this was
why Uninstall buttons rendered broken next to Install
* feat(desktop): polish settings pages and chat composer
Models page:
- The provider detail panel is always open: no X button, no empty
no-selection state. It defaults to the first connected provider (falling
back to the first in the catalog), which also removes the layout shift
that happened when the page swapped between full-width and panel
variants on selection
- Fixed the list pane becoming unscrollable while the panel was open:
grid items default to min-size auto, so the pane grew past its track
inside the overflow-hidden grid and its ScrollArea had nothing to
scroll; wrapped it in a min-h-0 min-w-0 cell
- Add Provider opens a Dialog instead of swapping the page
(AddProviderContent gained a dialog variant that renders only the form)
- Embedded inputs (provider search, model search, detail fields) share one
EMBEDDED_INPUT_CLASS stripping the Input component's own border/dark bg
tint/shadow/ring, which rendered as a mismatched inner box; the model
search box uses the same h-9/px-3 frame as the provider search
- Model list flows with the page instead of a max-h capped inner scroller
Other pages:
- Account uses the shared PageFrame/PageHeader: left-aligned, text-3xl
title, Sign Out in the header actions slot
- Desktop notifications is one General section: header row plus the
Event/Notify/Sound matrix nested in a card, so its rows no longer read
as top-level peers of Dark mode; 'Available in the desktop app' label
removed
- Schedule page retitled from Schedules with a real description; Customize
description rewritten
Chat composer:
- The voice dictation button only renders once a voice model is
configured (Settings -> Voice); the unconfigured deep-link state is
gone (prop type kept for an easy restore)
* chore(desktop): release v0.0.17
* fix(desktop): unblock sdk-test lint on the voice-input model picker (#13553)
The model picker renders a radiogroup of styled buttons with role=radio
and aria-checked; biome's useSemanticElements flags the role as an
error, which fails the sdk-test Quality Checks lint for every PR
touching sdk/ or apps/ paths. Suppress with a justification — switching
to input type=radio needs a restyle and belongs to the desktop settings
work.
* fix(vscode): include rich workspace metadata in system prompt (#13518)
* capture richer workspace information for vs code extension
* fix(shared): redact credentials from workspace remotes
* fix(shared): avoid regex backtracking in remote redaction
---------
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
* Hide task costs on vscode when ClinePass is selected (#13515)
* fix: stop showing cost estimates for subscription-billed providers (#13552)
* fix(vscode): stop showing cost estimates for subscription-billed providers
Providers whose usage is covered by a flat-rate subscription (ChatGPT
Plus/Pro via openai-codex, ClinePass) are marked with
metadata.usageCostDisplay = "subscription" in the SDK, and the CLI
already suppresses dollar figures for them. The VS Code host collapsed
that value into "show" before it reached the webview, so the task
header and model pricing rows rendered API-rate cost estimates that
users read as real charges on top of their subscription.
Pass all three usageCostDisplay values ("show" | "hide" |
"subscription") through the catalog listing and render cost only when
the value is "show", matching the CLI's shouldShowCliUsageCost
policy.
* feat(llms): mark Claude Code as a subscription-billed provider
Claude Code is typically authenticated with a Claude Pro/Max
subscription, but its models reuse Anthropic API pricing metadata, so
Cline rendered per-token prices and API-rate cost estimates for usage
that is covered by the subscription. Set usageCostDisplay =
"subscription" on the provider (picked up by the CLI and the VS Code
webview) and suppress the price rows in the Claude Code settings card.
The Claude Code CLI can also run on API-key billing, where a real cost
exists; the provider cannot distinguish the two, so we prefer showing
no number over a misleading one.
* fix(vscode): suppress cost display until provider listings load
While the ListProviders request is in flight (or after it fails), the
usage-cost hook had no listing to consult and fell back to "show",
flashing the API-rate estimate at subscription users on every chat-view
mount — the exact display the previous commit removes. Return
"unknown" whenever listings are absent; consumers already render cost
only for "show", so they suppress it during that window with no
changes. Briefly hiding a real cost is harmless, briefly showing a fake
charge is not.
* fix(desktop): reconcile voice settings after main sync
* test(llms): allow experimental ElevenLabs models
* fix(sdk): preserve canonical media model behavior
* feat(desktop): customize macOS DMG install window (#13563)
* feat(desktop): add Retina DMG background tooling
* feat(desktop): customize the macOS DMG layout
* ci(desktop): validate DMG background assets
* fix(desktop): adjust DMG Applications icon position
* ci(desktop): drop redundant DMG artwork validation from publish workflow
Tauri's beforeBuildCommand already runs dmg:background (with its own
validation) at the start of the build/sign/notarize step, and the
release/beta config overlays do not override the build section, so this
step duplicated work the publish job performs anyway. PR-time coverage
lives in desktop-test.yml.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): sidebar time view, Customize/Marketplace split, and schedule page UX (#13570)
* feat(desktop): split Customize into Installed and Marketplace pages
The Customize hub previously embedded a Browse section inside every tab
that had a catalog. That inlining made each tab long and buried the
catalog. Customize is now the installed inventory only (skills, MCP,
plugins, rules, hooks, tools tabs pass marketplaceVariant="installed"
to the embedded MarketplaceView; McpServersContent grew the same prop),
with an outline Marketplace button in the header.
Browsing moved to a dedicated Marketplace settings section that renders
the previously dead "directory" variant of MarketplaceView: one list
across all catalog types with type-filter chips, wrapping tag chips,
and light rules separating the filter tiers from each other and from
the results. The Clear control now renders inline at the end of the tag
row only while a tag is active, the Updated date is gone, and the
header hosts an Installed button mirroring the one on the Customize
page. Directory subheader copy: "A curated set of plugins, MCP
servers, and skills from the Cline community."
Tag and type chips wrap to new lines instead of scrolling
horizontally.
* feat(desktop): sidebar time view with sections, sort toggle, and scheduled detection
Restores the time-sorted session list as the default sidebar view, with
collapsible Pinned / Scheduled / Tasks sections (headers appear only
once something is pinned or scheduled) and the page-fill effect that
grows the fetched history window until a Show-more click makes visible
progress. Project grouping stays as the alternate mode behind a
one-click sort toggle whose icon reflects the active mode — the old
dropdown cost an extra click for a two-option choice.
Scheduled sessions are detected two ways: the hub-schedule origin
trigger in session metadata, plus a fallback that asks the hub which
session ids belong to schedule executions (list_routine_schedules,
fetched on mount and every two minutes, merged into a rolling set).
The fallback matters because locally executed scheduled runs do not
reliably stamp the trigger into session metadata — a real scheduled
session created today carried only {mode:"user"} provenance. The
scheduled clock icon now leads the row, left of the title; pin and
timestamp stay on the right.
The initial visible page grows from 10 to 30 rows so a tall sidebar
fills instead of stranding a stub of rows over empty space (history
fetches already start at 50).
The expanded sidebar's Customize row now hosts indented Installed and
Marketplace sub-tabs while a customize section is open; the active
sub-tab carries the full selected background while the parent keeps a
subtler one so the two simultaneous highlights read differently.
Also fixes the hover-card flash on click (logo card and session-row
cards): Radix HoverCardContent sits on a DismissableLayer, so a click
on the trigger registers as a pointer-down outside the card and
dismisses it, and the trigger's focus event immediately reopens it.
onPointerDownOutside preventDefault suppresses the dismissal; cards
still close on pointer leave.
* feat(desktop): schedule page row, dialog, and details UX polish
Schedule cards are now click targets: clicking anywhere on a card
outside its controls opens the details dialog (guarded via
closest("button,...") since every inline control, including the Radix
switch, renders a button element), with Enter/Space keyboard support.
The redundant eye button is gone. The remaining edit / run / pause /
delete buttons grow from the 12px icon-sm size to 28px targets with
16px icons, sized consistently with the adjacent enable toggle — the
icons use explicit size-4 classes so the Button base svg rule cannot
shrink them back.
The new/edit dialog gains breathing room between field labels and their
inputs (space-y-2 per field wrapper).
The details dialog no longer scrolls as a whole when the schedule JSON
is long: the dialog is a flex column capped at 85vh, the JSON pre
shrinks to the remaining space (min-h-0) and scrolls internally, and
the Runs tab list scrolls inside the tab the same way.
* feat(desktop): scheduled sessions UX — unified details dialog, run-now handoff, hidden steering, stuck-thinking fix (#13573)
* feat(desktop): merge schedule details into one view and open run-now sessions
The schedule details dialog drops its Overview/Runs tabs: one scrollable
column with the meta grid, the configuration JSON (capped at max-h-64
with internal scroll so it cannot crowd out what follows), and a Runs
section beneath it showing the three most recent runs with a ghost
"Show all N runs" expander (collapsed again whenever a different
schedule's details open). The "Full configuration for this schedule"
subtext is gone; the dialog passes aria-describedby={undefined} so
Radix does not warn about the missing description.
Run now hands you into the session it starts. The trigger command
queues the run and returns before the runner attaches a session id, so
after the toast the handler polls the schedule overview once a second
for up to 15 seconds — which doubles as keeping the page's run status
fresh (refreshSchedules now returns the fetched overview to make that
single-stream) — until the triggered execution reports its session id,
then calls onOpenSession. Guarded so it never auto-navigates after the
user left the page.
* feat(desktop): hide runtime steering messages from transcripts
Scheduled/automation runs inject user-role steering messages each
iteration ("[SYSTEM] This run is not complete until you call
submit_and_exit...", plus a team-obligations variant). The chat view
rendered them as user bubbles, as if the person had typed them — in a
scheduled session the transcript was mostly [SYSTEM] noise.
They are machinery talking to the model, not something the person said
or needs to read, so the transcript now hides them entirely:
MessageBubble renders null for any [SYSTEM]-prefixed user message.
Grouping still treats them as working-row machinery via a single
isSystemSteeringMessage predicate — they collapse into the run's work
span, are never a turn boundary, can never be mistaken for a run's
answer, and never advance the run count even when metadata is missing —
so work-block folding and checkpoint/edit run numbering stay correct.
A finished scheduled session now reads as prompt, work summary, answer.
* fix(desktop): poll history while an attached session's event stream is dead
Opening a scheduled session while (or right after) it runs left the
view stuck on the thinking shimmer until the user switched away and
back. Root cause is in core: the hub daemon executes scheduled runs on
a private LocalRuntimeHost inside createLocalHubScheduleRuntimeHandlers,
while the hub server only projects live events from its own session
host — so session.attach succeeds but no assistant/tool/status events
ever flow. And since multiple hub daemons share cron.db, a run claimed
by a different daemon is invisible to this hub regardless. The proper
core rewiring is tracked as ENG-2474.
Client-side heal that covers every case: while an attached history
session reports a busy status and no chat_event chunk has arrived for
five seconds (and no assistant bubble is mid-stream), poll every three
seconds — re-read canonical history, merged through the same dedupe
path hydration uses, and the session record's status — so the
transcript and the thinking indicator settle in place. Locally driven
turns keep chunks flowing, so the quiet-window guard keeps the fallback
inert there.
* chore(desktop): format workspace selector components
Biome formatting drift that landed on main; picked up by a formatter
pass over components/views/chat.
* fix(desktop): keep stale-stream poll inert during locally driven turns
The fallback poll could fire between a local submit and the model's
first chunk (optimistic user bubble added, stream quiet past the
window, no assistant bubble yet). It then replaced the optimistic
bubble — raw prompt text — with its canonical history twin, which is
stored wrapped in a user_input envelope. The rekey handler that runs
when the stream starts looks for a trailing user bubble matching the
raw prompt, finds only the wrapped copy, and appends a second bubble:
duplicated messages in normal interactive chat.
The poll now stays inert while a local turn is in flight
(turnEpoch !== turnSettledEpoch, or outstanding optimistic user
messages), checked both before polling and again after the snapshot
returns. Hydration marks the turn settled — the mount defaults
(epoch 0, settled -1) otherwise read as an open turn and would keep
the fallback inert forever for the scheduled-session case it exists
for. Applying a polled snapshot also rebuilds the live tool routing
keys, same as hydration, so later tool events update canonical rows
in place instead of appending.
* fix(desktop): keep the working indicator alive for narrating scheduled runs
Watching a scheduled run live: the first tool row appeared, then the
thinking indicator vanished with nothing streaming, and the rest of
the run (final answer, submit_and_exit) only showed up seconds later
in one lump.
inferHydratedChatStatus treats a "running" session record whose
transcript ends on an assistant message as a session that died without
a status flip and reports "completed". That heuristic is right for
stale records, but scheduled/automation models narrate between tool
calls, so a polled snapshot can genuinely end on assistant text
mid-run — the completed flip hid the working indicator, folded the
run early, and disarmed the stale-stream poll (status left the busy
set), dead-ending live updates until an in-flight poll happened to
deliver the finished run.
The heuristic now only applies once the transcript has actually gone
quiet (newest message older than two minutes — comfortably past model
latency plus tool runs). A recently active transcript keeps the
record's "running" verdict, so the indicator stays up and polling
stays armed until the record itself settles.
* fix(desktop): stale-stream poll mirrors the session record instead of inferring
Replaces the previous fix for the vanishing working indicator (the
time-window guard added to inferHydratedChatStatus) with a version
that adds no inference at all: the heuristic is restored to exactly
its long-standing form, and the poll now maps the session record's
status verbatim (mapSessionRecordStatus).
The record is the right authority in the poll's context: the sessions
this fallback serves have a live host maintaining their record, and it
flips to a terminal status when the run ends. Transcript-shape
inference belongs only where it has always lived — hydrating sessions
whose records may be orphaned — and would misread a mid-run snapshot
ending on assistant narration as a finished session, hiding the
working indicator and disarming the poll.
* fix(desktop): address review findings on steering detection and run-now matching
Steering detection additionally requires the injected-message marker
(meta.userRunSpan === 0) beside the [SYSTEM] prefix, so a person's
genuine prompt that happens to start with "[SYSTEM]" stays visible
and turn-counted. The failure direction is deliberate: an unstamped
injected reminder would merely show as a user bubble, while the
content-only check could hide a real prompt.
Run-now only follows the execution id the trigger reply itself named;
the newest-execution-for-this-schedule fallback could open a previous
run's session when the trigger failed to enqueue one.
* fix(desktop): report a failed run-now instead of confirming a start
A trigger reply without an execution means no run was enqueued (the
schedule may have been disabled or deleted since the page loaded). The
handler previously toasted "Run started" regardless and then silently
skipped the session-open polling. It now shows a destructive
"Run not started" toast, refreshes the schedule list so the row
reflects reality, and skips the polling entirely.
* fix(desktop): don't block the main thread on quit while stopping the sidecar (#13566)
Quitting the mac app beach-balled for ~5-7s. The shutdown POST was
built from the ws transport URL (appending /shutdown lands inside the
query string), so the sidecar was never told to exit, and stop() then
polled the child for up to 7s on the main thread - on macOS inside
applicationWillTerminate - before SIGKILLing it.
stop() now sends SIGTERM and returns immediately. The sidecar handles
SIGTERM with the same bounded (5s) graceful shutdown as the /shutdown
endpoint and exits itself, finishing session persistence as an orphan.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): hover trash button on sidebar session rows (#13582)
Each session row shows a trash icon on the right while hovered (or
when the button itself is focused), opening the same delete
confirmation dialog the row's context menu uses. The row is a button
and buttons cannot nest, so the trash is an absolutely positioned
sibling inside a group/row wrapper, overlaid where the timestamp sits:
row hover hides the timestamp, shows the trash, and moves the row's
hover background to the wrapper group so it holds while the pointer is
on the trash itself.
* fix(desktop): install marketplace plugins and MCP servers in-process instead of spawning a cline binary (#13585)
* fix: install marketplace plugins and MCP servers in-process instead of spawning a cline binary
The desktop app sidecar and cline-hub shelled out to 'cline plugin install'
and 'cline mcp install' for marketplace installs. Packaged GUI apps inherit
launchd's minimal PATH on macOS and most desktop users have no cline CLI
installed at all, so installs failed with a red
'Executable not found in $PATH: "cline"' error.
Install via @cline/core's installPlugin/installMcpServer in-process instead,
matching what the VS Code extension already does. Also fix
parseMcpInstallArgs in @cline/core to treat the marketplace catalog's '--'
separator as end-of-options; previously the separator itself became the
stdio command.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop test-injection plumbing from marketplace installers
Call @cline/core's installPlugin directly instead of threading an
installer option through the marketplace entry points; tests stub the
core module instead.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert: keep cline-hub marketplace installs CLI-backed
The hub dashboard is launched via 'cline dashboard', so a CLI is always
present and CLINE_WRAPPER_PATH resolves it; the PATH bug only affects
the desktop app, which does not ship a CLI.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(desktop): release v0.0.18
* chore(vscode): release v4.1.16
* chore(sdk): release v0.0.80
* chore(cli): release v3.0.59
* fix(hub): stop shipping full transcripts inside broadcast hub events (#13587)
* fix(hub): stop shipping full transcripts inside broadcast hub events
Every session.updated (and session.created/detached/run.started) event
embedded the session's ENTIRE message transcript via readCoreSessionSnapshot,
even though no consumer reads snapshot.messages off an event — clients fetch
messages with the session.messages command. For a multi-megabyte transcript
this turns every status flip into megabytes per subscriber, floods the
durable event log, and (until the send-queue backpressure fix lands) lets a
slow subscriber balloon the hub process by one full transcript copy per
event — reported as a 25GB cline process on a 16GB Mac.
Strip snapshot.messages centrally in HubServerTransport.publish() so every
current and future event publisher is covered, the event log stores slim
envelopes, and cursor replay stays byte-identical with live fan-out. All
other snapshot fields (status, usage, model, workspace, checkpoint) are kept,
and command replies are untouched.
* fix(hub): never capture the transcript into event/reply snapshots
Replaces the publish-boundary strip with the real fix: don't build
message-bearing snapshots in the first place. emitSessionSnapshot no longer
re-reads the entire transcript from disk on every status flip, and
readCoreSessionSnapshot no longer reads it for any event or reply — a
snapshot is a state notification (status, usage, model, workspace,
checkpoint); the transcript is fetched via the session.messages command.
Checkpoint-restore snapshots (session-versioning-service) are untouched:
restore replies carry messages in their own dedicated field.
* chore(desktop): release v0.0.19
* chore(sdk): release v0.0.81
* chore(cli): release v3.0.60
* fix(vscode): avoid render crash on malformed api_req payloads in combineApiRequests (#13560)
* fix(vscode): stop pinning DeepSeek model count in catalog smoke test (#13600)
* feat(ui): share agent welcome hero (#13567)
* feat(ui): share agent welcome hero
* test(ui): cover welcome hero pointer states
* refactor(ui): keep welcome hero API minimal
* test(ui): verify welcome hero package assets
* fix(ui): inline welcome hero masks
* fix(tools): preserve a file's own CRLF line endings across apply_patch updates (#13512)
* fix(desktop): keep the window title bar draggable across views (#13572)
* fix(desktop): keep window title bar persistent
* fix(desktop): reserve persistent title bar space
* fix(desktop): polish persistent title bar layout
* Sign Windows CLI binaries with Azure Trusted Signing; surface app-control launch errors (#13021)
* feat(cli): sign Windows binaries with Azure Trusted Signing and surface app-control launch errors
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): use _CLI-suffixed signing profile secret, normalize endpoint, fail loud on partial config
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* Tunnel ProtoBus over the existing Host Bridge (#13218)
* feat(core): tunnel ProtoBus over Host Bridge
* fix(core): harden Host Bridge stream lifecycle
* fix(core): serialize concurrent chunked responses per request
Streaming handlers deliver updates fire-and-forget, so two logical
responses for one request_id can be in flight at once. Chunked payloads
made forwarding non-atomic: each chunk write is an await, so concurrent
forwards could interleave their chunk sequences and the receiver --
which reassembles purely by arrival order -- would splice two payloads
into one. Route all forwards for a request through one promise chain; a
failed write rejects every later forward so a torn payload is never
followed by more chunks.
Rename the lock manager's instanceAddress to instanceOwner: it holds an
opaque per-spawn instance ID on the token path and a listener address
only on the CLI-harness path. Delete the caller-less getInstanceByPort
query that interpreted the owner as an address.
Also: document message_json as a legal wire encoding for small
payloads, close the gRPC client when startup fails, note the
intentional discard of the cancellation confirmation, and add the
proto's trailing newline.
---------
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
* Build and Authenticode-sign a Windows x64 desktop installer in desktop releases (#13607)
* feat(desktop): build and Authenticode-sign a Windows x64 NSIS installer in desktop releases
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): pin OIDC-adjacent actions to commit SHAs in the Windows signing job
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): pin checkout and upload-artifact to commit SHAs in the Windows signing job
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): show agent-created schedules on the Schedules page (#13613)
* fix(desktop): show agent-created schedules on the Schedules page
Schedule hub commands are scoped to the workspace registered by the
connection, but the desktop app's hub client registers the app launch
directory while agent-created schedules live under each chat's own
workspace folder - so they never appeared on the Schedules page.
Grant token-authenticated hub connections (which can already bind any
workspace at registration) explicit cross-workspace schedule access via
an allWorkspaces payload flag, and have the desktop sidecar request it
for routine schedule commands. Workspace-bound clients (local browser
origins) and default CLI behavior stay scoped.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(core): strip allWorkspaces flag from schedule inputs and pin it in the sidecar payload
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make suggested routine template prompts prescriptive about their final output (#13611)
* Make bug hunter routine template prescriptive about its final report
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make remaining routine templates prescriptive about their final output
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Desktop: surface scheduled-task final output — auto-expand submit_and_exit and render its summary as markdown (#13612)
* desktop: auto-expand submit_and_exit and render its summary as markdown
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: render submit summary in full foreground color
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: label the submit row 'Scheduled task completed'
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: label errored submit_and_exit rows as failed
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add tooltips explaining Live and After recording badges on voice input models (#13610)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Remove box shadow from chat message actions row (#13630)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): make the Tauri shell work on Windows (#13632)
- Defer updater installation to the user-initiated restart on Windows:
install() launches the NSIS installer and exits the process immediately,
so the background cycle now downloads only and stages the bytes, and
restart_to_apply_update installs them after stopping the sidecar.
- Spawn child processes (sidecar, git, cmd /C start) with CREATE_NO_WINDOW
so the GUI-subsystem app doesn't pop visible console windows.
- Fall back to USERPROFILE when HOME is unset resolving the MCP settings
path, matching the sidecar's homedir().
- Reap the sidecar after the Windows hard-kill so its exe file lock is
released before the NSIS installer replaces it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): stop watching agenda spec dirs while the todo tool is disabled (#13629)
* fix(core): stop watching agenda spec dirs while the todo tool is disabled
Since #13530 disabled the agent todo tool, the Agenda UI, and the
automation pump, the hub still created fs.watch watchers on the global
agenda specs dir and on every workspace root recorded in the task store
(at startup and on scope access). Nothing consumes the watcher-driven
task events while the feature is off, and the task.* hub commands
already reconcile spec files on demand, so the watchers are pure
overhead - one OS watch handle per known workspace.
Wire watchFiles to AGENDA_TODO_TOOL_ENABLED the same way
automationEnabled is, preserving a host's explicit watchFiles opt-out
for when the flag is turned back on. Schedules are unaffected: the
schedule list has no file watcher and updates through hub commands and
published schedule events.
* fix(core): reconcile external spec edits inside updateTask
With the spec watchers off there is no background reconciliation, so a
task spec edited directly on disk made every same-store task.update fail
the signature check with "task spec changed outside the manager" until
an unrelated task.get or task.list happened to reconcile the scope.
Reconcile the task's scope at the start of updateTask (mirroring what
refreshAndVerifyTaskIntent already does for approve/run), skipping it
when the file reconciler itself is the caller to avoid recursing from
reconcileFileStore. An external edit now surfaces as the store's normal
stale-revision conflict, and a re-read-and-retry succeeds. This also
closes the pre-existing watcher debounce race for updates.
* fix(sdk): don't log out Codex/OCA users when token refresh fails transiently (#13565)
* fix(sdk): don't log out Codex/OCA users when token refresh fails transiently
Port the cline-provider refresh semantics to openai-codex and oca:
a transient refresh failure (network error, timeout, server 5xx) with an
already-expired access token now rethrows instead of returning null.
A null return means the refresh token was REJECTED and re-auth is
required; treating an outage blip as a rejection is what turned it into
a forced 'openai-codex requires re-authentication.' task stop while the
settings UI still showed the user as signed in.
Both providers also emit user.auth_refresh_soft_failure telemetry on
transient failures (the 'prevented logout' counter the cline provider
already has) and attach status/errorCode details to the genuine
invalid_grant logout event.
* refactor: collapse duplicate soft-failure telemetry branches and test
Review feedback: compute tokenExpired once and emit the soft-failure
event once in both providers, then return current credentials or
rethrow. Fold the codex soft-failure telemetry assertions into the
existing still-usable-token test instead of a near-duplicate case.
* fix: make OpenAI Codex (ChatGPT subscription) sign-in fail loudly instead of silently dead-ending (#13537)
* fix: make OpenAI Codex sign-in fail loudly instead of silently dead-ending
When callback port 1455 is already in use (e.g. by the Codex CLI or a
previous pending sign-in), startLocalOAuthServer returns a no-op server
and loginOpenAICodex would open the browser anyway, then dead-end:
the callback could never be received, and in the VS Code extension the
user just saw nothing happen after clicking 'Sign in to OpenAI Codex'.
- loginOpenAICodex now fails fast with an actionable 'port in use'
error before opening the browser, unless the host provides manual
code entry (the CLI's paste fallback keeps working)
- surface OAuth redirect errors (e.g. access_denied) instead of
collapsing them into 'Missing authorization code'
- the extension dedupes concurrent sign-in clicks: a re-click re-opens
the auth page of the pending flow instead of spawning a second flow
that would collide with our own callback server
- browser-open failures now show an error message with the URL to
open manually instead of only logging
- abandoned-flow timeouts no longer surface a confusing 'Missing
authorization code' toast
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop host-side codex login dedupe, keep flow identical to CLI
The SDK owns the failure handling now (fail-fast on an unbindable
callback port), so the extension keeps the exact same simple
loginOpenAICodex call the CLI uses. A second click while a flow is
pending gets the SDK's clear port-in-use error, same as running
'cline auth openai-codex' twice would. Keep only the CLI-parallel
onOpenUrlError surfacing (the CLI prints 'open the URL above
manually'; the extension's equivalent is an error toast with the
URL).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(e2e): cover Codex sign-in callback-port failure and redirect errors
Two driven-VS Code tests for the OpenAI Codex (ChatGPT subscription)
sign-in flow:
- with port 1455 occupied on both loopback families, clicking the
sign-in button surfaces the fail-fast port-in-use toast
- with the port free, the callback server binds and an OAuth redirect
error (access_denied) propagates to a visible error toast
The second test opens a real browser tab to the OpenAI auth page as a
side effect of the genuine sign-in click.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
* feat(core): anchor agent-created schedules in the user's .cline schedules home (#13634)
* feat(core): anchor agent-created schedules in the user's .cline schedules home
Agent-created schedules inherited whichever workspace folder the chat
session happened to run in, scattering user-level routines across chat
and project folders. They were invisible to workspace-scoped listings
elsewhere, tied to folders that may be cleaned up, and each chat's
tasks tool saw a different set when checking for duplicates.
Anchor them in ~/.cline/schedules instead: the hub's scheduled-task
session defaults now resolve to that home (created on demand), so
agent-created schedules live and run in one stable user-level scope.
The tasks tool guidance now tells agents that scheduled sessions run in
the schedules home, so prompts must carry absolute paths to any project
they operate on.
Schedules created explicitly with a workspace (CLI --workspace, desktop
routine wizard) are unchanged, and existing rows keep their current
workspaceRoot - they stay visible through the all-workspaces listing
paths (#13613, #13633).
* test(core): restore any pre-existing CLINE_DIR after the agenda hub test
The test's cleanup deleted CLINE_DIR outright, so an environment that
had it configured would leave later tests in the same worker on the
default storage directory. Save the previous value and restore it.
* test(core): restore CLINE_DIR even when hub test setup throws early
Restoring the override in the try/finally missed failures thrown during
transport construction or start(), before the try was entered. Register
the restore with onTestFinished instead, which runs regardless of where
the test fails.
* fix(desktop): don't show providers as configured without real credentials (#13608)
* fix(desktop): don't show providers as configured without real credentials
The desktop settings marked any provider with a persisted settings entry
as Configured, but legacy VS Code migration and empty saves can seed
entries (e.g. qwen-code, sapaicore) holding only a default model and no
credentials. Move the CLI's isProviderSettingsUsable readiness check into
@cline/core, expose it as a computed 'configured' flag on the provider
catalog, and use it in the desktop's isProviderConnected.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): resync catalog after saves so Configured badge updates live
Optimistic provider mutations can't know the sidecar-computed 'configured'
flag, so after connecting a keyless provider or saving cloud credentials
(e.g. a Vertex project id) the row stayed 'Not configured' until remount.
Silently refetch the catalog after each successful save, guarded by the
existing generation counter so newer edits discard stale responses.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): claim a generation in post-save resync so overlapping refreshes can't apply stale snapshots
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): bump catalog generation on OAuth login success
Every other optimistic provider mutation claims a new generation; the
OAuth success path didn't, so a catalog load or resync still in flight
could arrive late and overwrite the just-connected state.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): resync catalog after OAuth login instead of bare generation bump
The resync claims a new generation (discarding any stale in-flight
response) and its own fetch covers both the new OAuth connection and any
provider saved moments earlier, matching the post-save path.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): refuse checkpoint workspace restore when HEAD moved past the checkpoint (#13626)
* fix(core): refuse checkpoint workspace restore when HEAD moved past the checkpoint
Restoring a checkpoint runs git reset --hard, which moves the current
branch pointer. If commits were made after the checkpoint (by the user
or by the agent), the reset silently knocked them off the branch,
leaving them reachable only through the reflog.
Guard the reset: if HEAD no longer matches the commit the checkpoint
was created on, throw a descriptive error (including how many commits
would be dropped) instead of destroying history. Chat-only restore is
unaffected, and users who really want to discard the commits can reset
the branch manually first.
Fixes#13550
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): close the guard-to-reset race with an atomic ref update
The moved-HEAD guard read HEAD, ran further git commands, then reset
unconditionally, so a commit landing in that window could still be
knocked off the branch. Replace the reset's branch move with git's
native compare-and-swap (git update-ref HEAD <new> <old>), which fails
if HEAD no longer points at the verified commit, and follow with a bare
reset --hard to sync the index and worktree to the already-moved HEAD.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: hide history cost estimates for subscription-billed tasks (#13562)
* fix(vscode): hide history cost estimates for subscription-billed tasks
The task-header fix for subscription providers cannot reach history:
history rows render the stored totalCost (an API-rate estimate) and do
not know which provider ran the task, so the history page printed
$X.XXXX on every row and the recent-task chips in an empty chat view
rendered a $ chip even for subscription-billed tasks.
The SDK session records already persist the provider — the CLI's
history view uses it for exactly this — but the VS Code mappers dropped
it. Map it through both transports (HistoryItem.apiProvider for the
state-pushed taskHistory, TaskItem.api_provider for getTaskHistory) and
suppress the dollar figure per row when that provider's
usageCostDisplay is not "show", via a new useUsageCostVisibility
predicate shared by both surfaces.
Rows without a recorded provider (tasks predating the field, legacy
imports) keep showing the stored value — there is nothing to key
suppression on.
* test(vscode): e2e-verify history cost suppression in real VS Code
Seeds SDK session records (one openai-codex subscription task, one
anthropic usage-billed task) into the isolated CLINE_DIR before the
webview loads, then asserts in a real VS Code instance that both the
recent-task chips and the full history page render the dollar figure
only for the usage-billed task. Covers the two boundaries the unit
tests stub: on-disk records reaching getTaskHistory with provider
populated, and the provider listings delivering the subscription mark
to the webview.
* Fix scheduled tasks disappearing after desktop app updates (#13627)
* Fix hub-managed schedules being wiped by cron reconciliation on hub restart
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Require the virtual hub/schedules path when exempting specs from removal reconciliation
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Treat recorded source mtime as proof a spec is file-backed, closing the hub/schedules spoof gap
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(shared): discover global rules at ~/Cline/Rules (#13614)
The VS Code Rules tab resolves the Documents folder via
'xdg-user-dir DOCUMENTS', which prints bare $HOME when no user-dirs
config exists (WSL/headless installs), so it reads and writes global
rules at ~/Cline/Rules. The SDK's rule search paths only covered
~/Documents/Cline/Rules, so those rules never reached the system prompt.
Add the missing path to the search list.
Fixes#13542
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat: add searchable session history (#13420)
* feat: add searchable session history
Rebased onto main and updated to supersede the sidebar search dialog
from #13533: the sidebar search icon now opens the indexed command bar
(Cmd/Ctrl+P) instead of a sidebar-local cmdk dialog that eagerly loaded
the entire session history via loadAllSessions(). CommandDialog gains a
shouldFilter passthrough so server-ranked FTS hits are displayed as-is.
* fix: harden session history search
* fix: evict failed restoration sessions from search
* fix: preserve deletion when search eviction fails
* fix: address session search review feedback
* fix: preserve search suppression during reconciliation
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* Fix CLI crash when a remote MCP server is offline but enabled (#13639)
Remote (SSE/streamable HTTP) MCP connects run on the session.create
critical path, which the hub caps at 30s. Without a connect budget an
unreachable server spent the full 60s default request timeout (with the
SSE transport stuck in a reconnect loop), stalling session.create past
the hub deadline and tearing the whole session down - the interactive
TUI exited and one-shot runs failed. Stdio servers already have a
bounded initialize budget for exactly this reason; give URL clients the
same treatment with a 10s default connect budget that an explicit
timeout overrides in either direction.
Fixes#13597
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(vscode): prevent E2E worker teardown hangs (#13644)
* test(vscode): capture external URLs in E2E runs
* docs(test): clarify browser capture rationale
* Add a GitHub integration step to the onboarding (#13225)
* Add feature flags to the app
* React to account updates
* Address comments
* Add a GitHub integration step to the onboarding
* validate domain and fix errors on auth
* Hide the step behind a feature flag
* update version
---------
Co-authored-by: John Choi <john.choi@cline.bot>
* fix(ci): stop e2e worker teardown timeouts and deflake hub daemon e2e on Windows (#13646)
* fix(e2e): stop VS Code e2e worker teardown from timing out
The ext-vscode-test-e2e job has been failing on main with 'Worker teardown
timeout of 60000ms exceeded' even though every test passes. Playwright only
reports an Electron app as closed once the process exits AND every holder of
its stdio pipes is gone (ChildProcess 'close' waits on the extra fd3/fd4
pipes Playwright creates for Electron). Any VS Code descendant that outlives
the main process (chrome_crashpad_handler, GLib's 'dconf watch' helper,
xdg-open browser handlers, VS Code 1.135's agent host CLI subprocess that
logs 'unable to kill the process') keeps those pipes open, so app.close()
never resolves and the worker teardown hangs on it until its 60s timeout
fails the job.
Harness fixes, each removing one source of that wedge:
- closeAppForTeardown now SIGKILLs the whole process group (taskkill /T on
Windows) when app.close() times out, instead of only the main pid — and
does so even when the main process already exited, which is exactly the
wedged state. Playwright launches Electron detached, so pid == pgid.
- Launch VS Code with --disable-crash-reporter so no crashpad handler
outlives the app holding the harness pipes.
- Seed the fresh user-data-dir with chat.disableAIFeatures: true so VS
Code's own AI features (rolled out via server-side experiments, so CI
breaks without any repo change) never start their agent host process.
- Drop the page.close() teardown: closing VS Code's last window quits the
whole app, and ElectronApplication.close() on an already-exited app
deadlocks; the app fixture's app.close() closes windows itself while the
app is alive.
- Codex sign-in no longer opens a real external browser under E2E_TEST; the
codex-oauth test drives the OAuth callback itself, and the browser was an
orphaned process holding the harness pipes on the runner.
* fix(core): deflake hub daemon e2e tests on Windows runners
sdk-test on windows-latest fails intermittently in the hub daemon e2e
files:
- shutdown.e2e.test.ts dies with a bare 'Error: socket hang up'. That
message is the ws handshake (http.ClientRequest) failing, not the
/shutdown fetch (an undici failure prints 'TypeError: fetch failed'):
a freshly spawned bun daemon on a loaded 2-core Windows runner
occasionally drops its first accepted connection before writing the
upgrade response. Real hub clients reconnect with backoff, and the test
asserts shutdown behavior rather than first-connection reliability, so
openAuthenticatedSocket now retries transient handshake failures within
a 15s budget.
- singleton.e2e.test.ts times out waiting for daemon discovery: it still
used the 10s hang guard that 0cfc90158 already raised to 30s in
shutdown.e2e.test.ts for the same reason. Use the same 30s guard.
- Raise the e2e testTimeout to 60s so a test that legitimately spawns two
daemons back to back can survive slow-runner startups instead of the
discovery hang guard being cut off by the test timeout.
* feat(desktop): render tool output images as attachments (#13643)
* fix(desktop): render tool output images as attachments
Add support for displaying media returned by tool calls (e.g. screenshots)
as rendered images with expand-to-fullscreen capability instead of raw
base64 text. Introduces an `ImageCarousel` component for navigating
multiple images, propagates the expand handler to tool message blocks,
and extracts/validates output media in tool summaries.
* test: cover multi-image and canonical media extraction in tool output (#13645)
extractOutputMedia and the desktop tool-message rendering path were only
ever exercised with exactly one distinct valid image, and
canonicalInlineMedia (MCP-style type: "media" blocks for audio/video/file)
had zero coverage. Add tests for: multiple distinct images in one tool
result (parser + desktop carousel navigation), inline audio via the
mime_type key spelling, canonical video/file media blocks, and rejection
of an invalid canonical image block.
---------
Co-authored-by: Harrison <harrison@cline.bot>
* chore(desktop): release v0.0.20
* feat(sdk): add discovery boundary ahead of Agent Plugins support (#13017)
* ENG-2490: Propagate session aborts to teammates (#13647)
* fix(core): propagate session abort to teammates
* fix(core): persist aborted teammate tasks as cancelled
* fix(core): settle teammate work on session abort
* fix(core): isolate replacement runs from stale aborts
* refactor(core): narrow teammate task status metadata
---------
Co-authored-by: abeatrix <beatrix@cline.bot>
* fix(llms): use AI SDK 7 Langfuse telemetry (#13651)
* fix(llms): use AI SDK 7 Langfuse telemetry
* test(llms): cover Langfuse runtime context
* chore(llms): built-in model list update 1787907289186 (#13663)
* chore(llms): built-in model list update 1787907289186
Result of `bun run build:models`.
Includes updated model list and fixed formatting issues across codebase.
* test(llms): update GLM reasoning toggle expectation
* test: cover session search fallback on hub timeout and rejection (#13642)
* feat: add searchable session history
Rebased onto main and updated to supersede the sidebar search dialog
from #13533: the sidebar search icon now opens the indexed command bar
(Cmd/Ctrl+P) instead of a sidebar-local cmdk dialog that eagerly loaded
the entire session history via loadAllSessions(). CommandDialog gains a
shouldFilter passthrough so server-ranked FTS hits are displayed as-is.
* fix: harden session history search
* fix: evict failed restoration sessions from search
* fix: preserve deletion when search eviction fails
* fix: address session search review feedback
* fix: preserve search suppression during reconciliation
* test: cover sidecar search fallback on hub timeout and rejection
The existing search_sessions tests only exercised the index-hit and
empty-index-fallback paths with an immediately-resolved hub reply.
Add coverage for the two other realistic Hub-connection failure
modes the fallback is meant to tolerate: the hub call rejecting, and
the hub call hanging past the 750ms withSearchDeadline race.
---------
Co-authored-by: abeatrix <beatrix@cline.bot>
Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
* fix(core): refresh Cline models from live catalog (#13670)
* feat(ui): share attachment drop zone (#13672)
* feat(ui): share attachment drop zone
* fix(ui): cancel disabled attachment drops
* chore(ui): simplify drop zone surface
* chore(ui): release v0.2.0-next.8
* Chore/bump undici mermaid (#13675)
* chore(deps): bump mermaid to 11.16.1 and raise undici floor to 7.29.0
* chore(deps): patch js-yaml and body-parser in the npm-managed subprojects
* fix(llms): make Langfuse tracer detection survive minified release builds (#13680)
* fix(llms): recognize direct tracer providers
* fix(llms): make Langfuse tracer detection survive minified release builds
Release binaries are compiled with minify enabled, which renames classes,
so initializeLangfuseTelemetry's constructor-name guard never matched
"ProxyTracerProvider" and silently returned readiness=false in every
production build (hub log: "creating span processor" followed by
"initialized readiness=false" with no branch message in between). Dev runs
execute unminified source, which is why the same env vars worked there.
Replace every constructor-name comparison with checks that survive
minification: detect the proxy structurally via getDelegate, distinguish a
recording provider from the no-op fallback by its lifecycle methods, and
confirm our NodeTracerProvider registration by object identity. When a
foreign provider already owns the global slot, attach the Langfuse span
processor to it when it accepts processors, and otherwise shut down the
orphaned provider and report the rejection instead of bailing silently.
Verified by bundling the module with Bun minify:true against the real
OpenTelemetry packages: the previous code reproduces readiness=false
(provider class name mangles to "H2"), the new code initializes with
readiness=true.
* fix(vscode): prevent hook spawn failures from crashing the core process (#13422)
* fix(vscode): prevent hook spawn failures from crashing the core process
A hook child-process spawn failure emitted "error" on HookProcess with no
listener registered, which Node's EventEmitter turns into an uncaught
exception - killing the entire cline-core process instead of failing the
one hook open. Guard the emit behind listenerCount so the rejection (which
StdioHookRunner handles) is the only propagation path.
The trigger was a workspace root that no longer exists on disk passed as
the spawn cwd: Node reports a nonexistent cwd as a misleading ENOENT on
the launcher binary ("spawn /bin/sh ENOENT"). Validate cwd existence in
HookProcess right before spawning - falling back to no explicit cwd with
a warning that names the missing directory - and when a spawn still fails
ENOENT because the directory vanished in between, name it in the error
message instead of blaming the shell.
* fix(vscode): fail hooks with a missing working directory instead of relocating them
Running a hook whose assigned cwd no longer exists from the host
process's own working directory would let its relative paths read and
write an unrelated location (e.g. the IDE install directory). Reject
before spawning, with an error naming the missing directory; the runner
reports the hook as failed and the task continues. Also carry pre-spawn
failure messages into HookExecutionError details so the cause is not
reduced to a bare "exited with code 1".
* fix(vscode): thread task id into hook runner creation so execution telemetry fires (#13547)
The SDK hooks adapter created every hook runner without a task id, and
StdioHookRunner gates all captureHookExecution calls on one being set —
so the next variant emitted zero hooks.execution events while discovery
telemetry fired normally. Pass the task id (and tool name for the tool
hooks) at all five factory.create call sites, and pin the threading
with a regression test.
* Desktop marketplace redesign: two-pane explorer with full catalog metadata (#13653)
* feat(desktop): add marketplace design exploration prototypes (storefront, explorer, registry)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): render catalog icon tiles without percentage padding
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): drop placeholder icon tiles from explorer marketplace direction
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): make explorer the marketplace view, drop design exploration harness
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): add category tag filters to marketplace explorer
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): collapse marketplace category pills behind a more toggle
* feat(desktop): remove maturity badges and CLI install section from marketplace
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(core): propagate parent aborts to delegated subagents (#13677)
* fix(core): propagate parent aborts to delegated subagents
* docs(core): narrow delegated abort guarantees
* fix(core): release delegated sessions after execution
* fix(core): scope abort listeners to active runs
* fix(core): inherit parent runtime pid for subagents
* fix(desktop): keep Stop available for running child agents (#13678)
* fix(desktop): keep Stop available for running child agents
* fix(desktop): reconcile aborted tool activity
* fix(desktop): guard abort and agent polling races
* fix(desktop): preserve authoritative abort status
* fix(desktop): track queue-verified completion
* test(desktop): trim duplicate abort coverage
* fix(desktop): settle delayed queue verification
* fix: sanitize stored API keys and make provider credential rejections actionable (#13549)
* fix(vscode): sanitize pasted provider API keys at the settings write boundary
Clipboards smuggle control and invisible formatting characters (newlines,
zero-width spaces, BOM) into pasted API keys. The masked key field hides
the corruption and providers reject the key with a 401 indistinguishable
from a genuinely wrong key. Strip those characters and surrounding
whitespace once in the provider config store write path, so both backing
stores (legacy state secrets and providers.json) receive the clean value.
A whitespace-only value now clears the key.
* feat(llms,vscode): classify provider 401/403 as auth errors and surface actionable guidance
Add an "auth" ProviderErrorClass, assigned when the HTTP layer reports
401/403 — status-only on purpose, since provider bodies can quote words
like "unauthorized" without the request being an auth failure. The class
rides the existing errorClass plumbing (finish -> run-failed ->
AgentErrorEvent), so every host receives it with no new wiring.
In the VS Code chat surface, rewrite classified credential rejections
from BYOK providers into actionable text pointing at the API key
configuration, keeping the provider's raw body as a diagnostic tail.
Raw bodies alone are dead ends: Mistral, for example, answers an
identical {"detail":"Invalid API Key"} for a wrong, empty, or
wrong-scope key. Cline-account providers keep the JSON path so the
webview still renders their auth failures as a sign-in card.
* Fix ask-question option text not wrapping (#13718)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(desktop): release v0.0.21
* fix(core): stop an empty capability list from stripping image input (#13583)
`modelHasCapability` documents a missing or empty capability list as
carrying no signal, so each gate declares its own default. Two readers
bypassed it and read `capabilities` directly, where an empty list is not
nullish but `[].includes(x)` is false:
- the session runtime's `modelSupportsImages` metadata used
`capabilities?.includes("images") ?? true`, so the intended fail-open
never fired for an empty list and the file-read tool silently dropped
every image from the request;
- `toProviderModel` projected an empty list onto `false`, telling pickers
a model definitively lacks vision, attachments, and reasoning when
nothing had been declared.
Both now route through the shared helpers, which state their unspecified
default explicitly: `modelSupportsImageInput` fails open for a capability
gate, and `declaredCapability` preserves `undefined` for `ProviderModel`'s
tri-state booleans. A populated list stays authoritative in both.
A thinking config now short-circuits `supportsReasoning` instead of being
OR-ed with the capability read, so its absence no longer collapses the
tri-state to `false`.
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
* fix(llms): translate gateway capabilities in one place (#13584)
* fix(core): stop an empty capability list from stripping image input
`modelHasCapability` documents a missing or empty capability list as
carrying no signal, so each gate declares its own default. Two readers
bypassed it and read `capabilities` directly, where an empty list is not
nullish but `[].includes(x)` is false:
- the session runtime's `modelSupportsImages` metadata used
`capabilities?.includes("images") ?? true`, so the intended fail-open
never fired for an empty list and the file-read tool silently dropped
every image from the request;
- `toProviderModel` projected an empty list onto `false`, telling pickers
a model definitively lacks vision, attachments, and reasoning when
nothing had been declared.
Both now route through the shared helpers, which state their unspecified
default explicitly: `modelSupportsImageInput` fails open for a capability
gate, and `declaredCapability` preserves `undefined` for `ProviderModel`'s
tri-state booleans. A populated list stays authoritative in both.
A thinking config now short-circuits `supportsReasoning` instead of being
OR-ed with the capability read, so its absence no longer collapses the
tri-state to `false`.
* fix(llms): translate gateway capabilities in one place
Three producers built gateway model definitions from catalog `ModelInfo`,
and each carried its own hand-written `switch` over the capability list.
Nothing tied them together, so they drifted:
- builtin providers always emitted a capability list, so a model whose
catalog entry declares no capabilities became `["text"]` where the other
producers emitted `undefined`. `modelSupportsToolCalling` fails open only
for an absent or empty list, so that list read as an authoritative denial
and stripped every tool definition from requests to the affected language
models (dify, sapaicore, opencode, and the Codex CLI);
- the OpenAI-compatible path mapped an `audio` capability that
`ModelCapabilitySchema` does not define, while the other two dropped it;
- the pass-through capabilities (`streaming`, `files`, `temperature`, ...)
were enumerated explicitly in one, folded into `default:` in another,
and ignored in the third.
One exported `toGatewayModelCapabilities` now serves every producer. It is
built on a `Record<ModelCapability, GatewayModelCapability | null>` rather
than a `switch`, so extending `ModelCapabilitySchema` without deciding the
new capability's mapping fails to compile instead of silently falling
through to a default.
The conformance tests walk the capability state space taken from
`ModelCapabilitySchema` itself and assert the real producers agree with the
translator, so a future producer that maps capabilities on its own fails
even when the translator's own unit tests still pass.
---------
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
Co-authored-by: Ara <arafat.da.khan@gmail.com>
* fix(cli): keep markdown streaming prop stable to stop settle flash (#13719)
Flipping the <markdown> streaming prop from true to false when an
assistant text segment settles makes MarkdownRenderable call
updateBlocks(true), which skips every block-reuse path and destroys and
recreates all block renderables. Until tree-sitter re-highlights them
the whole message renders blank/unhighlighted, which users see as the
text flashing at the end of each response. Keep streaming={true} for
the transcript markdown (opencode's TUI does the same); entry.streaming
still drives the spinner glyph.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Clarify model-facing message when user rejects a tool call (#12673)
* Clarify model-facing message when user rejects a tool call
* Include the rejected tool's name in denial reasons
* Move user-rejected tool reason into @cline/shared
* Route new user-rejection approval paths through shared reason builder
Since the original PR, several new approval surfaces landed on main with
their own terse denial strings (CLI connectors, ACP permissions, Cline Hub
webview, desktop webview, example VS Code extension). Route all of them
through buildUserRejectedToolReason so the model sees a consistent,
non-error rejection message.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add buildUserRejectedToolReason to the @cline/shared integration-test stub
The VS Code integration tests run the tsc-built CJS tree and stub the
ESM-only @cline/shared package in test-setup.js; the stub was missing the
new export, so tool-approval-denial.js threw at module load in CI.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Trim scope back to the minimal rejection-copy fix
Restore the connector deniedReason plumbing, ACP permission strings,
desktop webview reason, example extension reason, and hub server fallback
to their main versions. Those surfaces already attribute the denial to a
user and are outside ENG-2329. Keep the Cline Hub webview change since
that path emits its own rejection string the model sees.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Move rejection guidance suffix into agent runtime per review
* Apply review suggestions: neutral fallback reason and -- separator before rejection suffix
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Default web search on for the desktop app (#13725)
* Default web search on for the desktop app
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make desktop web search default seed best-effort
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(desktop): reconcile experimental sync behavior
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: JasmineLCY <38378321+JasmineLCY@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
Co-authored-by: Max <maxpaulus43@gmail.com>
Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bee <abeatrix@users.noreply.github.com>
Co-authored-by: Renee Huang <100229782+reneehuang1@users.noreply.github.com>
Co-authored-by: Ara <arafat.da.khan@gmail.com>
Co-authored-by: Haley Park <haleypark.design@gmail.com>
Co-authored-by: cline-cloud[bot] <276134852+cline-cloud[bot]@users.noreply.github.com>
Co-authored-by: yzxcj797 <54314860+yzxcj797@users.noreply.github.com>
Co-authored-by: yzxcj797 <yzxcj797@users.noreply.github.com>
Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
Co-authored-by: 𝓜𝓲𝓼𝓼𝓪𝓻𝓲 𝓐𝓱𝓲𝓵 🌿 <143264692+missarii@users.noreply.github.com>
Co-authored-by: Dominic Cooney <dominic.cooney@cline.bot>
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
Co-authored-by: Harrison <harrison@cline.bot>
Co-authored-by: abeatrix <beatrix@cline.bot>
Co-authored-by: TheRealSpencer <32678829+TheRealSpencer@users.noreply.github.com>
* Revert "feat(desktop): Composio connectors on desktop-experimental (#13685)"
This reverts commit 7844ae9250.
* feat(desktop): current Composio connectors state for desktop-experimental
Replaces the Aug 30 snapshot (#13685) with the reviewed state of
bee/poc-connectors-composio (PR #13684, Greptile 5/5): reverts the
snapshot commit, then applies the feature diff from that branch's
merge base, resolved against desktop-experimental's own changes
(cloud-agents flag wiring, marketplace layout).
This brings the fix for the beta-blocking bug - connector tools now
register in-process at session bootstrap from composio.json instead of
through a generated drop-in plugin, which packaged apps cannot load
(the plugin sandbox needs a JS runtime and bootstrap file on real disk
that the .app bundle does not ship). It also brings everything else
from review: durable cancellation tombstones with confirmed-revocation
pruning, fail-closed revocation error handling, serialized state
writes, per-slug delta reconciliation, connect/disconnect race
anchoring, zero-tools self-heal and warning, the @cline.bot
internal-feature gate with persisted account context, and the
env-var-only managed key model (no key in build artifacts).
* fix(desktop,core): revoke upstream grants on delete, scope flag cache per identity, paginate reconciliation
Review findings from johnwschoi on #13684 plus the follow-up Greptile
finding on #13739, all verified against the installed SDKs:
- Upstream revocation: @composio/core's connectedAccounts.delete never
sends revoke_on_delete and the API defaults it to false - a soft
delete that removes the Composio record while leaving the provider
OAuth grant (the actual Gmail/Calendar/GitHub token) authorized after
the UI reports a disconnect. All deletions now go through the raw
client (getClient()) with revoke_on_delete=true, covering disconnects
and every cancelled/abandoned-attempt revocation.
- Feature-flag identity scoping: FeatureFlagsService.setContext kept
the previous identity's cached flag values, so after an account
switch whose poll fails, the new account inherited the old one's
flags (including internal-feature access). An identity change now
resets the cache to defaults until a successful poll for the new
identity, and constructor hydration skips a persisted snapshot
written by a different known identity; the unresolved-at-startup
fallback is preserved.
- Pagination: reconciliation listed only the first page of connected
accounts while treating absence as "revoked remotely", so an account
on a later page was disconnected locally. The listing now follows
nextCursor to the end (capped), and absence-based removals are
skipped entirely if the cap is ever hit.
- Disconnect id-guard: a redirect-less reconnect that finalized while a
disconnect awaited the old account's revocation was blindly deleted
from local state, orphaning its still-authorized account for the next
refresh to import as a resurrection. Disconnect now removes only the
exact account it revoked and stamps lastDisconnectedAt only when the
removal took effect - the newer reconnect survives, consistent with
newest-intent-wins everywhere else.
- Schema resilience: one stored tool schema that createTool rejects
(e.g. an unsupported top-level allOf) threw during extension setup
and blocked session initialization; registration is now per-tool
fault-isolated (skip and log).
Regression tests for each: revoke flag asserted on every delete call,
identity-switch and failed-poll flag scoping, cross-identity persistent
cache, two-page reconciliation, disconnect-vs-reconnect race, malformed
schema skip.
* fix(vscode): continue the surviving session on resume instead of rebuilding with the original task text (#13175)
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilt the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
The preserved conversation history is the source of truth on resume, so
the fallback prompt now just asks the model to reassess the history and
continue, matching the legacy resume prompt which also never resent the
original task. User-typed text still takes precedence when provided.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): continue the surviving idle session on follow-ups instead of rebuilding
Stopping a turn keeps the session alive, but every idle follow-up (bare
Resume after Stop, and typed follow-ups after a completed turn) tore
that session down and rebuilt it from persisted task history before
sending. Continue the matching idle session in place instead, the same
way the CLI reuses the live session after an abort. Rebuilding from
history now only happens when no live session matches the displayed
task (task opened from history, extension host reload).
A bare resume still needs a prompt to start a turn, so it sends the
neutral [TASK RESUMPTION] prompt (shared with the rebuild fallback and
hidden from the transcript); user-typed content is echoed and sent
as-is. If the send lands while the abort is still settling, the runtime
auto-queues it and drains once the abort completes.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): consolidate follow-up send paths in SdkFollowupCoordinator
Now that idle follow-ups continue the live session in place, the
two-mode sendToActiveSession helper was redundant: its non-queued branch
duplicated continueIdleSession minus the bare-resume prompt. Split it
into a single-purpose queueToActiveSession and fold the idle no-task
send into continueIdleSession, flattening askResponse's decision tree
to: queue onto a running turn, continue a matching live idle session,
rebuild from history, or abandon.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): reuse the existing neutral resumption prompt for bare resumes
Drop the newly invented long resumption wording in favor of the phrase
that already existed as the no-history fallback and that the transcript
hiding logic and test fixtures recognize: '[TASK RESUMPTION] Please
continue where you left off.' The net change to resumeSessionFromTask
against main is now just deleting the branch that resubmitted
historyItem.task as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilds the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
Bare resumes now always use the neutral prompt that already existed as
the no-history fallback; user-typed text still takes precedence. This
matches the legacy resume prompt (responses.taskResumption), which only
ever included user-supplied text as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): hide synthetic prompts from the queued-prompt echo
A send that races a settling abort is auto-queued by the runtime, so a
bare Resume can reach the pending_prompt_submitted echo carrying the
synthetic [TASK RESUMPTION] prompt. Echoing it leaked model-facing text
as a visible user bubble and shifted the visible-user-message ordinals
that edit/regenerate mapping relies on. Filter synthetic prompts with
isSyntheticUserPrompt, keeping user attachments visible (matching
isSyntheticSdkUserMessage semantics).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): preserve LiteLLM input token limits (#13293)
* fix(vscode): preserve LiteLLM input token limits
* fix(vscode): prefer live LiteLLM model metadata
* fix(vscode): generalize private catalog metadata
* test(vscode): preserve llms exports in vscode lm mock
* fix(vscode): point provider signup URLs at their API key pages (#13337)
* fix(vscode): point Mistral signup URL at the general API keys console
The Mistral provider's signup link led to the Codestral console, which
issues Codestral-scoped keys that fail with 401 on api.mistral.ai — the
endpoint the provider actually calls. Point it at the general API keys
page instead.
Fixes#13288
* fix(vscode): deep-link DeepSeek and Fireworks signup URLs to their API key pages
Both pointed at marketing homepages; link straight to the key-creation
pages instead, matching the rest of the registry and the desktop app's
provider-key-urls map.
* fix(ci): always build the legacy bundle from the legacy-extension branch (#13349)
The combined-VSIX workflow took legacy-ref as a free-form dispatch input
with no publish-time validation (next-ref has one: publish requires main).
Any typed ref — a PR merge ref, an unprotected branch — would be built
into the published VSIX by the environment-less build job, and the publish
environment approver only ever sees an opaque prebuilt artifact, so the
approval protected the marketplace PAT but not the shipped bytes.
Remove the input entirely and hardcode the protected legacy-extension
branch, which makes that branch's protection rules load-bearing for
releases. The tested-sha pinning between test-legacy and build is
unchanged. publish-extension skill dispatch command updated to match.
* fix(ci): lock the legacy publish workflow to the legacy-extension branch (#13350)
The branch dispatch input was a free-form string with no validation. Both
jobs checked it out and ran full npm lifecycle scripts from it: the publish
job next to VSCE_PAT/OVSX_PAT (and npm run publish:marketplace executes a
script from that same ref with the PATs in env), and the test job with NO
environment approval at all while inheriting the workflow-level
contents/packages/checks/pull-requests write grants. A dispatch pointing at
e.g. refs/pull/N/head would run outside-contributor code with the
marketplace keys behind one approval, or with a repo-write token behind
none.
Remove the input and hardcode the protected legacy-extension branch, drop
the workflow-level permissions to contents: read, and elevate only the
publish job to contents: write (tag push + GitHub release). The branch
input's default was legacy-extension, so normal publishes are unchanged.
publish-extension skill dispatch command updated to match.
* fix(vscode): SDK remote-config parity — refresh coordination, session gating, and fail-closed opt-out (#13226)
* feat(desktop): native notifications (#13166)
* feat(desktop): native notifications
* macos target
* fix(desktop): isolate macOS dev app identity
* fix(desktop): address notification review feedback
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched (#13310)
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched
Toggling an auto-approve setting while a task is open writes
autoApprovalSettings into the StateManager's task-settings overlay
(updateAutoApprovalSettings -> setTaskSettings). The SDK controller never
cleared that overlay on clearTask/showTaskWithId (the legacy controller
did), so after New Task the stale overlay kept shadowing global settings
in getGlobalSettingsKey(): toggle RPCs were accepted into global state,
but every posted state still carried the overlay's old version, which the
webview rejects as not newer - the auto-approve checkboxes froze forever.
Restore legacy parity in SdkTaskControlCoordinator: drop the overlay
(persisting pending writes first) in clearTask() and before installing a
different task's proxy in showTaskWithId().
Fixes#13260
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* changeset
* test(vscode): add end-to-end regression test for auto-approve freeze after New Task
Wires the real StateManager, the real updateAutoApprovalSettings handler,
and the real SdkTaskControlCoordinator.clearTask() together with the
webview's version gate modeled on ExtensionStateContext, pinning the
end-to-end invariant behind #13260: checkbox toggles must keep reaching
the webview after a mid-task toggle followed by New Task. Verified the
test fails when the clearTaskSettings() call is removed from clearTask().
* fix implicit any in regression test
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): show provider web-search support under the settings toggle (#13328)
* feat(desktop): show provider web-search support under the settings toggle
The global Web search toggle silently does nothing unless the session's
provider offers native web search, which made the setting read as if it
worked with any provider. The desktop General settings row now explains
that only providers with built-in web search honor it, and shows a live
status line: which connected providers are ready to use it (no extra
setup needed), or an amber warning with a link to the Models section
when none of them support it.
Support is resolved in the webview via a new providerOffersModelTool
helper in @cline/llms (browser export), sharing the same builtin-manifest
source of truth as the runtime's supportsModelTool attachment check.
* fix(desktop): address review — refetch web-search status on catalog invalidation, clarify per-model support
Greptile P2: the one-time catalog fetch could race an in-flight provider
save and show stale status; the row now refetches when the provider
catalog cache is invalidated (fired after saves complete).
Greptile P1: the ready line implied every model on the provider works;
Vertex excludes Claude routes, so the copy now scopes the promise to
models that support it.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat(ui/desktop): collapse finished runs into a work summary and remove hover-state dead space (#13315)
* feat(ui): add WorkActivity collapsed-run summary and float message actions as a pill
WorkActivity/WorkActivityTrigger/WorkActivityContent fold a finished agent
run's working rows (tool calls, thinking traces, narration) behind a single
"Worked for 4m 12s · 14 tool calls" disclosure built on the shared animated
disclosure primitives, with formatWorkActivityLabel/formatWorkDuration
exported for consumers.
Message hover actions no longer rely on the transcript reserving blank space
below each message: the action row is now a self-backed pill (border,
blurred background, shadow) that floats over whatever follows, so
conversations can pack rows tightly without hover chrome colliding with the
next message.
* feat(desktop): collapse finished runs into a work summary and tighten chat spacing
collapseCompletedWork post-processes the grouped transcript: once a run ends
on assistant text with no further tool calls, its working rows fold into one
expandable WorkActivity row while the final answer stays visible. Runs are
delimited by user messages; the trailing run only collapses when the session
has stopped running and actually produced an answer, so live streams and
cancelled/failed tails keep their rows. Assistant messages carrying images
or media are treated as deliverables and never collapse.
The conversation list gap drops from gap-8 to gap-4 now that hover actions
are self-backed pills that need no reserved space, and user messages add
their own top margin so turn boundaries stay visually distinct.
* refactor(ui/desktop): work summary label wording, flat expansion, stable in-run rhythm
Feedback round on #13315:
- Label reads "Worked for 4m 12s and made 14 tool calls" instead of joining
with a dot; without a duration it falls back to "Made N tool calls".
- Expanded work rows render at transcript level — no rail or extra indent —
since tool rows and thinking traces already carry their own nesting when
expanded. The work content keeps the tight working-row rhythm.
- Live working rows (thinking traces + tool calls) now group into a 'run'
render item with the same tight 0.25rem rhythm, so there is no oversized
gap under a "Thought for Ns" row and every row keeps its exact position
when the finished run folds into the work summary. A trailing
answer-in-progress stays outside the group at transcript level, and pure
prose spans keep normal spacing.
- The transient "Thinking..." indicator moves inside the transcript column
and mirrors a trigger row's geometry, so the first real row replaces it in
place with no jump.
* style(ui/desktop): hover-pill metrics, right-pointing work chevron, scroll and spacing fixes
Another feedback round on #13315:
- Hover action pill: +2px internal padding, a trailing inset after the
timestamp (it sat flush against the pill border), and more clearance
between the message content and the pill (2px -> 6px; the hover bridge
grows to match).
- The work summary chevron points right while collapsed and continues
counterclockwise to point up when expanded.
- Conversation bottom padding drops pb-20 -> pb-8: the composer sits below
the scroller, so the padding only needs to clear a pinned action pill.
- Sending a message scrolls back to the bottom even if the reader had
scrolled up (new AutoScrollOnSend on the user-message count, which ignores
optimistic-bubble re-keying; @cline/ui now exports useConversation for
this).
- An assistant answer directly under its run's working rows pulls itself
0.5rem closer than the full transcript gap.
* style(desktop): leave a visible gap between a pinned action pill and the composer
pb-8 exactly matched the pill's ~40px footprint, so the last row's hover
actions sat flush against the composer top; pb-12 restores ~8px of daylight.
* style(desktop): widen the gap between the pinned action pill and the composer to ~24px
pb-12 left only ~8px of daylight under the pill; pb-16 reads comfortable
without reverting to pb-20's dead space.
* fix(desktop): keep the thinking indicator at the working-row offset mid-run
The indicator matched a trigger row's geometry but sat a full transcript gap
(1rem) below the last working row, while the tool/thinking row replacing it
joins the tight run group at 0.25rem — a visible upward jump. When the last
transcript item is working rows (or streamed assistant output), the
indicator now pulls up to the same tight offset; only at the start of a run,
under the user message, does it keep the normal gap.
* style(ui): calm the hover actions surface per team feedback
Borderless rectangle instead of the bordered pill: radius drops to
var(--radius), the side padding goes entirely (the icon buttons carry their
own hit areas), and the vertical padding halves. Blurred background and
shadow stay so it remains legible over following content.
* feat(ui/desktop): full-band hover reveal and iOS-style disclosure easing
The hover actions only appeared while the pointer was inside the message
box itself. The invisible bridge under each message now spans the full
height of the band the floating actions occupy (full row width), so
hovering anywhere in that strip reveals them. Sibling row types
(.cline-chat-tool, .cline-chat-work, and the desktop's run/tool groups)
become position: relative so they paint above the bridge — their own
content keeps its hover and clicks, and the bridge only wins in the band's
genuinely empty space.
All expandable rows (work summary, tool panels, thinking) open and close on
a 240ms symmetric ease-in-out cubic-bezier instead of the 60ms snap, with
chevron rotation on the same curve. Reduced-motion still disables both.
* revert(ui/desktop): drop the full-band hover reveal; quicken disclosure easing to 180ms
The full-band hover bridge (and the position: relative changes that made it
safe) is reverted per feedback — back to the narrow bridge that only spans
the gap under the message. The iOS-style ease-in-out on disclosures stays
but speeds up from 240ms to 180ms.
* fix(ui): recover live tool diffs that mount as a blank pierre skeleton
Live-streamed edit rows could show an empty diff for the whole run, with the
diff only appearing after the collapsed work row was expanded (fresh mount).
Root cause, confirmed by driving a live session and inspecting the element:
React StrictMode double-invokes @pierre/diffs' ref callback; the first
instance's async highlight work aborts on its immediate cleanup, and the
second instance adopts the abandoned half-rendered shadow tree as if it were
complete prerendered output — zero height, no code, no theme stylesheet,
permanently. A rendered diff always carries style[data-theme-css] in its
shadow root, so ToolFileDiff now checks for it shortly after mount and
remounts FileDiff (bounded attempts) when missing; the fresh host element
takes the normal render path and recovers within ~400ms. Verified live: the
diff now renders during the run.
* fix(desktop): keep interrupted runs expanded even with partial trailing text
The trailing-run collapse gated on 'ended with assistant text', which
misread a Stop that landed mid-answer as a finished run and folded the tool
calls the user wants to inspect. The gate is now the terminal status itself:
only completed (or restored-idle) sessions collapse the trailing run;
cancelled/failed/error tails keep their rows regardless of partial text.
(Greptile P1 on #13315 — matches the PR's stated rule.)
* feat(ui): share the markdown pipeline, chat polish, and ThinkingBlock across products (#13323)
* feat(ui): share the markdown pipeline, chat polish CSS, and ThinkingBlock
The desktop app and the cloud dashboard both consume @cline/ui yet rendered
assistant output differently, because Markdown policy and the thinking-trace
row lived app-side. This moves the shareable parts into the package:
- components/markdown (new export): the lazy Shiki code highlighter (GitHub
light/dark, pinned language set) and agentMarkdownControls — the standard
Streamdown configuration. streamdown/shiki/@shikijs/* become optional peer
dependencies, mirroring @pierre/diffs.
- components/markdown.css: the desktop's chat polish moves in — chat-scale
headings, outside list markers, single quiet code blocks with a
hover-revealed copy control, table cards. Kept unlayered so it beats
Streamdown's layered Tailwind utilities without !important.
- ThinkingBlock + formatThoughtLabel in agent-chat: the standard thinking
row (brain icon, Thinking/Thought-for-Ns label, streaming shimmer, rail
presentation, capped scrollable body). The shimmer and the
reasoning-hover-suppression rule move into agent-chat.css; triggers gain
the color transition the desktop applied locally.
Version bumps to 0.2.0-next.5 for the dashboard to pick up.
* refactor(desktop): consume shared markdown and thinking primitives from @cline/ui
The local Shiki highlighter, Streamdown controls, chat markdown polish CSS,
streaming-title shimmer, and reasoning hover-suppression rule are deleted in
favor of the @cline/ui versions (the highlighter test moves to the package's
suite). ReasoningBlock becomes a thin wrapper that hands MemoizedMarkdown to
the shared ThinkingBlock, and formatThoughtLabel re-exports from the package
so grouping code and tests keep their import path.
globals.css now imports @cline/ui/components/markdown.css (unlayered, so the
polish keeps beating Streamdown's layered utilities); the app keeps only what
is genuinely app-specific: link/image policy in markdown.tsx, selectability
rules, accent palettes, and the view-enter transition.
* style(ui/desktop): make thinking-trace prose legible
Thinking body text rendered too faint: plain muted-foreground plus the
desktop's font-thin weight. The shared thinking content now leans 75% of the
way back toward the body text color (still slightly de-emphasized), and the
desktop drops the thin font weight.
* ci(ui-publish): build @cline/shared before ui typecheck (#13354)
@cline/ui's generated-media imports @cline/shared/browser, which resolves to
shared's dist output. The build-shared step sat after typecheck/test/build,
so the first ui-publish dispatch since #13025 failed at Typecheck UI with
TS2307. Move the step to right after install.
* fix: run_commands object form without args routes through the shell instead of failing with ENOENT (#13336)
* fix: run_commands object form without args routes through the shell
The structured { command, args? } form of run_commands was always spawned
directly with shell: false. When a model emitted a full command line in
command with no args (e.g. { command: "echo hello" }), spawn failed with
ENOENT for any command containing a space, breaking command execution for
the whole session.
Direct exec now only applies when a non-empty args list is provided; the
object form without args is routed through getShellInvocation like the
string form. Schema descriptions are tightened so models put arguments in
args instead of embedding them in command.
Fixes#13279
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: trim structured-command schema descriptions
The union schema is only used for lenient validation of input the model
already sent; its descriptions never reach a model prompt. Keep them
short instead of restating executor behavior.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: simplify direct-exec comment in shell executor
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert: keep original structured-command schema description
The description never reaches a model prompt and the executor now handles
both shapes, so the wording change was cosmetic noise.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: gate direct exec on args key presence, not array length
Review feedback: an explicit empty args array is intentionally structured
input and stays direct exec; only an object with no args key is treated
as a full shell command line. Matches the key-presence rule already used
by the VS Code host's formatCommandForTerminal. Also replaces the
empty-args shell test (which was PowerShell-incompatible) with a test
pinning the direct-exec contract.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: normalize Gemini custom base URLs for legacy host-root values (#13329)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* docs: add GLM-5.3 to ClinePass models and reference pricing (#13357)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): stream run command output (#13179)
* feat(desktop): stream run command output
* fix(sdk): clean up detached command logs
* fix(sdk): reap detached logs after hub restarts
* fix(sdk): preserve live detached command logs
* fix(desktop): harden live command progress
* fix(sdk): recover detached logs for local hosts
* fix(desktop): reconcile command output tool rows
* fix(sdk): retain logs for surviving commands
* fix(core): prevent PID reuse from retaining detached logs
* fix(core): preserve detached logs on probe failures
* fix(core): retain detached logs during probe outages
* fix(desktop): resolve leftover merge conflict in messages projection test
Combine both sides of the assertion: main's incremented per-block
createdAt projection and this branch's toolCallId/hookEventName meta.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): make TUI dialog colors follow theme changes live (#13355)
* fix(cli): make TUI dialog colors follow theme changes live
Dialog content previously read the static palette constant, so open
dialogs (including the theme picker itself) kept the default dark-blue
accents while scrolling through theme previews. Add getDialogPalette /
useDialogPalette, which resolve dialog colors from the active theme's
dialog accents and re-render on every theme change, and migrate all
dialog-rendered components to it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(cli): derive dialog panel background from the active theme
Dark themes now lift their own background one OKLAB step for the dialog
surface, so panels keep the theme's hue instead of the library's fixed
#262626. DialogThemeSync pushes the surface into the dialog container
for new dialogs and repaints open panels, so the surface also follows
live theme previews. Light themes keep the neutral dark panel to match
the dark accent fallback and the light-on-dark dialog text.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: skill slash commands load via the skills tool instead of expanding into the user message (#13327)
* fix(desktop): show typed slash command instead of expanded skill markdown
The sidecar expands /skill and /workflow tokens into their instructions
before dispatching, so the runtime's persisted transcript only contains the
expanded text. After a turn (and when reopening a session) the webview
re-hydrates from that history and rendered the whole SKILL.md body as the
user's message; queue events echoing the expanded prompt could also add a
second user bubble, and fresh sessions were titled with the markdown's first
line. The CLI never shows this because its TUI keeps the typed text in its
own transcript and only sends the expanded prompt to the model.
Mirror that separation inside the desktop sidecar's display boundaries:
- history projection (readSessionMessages) inverts user text that starts
with a configured command's instructions back to '/name remainder',
which also repairs sessions recorded before this fix
- queue snapshots and chat_queued_prompt_start events echo the typed
prompt recorded at expansion time, so the webview's optimistic-bubble
re-key matches again
- an untitled session sent an expanded prompt gets titled from the typed
command instead of the instructions' first line
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't overwrite a mid-turn rename with the typed-command title
The untitled check ran before dispatch, so renaming a fresh slash-command
session while its first turn was running got clobbered by the post-turn
typed-command title. Re-check at write time and only replace a missing title
or the one the runtime auto-derived from the expanded prompt.
Also documents the inherent prefix-inversion ambiguity flagged in review:
text hand-typed with a command's exact instructions persists byte-identically
to that command's expansion, so stored history alone cannot distinguish them.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop expanding skill commands; let the skills tool load them
Pasting the skill body into the prompt is why the transcript could ever show
it: the desktop webview re-hydrates from the runtime's persisted history, so
whatever the sidecar splices into the user message renders as if the user
typed it. The runtime already registers the skills tool, whose description
requires the model to invoke it whenever the user references a slash command
— so send the typed /skill text through and let the tool deliver the
instructions as a tool result (previously they arrived twice: pasted and via
the tool). The persisted user message, session title, and queue entries are
then simply the typed command, which deletes the typed-prompt registry, the
queue event/snapshot rewriting, and the title machinery from the previous
approach.
Workflows are not served by the skills tool and keep textual expansion, so
the read-time display inverter stays: it collapses expanded workflow prompts
— and skill prompts persisted before this change — back to the typed
/command in the history projection.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): option to keep skill slash commands typed for the skills tool
resolveRuntimeSlashCommandFromWatcher (and the hub snapshot proxy) accept
expandSkillCommands: hosts whose sessions register the skills tool pass
false so the typed /skill goes through and the model loads the instructions
as a tool result, keeping the persisted transcript as what the user typed.
Workflows always expand — the tool does not serve them. isSkillsToolAvailable
exposes the catalog check hosts use to decide (yolo preset and the skills
tool toggle leave textual expansion as the only delivery path).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): skill slash commands load via the skills tool instead of expanding
The TUI user-command wrap and buildUserInputMessage now keep a typed /skill
as-is when the session's mode/toggles register the skills tool, matching the
desktop app; workflows keep expanding, and yolo (zen) keeps expanding skills
because its preset has no skills tool. This also fixes CLI resume/history
surfaces showing the skill body: the persisted user message is now the typed
command.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): keep configured skill slash commands typed for the skills tool
expandSlashCommands no longer splices a configured skill's instructions into
the model text; the SDK session's skills tool delivers them as a tool result
(previously they arrived twice). Builtin pseudo-skills like /deep-planning
are not served by that tool and keep expanding, as do workflows.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): use the shared skill-expansion option in the sidecar
Replaces the sidecar's workflow-detection dance with core's
expandSkillCommands option and gates on isSkillsToolAvailable, restoring
textual expansion where the tool is missing (yolo mode or the skills tool
toggle) — a gap in the previous desktop-only change.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): drop the display inverter for expanded transcripts
Accepted trade-off to keep the change minimal: sessions recorded before
skills switched to the skills tool, workflow sends (deprecated), and
yolo-mode skill sends persist expanded instructions and now render that text
as-is instead of being collapsed back to the typed /command at projection
time.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Use fixed selection chevron in account dialog to match other dialogs (#13364)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): align system prompt with session mode (#13361)
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(desktop): finalize queued turns on chat_done with canonical history reconcile (#13330)
Turns that settle through the event stream (queued prompts, including the
first prompt of a fresh session) resolve their send() RPC early, so nothing
cleared the streaming shimmer or reconciled live-streamed content against
the persisted transcript at turn end. A turn whose deltas were incomplete
stayed visually streaming forever and only healed when a later non-queued
send rehydrated history.
chat_done (and chat_session_ended / the queue-drain double check) now clears
the active assistant streaming id and schedules a short-delayed
read_session_messages + applyCanonicalHistory, guarded by turn epoch,
session id, and in-flight send submissions so it never clobbers a newer
turn or duplicates the blocking send path's own finalization.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(desktop): release v0.0.14
* fix(clients): filter non-chat models from chat pickers (#13317)
* fix(clients): filter non-chat models from chat pickers
* fix(clients): align chat model eligibility
* fix(desktop): strip user_input envelope when copying a user message (#13369)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bee <abeatrix@users.noreply.github.com>
* docs: show DeepSeek V4 peak and off-peak pricing (#13312)
* docs: update DeepSeek V4 average pricing
* docs: show DeepSeek peak and off-peak pricing
* docs: add GLM-5.3 reference pricing (same as GLM-5.2)
* docs: add GLM-5.3 to ClinePass models table
* fix(llms): display billed gateway cost (#13385)
* fix(shared): run PowerShell commands with fail-fast error semantics (#13358)
* fix(shared): run PowerShell commands with fail-fast error semantics
The run_commands PowerShell wrapper never set $ErrorActionPreference, so
the default 'Continue' applied: a pipeline erroring per item (e.g. a
malformed Where-Object over Get-ChildItem -Recurse) emitted one error
record per enumerated file - tens of thousands of stderr records on
large trees, looking like a hang - and could still resolve as SUCCESS
with exit 0.
Prepend $ErrorActionPreference='Stop'; to the script content executed
by the ScriptBlock so the first error terminates the command with a
non-zero exit and a single error message. Concatenated on the same line
as the user command so error line numbers stay unshifted.
Fixes#13285
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(shared): set the fail-fast preference in the bootstrap scope
Setting $ErrorActionPreference='Stop' by string-prepending it into the
scriptblock source displaced a leading param(...) from its mandatory
first-statement position, so scripts beginning with a param block failed
with CommandNotFoundException. Preference variables are dynamically
scoped, so setting Stop in the -Command bootstrap gives the invoked
scriptblock identical fail-fast semantics while keeping the user script
byte-identical (param works, error positions unshifted) and drops the
doubled-quote escaping.
* docs(shared): document the fail-fast tradeoffs in the PowerShell wrapper
Stop promotes every non-terminating error, not only per-item pipeline
floods: partial-result commands (recursive listings over access-denied
junctions) now stop at their first error, and Windows PowerShell 5.1
turns in-script stderr redirection of succeeding native commands fatal.
State this in the wrapper comment as a deliberate tradeoff, with the
GitHub Actions precedent and the per-command opt-outs.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
* ci: stop over-long changelogs from silently dropping release Slack posts (#12955)
Slack section blocks reject text longer than 3000 characters. The Slack
action logs that rejection as ##[error] but does not fail the step, so an
over-long changelog drops the release announcement while the run stays
green — cline@3.0.50 (3272 chars) published to npm, tagged, and cut a
GitHub release with no Slack post and nothing red to notice.
Every publish workflow pasted the changelog section verbatim into one
section block, so all six were exposed; the SDK, desktop, and extension
sections were only 150-350 chars under the ceiling.
Add a slack_content output alongside content: unchanged when the section
fits, otherwise trimmed on a line boundary with a link to the full
release notes. Only the Slack payload uses it — GitHub release bodies and
the desktop updater manifest still get the whole section.
* ci: tidy workflow cache config and job permissions (#13403)
Publish workflows now always do clean npm installs (no dependency
cache in their test gates), the e2e workflow's cache keys are
exact-match only, and the e2e job drops an id-token permission it
never used.
* Rename desktop app from "Cline Code" to "Cline" (#13401)
* Rename desktop app from Cline Code to Cline
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Format touched Rust test assertions
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): surface provider-executed tool activity as observational events (#13300)
* fix(llms): surface provider-executed tool activity as observational events
Provider-executed tool parts (e.g. every tool the Claude Code CLI runs
inside its own session) were dropped by the model-tool guard added for
web search: only declared model tools were re-emitted, everything else
hit continue with nothing yielded. Those sessions modified the workspace
with no tool activity in runtime events, transcripts, or the UI.
Route all providerExecuted parts onto the observational path instead:
emit execution-tagged tool-call-delta and tool-result events, matched by
tool-call ID for providers that omit the flag on the result half. They
stay out of AgentRuntime's execution/approval loop, and the runtime
already persists them as modelToolActivities and projects them for
display.
The AgentModelEvent tool-result variant widens toolName from
ModelToolName to string to carry the provider's own tool names.
* fix(agents): keep turns that are only provider-executed tool activity
A turn consisting solely of observational tool activity has an empty
assistant content array - the activity lives in message metadata, since
projecting it into content would replay tool_use blocks the model never
gets results for. The empty-content guard threw on such turns, erroring
the run and losing the activity from the transcript. Count model-tool
activity as content for the emptiness check (error finishes still
throw); replay stays safe through the codec's empty-content placeholder.
Also drop the trailing text delta from one gateway test so the tool-only
stream shape stays covered end to end.
* feat: allow agents to create scheduled tasks (#13331)
* feat(core, desktop): add durable todo agenda
* fix(desktop): secure todo approvals and track tool usage
* fix(desktop): clean up failed approval delivery
* fix(desktop): authenticate approval connections
* fix(desktop): cancel approvals on broadcast failure
* fix(desktop): authenticate development approvals
* fix(desktop): harden development approvals
* test(core): make task paths cross-platform
* fix(desktop): serialize approval readiness
* refactor(core): unify todo and schedule tools
* feat(core): distinguish user todos from agent suggestions
* fix(core): hide tasks tool in yolo mode
* fix(core): enforce schedule workspace scope
* fix(core): bind schedule scope to hub connection
* fix(core): establish task scope at hub startup
* fix(core): scope task automation by workspace
* test(core): normalize workspace path expectations
* test(core): serialize Windows CI workers
* fix(core): reject unregistered schedule authority
* fix(desktop): guard task execution commands
* fix(core): avoid polynomial regex in mention parsing
* fix(core): address schedule tool review feedback
* fix(core): bind websocket clients to hub workspace
* fix(core): flatten tasks tool input schema
* fix(core): authorize multi-workspace hub clients
* test(core): type hub transport authority mock
* fix(cli): register a workspace client for remote schedule commands (#13398)
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(desktop): treat ClinePass as OAuth-managed in the chat credential gate (#13404)
* fix(desktop): treat ClinePass as OAuth-managed in chat credential gate
ClinePass shares the Cline account OAuth credentials (its auth handler
stores under the "cline" provider), so the webview never sees a plain
API key for it. The chat pre-flight check only exempted cline/oca/
openai-codex, so switching to ClinePass while signed in via OAuth
blocked with "Missing API key" even though the sidecar resolves the
stored access token fine (which is why the CLI worked).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style: format helpers.test.ts with biome
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(ui): stack code block lines when streamdown lineNumbers is off (#13412)
streamdown renders each Shiki token line as a bare inline span with no
newline text between non-empty lines, and only applies its block line
class when lineNumbers is on. With lineNumbers off (the desktop app's
config) every multi-line fenced block collapsed into one run-on line.
Make the direct line spans under code-block-body display: block in the
shared markdown.css; empty lines keep their height via their lone "\n"
child under white-space: pre.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): work summary undercounts wall time when pre-tool thinking attaches to the answer (#13413)
* fix(desktop): anchor work summary duration on the answer row, not attached pre-tool reasoning
The collapsed 'Worked for Xs' row undercounted wall time whenever a turn's
assistant message contained thinking + tool_use with no narration text: the
canonical projection emitted the reasoning-only row after the tool row (both
stamped before the tool executed), the webview attached that row to the final
answer, and collapseCompletedWork used the answer's earliest attached
reasoning timestamp as the end anchor - excluding the entire tool execution
(e.g. 'Worked for 5s' for a turn with an 8s command).
- webview: end the work span at the answer row's own timestamp, clamped to
the last collapsed row so a fallback answer bubble with a synthetic early
timestamp cannot shrink the duration either
- sidecar: flush pending thinking before a tool_use row so rehydrated
transcripts keep the live-stream order (thinking before its tool call) and
pre-tool reasoning no longer rides on the next answer
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep interleaved thinking between the tool calls it separates
Address Greptile review: when one assistant message interleaves thinking
between multiple tool_use blocks, each reasoning segment now projects at its
own position (attached to a text row from its own segment when present,
otherwise as its own row) instead of merging into the first reasoning row,
which displayed later thinking before a tool call it actually followed.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): remove settings gear hover state while Account screen is open (#13408)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't show "No sessions found" while session history is still loading (#13414)
* fix(desktop): don't show 'No sessions found' while session history is still loading
Replace the isLoadingHistory flag with hasLoadedHistory, set only once the
backend has actually answered a list_discovered_sessions request. The sidebar
and Sessions view now keep their loading state until that first definitive
response, so the empty-state copy can no longer appear while history is still
being fetched (or while a failed fetch is being retried).
Also retry a failed initial fetch on the 2s event cadence instead of stranding
the UI until the 12s periodic poll, which is what stretched the misleading
empty state to ~10 seconds after a webview reload when the websocket lost the
race with the page load.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop history fast-retry from re-arming after hook unmount
A failed initial fetch that settles after the hook unmounted could schedule a
new retry timer after cleanup had already cleared the refs, leaving the
abandoned hook polling the backend every 2s. Guard scheduleRefresh with a
disposed ref set by the mount effect's cleanup.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix @ file mentions breaking on paths with spaces (#13391)
* Fix @ file mentions breaking on paths with spaces
Quote mentions generated by getFileMentionFromPath (Add to Cline /
Fix / Explain / Improve commands) when the relative path contains
spaces, so the mention regex no longer truncates the path at the
first space. Also quote the path part of workspace-prefixed mentions
(workspace:/path with spaces) inserted from the @ context menu, which
previously bypassed quoting because the value does not start with '/'.
Fixes#13338
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix import ordering in mentions test (biome organize imports)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reduce fix to minimal scope
Revert the webview quoting refactor and extra tests; keep only the
getFileMentionFromPath quoting fix with a single regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Normalize mention paths to posix separators for Windows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix code actions failing with "command not found" on VS Code 1.134 (#13402)
* Fix @ file mentions breaking on paths with spaces
Quote mentions generated by getFileMentionFromPath (Add to Cline /
Fix / Explain / Improve commands) when the relative path contains
spaces, so the mention regex no longer truncates the path at the
first space. Also quote the path part of workspace-prefixed mentions
(workspace:/path with spaces) inserted from the @ context menu, which
previously bypassed quoting because the value does not start with '/'.
Fixes#13338
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix import ordering in mentions test (biome organize imports)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reduce fix to minimal scope
Revert the webview quoting refactor and extra tests; keep only the
getFileMentionFromPath quoting fix with a single regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Normalize mention paths to posix separators for Windows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix code actions failing with 'command not found' on VS Code 1.134
Code action commands carried arguments (expandedRange, diagnostics),
which routes them through VS Code's CommandsConverter cache. VS Code
1.134 disposes the cached entries before the clicked action executes,
so every lightbulb action failed with 'Actual command not found,
wanted to execute cline.addToChat'.
Drop the arguments so the command id is passed through directly, and
recover the context in the handler instead: getContextForCommand now
expands an empty selection by 3 surrounding lines (matching the old
provider behavior) and gathers document diagnostics intersecting the
range when none are passed explicitly.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Scope gathered diagnostics to the selection/cursor
Match the old CodeActionContext.diagnostics behavior: only include
diagnostics intersecting the range the action was requested for, not
the surrounding lines the text gets expanded to.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Desktop: unify Plugins, MCP, and Skills into one Plugins hub with a dedicated Marketplace page (#13411)
* Unify desktop plugins, apps, MCP, and skills into one Plugins hub with a Browse directory mode
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Open the marketplace directory as a modal over the Plugins hub instead of swapping the page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename directory to Marketplace: Browse Marketplace button, Marketplace modal title with icon, search placeholder
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix search input focus ring clipped by the Marketplace modal scroll container
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Address Greptile review: keep selected tag chip visible when its count drops to zero, and remount installed tab when a marketplace install completes after the modal closed
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Track marketplace modal mutation flag in a ref so a close click racing a queued render cannot skip the inventory remount
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make Marketplace its own settings page under Customizations and restore Channels as a standalone page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Remove icon from Marketplace page header for consistency with other settings pages
* Notify mounted inventory views when the marketplace invalidates the cache so late install completions refresh the Plugins hub
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop/ui): recommended and free model tiers in the composer model selector (#13410)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK (#13415)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): recommended-feed badges and descriptions in provider settings (#13416)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* feat(desktop): recommended-feed badges and descriptions in provider settings
Review suggestion on #13410: the provider settings page has room for
more model detail than the composer's picker. The cline/cline-pass
provider cards now refresh their model list through
list_provider_models (the catalog snapshot deliberately skips the
recommended-feed overlay so the startup catalog fetch never blocks on
the feed) and render Recommended/Free tier badges plus feed tags (NEW)
next to the model name, with the model description underneath. The
refreshed list also surfaces the live entries instead of the bundled
snapshot.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): scope the settings featured model list to its provider and revision
The fetched featured list was unscoped component state: switching
between cline and cline-pass reused the component instance, so the
previous provider's models stayed visible while the new request was
pending (or forever, when it failed), and the retained copy shadowed
later provider.modelList updates — adding a second custom model
submitted the stale list as the complete configuration and dropped the
first addition.
The fetched list now only applies to the provider and modelList
revision it was fetched for (falling back to the catalog snapshot
otherwise and refetching on membership changes), and add-model submits
the union of the displayed and configured ids so an update can never
silently unconfigure existing entries.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(ui): update packed-Tailwind smoke contract for the picker's max-h-64 (#13421)
The ui-publish smoke check pins a set of Tailwind candidates the packed
sources must emit; #13410 grew the SearchCombobox options list from
max-h-56 to max-h-64, so the publish run failed on the stale candidate.
All other pinned candidates verified against the current sources.
* feat(desktop): refresh app icons and branding (#13400)
* ci(vscode): upload E2E failure recordings from the right path (#13427)
The job sets working-directory: apps/vscode, but that default applies to run
steps only, not to `uses:` steps. Since #10961 moved the extension under apps/
and added that default, the artifact path has resolved against the repo root,
matched nothing, and every failing run logged "No files were found with the
provided path: test-results/playwright/" instead of uploading recordings.
Widen to test-results/ so Playwright's error-context snapshots ship alongside
the videos.
* fix(hooks): deliver tool hook contextModification to the model (#13297)
* fix(hooks): deliver tool hook contextModification to the model
On the next engine, a tool_call (PreToolUse) hook's contextModification
was parsed into HookControl.context and then silently dropped: the
runtime beforeTool/afterTool result contract had no channel for
injecting conversation context. Legacy consumed it (ToolExecutor /
ToolHookUtils pushed <hook_context> blocks into the next user turn), so
this was a regression of documented behavior.
- Add appendContext to AgentBeforeToolResult/AgentAfterToolResult.
- AgentRuntime collects appendContext across hooks during an
iteration's tool executions and appends one <hook_context> user
message after the tool results, keeping tool-result parts contiguous.
- Map HookControl.context into appendContext in both subprocess hook
layers (skipped when the hook cancels, matching legacy, where the
message doubled as the error).
- Truncate injected context at 50KB per hook output, matching legacy.
- Concatenate appendContext across merged hook layers.
tool_result (PostToolUse) hooks still run detached with stdout ignored;
making them blocking so their context can be collected is a follow-up.
Ref: https://linear.app/cline-bot/issue/CLINE-2987
* fix(hooks): stamp tool identity on injected hook context blocks
Contexts are batched into one message after the tool results, and
parallel tool execution collects them in completion order, so position
alone cannot attribute a block to its tool call. Add tool_name and
tool_call_id attributes to each <hook_context> block.
* fix(hooks): sanitize hook context block markup
Attribute values (tool_name, tool_call_id) are stripped of quote/angle
characters and embedded </hook_context> closers in hook output are
neutralized, so neither provider-supplied ids nor hook text can corrupt
or spoof a block's stamped identity.
* fix(hooks): neutralize forged opening hook_context tags in hook output
The previous sanitization only neutralized closing tags, so hook output
could still open a forged <hook_context> block claiming another tool's
identity. Escape both opening and closing embedded tags with one rule.
* fix(hooks): hide injected hook context from user-facing transcripts
Stamp the injected hook-context user message with displayRole 'system'
(the compaction-summary convention) so it reaches the model but does
not render as a user bubble in live or replayed transcripts. Without
this, resuming a session showed the raw <hook_context> block as if the
user had typed it.
* fix(hooks): neutralize case-variant embedded hook_context tags
The tag-neutralization regex was case-sensitive, so hook output could
still smuggle a forged tag as <HOOK_CONTEXT>. Match case-insensitively.
* fix(vscode): map PreToolUse contextModification into runtime appendContext
The extension's hooks adapter bridged file hooks into the SDK runtime
but forwarded only cancel/errorMessage, so a PreToolUse hook's
contextModification never reached the model. Map it into the runtime's
appendContext channel; HookFactory already truncates it at 50KB.
* fix(vscode): hide hook-injected context from replayed transcripts
Live sessions never rendered the injected <hook_context> user message,
but session reload replayed it as a user bubble (and post-resume turns
kept doing so). Treat these messages as synthetic in the user-message
mapping: honor the displayRole 'system' stamp the runtime sets, with a
text-prefix guard for paths where metadata is unavailable. This also
keeps edit/regenerate ordinal mapping aligned with visible bubbles.
* fix(hooks): run file hooks through exactly one layer per host
The VS Code extension registered two independent hook execution layers:
its own hooks adapter (config.hooks) and the SDK core's file-hook
extension from the runtime bootstrap. When both discover the same hook
files, every hook executes twice per event — and with context injection
wired, each contextModification would be injected twice.
Add a 'hooks' runtime config extension kind (in the default set, so the
CLI keeps core file hooks unchanged) and gate the bootstrap's file-hook
extension on it. The extension excludes 'hooks' at session start, so
its adapter — which also provides the hook status UI and the
hooksEnabled setting — is its single execution path.
* fix(vscode): discover hooks from the session workspace, not only global state
Hook discovery read workspaceRoots from global state shared across
every Cline instance, so another window repointing it made workspace
hooks silently stop being discovered. With the extension's adapter now
the single hook execution layer, that meant no hooks at all.
HookFactory takes an optional sessionWorkspaceRoot and unions that
root's .clinerules/hooks into discovery (and into cwd resolution), fed
from the session config's cwd. Shared-state discovery still works, so
behavior in the single-window case is unchanged.
* fix(hooks): keep sanitized hook attribute values distinguishable
Replacing every markup delimiter with the same underscore could
collapse two tool call ids that differ only by such a character into
identical stamps. Escape each delimiter with a distinct token instead.
* fix(hooks): make hook attribute sanitization injective
Escaping the underscore itself turns the attribute escaping into a
uniquely decodable code, so no two distinct tool call ids can collapse
to the same sanitized stamp (previously an id containing a literal
escape token could collide with an id containing the delimiter).
* fix(vscode): reconstruct hook status rows when replaying transcripts
hook_status messages are emitted live but never persisted, so reloading
a session dropped every hook row. The injected <hook_context> blocks
carry the hook source and tool name, so the replay translator now
rebuilds a completed hook status row from each block. The injection is
also no longer treated as a user turn boundary, so the final turn's
completion retag is unaffected by it.
* fix(hooks): collect PostToolUse hook output and honor its control (#13298)
* fix(hooks): collect PostToolUse hook output and honor its control
tool_result (PostToolUse) hooks ran fire-and-forget with stdout
ignored, so their entire JSON output — contextModification and cancel —
was discarded. Legacy awaited PostToolUse, injected its
contextModification into the conversation, and honored cancel.
- Run tool_result hook commands blocking (same 120s default timeout as
tool_call) in both the hook-config-file layer and the agent-hook
subprocess layer.
- Map their output: cancel stops the run with the hook's error message
as the reason; otherwise context is injected via afterTool
appendContext.
This restores legacy blocking semantics: tool results now wait for
tool_result hooks, but only in sessions that have one configured.
Ref: https://linear.app/cline-bot/issue/CLINE-2987
* fix(hooks): bound tool_result hook wait and isolate cancel reason
Address review findings:
- The agent-hook subprocess layer forwarded an unset timeoutMs
unchanged, so a tool hook command that never exits would block the
agent indefinitely. Default both tool_call and tool_result to the
120s bound the hook-config-file layer already used.
- A cancelling hook's error message was folded into the same context
field as other hooks' injectable context, so merging controls could
leak unrelated hook context into the cancellation reason. Carry it as
a separate cancelReason, and surface it as the stop reason for
beforeTool cancels too.
* fix(hooks): prefer errorMessage as a cancelling hook's stop reason
When a cancelling hook returns both contextModification and
errorMessage, the context-first parse precedence made the injectable
context the cancel reason and discarded the actual error. Parse the two
fields separately: errorMessage wins as the cancel reason (matching
legacy), and a lone errorMessage still folds into injectable context
for non-cancelling hooks as before.
* fix(vscode): honor PostToolUse hook cancel and contextModification
The adapter awaited PostToolUse hooks but discarded their output
entirely. Map cancel to a stop control (with errorMessage as the
reason) and contextModification into the runtime appendContext channel,
matching the PreToolUse mapping and legacy semantics.
* fix(hooks): whitespace-only errorMessage no longer suppresses the cancel reason
A cancelling hook returning meaningful context alongside a blank
errorMessage lost both: the parsers selected the whitespace as the
reason and the result mappers trimmed it away. Require a non-blank
errorMessage before it wins, so context serves as the fallback reason.
Apply the same fallback in the extension adapter's stop mapping.
* fix(core): stop Windows CI worker crashes from the agenda spec watcher (#13428)
* fix(core): watch agenda task specs via the resolved long path
fs.watch on a path with 8.3 short components (e.g. C:\Users\RUNNER~1
temp dirs) trips a libuv assertion in fs-event.c on Windows and aborts
the whole process. Since the agenda task manager landed, every hub
server test spins up its spec watcher on such a path on hosted Windows
runners, killing the vitest worker and failing the sdk-test Windows job
on every branch. Resolve the specs dir with realpathSync.native before
watching so libuv only ever sees the long form.
* test(ui): stub ResizeObserver for @pierre/diffs in tool-diff tests
jsdom does not implement ResizeObserver, so every ToolFileDiff render
logged a ReferenceError from @pierre/diffs to stderr. Tests still
passed; this just silences the noise the same way the constructable
stylesheet shim does.
* fix(core): skip the agenda spec watcher when the dir does not resolve
Falling back to the raw path on realpath failure would reintroduce the
Windows short-path abort; log and go without the watcher instead.
* fix(vscode): honor the classic truncation range when migrating legacy tasks (#13419)
Classic Cline truncated long conversations by omitting an index range of
api_conversation_history from every API request (keep the first
user-assistant pair, drop everything through the range end, strip
orphaned tool_results from the first kept message). The range was
persisted on the history item while the full history stayed on disk.
legacyApiHistoryToSdkMessages ignored conversationHistoryDeletedRange
and converted the entire file, so resuming a migrated long task handed
the SDK an untruncated working context that could exceed the model's
context window by millions of tokens - every request failed with
'prompt is too long' and every compaction restarted from the full
history (#12996, confirmed by the reporter: the task was migrated from
an older version and broke after a restart, with each compaction
starting from ~3M tokens).
The migration now replays exactly what the classic extension sent:
slice out the deleted range and drop orphaned tool_results, mirroring
ContextManager.getTruncatedMessages (see origin/main). Malformed ranges
fall back to the full history (previous behavior).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): show the diff edit view for multi-line edits in CRLF files (#13417)
The edit preview computed proposed content with an exact old_text match, but
the SDK executor normalizes old/new text to the file's own line endings before
matching (#12305) - reads strip CR, so models emit LF-only text even for CRLF
files. Any multi-line old_text in a CRLF file therefore failed the preview's
match: the diff edit view silently never opened while the executor applied the
edit. Single-line edits (no line break in old_text) were unaffected, which is
why the diff view appeared to trigger inconsistently.
Mirror the executor's EOL normalization (and its literal $-sequence insertion)
in the preview computation.
Fixes#13296
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): report truthful session status so desktop checkpoint restore stops wedging (#13418)
* fix(core): keep hub session status truthful across queue-drained turns
Queue-drained turns settle only through the event stream, but the hub
runtime host mistranslated their lifecycle in two ways:
- session.updated events carrying only a snapshot (persistence updates)
defaulted the projected status to "running". When one trailed the
final idle update after a turn, clients that track busy state from
status events (the desktop sidecar's workspace restore gate) stayed
busy forever. Use the snapshot's real status and emit nothing when
neither source reports one.
- the per-run agent.done dedup was only reset by run.started, which the
daemon-side queue drain never publishes, so a drained turn's done was
swallowed as a duplicate of the previous turn's. Reset the dedup on
session.pending_prompt_submitted, and suppress stale run.completed
events that land inside a drained turn's window so they can neither
emit a phantom done nor consume the drained turn's dedup slot.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(desktop): cover restore unlock after an event-settled queued turn
Exports the sidecar's core-session event handler so the queued-turn
lifecycle (busy via status events, cleared by the done agent event,
restore allowed afterwards) is testable end-to-end.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): start interactive sessions without a prompt as idle
The runtime host reported every new session as "running" until its
first turn ended. Interactive hosts (the desktop app) start sessions
with no prompt and dispatch turns through separate send calls, so a
created-but-never-prompted session stayed "running" forever — wedging
clients that gate workspace operations (checkpoint restore, message
edit) on active turns.
Interactive no-prompt starts now begin idle, start emits the session's
actual status (resumed sessions no longer masquerade as running), and
markTurn* transitions keep tracking in-memory status for lazily
persisted sessions so the first turn still reports running -> idle.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style: format hub-runtime-host test filter
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop the drained-turn done bookkeeping, keep the minimal fix
The stuck restore is fully explained by the two status defects (fabricated
"running" from snapshot-only session.updated events, and never-prompted
interactive sessions reporting "running"). The done-dedup machinery for
queue-drained turns addressed a separate cosmetic gap (queued turns emit no
chat_done, pre-existing) and required fragile run-window heuristics, so it
is removed to keep this change reviewable. Sidecar test now settles the
queued turn through the status event, matching the shipped mechanism.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* docs(sdk): document the truthful session-status contract
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(deps): update Langfuse packages and bump app versions (#13443)
* chore(deps): update Langfuse packages and bump app versions
Update @langfuse/otel to v5.10.1 and add @langfuse/vercel-ai-sdk v5.9.1 for improved observability with Vercel AI SDK.
Bump versions for @cline/code to 0.0.14 and @cline/ui to 0.2.0-next.6, updated via bun.lock.
Other Changes:
Added optional userId to AgentRuntimeConfig.
Propagated userId, sessionId, conversationId, runId, iteration, provider, and model context into AI SDK telemetry.
Added AI SDK 7 runtimeContext with explicit includeRuntimeContext.
Added stable OTEL_SERVICE_NAME=cline-sdk.
Added runtime metadata assertions in agent tests.
* add taskId
* Revert "add taskId"
This reverts commit f20d31d96d.
* docs: simplify Open Cline step in installing guide (#13405)
* docs: remove duplicate GLM-5.3 rows in ClinePass tables (#13449)
Co-authored-by: cline-cloud[bot] <276134852+cline-cloud[bot]@users.noreply.github.com>
* chore(sdk): release v0.0.76
* chore(cli): release v3.0.56
* docs(cli): scope the v3.0.56 release notes to CLI-visible changes
* feat(desktop): interactive welcome hero graphic (#13399)
* feat(desktop): add interactive welcome hero
* feat(desktop): support composable welcome hero variants
* feat(desktop): reskin first-run onboarding (#13441)
* refactor: centralize client tool availability (#13451)
* chore(sdk): release v0.0.77
* docs(cli): drop the tasks tool from the v3.0.56 notes, it is desktop-only
* chore(vscode): prepare 4.1.11 release
* chore(desktop): release v0.0.15
* fix(vscode): remote config MCP settings (#13466)
* fix(vscode): enforce enterprise MCP controls on the Customize marketplace
The unified Customize marketplace replaced the old MCP marketplace
without carrying over enterprise remote-config enforcement: the catalog
RPC returned every MCP entry and installs were never policy-checked,
so orgs with mcpMarketplaceEnabled=false or an allowedMCPServers
allowlist saw (and could install) all marketplace MCP servers.
- Filter MCP entries out of getMarketplaceCatalog when the marketplace
is disabled, and restrict entries to the allowlist when configured
(matching entry id, display name, installed server name, or source
repo URL, mirroring legacy GitHub-URL allowlist ids)
- Reject installMarketplaceEntry requests that violate the policy
- Map the published catalog's repo/homepage fields onto
sourceUrl/homepageUrl so URL-based allowlists can match
- Update the enterprise MCP server controls docs
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: simplify MCP marketplace policy enforcement
Fold the policy check into marketplace-helpers, drop the dedicated
test suite, and trim the docs edit to the strictly necessary line.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Treat an empty preserved capability list as unspecified when seeding tools (#13465)
* Treat an empty preserved capability list as unspecified when seeding tools
toSdkModelInfo guarded the tools seeding with a strict
preservedCapabilities === undefined check, but modelHasCapability —
the runtime's own reader — treats undefined AND length === 0 as
"unspecified". A custom OpenAI-Compatible model whose stored
capabilities field is a defined-but-empty array (a config carried over
from before the field existed, or one round-tripped through a boundary
that defaults it to []) skipped the seeding; the first boolean
projection to run afterwards (e.g. supportsReasoning) then populated
the array, the runtime gate read the non-empty, tool-less list as
authoritative, and every tool definition was silently dropped from the
session (#13463).
The guard now covers the empty array too, matching the reader's
unspecified semantics.
* test: satisfy the store's isModelInfo gate so the empty-capabilities case actually reaches knownModels
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: yzxcj797 <yzxcj797@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): prepare 4.1.12 release
* Add feature flags to the desktop app (#13289)
* Add feature flags to the app
* React to account updates
* Address comments
* use a per-app file
* fix: propagate Langfuse session telemetry (#13473)
* fix telemetry session propagation
* feat telemetry client version metadata
* fix(core): address Langfuse review feedback — hub client identity + delegated agent session grouping (#13475)
* fix(core): rebuild hub session client identity from request headers
Hub-backed sessions do not transport extensionContext (it is local-only),
so the daemon's runtime built traces without the clientName/clientVersion
metadata even though the hub client bakes X-CLIENT-TYPE / X-CLIENT-VERSION
into the session's provider headers. Reconstruct extensionContext.client
from those headers during local runtime bootstrap so hub-backed Langfuse
traces carry the same client identity as local runtimes, and the daemon's
header re-resolution stops clobbering the original X-CLIENT-TYPE.
* fix(core): propagate parent distinctId/sessionId to delegated agents
Delegated agents (spawned sub-agents, configured agents, teammates) were
built without distinctId and sessionId, so their Langfuse traces had no
userId or sessionId and did not group with the parent user or session.
Thread the host-resolved distinctId through RuntimeBuilderInput and the
root sessionId through the delegated-agent config provider, and copy both
onto the delegated AgentConfig.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* ci(vscode): make combined nightly manual-dispatch only
The PublishNightly environment gained required reviewers, so each cron
run parked on approval, held the workflow's concurrency group, and
silently cancelled every scheduled run queued behind it. 20 consecutive
scheduled nightlies died this way between 2026-07-31 and 2026-08-21;
the only nightlies that shipped in that window were manual dispatches.
Drop the cron rather than leave a trigger that cannot succeed unattended.
* feat(hub): add drain and upgrade commands with replay support (#13468)
* feat(hub): add drain and upgrade commands with replay support
* handles disconnection
* feat(hub): wire bot profiles, drain, and durable event/run-queue into the live transport
Completes the wiring the previous commits' primitives needed:
HubServerTransport gains isDraining(), hub.drain/hub.status/profile.get
command handling, and replayEventsAfter() (backed by the durable event
log), plus the sequence/sinceSequence wire types they depend on in
shared/hub.ts. run-queue-handlers.ts reads the active bot profile's
plugin roots when executing durable runs.
Also adds hub/profiles/: profile.json (identity/rules/plugins) ->
system prompt composition, --profile / CLINE_HUB_BOT_PROFILE
resolution, and the bundled cline-dad profile with its
cline_hub_support read-only diagnostics tool.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Revert "feat(hub): wire bot profiles, drain, and durable event/run-queue into the live transport"
This reverts commit 6696d5d202.
* fix(hub): dedupe replayed events by eventId, not just sequence
HubEventLogStore.append() returns a new envelope stamped with a
sequence rather than mutating the input, so a pending approval
re-issued sequence-less by subscribe() (it predates any durable-log
append) and its later sequence-stamped copy from the durable log are
two different objects carrying the same eventId. The replay-then-live
buffer in browser-websocket.ts only deduped by sequence, so the
sequence-less copy's guard never tripped and it was delivered a second
time when the buffer flushed after replay.
Track delivered eventIds alongside the sequence cursor; eventId
survives the append/stamp round-trip unchanged, so this dedupes the
exact-same logical event regardless of which copy arrives first.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): wire drain, durable event log, and run queue into the live transport
CI on this branch failed bun run build:sdk: browser-websocket.ts,
client/index.ts, and hub-websocket-server.ts (already on this branch)
reference sequence/sinceSequence, HubServerTransport.isDraining(), and
the "hub.drain" command — but the commit that reverted bot profiles
out of this branch also reverted this wiring, since it shared a commit
with the profiles work. That wiring is a hub concern, not a
bot-profiles one; split it back out.
- shared/hub.ts: sequence/sinceSequence types, run.enqueue/run.list/
hub.drain/hub.status/stream.replay capability, command, and event
names. profile.get intentionally excluded — stays bot-profiles-only.
- context.ts: isDraining() on HubTransportContext. botProfile field
intentionally excluded.
- hub-server-transport.ts: eventLog/runQueue fields and start/stop
lifecycle, publish() appends to the durable log, handleCommand cases
for run.enqueue/run.list/hub.drain/hub.status, drain-refusal check,
replayEventsAfter()/lastEventSequence(). startBotProfile()/
startHubSupportTool() and the profile.get case intentionally
excluded.
- run-queue-handlers.ts: added without handleProfileGet (needs
ctx.botProfile, which doesn't exist here).
- hub-upgrades.test.ts: added without its two bot-profile-injection
tests (they need a resolved bot profile to assert against).
Verified bun run build:sdk exits 0 (the exact CI command) and
bunx vitest run src/hub passes (311/312; the one failure is the
same pre-existing environment-timing flake already present before
this change).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): export instance-lock, event-log, and run-queue from the hub barrel
These landed as internal modules only; hub-server-transport.ts and
hub-websocket-server.ts import them by direct path, but nothing
re-exported them from the public @cline/core/hub surface the way
sibling discovery/server modules already are.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): wire the instance lock into the daemon entry point
The singleton lock (discovery/instance-lock.ts) and its consumption in
startHubWebSocketServer/ensureHubWebSocketServer were already on this
branch, but the daemon entry point's own half was not: retrying a bind
when a retiring predecessor still holds the lock, and exiting with a
distinct code (3) instead of the generic fatal path when a live Hub
already owns the data directory. Without this, a daemon racing a
retiring predecessor could fail outright instead of waiting the lock
out, and losing the singleton race looked identical to a crash.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): address drain/upgrade review findings (#13478)
- cline hub upgrade: check idleness at least once (--wait 0 works), reject
non-numeric --wait, and un-drain on every abort path so an aborted
upgrade can never leave the hub refusing new work
- add cline hub drain --off and the off query param to requestHubDrain so
POST /drain?off is reachable from shipped code
- HubEventLogStore/HubRunQueue: WAL journal mode + busy_timeout, and stamp
sequences from lastInsertRowid instead of SELECT MAX(sequence)
- HubInstanceLock.acquire: degrade to an unheld lock when SQLite is
unavailable instead of refusing hub startup; only BUSY/LOCKED still
raises HubLockHeldError
- ensureHubWebSocketServer: retire an unusable discovered hub through the
shared retireDiscoveredHub (busy hubs are attached to, drain precedes
shutdown, discovery cleared only when the hub actually retired)
- replay adapter: advance the cursor past eventId-deduped events, cap
replay pages, stop when the cursor stalls, and drop the dedupe set after
the buffered flush so it cannot grow for the socket lifetime
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(hub): derive the singleton e2e challenger cwd portably
The challenger's working directory was derived by round-tripping the
discovery path through a file: URL and stripping the last pathname
segment. On Windows that yields a POSIX-style '/C:/...' path, which is
not a valid spawn cwd, so the spawn fails ENOENT before the singleton
lock is ever contested and the Windows SDK test job goes red.
The data dir is simply the discovery file's parent: use dirname().
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): stop stored capability lists from silently revoking tool calling for custom models (#13476)
* fix(core): seed tools capability when custom model capabilities are synthesized from boolean flags
For a models.json entry with no explicit capabilities list, toStoredModelInfo
synthesized a capability array purely from boolean convenience flags (e.g.
supportsReasoning: true -> ["reasoning"]). modelSupportsToolCalling fails open
only for a missing or empty list, so the synthesized non-empty list read as an
authoritative denial and silently stripped every tool definition from requests
to custom OpenAI-compatible models (#13463).
Seed "tools" whenever the list was not explicitly authored and the boolean
projections made it non-empty, preserving the fail-open contract. Explicitly
authored capability lists remain authoritative and can still disable tools.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(core): cover stale catalog capability overrides
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: treat stored capability lists as non-authoritative for tool calling
The hasExplicitCapabilities guard still let two producers of tool-less
lists through:
- The VS Code legacy-override migration (legacyModelInfoToOverrides)
persists explicit partial lists like ["prompt-cache"] into models.json
for custom OpenAI-compatible models, which then read as an authoritative
"cannot call tools" and drop every tool - same symptom as #13463.
- Any hand- or UI-authored partial list on a non-catalog model.
Stored entries and user-authored provider metadata have no way to declare
"cannot call tools" (there is no supportsTools field, and every writer
that authors a full list includes "tools"), so seed "tools" into any
non-empty list for a language model. Only generated catalog capabilities
remain authoritative - a genuine no-tools catalog model stays that way -
and non-language models (e.g. image generation) never gain a tools claim.
Also make legacyModelInfoToOverrides write "tools" into the arrays it
fabricates, matching the providers.json migration, so models.json stops
being poisoned for older readers.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): prepare 4.1.13 release
* chore(sdk): release v0.0.78
* chore(cli): release v3.0.57
* fix(core): run hub e2e files serially so daemon timing budgets survive CI contention
singleton.e2e.test.ts (added in #13468) spawns real daemons and runs for
~15s. Vitest's default file parallelism let it run alongside
shutdown.e2e.test.ts, whose assertions are wall-clock bound: discovery
within 10s, exit within 5s, and a 2s shutdown watchdog. On the 2-core
windows-latest runner that contention alone broke those budgets, failing
the shutdown test two different ways across runs — once never observing
discovery, once with the daemon forced to exit before its HTTP 202
flushed (socket hang up). The test passed on Windows before #13468 and
has failed every SDK publish run since.
* chore(desktop): release v0.0.16
* test(sdk): give windows-sensitive suites realistic timeouts
Four consecutive SDK publish runs failed on windows-latest, each on a
different test, all of them plain timeouts: two @cline/shared SQLite
tests at the 5s vitest default, core's bash executor at 10s, and the hub
singleton endpoint test at 10s. The 2-core Windows runner spawns forks
and takes SQLite locks slowly enough to blow those budgets under load.
These timeouts guard against hangs; they are not timing assertions (the
one suite that does assert elapsed time, shutdown.e2e, was fixed by
removing file-level parallelism instead). Raise core to 20s and give
@cline/shared an explicit 15s in place of the inherited 5s default.
* fix(telemetry): emit task.completed from every session teardown path (#13489)
The task.completed fallback lived only inside shutdownSession, but
stopSession/dispose route interactive sessions with a terminal reported
status through releaseSessionRuntime, which never emitted. Truthful
session-status reporting (shipped in 4.1.11) re-routed a large share of
interactive stops onto that branch and silently dropped the event.
Route the emission through a single choke point,
emitTaskCompletedOnTeardown, called from both shutdownSession and
releaseSessionRuntime. The completion criterion no longer reads
session.status: interactive sessions use the recorded final-turn
outcome (lastInteractiveTurnFinishReason), non-interactive sessions
keep the existing input.status === "completed" logic. A new
taskCompletedEmitted flag (also set by the submit_and_exit observer)
enforces exactly one task.completed per session. failSession now
records the errored final turn so a stale "completed" from an earlier
turn can never leak into the teardown emission. Telemetry only; no
user-facing behavior changes.
* chore(vscode): release v4.1.14
* fix(vscode): auto-approve all MCP tool calls when the MCP toggle is on (#13498)
* fix(vscode): honor MCP auto-approve settings for SDK tool calls
The SDK extension required both the global 'Use MCP servers' auto-approve
toggle AND each tool's per-tool autoApprove flag before silently approving
an MCP call, while the legacy extension treated them as either/or. Restore
the legacy OR semantics so toggling MCP auto-approve works again.
Also key toolPolicies by the registered SDK tool name (via
defaultMcpToolNameTransform, now exported from @cline/core) instead of raw
server__tool. Servers whose names contain sanitized characters (e.g.
marketplace names like github.com/user/repo) or exceed 64 chars produced
policy keys that never matched the registered tool, so those MCP tools ran
without any approval gate; the live auto-approve lookup now re-applies the
transform instead of string-splitting the name.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Revert "fix(vscode): honor MCP auto-approve settings for SDK tool calls"
This reverts commit 86c568fbba.
* fix(vscode): auto-approve all MCP tool calls when the MCP toggle is on
The SDK extension only auto-approved an MCP call when the global 'Use MCP
servers' auto-approve toggle AND that tool's per-tool autoApprove flag were
both set, so toggling MCP auto-approve appeared to do nothing and users had
to opt in each tool individually. The toggle alone now governs all MCP
tools; the per-tool flag is no longer consulted.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): release v4.1.15
* fix(cli): remove the $4.99 ClinePass promo copy (#13514)
The $4.99 first-month promo is ending, so the CLI's first-launch "Try ClinePass" dialog should no longer advertise it. Also drops the leftover CLI_PROMO_CODE plumbing, which has been an empty string since the promo-code flow was removed.
* fix(vscode): resolve hook workspace identity from the window, not shared global state (#13352)
* fix(vscode): resolve hook workspace identity from the window, not shared global state
Hook discovery, hook cwd selection, and the workspaceRoots metadata passed
to hook scripts all read the workspaceRoots/primaryRootIndex global state
keys. Global state lives in ~/.cline and is shared by every Cline instance
(all VS Code windows, the CLI, the JetBrains plugin), and nothing writes
these keys anymore, so hooks resolved against whatever project some other
or older instance last recorded. With a second window open on another
project, a workspace's .clinerules/hooks scripts were never discovered.
Resolve workspace roots via a single guarded helper backed by
HostProvider.workspace.getWorkspacePaths() (in-process, window-scoped,
same as refreshHooks): blank paths are filtered, a host-bridge failure
degrades to no workspace roots instead of silently disabling global hooks
or skipping blocking PreToolUse guards, and one resolution is threaded
through hooks-dir discovery, cache misses, cwd selection, and hook input
metadata so they can't disagree (previously up to four host lookups per
hook execution — real gRPC round trips in the standalone host). Roots and
hooks dirs are matched on whole path segments with the longest root
winning, so prefix-sharing or nested workspace roots resolve to the right
project. The adapter creates the runner once per event and skips no-op
runners, making creation the single resolution point; the separate
hasHook/getHookInfo checks are removed. The dead workspaceRoots and
primaryRootIndex state keys are dropped, and the four hand-rolled
HostProvider.workspace test stubs are consolidated into one shared
helper.
* test(vscode): add e2e coverage for workspace-scoped hook discovery
Boots real VS Code with the packaged extension against the workspace
fixture, sends a prompt, and asserts the fixture's UserPromptSubmit hook
was discovered from the open window's workspace, executed with that
workspace root as its cwd, and received the same root in its
workspaceRoots input — the end-to-end contract the hook workspace
identity fix establishes.
* test(vscode): isolate the e2e hook fixture from the shared workspace
The UserPromptSubmit fixture hook lived in the shared e2e workspace, so
every prompt-sending spec executed it (hooksEnabled defaults to true) —
and its cold PowerShell spawn on Windows pushed chat.test.ts past the
5s expect timeout. hooks.test.ts now overrides workspaceDir to a
dedicated workspace-hooks fixture, so only the hooks spec pays the hook
spawn.
* fix(hub): cap hub-events db size so it can't fill the disk (#13516)
* fix(hub): cap hub-events db size so it can't fill the disk
Row/time retention alone didn't bound disk usage: envelopes carrying
full session snapshots reach hundreds of KB each, so retained rows
could total tens of GB, sweeps only ran hourly, and DELETE never
shrinks a SQLite file. Enforce a 64 MiB size budget in prune() (oldest
rows first, VACUUM to return the space), and also prune after every
16 MiB appended so bursts can't outrun the hourly timer.
Fixes#13505
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): tolerate VACUUM failure on a full disk
VACUUM needs scratch space and can fail in exactly the state a
ballooned event log causes. The byte-budget deletes already bound live
data, so swallow the error and let the next sweep retry the reclaim
instead of aborting startup pruning and disabling the durable log.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): count the size budget in UTF-8 bytes, not characters
envelopeJson.length (UTF-16 units) and SQLite LENGTH() (characters)
undercount multibyte text by up to 3x, which could leave a CJK-heavy
log settled above budget and re-running VACUUM every sweep. Use
Buffer.byteLength and LENGTH(CAST(... AS BLOB)) instead.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(sdk): carry root overrides into the Node smoke-test sandbox (#13517)
ci-node-smoke.ts installs the packed SDK tarballs with a plain npm
install in a fresh temp dir, where the repo root package.json overrides
do not apply. When @sap-cloud-sdk 4.9.0 shipped (2026-08-24) it broke
@sap-ai-sdk/ai-api 2.14.0 (via @jerome-benoit/sap-ai-provider in
@cline/llms) with ERR_PACKAGE_PATH_NOT_EXPORTED, failing the smoke step
on every PR even though the root already pins @sap-cloud-sdk/* to 4.6.0.
Copy the root overrides block into the generated sandbox package.json
so the smoke install resolves the same pinned versions as the repo and
future third-party releases cannot break it independently.
* chore(sdk): release v0.0.79
* fix(vscode): don't steal last-used provider from ClinePass on credential refresh (#13520)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): flush the /shutdown 202 before daemon teardown
The /shutdown handler queued teardown on a microtask, which runs before
the event loop's write phase, so the daemon could process.exit() before
the accepted 202 was handed to the socket. Unix masked it (uv_try_write
lands small loopback writes synchronously); Windows has no such fast
path and lost the race regularly — the recurring shutdown.e2e.test.ts
'socket hang up' failures on windows-latest. Start teardown from the
response's write callback instead, with an idempotent 1s fallback so a
client that vanishes mid-write cannot strand the daemon, and send
Connection: close so the client gets a FIN rather than an abort.
Since the flakiness this compensated for is fixed at the source, restore
maxWorkers: 2 for the Windows core suite (serializing it cost ~3 min of
CI per run), and raise the e2e daemon discovery hang guard 10s→30s —
it guards against hangs, not runner speed.
* chore(cli): release v3.0.58
* fix(core): prevent search_codebase from crashing the process on giant single-line files (#13525)
* fix(core): prevent search_codebase from crashing the process on giant single-line files
searchWithRipgrep buffered all of rg's --json stdout into one string. Each
JSON event embeds the full text of the matched line (--max-columns is
ignored in JSON mode), so searching a directory of serialized trace dumps
(single-line multi-hundred-MB JSON files) accumulated gigabytes of stdout
until string concatenation threw RangeError: Out of memory inside the
stream data handler. That throw is outside the tool's try/catch, so it
escalated to an uncaughtException and killed the CLI/hub daemon.
Parse rg's JSON events incrementally line by line, drop events larger
than 256KB, truncate matched/context lines to MAX_LINE_CHARS, and stop
reading once maxResults is reached. The fallback regex scan now skips
files larger than 10MB (reporting the skip count) and truncates its
context lines the same way.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* simplify search_codebase crash fix to a minimal diff
Replace the incremental JSON-event parser with three small guards: stop
buffering rg stdout past 10MB, drop the trailing partial event before
parsing, and slice fallback context lines to MAX_LINE_CHARS. Drops the
fallback file-size skip and skip-count reporting.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): hide per-tool MCP auto-approve checkboxes behind a flag (#13522)
* chore(vscode): remove per-tool MCP auto-approve checkboxes from webview
MCP auto-approval is now governed solely by the global 'Use MCP servers'
toggle; the SDK approval path (shared with the CLI and desktop app) has no
per-tool granularity, so the per-tool and 'Auto-approve all tools'
checkboxes were no-ops that implied control that no longer exists. Remove
them from the MCP settings view and chat tool rows. The autoApprove arrays
in cline_mcp_settings.json and the toggleToolAutoApprove RPC are left
intact for the legacy extension.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): hide per-tool MCP auto-approve checkboxes behind a flag
Keep the checkbox components, handlers, and RPC plumbing intact but gate
rendering behind SHOW_MCP_PER_TOOL_AUTO_APPROVE=false: the SDK approval
path (shared with the CLI and desktop app) is all-or-nothing via the
global 'Use MCP servers' toggle, so the per-tool checkboxes were no-ops.
Flip the flag back on if the SDK gains per-tool approval granularity.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(tools): create new files with the platform-native line ending (#13521)
* fix(tools): use platform-native EOL for new files and preserve CRLF in apply_patch updates
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* simplify to the minimal new-file EOL fix
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* extract shared normalizeNewFileLineEndings helper
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add suggested schedule templates to the desktop Schedules page (#13529)
* Add suggested schedule templates to desktop Schedules page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix unreadable selected text in inputs caused by selection utility conflict
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Restyle Suggested section label as small gray uppercase
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Hide suggested schedule cards that match an existing schedule name
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Disable the agent todo tool and hide the Agenda UI in the desktop app (#13530)
* remove todo tool and Agenda UI, keep schedule-only tasks tool
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: biome formatting fixes
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* restore agenda backend; disable todo kind behind a flag instead of deleting
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* keep agenda automation pump idle while the todo tool is disabled
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* remove todo tool and Agenda UI altogether (revert the disable-flag hybrid)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* restore all agenda code to main state
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* disable agent todo tool and hide Agenda UI behind flags
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add Desktop App and Cloud Platform to bug report issue template (#13532)
* Add Desktop App and Cloud Platform to bug report surfaces
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename Surface Diagnostics field to Diagnostics
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: sidebar navigation cleanup with New/Schedule/Customize rows and dialog-based search (#13533)
* desktop: clean up sidebar navigation chrome
- Give New Task its own full-width labeled row below the logo row
instead of an ambiguous icon next to the agenda toggle
- Wire the New Task row to the home action so starting a new task
clearly takes you home (the logo still works as a fallback)
- Swap back/forward chevrons for browser-style arrow icons and
bump their size
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: sidebar New/Schedule/Customize rows and always-visible search
- Stack New (plus icon), Schedule, and Customize as full-width labeled
rows below the logo; whole row highlights on hover via sidebarItem
- New starts a fresh task (home), Schedule opens Settings > Schedules,
Customize opens the Customizations sections (Plugins first)
- Show the session search bar permanently above the sessions list
instead of hiding it behind a search icon toggle
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: move session search into a dialog behind a logo-row icon
- Replace the inline sidebar search bar with a search icon in the
logo row that opens a cmdk command dialog listing sessions
- Selecting a result opens that session and closes the dialog
- Remove the agenda/tasks toggle the icon replaces, along with the
now-unreachable sidebar Agenda panel (the welcome screen still
surfaces agenda tasks)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: load full session history when the search dialog opens
Addresses Greptile review on #13533: the dialog only searched the
currently loaded history batch, so older unloaded sessions could not
be found. Opening search now kicks off loadAllSessions() (the hook's
purpose-built global-search loader), and the empty state reads
'Searching older sessions...' while more history is streaming in.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Hide Channels and Agents sections from desktop app sidebar (#13527)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Desktop app: organize sidebar sessions into Pinned, Scheduled, and Tasks sections (#13528)
* Add Pinned/Scheduled/Tasks categories to desktop app sidebar
Replace the Schedules and Favorites filter-menu options with visible
collapsible category sections in the session sidebar, and rename the
Favorite action to Pin across the sidebar and sessions view.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Grow full history window when Tasks show-more outpaces loaded tasks
loadMoreSessions treats its argument as a limit on all sessions, but the
Tasks show-more count only tracks Task rows, so once pinned/scheduled
rows pushed the loaded total past the requested count the call no-oped
and clicks went dead. Grow the whole history window via
loadOlderSessions instead, and only when the loaded tasks cannot fill
the next page. Addresses Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Auto-fill the Tasks page instead of fetching once per show-more click
A single 50-session window growth can consist entirely of pinned or
scheduled sessions, leaving a show-more click with no visible Tasks
progress. Replace the one-shot fetch with a page-fill effect that keeps
growing the history window until the requested Tasks page fills or
history runs out. Addresses the follow-up Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Halt page-fill retries after a failed history fetch
A failed fetch leaves the task count and has-more state unchanged,
which are exactly the conditions the page-fill effect fires on, so one
failing request would retry and re-toast forever. Halt the effect after
a failure and let the next explicit show-more click retry. Addresses
the third Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Redesign desktop Model Providers page and split voice input into its own settings page (#13531)
* Redesign desktop Model Providers page and split voice input into its own settings page
- Group providers into Connected / Popular / All with auth-kind hints and
connection status instead of per-row enable toggles
- Show browser sign-in (not an API key field) for OAuth providers, with a
collapsed manual-key escape hatch where supported, plus explicit
Connect / Disconnect / Sign out actions
- Move voice input to a dedicated Settings > Voice page that only offers
connected transcription-capable providers, preselects a default model
(streaming preferred), and stays disabled in the sidebar until a
provider is connected
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Show native tooltip on the disabled Voice settings nav item
Disabled buttons drop pointer events, so the 'connect a model provider'
hint moves to a wrapping span for the browser tooltip to render.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Drop letter avatars and gray provider ids from provider rows and voice chips
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Drop model counts from provider list rows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename provider Connected status to Configured and drop the green styling
A settings entry is configuration, not a live connection; neutral gray
text avoids implying an active link, since the user still picks which
configured provider to use per chat.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Resync provider catalog from disk when a settings save fails
Connect/disconnect/credential edits update the list optimistically; a
failed save now reloads the catalog instead of leaving the optimistic
state (and the view's module cache) claiming a configuration that was
never persisted.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename oauthProvider test fixture to dodge CodeQL name heuristic
CodeQL's clear-text-storage query flags any identifier matching 'oauth'
as a credential source and traced the fixture's provider id into the
favorite-models localStorage write, which stores only provider/model id
strings. Renaming the fixture removes the false-positive source.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Guard catalog reloads against races and resync detail drafts on failed saves
Optimistic provider mutations now bump a generation that discards any
in-flight catalog response, so a failed-save recovery reload can't
overwrite a newer edit with an older disk snapshot. The recovery also
remounts the provider detail panel via a reset token so its local field
drafts reflect the reloaded on-disk state instead of unpersisted edits
or an optimistically cleared disconnect.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix failed-save recovery ordering and retry superseded reloads
Remount the provider detail only after the authoritative catalog reload
lands, so its drafts re-seed from disk state rather than the optimistic
values that failed to persist. When a concurrent edit supersedes the
recovery's in-flight response, retry the reload (bounded) instead of
dropping it, since that edit performs no reload of its own.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): Customize hub, sidebar overhaul, and settings polish (#13538)
* feat(desktop): merge customization pages into a Customize hub with inline marketplace
Replaces the Plugins page and the dedicated Marketplace page with a single
Customize hub. Tabs: Skills, MCP, Plugins, Rules, Hooks, Tools, each with
live counts. Tabs backed by a marketplace catalog render the installed
items followed by an inline browsable Browse section (CLI-hub style), so
installing from the catalog immediately reflects in Installed above.
- Installed cards restyled to mirror the browse-card anatomy: bg-card p-4
containers, absolute top-right xs Uninstall matching Install, truncating
semibold titles, primary-tinted icons, real Badge components instead of
ad-hoc bordered spans, un-indented line-clamped descriptions
- Rules/Hooks/Tools rows brought into the same card language; redundant
intro paragraphs (duplicating the page description) removed; Tools group
headers match the Installed header style with counts
- Marketplace section header renamed to Browse; duplicate 'N results' row
removed (the header count is the single source)
- MCP embedded view now shows the full marketplace instead of
installed-only
* feat(desktop): overhaul sidebar sessions and navigation
Sessions list:
- Sort toggle removed; sessions are always grouped by project, with pinned
sessions leading each group (both subsets ordered by recency). The
Pinned/Scheduled/Tasks category sections and their time-mode paging
machinery (page-fill effect included) are deleted
- Scheduled sessions get an inline clock icon next to the pin position;
pin + clock render together when both apply, and the running/unread
status dot now coexists with them
- One font size (text-sm) across the list: titles, timestamps, project
headers, show-more buttons, empty states. sidebarText needed !text-sm
because the default button size's text-base wins the twMerge conflict
- Gradient fade under the Sessions header once the list scrolls, so rows
fade out instead of hard-clipping
- The session-detail hover card is controlled from the sidebar and closes
on scroll (Radix receives no pointer events while scrolling, so it used
to float over moving content)
- Sidebar min resize width raised 224->260px; the per-project show-more
label truncates so its nowrap text can't force rows to overflow and clip
timestamps at narrow widths
Navigation:
- Customize replaces the Plugins/Marketplace/Hooks/Rules/Tools sidebar
entries; Schedules and Customize are hidden from the expanded settings
nav (their top rows cover them) but stay reachable when collapsed
- The settings gear always opens General instead of resuming the last
section; the Account no-op hover special case is gone
- The New row highlights (aria-current) while the fresh not-yet-started
task page is showing and hands off to the session row once the task
starts; hitting New also focuses the prompt input via a window-event
signal (lib/prompt-input-focus.ts) since the sidebar and composer sit in
distant subtrees
- Fixed the xs button size collapsing any icon-bearing button to 12x12
(leftover has-[>svg]:size-3 from when xs was a micro button) — this was
why Uninstall buttons rendered broken next to Install
* feat(desktop): polish settings pages and chat composer
Models page:
- The provider detail panel is always open: no X button, no empty
no-selection state. It defaults to the first connected provider (falling
back to the first in the catalog), which also removes the layout shift
that happened when the page swapped between full-width and panel
variants on selection
- Fixed the list pane becoming unscrollable while the panel was open:
grid items default to min-size auto, so the pane grew past its track
inside the overflow-hidden grid and its ScrollArea had nothing to
scroll; wrapped it in a min-h-0 min-w-0 cell
- Add Provider opens a Dialog instead of swapping the page
(AddProviderContent gained a dialog variant that renders only the form)
- Embedded inputs (provider search, model search, detail fields) share one
EMBEDDED_INPUT_CLASS stripping the Input component's own border/dark bg
tint/shadow/ring, which rendered as a mismatched inner box; the model
search box uses the same h-9/px-3 frame as the provider search
- Model list flows with the page instead of a max-h capped inner scroller
Other pages:
- Account uses the shared PageFrame/PageHeader: left-aligned, text-3xl
title, Sign Out in the header actions slot
- Desktop notifications is one General section: header row plus the
Event/Notify/Sound matrix nested in a card, so its rows no longer read
as top-level peers of Dark mode; 'Available in the desktop app' label
removed
- Schedule page retitled from Schedules with a real description; Customize
description rewritten
Chat composer:
- The voice dictation button only renders once a voice model is
configured (Settings -> Voice); the unconfigured deep-link state is
gone (prop type kept for an easy restore)
* chore(desktop): release v0.0.17
* fix(desktop): unblock sdk-test lint on the voice-input model picker (#13553)
The model picker renders a radiogroup of styled buttons with role=radio
and aria-checked; biome's useSemanticElements flags the role as an
error, which fails the sdk-test Quality Checks lint for every PR
touching sdk/ or apps/ paths. Suppress with a justification — switching
to input type=radio needs a restyle and belongs to the desktop settings
work.
* fix(vscode): include rich workspace metadata in system prompt (#13518)
* capture richer workspace information for vs code extension
* fix(shared): redact credentials from workspace remotes
* fix(shared): avoid regex backtracking in remote redaction
---------
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
* Hide task costs on vscode when ClinePass is selected (#13515)
* fix: stop showing cost estimates for subscription-billed providers (#13552)
* fix(vscode): stop showing cost estimates for subscription-billed providers
Providers whose usage is covered by a flat-rate subscription (ChatGPT
Plus/Pro via openai-codex, ClinePass) are marked with
metadata.usageCostDisplay = "subscription" in the SDK, and the CLI
already suppresses dollar figures for them. The VS Code host collapsed
that value into "show" before it reached the webview, so the task
header and model pricing rows rendered API-rate cost estimates that
users read as real charges on top of their subscription.
Pass all three usageCostDisplay values ("show" | "hide" |
"subscription") through the catalog listing and render cost only when
the value is "show", matching the CLI's shouldShowCliUsageCost
policy.
* feat(llms): mark Claude Code as a subscription-billed provider
Claude Code is typically authenticated with a Claude Pro/Max
subscription, but its models reuse Anthropic API pricing metadata, so
Cline rendered per-token prices and API-rate cost estimates for usage
that is covered by the subscription. Set usageCostDisplay =
"subscription" on the provider (picked up by the CLI and the VS Code
webview) and suppress the price rows in the Claude Code settings card.
The Claude Code CLI can also run on API-key billing, where a real cost
exists; the provider cannot distinguish the two, so we prefer showing
no number over a misleading one.
* fix(vscode): suppress cost display until provider listings load
While the ListProviders request is in flight (or after it fails), the
usage-cost hook had no listing to consult and fell back to "show",
flashing the API-rate estimate at subscription users on every chat-view
mount — the exact display the previous commit removes. Return
"unknown" whenever listings are absent; consumers already render cost
only for "show", so they suppress it during that window with no
changes. Briefly hiding a real cost is harmless, briefly showing a fake
charge is not.
* fix(desktop): reconcile voice settings after main sync
* test(llms): allow experimental ElevenLabs models
* fix(sdk): preserve canonical media model behavior
* feat(desktop): customize macOS DMG install window (#13563)
* feat(desktop): add Retina DMG background tooling
* feat(desktop): customize the macOS DMG layout
* ci(desktop): validate DMG background assets
* fix(desktop): adjust DMG Applications icon position
* ci(desktop): drop redundant DMG artwork validation from publish workflow
Tauri's beforeBuildCommand already runs dmg:background (with its own
validation) at the start of the build/sign/notarize step, and the
release/beta config overlays do not override the build section, so this
step duplicated work the publish job performs anyway. PR-time coverage
lives in desktop-test.yml.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): sidebar time view, Customize/Marketplace split, and schedule page UX (#13570)
* feat(desktop): split Customize into Installed and Marketplace pages
The Customize hub previously embedded a Browse section inside every tab
that had a catalog. That inlining made each tab long and buried the
catalog. Customize is now the installed inventory only (skills, MCP,
plugins, rules, hooks, tools tabs pass marketplaceVariant="installed"
to the embedded MarketplaceView; McpServersContent grew the same prop),
with an outline Marketplace button in the header.
Browsing moved to a dedicated Marketplace settings section that renders
the previously dead "directory" variant of MarketplaceView: one list
across all catalog types with type-filter chips, wrapping tag chips,
and light rules separating the filter tiers from each other and from
the results. The Clear control now renders inline at the end of the tag
row only while a tag is active, the Updated date is gone, and the
header hosts an Installed button mirroring the one on the Customize
page. Directory subheader copy: "A curated set of plugins, MCP
servers, and skills from the Cline community."
Tag and type chips wrap to new lines instead of scrolling
horizontally.
* feat(desktop): sidebar time view with sections, sort toggle, and scheduled detection
Restores the time-sorted session list as the default sidebar view, with
collapsible Pinned / Scheduled / Tasks sections (headers appear only
once something is pinned or scheduled) and the page-fill effect that
grows the fetched history window until a Show-more click makes visible
progress. Project grouping stays as the alternate mode behind a
one-click sort toggle whose icon reflects the active mode — the old
dropdown cost an extra click for a two-option choice.
Scheduled sessions are detected two ways: the hub-schedule origin
trigger in session metadata, plus a fallback that asks the hub which
session ids belong to schedule executions (list_routine_schedules,
fetched on mount and every two minutes, merged into a rolling set).
The fallback matters because locally executed scheduled runs do not
reliably stamp the trigger into session metadata — a real scheduled
session created today carried only {mode:"user"} provenance. The
scheduled clock icon now leads the row, left of the title; pin and
timestamp stay on the right.
The initial visible page grows from 10 to 30 rows so a tall sidebar
fills instead of stranding a stub of rows over empty space (history
fetches already start at 50).
The expanded sidebar's Customize row now hosts indented Installed and
Marketplace sub-tabs while a customize section is open; the active
sub-tab carries the full selected background while the parent keeps a
subtler one so the two simultaneous highlights read differently.
Also fixes the hover-card flash on click (logo card and session-row
cards): Radix HoverCardContent sits on a DismissableLayer, so a click
on the trigger registers as a pointer-down outside the card and
dismisses it, and the trigger's focus event immediately reopens it.
onPointerDownOutside preventDefault suppresses the dismissal; cards
still close on pointer leave.
* feat(desktop): schedule page row, dialog, and details UX polish
Schedule cards are now click targets: clicking anywhere on a card
outside its controls opens the details dialog (guarded via
closest("button,...") since every inline control, including the Radix
switch, renders a button element), with Enter/Space keyboard support.
The redundant eye button is gone. The remaining edit / run / pause /
delete buttons grow from the 12px icon-sm size to 28px targets with
16px icons, sized consistently with the adjacent enable toggle — the
icons use explicit size-4 classes so the Button base svg rule cannot
shrink them back.
The new/edit dialog gains breathing room between field labels and their
inputs (space-y-2 per field wrapper).
The details dialog no longer scrolls as a whole when the schedule JSON
is long: the dialog is a flex column capped at 85vh, the JSON pre
shrinks to the remaining space (min-h-0) and scrolls internally, and
the Runs tab list scrolls inside the tab the same way.
* feat(desktop): scheduled sessions UX — unified details dialog, run-now handoff, hidden steering, stuck-thinking fix (#13573)
* feat(desktop): merge schedule details into one view and open run-now sessions
The schedule details dialog drops its Overview/Runs tabs: one scrollable
column with the meta grid, the configuration JSON (capped at max-h-64
with internal scroll so it cannot crowd out what follows), and a Runs
section beneath it showing the three most recent runs with a ghost
"Show all N runs" expander (collapsed again whenever a different
schedule's details open). The "Full configuration for this schedule"
subtext is gone; the dialog passes aria-describedby={undefined} so
Radix does not warn about the missing description.
Run now hands you into the session it starts. The trigger command
queues the run and returns before the runner attaches a session id, so
after the toast the handler polls the schedule overview once a second
for up to 15 seconds — which doubles as keeping the page's run status
fresh (refreshSchedules now returns the fetched overview to make that
single-stream) — until the triggered execution reports its session id,
then calls onOpenSession. Guarded so it never auto-navigates after the
user left the page.
* feat(desktop): hide runtime steering messages from transcripts
Scheduled/automation runs inject user-role steering messages each
iteration ("[SYSTEM] This run is not complete until you call
submit_and_exit...", plus a team-obligations variant). The chat view
rendered them as user bubbles, as if the person had typed them — in a
scheduled session the transcript was mostly [SYSTEM] noise.
They are machinery talking to the model, not something the person said
or needs to read, so the transcript now hides them entirely:
MessageBubble renders null for any [SYSTEM]-prefixed user message.
Grouping still treats them as working-row machinery via a single
isSystemSteeringMessage predicate — they collapse into the run's work
span, are never a turn boundary, can never be mistaken for a run's
answer, and never advance the run count even when metadata is missing —
so work-block folding and checkpoint/edit run numbering stay correct.
A finished scheduled session now reads as prompt, work summary, answer.
* fix(desktop): poll history while an attached session's event stream is dead
Opening a scheduled session while (or right after) it runs left the
view stuck on the thinking shimmer until the user switched away and
back. Root cause is in core: the hub daemon executes scheduled runs on
a private LocalRuntimeHost inside createLocalHubScheduleRuntimeHandlers,
while the hub server only projects live events from its own session
host — so session.attach succeeds but no assistant/tool/status events
ever flow. And since multiple hub daemons share cron.db, a run claimed
by a different daemon is invisible to this hub regardless. The proper
core rewiring is tracked as ENG-2474.
Client-side heal that covers every case: while an attached history
session reports a busy status and no chat_event chunk has arrived for
five seconds (and no assistant bubble is mid-stream), poll every three
seconds — re-read canonical history, merged through the same dedupe
path hydration uses, and the session record's status — so the
transcript and the thinking indicator settle in place. Locally driven
turns keep chunks flowing, so the quiet-window guard keeps the fallback
inert there.
* chore(desktop): format workspace selector components
Biome formatting drift that landed on main; picked up by a formatter
pass over components/views/chat.
* fix(desktop): keep stale-stream poll inert during locally driven turns
The fallback poll could fire between a local submit and the model's
first chunk (optimistic user bubble added, stream quiet past the
window, no assistant bubble yet). It then replaced the optimistic
bubble — raw prompt text — with its canonical history twin, which is
stored wrapped in a user_input envelope. The rekey handler that runs
when the stream starts looks for a trailing user bubble matching the
raw prompt, finds only the wrapped copy, and appends a second bubble:
duplicated messages in normal interactive chat.
The poll now stays inert while a local turn is in flight
(turnEpoch !== turnSettledEpoch, or outstanding optimistic user
messages), checked both before polling and again after the snapshot
returns. Hydration marks the turn settled — the mount defaults
(epoch 0, settled -1) otherwise read as an open turn and would keep
the fallback inert forever for the scheduled-session case it exists
for. Applying a polled snapshot also rebuilds the live tool routing
keys, same as hydration, so later tool events update canonical rows
in place instead of appending.
* fix(desktop): keep the working indicator alive for narrating scheduled runs
Watching a scheduled run live: the first tool row appeared, then the
thinking indicator vanished with nothing streaming, and the rest of
the run (final answer, submit_and_exit) only showed up seconds later
in one lump.
inferHydratedChatStatus treats a "running" session record whose
transcript ends on an assistant message as a session that died without
a status flip and reports "completed". That heuristic is right for
stale records, but scheduled/automation models narrate between tool
calls, so a polled snapshot can genuinely end on assistant text
mid-run — the completed flip hid the working indicator, folded the
run early, and disarmed the stale-stream poll (status left the busy
set), dead-ending live updates until an in-flight poll happened to
deliver the finished run.
The heuristic now only applies once the transcript has actually gone
quiet (newest message older than two minutes — comfortably past model
latency plus tool runs). A recently active transcript keeps the
record's "running" verdict, so the indicator stays up and polling
stays armed until the record itself settles.
* fix(desktop): stale-stream poll mirrors the session record instead of inferring
Replaces the previous fix for the vanishing working indicator (the
time-window guard added to inferHydratedChatStatus) with a version
that adds no inference at all: the heuristic is restored to exactly
its long-standing form, and the poll now maps the session record's
status verbatim (mapSessionRecordStatus).
The record is the right authority in the poll's context: the sessions
this fallback serves have a live host maintaining their record, and it
flips to a terminal status when the run ends. Transcript-shape
inference belongs only where it has always lived — hydrating sessions
whose records may be orphaned — and would misread a mid-run snapshot
ending on assistant narration as a finished session, hiding the
working indicator and disarming the poll.
* fix(desktop): address review findings on steering detection and run-now matching
Steering detection additionally requires the injected-message marker
(meta.userRunSpan === 0) beside the [SYSTEM] prefix, so a person's
genuine prompt that happens to start with "[SYSTEM]" stays visible
and turn-counted. The failure direction is deliberate: an unstamped
injected reminder would merely show as a user bubble, while the
content-only check could hide a real prompt.
Run-now only follows the execution id the trigger reply itself named;
the newest-execution-for-this-schedule fallback could open a previous
run's session when the trigger failed to enqueue one.
* fix(desktop): report a failed run-now instead of confirming a start
A trigger reply without an execution means no run was enqueued (the
schedule may have been disabled or deleted since the page loaded). The
handler previously toasted "Run started" regardless and then silently
skipped the session-open polling. It now shows a destructive
"Run not started" toast, refreshes the schedule list so the row
reflects reality, and skips the polling entirely.
* fix(desktop): don't block the main thread on quit while stopping the sidecar (#13566)
Quitting the mac app beach-balled for ~5-7s. The shutdown POST was
built from the ws transport URL (appending /shutdown lands inside the
query string), so the sidecar was never told to exit, and stop() then
polled the child for up to 7s on the main thread - on macOS inside
applicationWillTerminate - before SIGKILLing it.
stop() now sends SIGTERM and returns immediately. The sidecar handles
SIGTERM with the same bounded (5s) graceful shutdown as the /shutdown
endpoint and exits itself, finishing session persistence as an orphan.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): hover trash button on sidebar session rows (#13582)
Each session row shows a trash icon on the right while hovered (or
when the button itself is focused), opening the same delete
confirmation dialog the row's context menu uses. The row is a button
and buttons cannot nest, so the trash is an absolutely positioned
sibling inside a group/row wrapper, overlaid where the timestamp sits:
row hover hides the timestamp, shows the trash, and moves the row's
hover background to the wrapper group so it holds while the pointer is
on the trash itself.
* fix(desktop): install marketplace plugins and MCP servers in-process instead of spawning a cline binary (#13585)
* fix: install marketplace plugins and MCP servers in-process instead of spawning a cline binary
The desktop app sidecar and cline-hub shelled out to 'cline plugin install'
and 'cline mcp install' for marketplace installs. Packaged GUI apps inherit
launchd's minimal PATH on macOS and most desktop users have no cline CLI
installed at all, so installs failed with a red
'Executable not found in $PATH: "cline"' error.
Install via @cline/core's installPlugin/installMcpServer in-process instead,
matching what the VS Code extension already does. Also fix
parseMcpInstallArgs in @cline/core to treat the marketplace catalog's '--'
separator as end-of-options; previously the separator itself became the
stdio command.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop test-injection plumbing from marketplace installers
Call @cline/core's installPlugin directly instead of threading an
installer option through the marketplace entry points; tests stub the
core module instead.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert: keep cline-hub marketplace installs CLI-backed
The hub dashboard is launched via 'cline dashboard', so a CLI is always
present and CLINE_WRAPPER_PATH resolves it; the PATH bug only affects
the desktop app, which does not ship a CLI.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(desktop): release v0.0.18
* chore(vscode): release v4.1.16
* chore(sdk): release v0.0.80
* chore(cli): release v3.0.59
* fix(hub): stop shipping full transcripts inside broadcast hub events (#13587)
* fix(hub): stop shipping full transcripts inside broadcast hub events
Every session.updated (and session.created/detached/run.started) event
embedded the session's ENTIRE message transcript via readCoreSessionSnapshot,
even though no consumer reads snapshot.messages off an event — clients fetch
messages with the session.messages command. For a multi-megabyte transcript
this turns every status flip into megabytes per subscriber, floods the
durable event log, and (until the send-queue backpressure fix lands) lets a
slow subscriber balloon the hub process by one full transcript copy per
event — reported as a 25GB cline process on a 16GB Mac.
Strip snapshot.messages centrally in HubServerTransport.publish() so every
current and future event publisher is covered, the event log stores slim
envelopes, and cursor replay stays byte-identical with live fan-out. All
other snapshot fields (status, usage, model, workspace, checkpoint) are kept,
and command replies are untouched.
* fix(hub): never capture the transcript into event/reply snapshots
Replaces the publish-boundary strip with the real fix: don't build
message-bearing snapshots in the first place. emitSessionSnapshot no longer
re-reads the entire transcript from disk on every status flip, and
readCoreSessionSnapshot no longer reads it for any event or reply — a
snapshot is a state notification (status, usage, model, workspace,
checkpoint); the transcript is fetched via the session.messages command.
Checkpoint-restore snapshots (session-versioning-service) are untouched:
restore replies carry messages in their own dedicated field.
* chore(desktop): release v0.0.19
* chore(sdk): release v0.0.81
* chore(cli): release v3.0.60
* fix(vscode): avoid render crash on malformed api_req payloads in combineApiRequests (#13560)
* fix(vscode): stop pinning DeepSeek model count in catalog smoke test (#13600)
* feat(ui): share agent welcome hero (#13567)
* feat(ui): share agent welcome hero
* test(ui): cover welcome hero pointer states
* refactor(ui): keep welcome hero API minimal
* test(ui): verify welcome hero package assets
* fix(ui): inline welcome hero masks
* fix(tools): preserve a file's own CRLF line endings across apply_patch updates (#13512)
* fix(desktop): keep the window title bar draggable across views (#13572)
* fix(desktop): keep window title bar persistent
* fix(desktop): reserve persistent title bar space
* fix(desktop): polish persistent title bar layout
* Sign Windows CLI binaries with Azure Trusted Signing; surface app-control launch errors (#13021)
* feat(cli): sign Windows binaries with Azure Trusted Signing and surface app-control launch errors
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): use _CLI-suffixed signing profile secret, normalize endpoint, fail loud on partial config
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* Tunnel ProtoBus over the existing Host Bridge (#13218)
* feat(core): tunnel ProtoBus over Host Bridge
* fix(core): harden Host Bridge stream lifecycle
* fix(core): serialize concurrent chunked responses per request
Streaming handlers deliver updates fire-and-forget, so two logical
responses for one request_id can be in flight at once. Chunked payloads
made forwarding non-atomic: each chunk write is an await, so concurrent
forwards could interleave their chunk sequences and the receiver --
which reassembles purely by arrival order -- would splice two payloads
into one. Route all forwards for a request through one promise chain; a
failed write rejects every later forward so a torn payload is never
followed by more chunks.
Rename the lock manager's instanceAddress to instanceOwner: it holds an
opaque per-spawn instance ID on the token path and a listener address
only on the CLI-harness path. Delete the caller-less getInstanceByPort
query that interpreted the owner as an address.
Also: document message_json as a legal wire encoding for small
payloads, close the gRPC client when startup fails, note the
intentional discard of the cancellation confirmation, and add the
proto's trailing newline.
---------
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
* Build and Authenticode-sign a Windows x64 desktop installer in desktop releases (#13607)
* feat(desktop): build and Authenticode-sign a Windows x64 NSIS installer in desktop releases
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): pin OIDC-adjacent actions to commit SHAs in the Windows signing job
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): pin checkout and upload-artifact to commit SHAs in the Windows signing job
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): show agent-created schedules on the Schedules page (#13613)
* fix(desktop): show agent-created schedules on the Schedules page
Schedule hub commands are scoped to the workspace registered by the
connection, but the desktop app's hub client registers the app launch
directory while agent-created schedules live under each chat's own
workspace folder - so they never appeared on the Schedules page.
Grant token-authenticated hub connections (which can already bind any
workspace at registration) explicit cross-workspace schedule access via
an allWorkspaces payload flag, and have the desktop sidecar request it
for routine schedule commands. Workspace-bound clients (local browser
origins) and default CLI behavior stay scoped.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(core): strip allWorkspaces flag from schedule inputs and pin it in the sidecar payload
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make suggested routine template prompts prescriptive about their final output (#13611)
* Make bug hunter routine template prescriptive about its final report
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make remaining routine templates prescriptive about their final output
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Desktop: surface scheduled-task final output — auto-expand submit_and_exit and render its summary as markdown (#13612)
* desktop: auto-expand submit_and_exit and render its summary as markdown
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: render submit summary in full foreground color
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: label the submit row 'Scheduled task completed'
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: label errored submit_and_exit rows as failed
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add tooltips explaining Live and After recording badges on voice input models (#13610)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Remove box shadow from chat message actions row (#13630)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): make the Tauri shell work on Windows (#13632)
- Defer updater installation to the user-initiated restart on Windows:
install() launches the NSIS installer and exits the process immediately,
so the background cycle now downloads only and stages the bytes, and
restart_to_apply_update installs them after stopping the sidecar.
- Spawn child processes (sidecar, git, cmd /C start) with CREATE_NO_WINDOW
so the GUI-subsystem app doesn't pop visible console windows.
- Fall back to USERPROFILE when HOME is unset resolving the MCP settings
path, matching the sidecar's homedir().
- Reap the sidecar after the Windows hard-kill so its exe file lock is
released before the NSIS installer replaces it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): stop watching agenda spec dirs while the todo tool is disabled (#13629)
* fix(core): stop watching agenda spec dirs while the todo tool is disabled
Since #13530 disabled the agent todo tool, the Agenda UI, and the
automation pump, the hub still created fs.watch watchers on the global
agenda specs dir and on every workspace root recorded in the task store
(at startup and on scope access). Nothing consumes the watcher-driven
task events while the feature is off, and the task.* hub commands
already reconcile spec files on demand, so the watchers are pure
overhead - one OS watch handle per known workspace.
Wire watchFiles to AGENDA_TODO_TOOL_ENABLED the same way
automationEnabled is, preserving a host's explicit watchFiles opt-out
for when the flag is turned back on. Schedules are unaffected: the
schedule list has no file watcher and updates through hub commands and
published schedule events.
* fix(core): reconcile external spec edits inside updateTask
With the spec watchers off there is no background reconciliation, so a
task spec edited directly on disk made every same-store task.update fail
the signature check with "task spec changed outside the manager" until
an unrelated task.get or task.list happened to reconcile the scope.
Reconcile the task's scope at the start of updateTask (mirroring what
refreshAndVerifyTaskIntent already does for approve/run), skipping it
when the file reconciler itself is the caller to avoid recursing from
reconcileFileStore. An external edit now surfaces as the store's normal
stale-revision conflict, and a re-read-and-retry succeeds. This also
closes the pre-existing watcher debounce race for updates.
* fix(sdk): don't log out Codex/OCA users when token refresh fails transiently (#13565)
* fix(sdk): don't log out Codex/OCA users when token refresh fails transiently
Port the cline-provider refresh semantics to openai-codex and oca:
a transient refresh failure (network error, timeout, server 5xx) with an
already-expired access token now rethrows instead of returning null.
A null return means the refresh token was REJECTED and re-auth is
required; treating an outage blip as a rejection is what turned it into
a forced 'openai-codex requires re-authentication.' task stop while the
settings UI still showed the user as signed in.
Both providers also emit user.auth_refresh_soft_failure telemetry on
transient failures (the 'prevented logout' counter the cline provider
already has) and attach status/errorCode details to the genuine
invalid_grant logout event.
* refactor: collapse duplicate soft-failure telemetry branches and test
Review feedback: compute tokenExpired once and emit the soft-failure
event once in both providers, then return current credentials or
rethrow. Fold the codex soft-failure telemetry assertions into the
existing still-usable-token test instead of a near-duplicate case.
* fix: make OpenAI Codex (ChatGPT subscription) sign-in fail loudly instead of silently dead-ending (#13537)
* fix: make OpenAI Codex sign-in fail loudly instead of silently dead-ending
When callback port 1455 is already in use (e.g. by the Codex CLI or a
previous pending sign-in), startLocalOAuthServer returns a no-op server
and loginOpenAICodex would open the browser anyway, then dead-end:
the callback could never be received, and in the VS Code extension the
user just saw nothing happen after clicking 'Sign in to OpenAI Codex'.
- loginOpenAICodex now fails fast with an actionable 'port in use'
error before opening the browser, unless the host provides manual
code entry (the CLI's paste fallback keeps working)
- surface OAuth redirect errors (e.g. access_denied) instead of
collapsing them into 'Missing authorization code'
- the extension dedupes concurrent sign-in clicks: a re-click re-opens
the auth page of the pending flow instead of spawning a second flow
that would collide with our own callback server
- browser-open failures now show an error message with the URL to
open manually instead of only logging
- abandoned-flow timeouts no longer surface a confusing 'Missing
authorization code' toast
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop host-side codex login dedupe, keep flow identical to CLI
The SDK owns the failure handling now (fail-fast on an unbindable
callback port), so the extension keeps the exact same simple
loginOpenAICodex call the CLI uses. A second click while a flow is
pending gets the SDK's clear port-in-use error, same as running
'cline auth openai-codex' twice would. Keep only the CLI-parallel
onOpenUrlError surfacing (the CLI prints 'open the URL above
manually'; the extension's equivalent is an error toast with the
URL).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(e2e): cover Codex sign-in callback-port failure and redirect errors
Two driven-VS Code tests for the OpenAI Codex (ChatGPT subscription)
sign-in flow:
- with port 1455 occupied on both loopback families, clicking the
sign-in button surfaces the fail-fast port-in-use toast
- with the port free, the callback server binds and an OAuth redirect
error (access_denied) propagates to a visible error toast
The second test opens a real browser tab to the OpenAI auth page as a
side effect of the genuine sign-in click.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
* feat(core): anchor agent-created schedules in the user's .cline schedules home (#13634)
* feat(core): anchor agent-created schedules in the user's .cline schedules home
Agent-created schedules inherited whichever workspace folder the chat
session happened to run in, scattering user-level routines across chat
and project folders. They were invisible to workspace-scoped listings
elsewhere, tied to folders that may be cleaned up, and each chat's
tasks tool saw a different set when checking for duplicates.
Anchor them in ~/.cline/schedules instead: the hub's scheduled-task
session defaults now resolve to that home (created on demand), so
agent-created schedules live and run in one stable user-level scope.
The tasks tool guidance now tells agents that scheduled sessions run in
the schedules home, so prompts must carry absolute paths to any project
they operate on.
Schedules created explicitly with a workspace (CLI --workspace, desktop
routine wizard) are unchanged, and existing rows keep their current
workspaceRoot - they stay visible through the all-workspaces listing
paths (#13613, #13633).
* test(core): restore any pre-existing CLINE_DIR after the agenda hub test
The test's cleanup deleted CLINE_DIR outright, so an environment that
had it configured would leave later tests in the same worker on the
default storage directory. Save the previous value and restore it.
* test(core): restore CLINE_DIR even when hub test setup throws early
Restoring the override in the try/finally missed failures thrown during
transport construction or start(), before the try was entered. Register
the restore with onTestFinished instead, which runs regardless of where
the test fails.
* fix(desktop): don't show providers as configured without real credentials (#13608)
* fix(desktop): don't show providers as configured without real credentials
The desktop settings marked any provider with a persisted settings entry
as Configured, but legacy VS Code migration and empty saves can seed
entries (e.g. qwen-code, sapaicore) holding only a default model and no
credentials. Move the CLI's isProviderSettingsUsable readiness check into
@cline/core, expose it as a computed 'configured' flag on the provider
catalog, and use it in the desktop's isProviderConnected.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): resync catalog after saves so Configured badge updates live
Optimistic provider mutations can't know the sidecar-computed 'configured'
flag, so after connecting a keyless provider or saving cloud credentials
(e.g. a Vertex project id) the row stayed 'Not configured' until remount.
Silently refetch the catalog after each successful save, guarded by the
existing generation counter so newer edits discard stale responses.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): claim a generation in post-save resync so overlapping refreshes can't apply stale snapshots
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): bump catalog generation on OAuth login success
Every other optimistic provider mutation claims a new generation; the
OAuth success path didn't, so a catalog load or resync still in flight
could arrive late and overwrite the just-connected state.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): resync catalog after OAuth login instead of bare generation bump
The resync claims a new generation (discarding any stale in-flight
response) and its own fetch covers both the new OAuth connection and any
provider saved moments earlier, matching the post-save path.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): refuse checkpoint workspace restore when HEAD moved past the checkpoint (#13626)
* fix(core): refuse checkpoint workspace restore when HEAD moved past the checkpoint
Restoring a checkpoint runs git reset --hard, which moves the current
branch pointer. If commits were made after the checkpoint (by the user
or by the agent), the reset silently knocked them off the branch,
leaving them reachable only through the reflog.
Guard the reset: if HEAD no longer matches the commit the checkpoint
was created on, throw a descriptive error (including how many commits
would be dropped) instead of destroying history. Chat-only restore is
unaffected, and users who really want to discard the commits can reset
the branch manually first.
Fixes#13550
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): close the guard-to-reset race with an atomic ref update
The moved-HEAD guard read HEAD, ran further git commands, then reset
unconditionally, so a commit landing in that window could still be
knocked off the branch. Replace the reset's branch move with git's
native compare-and-swap (git update-ref HEAD <new> <old>), which fails
if HEAD no longer points at the verified commit, and follow with a bare
reset --hard to sync the index and worktree to the already-moved HEAD.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: hide history cost estimates for subscription-billed tasks (#13562)
* fix(vscode): hide history cost estimates for subscription-billed tasks
The task-header fix for subscription providers cannot reach history:
history rows render the stored totalCost (an API-rate estimate) and do
not know which provider ran the task, so the history page printed
$X.XXXX on every row and the recent-task chips in an empty chat view
rendered a $ chip even for subscription-billed tasks.
The SDK session records already persist the provider — the CLI's
history view uses it for exactly this — but the VS Code mappers dropped
it. Map it through both transports (HistoryItem.apiProvider for the
state-pushed taskHistory, TaskItem.api_provider for getTaskHistory) and
suppress the dollar figure per row when that provider's
usageCostDisplay is not "show", via a new useUsageCostVisibility
predicate shared by both surfaces.
Rows without a recorded provider (tasks predating the field, legacy
imports) keep showing the stored value — there is nothing to key
suppression on.
* test(vscode): e2e-verify history cost suppression in real VS Code
Seeds SDK session records (one openai-codex subscription task, one
anthropic usage-billed task) into the isolated CLINE_DIR before the
webview loads, then asserts in a real VS Code instance that both the
recent-task chips and the full history page render the dollar figure
only for the usage-billed task. Covers the two boundaries the unit
tests stub: on-disk records reaching getTaskHistory with provider
populated, and the provider listings delivering the subscription mark
to the webview.
* Fix scheduled tasks disappearing after desktop app updates (#13627)
* Fix hub-managed schedules being wiped by cron reconciliation on hub restart
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Require the virtual hub/schedules path when exempting specs from removal reconciliation
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Treat recorded source mtime as proof a spec is file-backed, closing the hub/schedules spoof gap
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(shared): discover global rules at ~/Cline/Rules (#13614)
The VS Code Rules tab resolves the Documents folder via
'xdg-user-dir DOCUMENTS', which prints bare $HOME when no user-dirs
config exists (WSL/headless installs), so it reads and writes global
rules at ~/Cline/Rules. The SDK's rule search paths only covered
~/Documents/Cline/Rules, so those rules never reached the system prompt.
Add the missing path to the search list.
Fixes#13542
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat: add searchable session history (#13420)
* feat: add searchable session history
Rebased onto main and updated to supersede the sidebar search dialog
from #13533: the sidebar search icon now opens the indexed command bar
(Cmd/Ctrl+P) instead of a sidebar-local cmdk dialog that eagerly loaded
the entire session history via loadAllSessions(). CommandDialog gains a
shouldFilter passthrough so server-ranked FTS hits are displayed as-is.
* fix: harden session history search
* fix: evict failed restoration sessions from search
* fix: preserve deletion when search eviction fails
* fix: address session search review feedback
* fix: preserve search suppression during reconciliation
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* Fix CLI crash when a remote MCP server is offline but enabled (#13639)
Remote (SSE/streamable HTTP) MCP connects run on the session.create
critical path, which the hub caps at 30s. Without a connect budget an
unreachable server spent the full 60s default request timeout (with the
SSE transport stuck in a reconnect loop), stalling session.create past
the hub deadline and tearing the whole session down - the interactive
TUI exited and one-shot runs failed. Stdio servers already have a
bounded initialize budget for exactly this reason; give URL clients the
same treatment with a 10s default connect budget that an explicit
timeout overrides in either direction.
Fixes#13597
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(vscode): prevent E2E worker teardown hangs (#13644)
* test(vscode): capture external URLs in E2E runs
* docs(test): clarify browser capture rationale
* Add a GitHub integration step to the onboarding (#13225)
* Add feature flags to the app
* React to account updates
* Address comments
* Add a GitHub integration step to the onboarding
* validate domain and fix errors on auth
* Hide the step behind a feature flag
* update version
---------
Co-authored-by: John Choi <john.choi@cline.bot>
* fix(ci): stop e2e worker teardown timeouts and deflake hub daemon e2e on Windows (#13646)
* fix(e2e): stop VS Code e2e worker teardown from timing out
The ext-vscode-test-e2e job has been failing on main with 'Worker teardown
timeout of 60000ms exceeded' even though every test passes. Playwright only
reports an Electron app as closed once the process exits AND every holder of
its stdio pipes is gone (ChildProcess 'close' waits on the extra fd3/fd4
pipes Playwright creates for Electron). Any VS Code descendant that outlives
the main process (chrome_crashpad_handler, GLib's 'dconf watch' helper,
xdg-open browser handlers, VS Code 1.135's agent host CLI subprocess that
logs 'unable to kill the process') keeps those pipes open, so app.close()
never resolves and the worker teardown hangs on it until its 60s timeout
fails the job.
Harness fixes, each removing one source of that wedge:
- closeAppForTeardown now SIGKILLs the whole process group (taskkill /T on
Windows) when app.close() times out, instead of only the main pid — and
does so even when the main process already exited, which is exactly the
wedged state. Playwright launches Electron detached, so pid == pgid.
- Launch VS Code with --disable-crash-reporter so no crashpad handler
outlives the app holding the harness pipes.
- Seed the fresh user-data-dir with chat.disableAIFeatures: true so VS
Code's own AI features (rolled out via server-side experiments, so CI
breaks without any repo change) never start their agent host process.
- Drop the page.close() teardown: closing VS Code's last window quits the
whole app, and ElectronApplication.close() on an already-exited app
deadlocks; the app fixture's app.close() closes windows itself while the
app is alive.
- Codex sign-in no longer opens a real external browser under E2E_TEST; the
codex-oauth test drives the OAuth callback itself, and the browser was an
orphaned process holding the harness pipes on the runner.
* fix(core): deflake hub daemon e2e tests on Windows runners
sdk-test on windows-latest fails intermittently in the hub daemon e2e
files:
- shutdown.e2e.test.ts dies with a bare 'Error: socket hang up'. That
message is the ws handshake (http.ClientRequest) failing, not the
/shutdown fetch (an undici failure prints 'TypeError: fetch failed'):
a freshly spawned bun daemon on a loaded 2-core Windows runner
occasionally drops its first accepted connection before writing the
upgrade response. Real hub clients reconnect with backoff, and the test
asserts shutdown behavior rather than first-connection reliability, so
openAuthenticatedSocket now retries transient handshake failures within
a 15s budget.
- singleton.e2e.test.ts times out waiting for daemon discovery: it still
used the 10s hang guard that 0cfc90158 already raised to 30s in
shutdown.e2e.test.ts for the same reason. Use the same 30s guard.
- Raise the e2e testTimeout to 60s so a test that legitimately spawns two
daemons back to back can survive slow-runner startups instead of the
discovery hang guard being cut off by the test timeout.
* feat(desktop): render tool output images as attachments (#13643)
* fix(desktop): render tool output images as attachments
Add support for displaying media returned by tool calls (e.g. screenshots)
as rendered images with expand-to-fullscreen capability instead of raw
base64 text. Introduces an `ImageCarousel` component for navigating
multiple images, propagates the expand handler to tool message blocks,
and extracts/validates output media in tool summaries.
* test: cover multi-image and canonical media extraction in tool output (#13645)
extractOutputMedia and the desktop tool-message rendering path were only
ever exercised with exactly one distinct valid image, and
canonicalInlineMedia (MCP-style type: "media" blocks for audio/video/file)
had zero coverage. Add tests for: multiple distinct images in one tool
result (parser + desktop carousel navigation), inline audio via the
mime_type key spelling, canonical video/file media blocks, and rejection
of an invalid canonical image block.
---------
Co-authored-by: Harrison <harrison@cline.bot>
* chore(desktop): release v0.0.20
* feat(sdk): add discovery boundary ahead of Agent Plugins support (#13017)
* ENG-2490: Propagate session aborts to teammates (#13647)
* fix(core): propagate session abort to teammates
* fix(core): persist aborted teammate tasks as cancelled
* fix(core): settle teammate work on session abort
* fix(core): isolate replacement runs from stale aborts
* refactor(core): narrow teammate task status metadata
---------
Co-authored-by: abeatrix <beatrix@cline.bot>
* fix(llms): use AI SDK 7 Langfuse telemetry (#13651)
* fix(llms): use AI SDK 7 Langfuse telemetry
* test(llms): cover Langfuse runtime context
* chore(llms): built-in model list update 1787907289186 (#13663)
* chore(llms): built-in model list update 1787907289186
Result of `bun run build:models`.
Includes updated model list and fixed formatting issues across codebase.
* test(llms): update GLM reasoning toggle expectation
* test: cover session search fallback on hub timeout and rejection (#13642)
* feat: add searchable session history
Rebased onto main and updated to supersede the sidebar search dialog
from #13533: the sidebar search icon now opens the indexed command bar
(Cmd/Ctrl+P) instead of a sidebar-local cmdk dialog that eagerly loaded
the entire session history via loadAllSessions(). CommandDialog gains a
shouldFilter passthrough so server-ranked FTS hits are displayed as-is.
* fix: harden session history search
* fix: evict failed restoration sessions from search
* fix: preserve deletion when search eviction fails
* fix: address session search review feedback
* fix: preserve search suppression during reconciliation
* test: cover sidecar search fallback on hub timeout and rejection
The existing search_sessions tests only exercised the index-hit and
empty-index-fallback paths with an immediately-resolved hub reply.
Add coverage for the two other realistic Hub-connection failure
modes the fallback is meant to tolerate: the hub call rejecting, and
the hub call hanging past the 750ms withSearchDeadline race.
---------
Co-authored-by: abeatrix <beatrix@cline.bot>
Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
* fix(core): refresh Cline models from live catalog (#13670)
* feat(ui): share attachment drop zone (#13672)
* feat(ui): share attachment drop zone
* fix(ui): cancel disabled attachment drops
* chore(ui): simplify drop zone surface
* chore(ui): release v0.2.0-next.8
* Chore/bump undici mermaid (#13675)
* chore(deps): bump mermaid to 11.16.1 and raise undici floor to 7.29.0
* chore(deps): patch js-yaml and body-parser in the npm-managed subprojects
* fix(llms): make Langfuse tracer detection survive minified release builds (#13680)
* fix(llms): recognize direct tracer providers
* fix(llms): make Langfuse tracer detection survive minified release builds
Release binaries are compiled with minify enabled, which renames classes,
so initializeLangfuseTelemetry's constructor-name guard never matched
"ProxyTracerProvider" and silently returned readiness=false in every
production build (hub log: "creating span processor" followed by
"initialized readiness=false" with no branch message in between). Dev runs
execute unminified source, which is why the same env vars worked there.
Replace every constructor-name comparison with checks that survive
minification: detect the proxy structurally via getDelegate, distinguish a
recording provider from the no-op fallback by its lifecycle methods, and
confirm our NodeTracerProvider registration by object identity. When a
foreign provider already owns the global slot, attach the Langfuse span
processor to it when it accepts processors, and otherwise shut down the
orphaned provider and report the rejection instead of bailing silently.
Verified by bundling the module with Bun minify:true against the real
OpenTelemetry packages: the previous code reproduces readiness=false
(provider class name mangles to "H2"), the new code initializes with
readiness=true.
* fix(vscode): prevent hook spawn failures from crashing the core process (#13422)
* fix(vscode): prevent hook spawn failures from crashing the core process
A hook child-process spawn failure emitted "error" on HookProcess with no
listener registered, which Node's EventEmitter turns into an uncaught
exception - killing the entire cline-core process instead of failing the
one hook open. Guard the emit behind listenerCount so the rejection (which
StdioHookRunner handles) is the only propagation path.
The trigger was a workspace root that no longer exists on disk passed as
the spawn cwd: Node reports a nonexistent cwd as a misleading ENOENT on
the launcher binary ("spawn /bin/sh ENOENT"). Validate cwd existence in
HookProcess right before spawning - falling back to no explicit cwd with
a warning that names the missing directory - and when a spawn still fails
ENOENT because the directory vanished in between, name it in the error
message instead of blaming the shell.
* fix(vscode): fail hooks with a missing working directory instead of relocating them
Running a hook whose assigned cwd no longer exists from the host
process's own working directory would let its relative paths read and
write an unrelated location (e.g. the IDE install directory). Reject
before spawning, with an error naming the missing directory; the runner
reports the hook as failed and the task continues. Also carry pre-spawn
failure messages into HookExecutionError details so the cause is not
reduced to a bare "exited with code 1".
* fix(vscode): thread task id into hook runner creation so execution telemetry fires (#13547)
The SDK hooks adapter created every hook runner without a task id, and
StdioHookRunner gates all captureHookExecution calls on one being set —
so the next variant emitted zero hooks.execution events while discovery
telemetry fired normally. Pass the task id (and tool name for the tool
hooks) at all five factory.create call sites, and pin the threading
with a regression test.
* chore(desktop): cut 0.0.21-beta.1
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: JasmineLCY <38378321+JasmineLCY@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
Co-authored-by: Max <maxpaulus43@gmail.com>
Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bee <abeatrix@users.noreply.github.com>
Co-authored-by: Renee Huang <100229782+reneehuang1@users.noreply.github.com>
Co-authored-by: Ara <arafat.da.khan@gmail.com>
Co-authored-by: Haley Park <haleypark.design@gmail.com>
Co-authored-by: cline-cloud[bot] <276134852+cline-cloud[bot]@users.noreply.github.com>
Co-authored-by: yzxcj797 <54314860+yzxcj797@users.noreply.github.com>
Co-authored-by: yzxcj797 <yzxcj797@users.noreply.github.com>
Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
Co-authored-by: 𝓜𝓲𝓼𝓼𝓪𝓻𝓲 𝓐𝓱𝓲𝓵 🌿 <143264692+missarii@users.noreply.github.com>
Co-authored-by: Dominic Cooney <dominic.cooney@cline.bot>
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
Co-authored-by: Harrison <harrison@cline.bot>
Co-authored-by: abeatrix <beatrix@cline.bot>
Co-authored-by: TheRealSpencer <32678829+TheRealSpencer@users.noreply.github.com>
* fix(vscode): continue the surviving session on resume instead of rebuilding with the original task text (#13175)
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilt the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
The preserved conversation history is the source of truth on resume, so
the fallback prompt now just asks the model to reassess the history and
continue, matching the legacy resume prompt which also never resent the
original task. User-typed text still takes precedence when provided.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): continue the surviving idle session on follow-ups instead of rebuilding
Stopping a turn keeps the session alive, but every idle follow-up (bare
Resume after Stop, and typed follow-ups after a completed turn) tore
that session down and rebuilt it from persisted task history before
sending. Continue the matching idle session in place instead, the same
way the CLI reuses the live session after an abort. Rebuilding from
history now only happens when no live session matches the displayed
task (task opened from history, extension host reload).
A bare resume still needs a prompt to start a turn, so it sends the
neutral [TASK RESUMPTION] prompt (shared with the rebuild fallback and
hidden from the transcript); user-typed content is echoed and sent
as-is. If the send lands while the abort is still settling, the runtime
auto-queues it and drains once the abort completes.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): consolidate follow-up send paths in SdkFollowupCoordinator
Now that idle follow-ups continue the live session in place, the
two-mode sendToActiveSession helper was redundant: its non-queued branch
duplicated continueIdleSession minus the bare-resume prompt. Split it
into a single-purpose queueToActiveSession and fold the idle no-task
send into continueIdleSession, flattening askResponse's decision tree
to: queue onto a running turn, continue a matching live idle session,
rebuild from history, or abandon.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): reuse the existing neutral resumption prompt for bare resumes
Drop the newly invented long resumption wording in favor of the phrase
that already existed as the no-history fallback and that the transcript
hiding logic and test fixtures recognize: '[TASK RESUMPTION] Please
continue where you left off.' The net change to resumeSessionFromTask
against main is now just deleting the branch that resubmitted
historyItem.task as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilds the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
Bare resumes now always use the neutral prompt that already existed as
the no-history fallback; user-typed text still takes precedence. This
matches the legacy resume prompt (responses.taskResumption), which only
ever included user-supplied text as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): hide synthetic prompts from the queued-prompt echo
A send that races a settling abort is auto-queued by the runtime, so a
bare Resume can reach the pending_prompt_submitted echo carrying the
synthetic [TASK RESUMPTION] prompt. Echoing it leaked model-facing text
as a visible user bubble and shifted the visible-user-message ordinals
that edit/regenerate mapping relies on. Filter synthetic prompts with
isSyntheticUserPrompt, keeping user attachments visible (matching
isSyntheticSdkUserMessage semantics).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): preserve LiteLLM input token limits (#13293)
* fix(vscode): preserve LiteLLM input token limits
* fix(vscode): prefer live LiteLLM model metadata
* fix(vscode): generalize private catalog metadata
* test(vscode): preserve llms exports in vscode lm mock
* fix(vscode): point provider signup URLs at their API key pages (#13337)
* fix(vscode): point Mistral signup URL at the general API keys console
The Mistral provider's signup link led to the Codestral console, which
issues Codestral-scoped keys that fail with 401 on api.mistral.ai — the
endpoint the provider actually calls. Point it at the general API keys
page instead.
Fixes#13288
* fix(vscode): deep-link DeepSeek and Fireworks signup URLs to their API key pages
Both pointed at marketing homepages; link straight to the key-creation
pages instead, matching the rest of the registry and the desktop app's
provider-key-urls map.
* fix(ci): always build the legacy bundle from the legacy-extension branch (#13349)
The combined-VSIX workflow took legacy-ref as a free-form dispatch input
with no publish-time validation (next-ref has one: publish requires main).
Any typed ref — a PR merge ref, an unprotected branch — would be built
into the published VSIX by the environment-less build job, and the publish
environment approver only ever sees an opaque prebuilt artifact, so the
approval protected the marketplace PAT but not the shipped bytes.
Remove the input entirely and hardcode the protected legacy-extension
branch, which makes that branch's protection rules load-bearing for
releases. The tested-sha pinning between test-legacy and build is
unchanged. publish-extension skill dispatch command updated to match.
* fix(ci): lock the legacy publish workflow to the legacy-extension branch (#13350)
The branch dispatch input was a free-form string with no validation. Both
jobs checked it out and ran full npm lifecycle scripts from it: the publish
job next to VSCE_PAT/OVSX_PAT (and npm run publish:marketplace executes a
script from that same ref with the PATs in env), and the test job with NO
environment approval at all while inheriting the workflow-level
contents/packages/checks/pull-requests write grants. A dispatch pointing at
e.g. refs/pull/N/head would run outside-contributor code with the
marketplace keys behind one approval, or with a repo-write token behind
none.
Remove the input and hardcode the protected legacy-extension branch, drop
the workflow-level permissions to contents: read, and elevate only the
publish job to contents: write (tag push + GitHub release). The branch
input's default was legacy-extension, so normal publishes are unchanged.
publish-extension skill dispatch command updated to match.
* fix(vscode): SDK remote-config parity — refresh coordination, session gating, and fail-closed opt-out (#13226)
* feat(desktop): native notifications (#13166)
* feat(desktop): native notifications
* macos target
* fix(desktop): isolate macOS dev app identity
* fix(desktop): address notification review feedback
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched (#13310)
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched
Toggling an auto-approve setting while a task is open writes
autoApprovalSettings into the StateManager's task-settings overlay
(updateAutoApprovalSettings -> setTaskSettings). The SDK controller never
cleared that overlay on clearTask/showTaskWithId (the legacy controller
did), so after New Task the stale overlay kept shadowing global settings
in getGlobalSettingsKey(): toggle RPCs were accepted into global state,
but every posted state still carried the overlay's old version, which the
webview rejects as not newer - the auto-approve checkboxes froze forever.
Restore legacy parity in SdkTaskControlCoordinator: drop the overlay
(persisting pending writes first) in clearTask() and before installing a
different task's proxy in showTaskWithId().
Fixes#13260
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* changeset
* test(vscode): add end-to-end regression test for auto-approve freeze after New Task
Wires the real StateManager, the real updateAutoApprovalSettings handler,
and the real SdkTaskControlCoordinator.clearTask() together with the
webview's version gate modeled on ExtensionStateContext, pinning the
end-to-end invariant behind #13260: checkbox toggles must keep reaching
the webview after a mid-task toggle followed by New Task. Verified the
test fails when the clearTaskSettings() call is removed from clearTask().
* fix implicit any in regression test
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): show provider web-search support under the settings toggle (#13328)
* feat(desktop): show provider web-search support under the settings toggle
The global Web search toggle silently does nothing unless the session's
provider offers native web search, which made the setting read as if it
worked with any provider. The desktop General settings row now explains
that only providers with built-in web search honor it, and shows a live
status line: which connected providers are ready to use it (no extra
setup needed), or an amber warning with a link to the Models section
when none of them support it.
Support is resolved in the webview via a new providerOffersModelTool
helper in @cline/llms (browser export), sharing the same builtin-manifest
source of truth as the runtime's supportsModelTool attachment check.
* fix(desktop): address review — refetch web-search status on catalog invalidation, clarify per-model support
Greptile P2: the one-time catalog fetch could race an in-flight provider
save and show stale status; the row now refetches when the provider
catalog cache is invalidated (fired after saves complete).
Greptile P1: the ready line implied every model on the provider works;
Vertex excludes Claude routes, so the copy now scopes the promise to
models that support it.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat(ui/desktop): collapse finished runs into a work summary and remove hover-state dead space (#13315)
* feat(ui): add WorkActivity collapsed-run summary and float message actions as a pill
WorkActivity/WorkActivityTrigger/WorkActivityContent fold a finished agent
run's working rows (tool calls, thinking traces, narration) behind a single
"Worked for 4m 12s · 14 tool calls" disclosure built on the shared animated
disclosure primitives, with formatWorkActivityLabel/formatWorkDuration
exported for consumers.
Message hover actions no longer rely on the transcript reserving blank space
below each message: the action row is now a self-backed pill (border,
blurred background, shadow) that floats over whatever follows, so
conversations can pack rows tightly without hover chrome colliding with the
next message.
* feat(desktop): collapse finished runs into a work summary and tighten chat spacing
collapseCompletedWork post-processes the grouped transcript: once a run ends
on assistant text with no further tool calls, its working rows fold into one
expandable WorkActivity row while the final answer stays visible. Runs are
delimited by user messages; the trailing run only collapses when the session
has stopped running and actually produced an answer, so live streams and
cancelled/failed tails keep their rows. Assistant messages carrying images
or media are treated as deliverables and never collapse.
The conversation list gap drops from gap-8 to gap-4 now that hover actions
are self-backed pills that need no reserved space, and user messages add
their own top margin so turn boundaries stay visually distinct.
* refactor(ui/desktop): work summary label wording, flat expansion, stable in-run rhythm
Feedback round on #13315:
- Label reads "Worked for 4m 12s and made 14 tool calls" instead of joining
with a dot; without a duration it falls back to "Made N tool calls".
- Expanded work rows render at transcript level — no rail or extra indent —
since tool rows and thinking traces already carry their own nesting when
expanded. The work content keeps the tight working-row rhythm.
- Live working rows (thinking traces + tool calls) now group into a 'run'
render item with the same tight 0.25rem rhythm, so there is no oversized
gap under a "Thought for Ns" row and every row keeps its exact position
when the finished run folds into the work summary. A trailing
answer-in-progress stays outside the group at transcript level, and pure
prose spans keep normal spacing.
- The transient "Thinking..." indicator moves inside the transcript column
and mirrors a trigger row's geometry, so the first real row replaces it in
place with no jump.
* style(ui/desktop): hover-pill metrics, right-pointing work chevron, scroll and spacing fixes
Another feedback round on #13315:
- Hover action pill: +2px internal padding, a trailing inset after the
timestamp (it sat flush against the pill border), and more clearance
between the message content and the pill (2px -> 6px; the hover bridge
grows to match).
- The work summary chevron points right while collapsed and continues
counterclockwise to point up when expanded.
- Conversation bottom padding drops pb-20 -> pb-8: the composer sits below
the scroller, so the padding only needs to clear a pinned action pill.
- Sending a message scrolls back to the bottom even if the reader had
scrolled up (new AutoScrollOnSend on the user-message count, which ignores
optimistic-bubble re-keying; @cline/ui now exports useConversation for
this).
- An assistant answer directly under its run's working rows pulls itself
0.5rem closer than the full transcript gap.
* style(desktop): leave a visible gap between a pinned action pill and the composer
pb-8 exactly matched the pill's ~40px footprint, so the last row's hover
actions sat flush against the composer top; pb-12 restores ~8px of daylight.
* style(desktop): widen the gap between the pinned action pill and the composer to ~24px
pb-12 left only ~8px of daylight under the pill; pb-16 reads comfortable
without reverting to pb-20's dead space.
* fix(desktop): keep the thinking indicator at the working-row offset mid-run
The indicator matched a trigger row's geometry but sat a full transcript gap
(1rem) below the last working row, while the tool/thinking row replacing it
joins the tight run group at 0.25rem — a visible upward jump. When the last
transcript item is working rows (or streamed assistant output), the
indicator now pulls up to the same tight offset; only at the start of a run,
under the user message, does it keep the normal gap.
* style(ui): calm the hover actions surface per team feedback
Borderless rectangle instead of the bordered pill: radius drops to
var(--radius), the side padding goes entirely (the icon buttons carry their
own hit areas), and the vertical padding halves. Blurred background and
shadow stay so it remains legible over following content.
* feat(ui/desktop): full-band hover reveal and iOS-style disclosure easing
The hover actions only appeared while the pointer was inside the message
box itself. The invisible bridge under each message now spans the full
height of the band the floating actions occupy (full row width), so
hovering anywhere in that strip reveals them. Sibling row types
(.cline-chat-tool, .cline-chat-work, and the desktop's run/tool groups)
become position: relative so they paint above the bridge — their own
content keeps its hover and clicks, and the bridge only wins in the band's
genuinely empty space.
All expandable rows (work summary, tool panels, thinking) open and close on
a 240ms symmetric ease-in-out cubic-bezier instead of the 60ms snap, with
chevron rotation on the same curve. Reduced-motion still disables both.
* revert(ui/desktop): drop the full-band hover reveal; quicken disclosure easing to 180ms
The full-band hover bridge (and the position: relative changes that made it
safe) is reverted per feedback — back to the narrow bridge that only spans
the gap under the message. The iOS-style ease-in-out on disclosures stays
but speeds up from 240ms to 180ms.
* fix(ui): recover live tool diffs that mount as a blank pierre skeleton
Live-streamed edit rows could show an empty diff for the whole run, with the
diff only appearing after the collapsed work row was expanded (fresh mount).
Root cause, confirmed by driving a live session and inspecting the element:
React StrictMode double-invokes @pierre/diffs' ref callback; the first
instance's async highlight work aborts on its immediate cleanup, and the
second instance adopts the abandoned half-rendered shadow tree as if it were
complete prerendered output — zero height, no code, no theme stylesheet,
permanently. A rendered diff always carries style[data-theme-css] in its
shadow root, so ToolFileDiff now checks for it shortly after mount and
remounts FileDiff (bounded attempts) when missing; the fresh host element
takes the normal render path and recovers within ~400ms. Verified live: the
diff now renders during the run.
* fix(desktop): keep interrupted runs expanded even with partial trailing text
The trailing-run collapse gated on 'ended with assistant text', which
misread a Stop that landed mid-answer as a finished run and folded the tool
calls the user wants to inspect. The gate is now the terminal status itself:
only completed (or restored-idle) sessions collapse the trailing run;
cancelled/failed/error tails keep their rows regardless of partial text.
(Greptile P1 on #13315 — matches the PR's stated rule.)
* feat(ui): share the markdown pipeline, chat polish, and ThinkingBlock across products (#13323)
* feat(ui): share the markdown pipeline, chat polish CSS, and ThinkingBlock
The desktop app and the cloud dashboard both consume @cline/ui yet rendered
assistant output differently, because Markdown policy and the thinking-trace
row lived app-side. This moves the shareable parts into the package:
- components/markdown (new export): the lazy Shiki code highlighter (GitHub
light/dark, pinned language set) and agentMarkdownControls — the standard
Streamdown configuration. streamdown/shiki/@shikijs/* become optional peer
dependencies, mirroring @pierre/diffs.
- components/markdown.css: the desktop's chat polish moves in — chat-scale
headings, outside list markers, single quiet code blocks with a
hover-revealed copy control, table cards. Kept unlayered so it beats
Streamdown's layered Tailwind utilities without !important.
- ThinkingBlock + formatThoughtLabel in agent-chat: the standard thinking
row (brain icon, Thinking/Thought-for-Ns label, streaming shimmer, rail
presentation, capped scrollable body). The shimmer and the
reasoning-hover-suppression rule move into agent-chat.css; triggers gain
the color transition the desktop applied locally.
Version bumps to 0.2.0-next.5 for the dashboard to pick up.
* refactor(desktop): consume shared markdown and thinking primitives from @cline/ui
The local Shiki highlighter, Streamdown controls, chat markdown polish CSS,
streaming-title shimmer, and reasoning hover-suppression rule are deleted in
favor of the @cline/ui versions (the highlighter test moves to the package's
suite). ReasoningBlock becomes a thin wrapper that hands MemoizedMarkdown to
the shared ThinkingBlock, and formatThoughtLabel re-exports from the package
so grouping code and tests keep their import path.
globals.css now imports @cline/ui/components/markdown.css (unlayered, so the
polish keeps beating Streamdown's layered utilities); the app keeps only what
is genuinely app-specific: link/image policy in markdown.tsx, selectability
rules, accent palettes, and the view-enter transition.
* style(ui/desktop): make thinking-trace prose legible
Thinking body text rendered too faint: plain muted-foreground plus the
desktop's font-thin weight. The shared thinking content now leans 75% of the
way back toward the body text color (still slightly de-emphasized), and the
desktop drops the thin font weight.
* ci(ui-publish): build @cline/shared before ui typecheck (#13354)
@cline/ui's generated-media imports @cline/shared/browser, which resolves to
shared's dist output. The build-shared step sat after typecheck/test/build,
so the first ui-publish dispatch since #13025 failed at Typecheck UI with
TS2307. Move the step to right after install.
* fix: run_commands object form without args routes through the shell instead of failing with ENOENT (#13336)
* fix: run_commands object form without args routes through the shell
The structured { command, args? } form of run_commands was always spawned
directly with shell: false. When a model emitted a full command line in
command with no args (e.g. { command: "echo hello" }), spawn failed with
ENOENT for any command containing a space, breaking command execution for
the whole session.
Direct exec now only applies when a non-empty args list is provided; the
object form without args is routed through getShellInvocation like the
string form. Schema descriptions are tightened so models put arguments in
args instead of embedding them in command.
Fixes#13279
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: trim structured-command schema descriptions
The union schema is only used for lenient validation of input the model
already sent; its descriptions never reach a model prompt. Keep them
short instead of restating executor behavior.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: simplify direct-exec comment in shell executor
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert: keep original structured-command schema description
The description never reaches a model prompt and the executor now handles
both shapes, so the wording change was cosmetic noise.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: gate direct exec on args key presence, not array length
Review feedback: an explicit empty args array is intentionally structured
input and stays direct exec; only an object with no args key is treated
as a full shell command line. Matches the key-presence rule already used
by the VS Code host's formatCommandForTerminal. Also replaces the
empty-args shell test (which was PowerShell-incompatible) with a test
pinning the direct-exec contract.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: normalize Gemini custom base URLs for legacy host-root values (#13329)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* docs: add GLM-5.3 to ClinePass models and reference pricing (#13357)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): stream run command output (#13179)
* feat(desktop): stream run command output
* fix(sdk): clean up detached command logs
* fix(sdk): reap detached logs after hub restarts
* fix(sdk): preserve live detached command logs
* fix(desktop): harden live command progress
* fix(sdk): recover detached logs for local hosts
* fix(desktop): reconcile command output tool rows
* fix(sdk): retain logs for surviving commands
* fix(core): prevent PID reuse from retaining detached logs
* fix(core): preserve detached logs on probe failures
* fix(core): retain detached logs during probe outages
* fix(desktop): resolve leftover merge conflict in messages projection test
Combine both sides of the assertion: main's incremented per-block
createdAt projection and this branch's toolCallId/hookEventName meta.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): make TUI dialog colors follow theme changes live (#13355)
* fix(cli): make TUI dialog colors follow theme changes live
Dialog content previously read the static palette constant, so open
dialogs (including the theme picker itself) kept the default dark-blue
accents while scrolling through theme previews. Add getDialogPalette /
useDialogPalette, which resolve dialog colors from the active theme's
dialog accents and re-render on every theme change, and migrate all
dialog-rendered components to it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(cli): derive dialog panel background from the active theme
Dark themes now lift their own background one OKLAB step for the dialog
surface, so panels keep the theme's hue instead of the library's fixed
#262626. DialogThemeSync pushes the surface into the dialog container
for new dialogs and repaints open panels, so the surface also follows
live theme previews. Light themes keep the neutral dark panel to match
the dark accent fallback and the light-on-dark dialog text.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: skill slash commands load via the skills tool instead of expanding into the user message (#13327)
* fix(desktop): show typed slash command instead of expanded skill markdown
The sidecar expands /skill and /workflow tokens into their instructions
before dispatching, so the runtime's persisted transcript only contains the
expanded text. After a turn (and when reopening a session) the webview
re-hydrates from that history and rendered the whole SKILL.md body as the
user's message; queue events echoing the expanded prompt could also add a
second user bubble, and fresh sessions were titled with the markdown's first
line. The CLI never shows this because its TUI keeps the typed text in its
own transcript and only sends the expanded prompt to the model.
Mirror that separation inside the desktop sidecar's display boundaries:
- history projection (readSessionMessages) inverts user text that starts
with a configured command's instructions back to '/name remainder',
which also repairs sessions recorded before this fix
- queue snapshots and chat_queued_prompt_start events echo the typed
prompt recorded at expansion time, so the webview's optimistic-bubble
re-key matches again
- an untitled session sent an expanded prompt gets titled from the typed
command instead of the instructions' first line
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't overwrite a mid-turn rename with the typed-command title
The untitled check ran before dispatch, so renaming a fresh slash-command
session while its first turn was running got clobbered by the post-turn
typed-command title. Re-check at write time and only replace a missing title
or the one the runtime auto-derived from the expanded prompt.
Also documents the inherent prefix-inversion ambiguity flagged in review:
text hand-typed with a command's exact instructions persists byte-identically
to that command's expansion, so stored history alone cannot distinguish them.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop expanding skill commands; let the skills tool load them
Pasting the skill body into the prompt is why the transcript could ever show
it: the desktop webview re-hydrates from the runtime's persisted history, so
whatever the sidecar splices into the user message renders as if the user
typed it. The runtime already registers the skills tool, whose description
requires the model to invoke it whenever the user references a slash command
— so send the typed /skill text through and let the tool deliver the
instructions as a tool result (previously they arrived twice: pasted and via
the tool). The persisted user message, session title, and queue entries are
then simply the typed command, which deletes the typed-prompt registry, the
queue event/snapshot rewriting, and the title machinery from the previous
approach.
Workflows are not served by the skills tool and keep textual expansion, so
the read-time display inverter stays: it collapses expanded workflow prompts
— and skill prompts persisted before this change — back to the typed
/command in the history projection.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): option to keep skill slash commands typed for the skills tool
resolveRuntimeSlashCommandFromWatcher (and the hub snapshot proxy) accept
expandSkillCommands: hosts whose sessions register the skills tool pass
false so the typed /skill goes through and the model loads the instructions
as a tool result, keeping the persisted transcript as what the user typed.
Workflows always expand — the tool does not serve them. isSkillsToolAvailable
exposes the catalog check hosts use to decide (yolo preset and the skills
tool toggle leave textual expansion as the only delivery path).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): skill slash commands load via the skills tool instead of expanding
The TUI user-command wrap and buildUserInputMessage now keep a typed /skill
as-is when the session's mode/toggles register the skills tool, matching the
desktop app; workflows keep expanding, and yolo (zen) keeps expanding skills
because its preset has no skills tool. This also fixes CLI resume/history
surfaces showing the skill body: the persisted user message is now the typed
command.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): keep configured skill slash commands typed for the skills tool
expandSlashCommands no longer splices a configured skill's instructions into
the model text; the SDK session's skills tool delivers them as a tool result
(previously they arrived twice). Builtin pseudo-skills like /deep-planning
are not served by that tool and keep expanding, as do workflows.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): use the shared skill-expansion option in the sidecar
Replaces the sidecar's workflow-detection dance with core's
expandSkillCommands option and gates on isSkillsToolAvailable, restoring
textual expansion where the tool is missing (yolo mode or the skills tool
toggle) — a gap in the previous desktop-only change.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): drop the display inverter for expanded transcripts
Accepted trade-off to keep the change minimal: sessions recorded before
skills switched to the skills tool, workflow sends (deprecated), and
yolo-mode skill sends persist expanded instructions and now render that text
as-is instead of being collapsed back to the typed /command at projection
time.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Use fixed selection chevron in account dialog to match other dialogs (#13364)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): align system prompt with session mode (#13361)
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(desktop): finalize queued turns on chat_done with canonical history reconcile (#13330)
Turns that settle through the event stream (queued prompts, including the
first prompt of a fresh session) resolve their send() RPC early, so nothing
cleared the streaming shimmer or reconciled live-streamed content against
the persisted transcript at turn end. A turn whose deltas were incomplete
stayed visually streaming forever and only healed when a later non-queued
send rehydrated history.
chat_done (and chat_session_ended / the queue-drain double check) now clears
the active assistant streaming id and schedules a short-delayed
read_session_messages + applyCanonicalHistory, guarded by turn epoch,
session id, and in-flight send submissions so it never clobbers a newer
turn or duplicates the blocking send path's own finalization.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(desktop): release v0.0.14
* fix(clients): filter non-chat models from chat pickers (#13317)
* fix(clients): filter non-chat models from chat pickers
* fix(clients): align chat model eligibility
* fix(desktop): strip user_input envelope when copying a user message (#13369)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bee <abeatrix@users.noreply.github.com>
* docs: show DeepSeek V4 peak and off-peak pricing (#13312)
* docs: update DeepSeek V4 average pricing
* docs: show DeepSeek peak and off-peak pricing
* docs: add GLM-5.3 reference pricing (same as GLM-5.2)
* docs: add GLM-5.3 to ClinePass models table
* fix(llms): display billed gateway cost (#13385)
* fix(shared): run PowerShell commands with fail-fast error semantics (#13358)
* fix(shared): run PowerShell commands with fail-fast error semantics
The run_commands PowerShell wrapper never set $ErrorActionPreference, so
the default 'Continue' applied: a pipeline erroring per item (e.g. a
malformed Where-Object over Get-ChildItem -Recurse) emitted one error
record per enumerated file - tens of thousands of stderr records on
large trees, looking like a hang - and could still resolve as SUCCESS
with exit 0.
Prepend $ErrorActionPreference='Stop'; to the script content executed
by the ScriptBlock so the first error terminates the command with a
non-zero exit and a single error message. Concatenated on the same line
as the user command so error line numbers stay unshifted.
Fixes#13285
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(shared): set the fail-fast preference in the bootstrap scope
Setting $ErrorActionPreference='Stop' by string-prepending it into the
scriptblock source displaced a leading param(...) from its mandatory
first-statement position, so scripts beginning with a param block failed
with CommandNotFoundException. Preference variables are dynamically
scoped, so setting Stop in the -Command bootstrap gives the invoked
scriptblock identical fail-fast semantics while keeping the user script
byte-identical (param works, error positions unshifted) and drops the
doubled-quote escaping.
* docs(shared): document the fail-fast tradeoffs in the PowerShell wrapper
Stop promotes every non-terminating error, not only per-item pipeline
floods: partial-result commands (recursive listings over access-denied
junctions) now stop at their first error, and Windows PowerShell 5.1
turns in-script stderr redirection of succeeding native commands fatal.
State this in the wrapper comment as a deliberate tradeoff, with the
GitHub Actions precedent and the per-command opt-outs.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
* ci: stop over-long changelogs from silently dropping release Slack posts (#12955)
Slack section blocks reject text longer than 3000 characters. The Slack
action logs that rejection as ##[error] but does not fail the step, so an
over-long changelog drops the release announcement while the run stays
green — cline@3.0.50 (3272 chars) published to npm, tagged, and cut a
GitHub release with no Slack post and nothing red to notice.
Every publish workflow pasted the changelog section verbatim into one
section block, so all six were exposed; the SDK, desktop, and extension
sections were only 150-350 chars under the ceiling.
Add a slack_content output alongside content: unchanged when the section
fits, otherwise trimmed on a line boundary with a link to the full
release notes. Only the Slack payload uses it — GitHub release bodies and
the desktop updater manifest still get the whole section.
* ci: tidy workflow cache config and job permissions (#13403)
Publish workflows now always do clean npm installs (no dependency
cache in their test gates), the e2e workflow's cache keys are
exact-match only, and the e2e job drops an id-token permission it
never used.
* Rename desktop app from "Cline Code" to "Cline" (#13401)
* Rename desktop app from Cline Code to Cline
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Format touched Rust test assertions
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): surface provider-executed tool activity as observational events (#13300)
* fix(llms): surface provider-executed tool activity as observational events
Provider-executed tool parts (e.g. every tool the Claude Code CLI runs
inside its own session) were dropped by the model-tool guard added for
web search: only declared model tools were re-emitted, everything else
hit continue with nothing yielded. Those sessions modified the workspace
with no tool activity in runtime events, transcripts, or the UI.
Route all providerExecuted parts onto the observational path instead:
emit execution-tagged tool-call-delta and tool-result events, matched by
tool-call ID for providers that omit the flag on the result half. They
stay out of AgentRuntime's execution/approval loop, and the runtime
already persists them as modelToolActivities and projects them for
display.
The AgentModelEvent tool-result variant widens toolName from
ModelToolName to string to carry the provider's own tool names.
* fix(agents): keep turns that are only provider-executed tool activity
A turn consisting solely of observational tool activity has an empty
assistant content array - the activity lives in message metadata, since
projecting it into content would replay tool_use blocks the model never
gets results for. The empty-content guard threw on such turns, erroring
the run and losing the activity from the transcript. Count model-tool
activity as content for the emptiness check (error finishes still
throw); replay stays safe through the codec's empty-content placeholder.
Also drop the trailing text delta from one gateway test so the tool-only
stream shape stays covered end to end.
* feat: allow agents to create scheduled tasks (#13331)
* feat(core, desktop): add durable todo agenda
* fix(desktop): secure todo approvals and track tool usage
* fix(desktop): clean up failed approval delivery
* fix(desktop): authenticate approval connections
* fix(desktop): cancel approvals on broadcast failure
* fix(desktop): authenticate development approvals
* fix(desktop): harden development approvals
* test(core): make task paths cross-platform
* fix(desktop): serialize approval readiness
* refactor(core): unify todo and schedule tools
* feat(core): distinguish user todos from agent suggestions
* fix(core): hide tasks tool in yolo mode
* fix(core): enforce schedule workspace scope
* fix(core): bind schedule scope to hub connection
* fix(core): establish task scope at hub startup
* fix(core): scope task automation by workspace
* test(core): normalize workspace path expectations
* test(core): serialize Windows CI workers
* fix(core): reject unregistered schedule authority
* fix(desktop): guard task execution commands
* fix(core): avoid polynomial regex in mention parsing
* fix(core): address schedule tool review feedback
* fix(core): bind websocket clients to hub workspace
* fix(core): flatten tasks tool input schema
* fix(core): authorize multi-workspace hub clients
* test(core): type hub transport authority mock
* fix(cli): register a workspace client for remote schedule commands (#13398)
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(desktop): treat ClinePass as OAuth-managed in the chat credential gate (#13404)
* fix(desktop): treat ClinePass as OAuth-managed in chat credential gate
ClinePass shares the Cline account OAuth credentials (its auth handler
stores under the "cline" provider), so the webview never sees a plain
API key for it. The chat pre-flight check only exempted cline/oca/
openai-codex, so switching to ClinePass while signed in via OAuth
blocked with "Missing API key" even though the sidecar resolves the
stored access token fine (which is why the CLI worked).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style: format helpers.test.ts with biome
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(ui): stack code block lines when streamdown lineNumbers is off (#13412)
streamdown renders each Shiki token line as a bare inline span with no
newline text between non-empty lines, and only applies its block line
class when lineNumbers is on. With lineNumbers off (the desktop app's
config) every multi-line fenced block collapsed into one run-on line.
Make the direct line spans under code-block-body display: block in the
shared markdown.css; empty lines keep their height via their lone "\n"
child under white-space: pre.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): work summary undercounts wall time when pre-tool thinking attaches to the answer (#13413)
* fix(desktop): anchor work summary duration on the answer row, not attached pre-tool reasoning
The collapsed 'Worked for Xs' row undercounted wall time whenever a turn's
assistant message contained thinking + tool_use with no narration text: the
canonical projection emitted the reasoning-only row after the tool row (both
stamped before the tool executed), the webview attached that row to the final
answer, and collapseCompletedWork used the answer's earliest attached
reasoning timestamp as the end anchor - excluding the entire tool execution
(e.g. 'Worked for 5s' for a turn with an 8s command).
- webview: end the work span at the answer row's own timestamp, clamped to
the last collapsed row so a fallback answer bubble with a synthetic early
timestamp cannot shrink the duration either
- sidecar: flush pending thinking before a tool_use row so rehydrated
transcripts keep the live-stream order (thinking before its tool call) and
pre-tool reasoning no longer rides on the next answer
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep interleaved thinking between the tool calls it separates
Address Greptile review: when one assistant message interleaves thinking
between multiple tool_use blocks, each reasoning segment now projects at its
own position (attached to a text row from its own segment when present,
otherwise as its own row) instead of merging into the first reasoning row,
which displayed later thinking before a tool call it actually followed.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): remove settings gear hover state while Account screen is open (#13408)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't show "No sessions found" while session history is still loading (#13414)
* fix(desktop): don't show 'No sessions found' while session history is still loading
Replace the isLoadingHistory flag with hasLoadedHistory, set only once the
backend has actually answered a list_discovered_sessions request. The sidebar
and Sessions view now keep their loading state until that first definitive
response, so the empty-state copy can no longer appear while history is still
being fetched (or while a failed fetch is being retried).
Also retry a failed initial fetch on the 2s event cadence instead of stranding
the UI until the 12s periodic poll, which is what stretched the misleading
empty state to ~10 seconds after a webview reload when the websocket lost the
race with the page load.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop history fast-retry from re-arming after hook unmount
A failed initial fetch that settles after the hook unmounted could schedule a
new retry timer after cleanup had already cleared the refs, leaving the
abandoned hook polling the backend every 2s. Guard scheduleRefresh with a
disposed ref set by the mount effect's cleanup.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix @ file mentions breaking on paths with spaces (#13391)
* Fix @ file mentions breaking on paths with spaces
Quote mentions generated by getFileMentionFromPath (Add to Cline /
Fix / Explain / Improve commands) when the relative path contains
spaces, so the mention regex no longer truncates the path at the
first space. Also quote the path part of workspace-prefixed mentions
(workspace:/path with spaces) inserted from the @ context menu, which
previously bypassed quoting because the value does not start with '/'.
Fixes#13338
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix import ordering in mentions test (biome organize imports)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reduce fix to minimal scope
Revert the webview quoting refactor and extra tests; keep only the
getFileMentionFromPath quoting fix with a single regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Normalize mention paths to posix separators for Windows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix code actions failing with "command not found" on VS Code 1.134 (#13402)
* Fix @ file mentions breaking on paths with spaces
Quote mentions generated by getFileMentionFromPath (Add to Cline /
Fix / Explain / Improve commands) when the relative path contains
spaces, so the mention regex no longer truncates the path at the
first space. Also quote the path part of workspace-prefixed mentions
(workspace:/path with spaces) inserted from the @ context menu, which
previously bypassed quoting because the value does not start with '/'.
Fixes#13338
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix import ordering in mentions test (biome organize imports)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reduce fix to minimal scope
Revert the webview quoting refactor and extra tests; keep only the
getFileMentionFromPath quoting fix with a single regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Normalize mention paths to posix separators for Windows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix code actions failing with 'command not found' on VS Code 1.134
Code action commands carried arguments (expandedRange, diagnostics),
which routes them through VS Code's CommandsConverter cache. VS Code
1.134 disposes the cached entries before the clicked action executes,
so every lightbulb action failed with 'Actual command not found,
wanted to execute cline.addToChat'.
Drop the arguments so the command id is passed through directly, and
recover the context in the handler instead: getContextForCommand now
expands an empty selection by 3 surrounding lines (matching the old
provider behavior) and gathers document diagnostics intersecting the
range when none are passed explicitly.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Scope gathered diagnostics to the selection/cursor
Match the old CodeActionContext.diagnostics behavior: only include
diagnostics intersecting the range the action was requested for, not
the surrounding lines the text gets expanded to.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Desktop: unify Plugins, MCP, and Skills into one Plugins hub with a dedicated Marketplace page (#13411)
* Unify desktop plugins, apps, MCP, and skills into one Plugins hub with a Browse directory mode
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Open the marketplace directory as a modal over the Plugins hub instead of swapping the page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename directory to Marketplace: Browse Marketplace button, Marketplace modal title with icon, search placeholder
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix search input focus ring clipped by the Marketplace modal scroll container
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Address Greptile review: keep selected tag chip visible when its count drops to zero, and remount installed tab when a marketplace install completes after the modal closed
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Track marketplace modal mutation flag in a ref so a close click racing a queued render cannot skip the inventory remount
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make Marketplace its own settings page under Customizations and restore Channels as a standalone page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Remove icon from Marketplace page header for consistency with other settings pages
* Notify mounted inventory views when the marketplace invalidates the cache so late install completions refresh the Plugins hub
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop/ui): recommended and free model tiers in the composer model selector (#13410)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK (#13415)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): recommended-feed badges and descriptions in provider settings (#13416)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* feat(desktop): recommended-feed badges and descriptions in provider settings
Review suggestion on #13410: the provider settings page has room for
more model detail than the composer's picker. The cline/cline-pass
provider cards now refresh their model list through
list_provider_models (the catalog snapshot deliberately skips the
recommended-feed overlay so the startup catalog fetch never blocks on
the feed) and render Recommended/Free tier badges plus feed tags (NEW)
next to the model name, with the model description underneath. The
refreshed list also surfaces the live entries instead of the bundled
snapshot.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): scope the settings featured model list to its provider and revision
The fetched featured list was unscoped component state: switching
between cline and cline-pass reused the component instance, so the
previous provider's models stayed visible while the new request was
pending (or forever, when it failed), and the retained copy shadowed
later provider.modelList updates — adding a second custom model
submitted the stale list as the complete configuration and dropped the
first addition.
The fetched list now only applies to the provider and modelList
revision it was fetched for (falling back to the catalog snapshot
otherwise and refetching on membership changes), and add-model submits
the union of the displayed and configured ids so an update can never
silently unconfigure existing entries.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(ui): update packed-Tailwind smoke contract for the picker's max-h-64 (#13421)
The ui-publish smoke check pins a set of Tailwind candidates the packed
sources must emit; #13410 grew the SearchCombobox options list from
max-h-56 to max-h-64, so the publish run failed on the stale candidate.
All other pinned candidates verified against the current sources.
* feat(desktop): refresh app icons and branding (#13400)
* ci(vscode): upload E2E failure recordings from the right path (#13427)
The job sets working-directory: apps/vscode, but that default applies to run
steps only, not to `uses:` steps. Since #10961 moved the extension under apps/
and added that default, the artifact path has resolved against the repo root,
matched nothing, and every failing run logged "No files were found with the
provided path: test-results/playwright/" instead of uploading recordings.
Widen to test-results/ so Playwright's error-context snapshots ship alongside
the videos.
* fix(hooks): deliver tool hook contextModification to the model (#13297)
* fix(hooks): deliver tool hook contextModification to the model
On the next engine, a tool_call (PreToolUse) hook's contextModification
was parsed into HookControl.context and then silently dropped: the
runtime beforeTool/afterTool result contract had no channel for
injecting conversation context. Legacy consumed it (ToolExecutor /
ToolHookUtils pushed <hook_context> blocks into the next user turn), so
this was a regression of documented behavior.
- Add appendContext to AgentBeforeToolResult/AgentAfterToolResult.
- AgentRuntime collects appendContext across hooks during an
iteration's tool executions and appends one <hook_context> user
message after the tool results, keeping tool-result parts contiguous.
- Map HookControl.context into appendContext in both subprocess hook
layers (skipped when the hook cancels, matching legacy, where the
message doubled as the error).
- Truncate injected context at 50KB per hook output, matching legacy.
- Concatenate appendContext across merged hook layers.
tool_result (PostToolUse) hooks still run detached with stdout ignored;
making them blocking so their context can be collected is a follow-up.
Ref: https://linear.app/cline-bot/issue/CLINE-2987
* fix(hooks): stamp tool identity on injected hook context blocks
Contexts are batched into one message after the tool results, and
parallel tool execution collects them in completion order, so position
alone cannot attribute a block to its tool call. Add tool_name and
tool_call_id attributes to each <hook_context> block.
* fix(hooks): sanitize hook context block markup
Attribute values (tool_name, tool_call_id) are stripped of quote/angle
characters and embedded </hook_context> closers in hook output are
neutralized, so neither provider-supplied ids nor hook text can corrupt
or spoof a block's stamped identity.
* fix(hooks): neutralize forged opening hook_context tags in hook output
The previous sanitization only neutralized closing tags, so hook output
could still open a forged <hook_context> block claiming another tool's
identity. Escape both opening and closing embedded tags with one rule.
* fix(hooks): hide injected hook context from user-facing transcripts
Stamp the injected hook-context user message with displayRole 'system'
(the compaction-summary convention) so it reaches the model but does
not render as a user bubble in live or replayed transcripts. Without
this, resuming a session showed the raw <hook_context> block as if the
user had typed it.
* fix(hooks): neutralize case-variant embedded hook_context tags
The tag-neutralization regex was case-sensitive, so hook output could
still smuggle a forged tag as <HOOK_CONTEXT>. Match case-insensitively.
* fix(vscode): map PreToolUse contextModification into runtime appendContext
The extension's hooks adapter bridged file hooks into the SDK runtime
but forwarded only cancel/errorMessage, so a PreToolUse hook's
contextModification never reached the model. Map it into the runtime's
appendContext channel; HookFactory already truncates it at 50KB.
* fix(vscode): hide hook-injected context from replayed transcripts
Live sessions never rendered the injected <hook_context> user message,
but session reload replayed it as a user bubble (and post-resume turns
kept doing so). Treat these messages as synthetic in the user-message
mapping: honor the displayRole 'system' stamp the runtime sets, with a
text-prefix guard for paths where metadata is unavailable. This also
keeps edit/regenerate ordinal mapping aligned with visible bubbles.
* fix(hooks): run file hooks through exactly one layer per host
The VS Code extension registered two independent hook execution layers:
its own hooks adapter (config.hooks) and the SDK core's file-hook
extension from the runtime bootstrap. When both discover the same hook
files, every hook executes twice per event — and with context injection
wired, each contextModification would be injected twice.
Add a 'hooks' runtime config extension kind (in the default set, so the
CLI keeps core file hooks unchanged) and gate the bootstrap's file-hook
extension on it. The extension excludes 'hooks' at session start, so
its adapter — which also provides the hook status UI and the
hooksEnabled setting — is its single execution path.
* fix(vscode): discover hooks from the session workspace, not only global state
Hook discovery read workspaceRoots from global state shared across
every Cline instance, so another window repointing it made workspace
hooks silently stop being discovered. With the extension's adapter now
the single hook execution layer, that meant no hooks at all.
HookFactory takes an optional sessionWorkspaceRoot and unions that
root's .clinerules/hooks into discovery (and into cwd resolution), fed
from the session config's cwd. Shared-state discovery still works, so
behavior in the single-window case is unchanged.
* fix(hooks): keep sanitized hook attribute values distinguishable
Replacing every markup delimiter with the same underscore could
collapse two tool call ids that differ only by such a character into
identical stamps. Escape each delimiter with a distinct token instead.
* fix(hooks): make hook attribute sanitization injective
Escaping the underscore itself turns the attribute escaping into a
uniquely decodable code, so no two distinct tool call ids can collapse
to the same sanitized stamp (previously an id containing a literal
escape token could collide with an id containing the delimiter).
* fix(vscode): reconstruct hook status rows when replaying transcripts
hook_status messages are emitted live but never persisted, so reloading
a session dropped every hook row. The injected <hook_context> blocks
carry the hook source and tool name, so the replay translator now
rebuilds a completed hook status row from each block. The injection is
also no longer treated as a user turn boundary, so the final turn's
completion retag is unaffected by it.
* fix(hooks): collect PostToolUse hook output and honor its control (#13298)
* fix(hooks): collect PostToolUse hook output and honor its control
tool_result (PostToolUse) hooks ran fire-and-forget with stdout
ignored, so their entire JSON output — contextModification and cancel —
was discarded. Legacy awaited PostToolUse, injected its
contextModification into the conversation, and honored cancel.
- Run tool_result hook commands blocking (same 120s default timeout as
tool_call) in both the hook-config-file layer and the agent-hook
subprocess layer.
- Map their output: cancel stops the run with the hook's error message
as the reason; otherwise context is injected via afterTool
appendContext.
This restores legacy blocking semantics: tool results now wait for
tool_result hooks, but only in sessions that have one configured.
Ref: https://linear.app/cline-bot/issue/CLINE-2987
* fix(hooks): bound tool_result hook wait and isolate cancel reason
Address review findings:
- The agent-hook subprocess layer forwarded an unset timeoutMs
unchanged, so a tool hook command that never exits would block the
agent indefinitely. Default both tool_call and tool_result to the
120s bound the hook-config-file layer already used.
- A cancelling hook's error message was folded into the same context
field as other hooks' injectable context, so merging controls could
leak unrelated hook context into the cancellation reason. Carry it as
a separate cancelReason, and surface it as the stop reason for
beforeTool cancels too.
* fix(hooks): prefer errorMessage as a cancelling hook's stop reason
When a cancelling hook returns both contextModification and
errorMessage, the context-first parse precedence made the injectable
context the cancel reason and discarded the actual error. Parse the two
fields separately: errorMessage wins as the cancel reason (matching
legacy), and a lone errorMessage still folds into injectable context
for non-cancelling hooks as before.
* fix(vscode): honor PostToolUse hook cancel and contextModification
The adapter awaited PostToolUse hooks but discarded their output
entirely. Map cancel to a stop control (with errorMessage as the
reason) and contextModification into the runtime appendContext channel,
matching the PreToolUse mapping and legacy semantics.
* fix(hooks): whitespace-only errorMessage no longer suppresses the cancel reason
A cancelling hook returning meaningful context alongside a blank
errorMessage lost both: the parsers selected the whitespace as the
reason and the result mappers trimmed it away. Require a non-blank
errorMessage before it wins, so context serves as the fallback reason.
Apply the same fallback in the extension adapter's stop mapping.
* fix(core): stop Windows CI worker crashes from the agenda spec watcher (#13428)
* fix(core): watch agenda task specs via the resolved long path
fs.watch on a path with 8.3 short components (e.g. C:\Users\RUNNER~1
temp dirs) trips a libuv assertion in fs-event.c on Windows and aborts
the whole process. Since the agenda task manager landed, every hub
server test spins up its spec watcher on such a path on hosted Windows
runners, killing the vitest worker and failing the sdk-test Windows job
on every branch. Resolve the specs dir with realpathSync.native before
watching so libuv only ever sees the long form.
* test(ui): stub ResizeObserver for @pierre/diffs in tool-diff tests
jsdom does not implement ResizeObserver, so every ToolFileDiff render
logged a ReferenceError from @pierre/diffs to stderr. Tests still
passed; this just silences the noise the same way the constructable
stylesheet shim does.
* fix(core): skip the agenda spec watcher when the dir does not resolve
Falling back to the raw path on realpath failure would reintroduce the
Windows short-path abort; log and go without the watcher instead.
* fix(vscode): honor the classic truncation range when migrating legacy tasks (#13419)
Classic Cline truncated long conversations by omitting an index range of
api_conversation_history from every API request (keep the first
user-assistant pair, drop everything through the range end, strip
orphaned tool_results from the first kept message). The range was
persisted on the history item while the full history stayed on disk.
legacyApiHistoryToSdkMessages ignored conversationHistoryDeletedRange
and converted the entire file, so resuming a migrated long task handed
the SDK an untruncated working context that could exceed the model's
context window by millions of tokens - every request failed with
'prompt is too long' and every compaction restarted from the full
history (#12996, confirmed by the reporter: the task was migrated from
an older version and broke after a restart, with each compaction
starting from ~3M tokens).
The migration now replays exactly what the classic extension sent:
slice out the deleted range and drop orphaned tool_results, mirroring
ContextManager.getTruncatedMessages (see origin/main). Malformed ranges
fall back to the full history (previous behavior).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): show the diff edit view for multi-line edits in CRLF files (#13417)
The edit preview computed proposed content with an exact old_text match, but
the SDK executor normalizes old/new text to the file's own line endings before
matching (#12305) - reads strip CR, so models emit LF-only text even for CRLF
files. Any multi-line old_text in a CRLF file therefore failed the preview's
match: the diff edit view silently never opened while the executor applied the
edit. Single-line edits (no line break in old_text) were unaffected, which is
why the diff view appeared to trigger inconsistently.
Mirror the executor's EOL normalization (and its literal $-sequence insertion)
in the preview computation.
Fixes#13296
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): report truthful session status so desktop checkpoint restore stops wedging (#13418)
* fix(core): keep hub session status truthful across queue-drained turns
Queue-drained turns settle only through the event stream, but the hub
runtime host mistranslated their lifecycle in two ways:
- session.updated events carrying only a snapshot (persistence updates)
defaulted the projected status to "running". When one trailed the
final idle update after a turn, clients that track busy state from
status events (the desktop sidecar's workspace restore gate) stayed
busy forever. Use the snapshot's real status and emit nothing when
neither source reports one.
- the per-run agent.done dedup was only reset by run.started, which the
daemon-side queue drain never publishes, so a drained turn's done was
swallowed as a duplicate of the previous turn's. Reset the dedup on
session.pending_prompt_submitted, and suppress stale run.completed
events that land inside a drained turn's window so they can neither
emit a phantom done nor consume the drained turn's dedup slot.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(desktop): cover restore unlock after an event-settled queued turn
Exports the sidecar's core-session event handler so the queued-turn
lifecycle (busy via status events, cleared by the done agent event,
restore allowed afterwards) is testable end-to-end.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): start interactive sessions without a prompt as idle
The runtime host reported every new session as "running" until its
first turn ended. Interactive hosts (the desktop app) start sessions
with no prompt and dispatch turns through separate send calls, so a
created-but-never-prompted session stayed "running" forever — wedging
clients that gate workspace operations (checkpoint restore, message
edit) on active turns.
Interactive no-prompt starts now begin idle, start emits the session's
actual status (resumed sessions no longer masquerade as running), and
markTurn* transitions keep tracking in-memory status for lazily
persisted sessions so the first turn still reports running -> idle.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style: format hub-runtime-host test filter
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop the drained-turn done bookkeeping, keep the minimal fix
The stuck restore is fully explained by the two status defects (fabricated
"running" from snapshot-only session.updated events, and never-prompted
interactive sessions reporting "running"). The done-dedup machinery for
queue-drained turns addressed a separate cosmetic gap (queued turns emit no
chat_done, pre-existing) and required fragile run-window heuristics, so it
is removed to keep this change reviewable. Sidecar test now settles the
queued turn through the status event, matching the shipped mechanism.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* docs(sdk): document the truthful session-status contract
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(deps): update Langfuse packages and bump app versions (#13443)
* chore(deps): update Langfuse packages and bump app versions
Update @langfuse/otel to v5.10.1 and add @langfuse/vercel-ai-sdk v5.9.1 for improved observability with Vercel AI SDK.
Bump versions for @cline/code to 0.0.14 and @cline/ui to 0.2.0-next.6, updated via bun.lock.
Other Changes:
Added optional userId to AgentRuntimeConfig.
Propagated userId, sessionId, conversationId, runId, iteration, provider, and model context into AI SDK telemetry.
Added AI SDK 7 runtimeContext with explicit includeRuntimeContext.
Added stable OTEL_SERVICE_NAME=cline-sdk.
Added runtime metadata assertions in agent tests.
* add taskId
* Revert "add taskId"
This reverts commit f20d31d96d.
* docs: simplify Open Cline step in installing guide (#13405)
* docs: remove duplicate GLM-5.3 rows in ClinePass tables (#13449)
Co-authored-by: cline-cloud[bot] <276134852+cline-cloud[bot]@users.noreply.github.com>
* chore(sdk): release v0.0.76
* chore(cli): release v3.0.56
* docs(cli): scope the v3.0.56 release notes to CLI-visible changes
* feat(desktop): interactive welcome hero graphic (#13399)
* feat(desktop): add interactive welcome hero
* feat(desktop): support composable welcome hero variants
* feat(desktop): reskin first-run onboarding (#13441)
* refactor: centralize client tool availability (#13451)
* chore(sdk): release v0.0.77
* docs(cli): drop the tasks tool from the v3.0.56 notes, it is desktop-only
* chore(vscode): prepare 4.1.11 release
* chore(desktop): release v0.0.15
* fix(vscode): remote config MCP settings (#13466)
* fix(vscode): enforce enterprise MCP controls on the Customize marketplace
The unified Customize marketplace replaced the old MCP marketplace
without carrying over enterprise remote-config enforcement: the catalog
RPC returned every MCP entry and installs were never policy-checked,
so orgs with mcpMarketplaceEnabled=false or an allowedMCPServers
allowlist saw (and could install) all marketplace MCP servers.
- Filter MCP entries out of getMarketplaceCatalog when the marketplace
is disabled, and restrict entries to the allowlist when configured
(matching entry id, display name, installed server name, or source
repo URL, mirroring legacy GitHub-URL allowlist ids)
- Reject installMarketplaceEntry requests that violate the policy
- Map the published catalog's repo/homepage fields onto
sourceUrl/homepageUrl so URL-based allowlists can match
- Update the enterprise MCP server controls docs
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: simplify MCP marketplace policy enforcement
Fold the policy check into marketplace-helpers, drop the dedicated
test suite, and trim the docs edit to the strictly necessary line.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Treat an empty preserved capability list as unspecified when seeding tools (#13465)
* Treat an empty preserved capability list as unspecified when seeding tools
toSdkModelInfo guarded the tools seeding with a strict
preservedCapabilities === undefined check, but modelHasCapability —
the runtime's own reader — treats undefined AND length === 0 as
"unspecified". A custom OpenAI-Compatible model whose stored
capabilities field is a defined-but-empty array (a config carried over
from before the field existed, or one round-tripped through a boundary
that defaults it to []) skipped the seeding; the first boolean
projection to run afterwards (e.g. supportsReasoning) then populated
the array, the runtime gate read the non-empty, tool-less list as
authoritative, and every tool definition was silently dropped from the
session (#13463).
The guard now covers the empty array too, matching the reader's
unspecified semantics.
* test: satisfy the store's isModelInfo gate so the empty-capabilities case actually reaches knownModels
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: yzxcj797 <yzxcj797@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): prepare 4.1.12 release
* Add feature flags to the desktop app (#13289)
* Add feature flags to the app
* React to account updates
* Address comments
* use a per-app file
* fix: propagate Langfuse session telemetry (#13473)
* fix telemetry session propagation
* feat telemetry client version metadata
* fix(core): address Langfuse review feedback — hub client identity + delegated agent session grouping (#13475)
* fix(core): rebuild hub session client identity from request headers
Hub-backed sessions do not transport extensionContext (it is local-only),
so the daemon's runtime built traces without the clientName/clientVersion
metadata even though the hub client bakes X-CLIENT-TYPE / X-CLIENT-VERSION
into the session's provider headers. Reconstruct extensionContext.client
from those headers during local runtime bootstrap so hub-backed Langfuse
traces carry the same client identity as local runtimes, and the daemon's
header re-resolution stops clobbering the original X-CLIENT-TYPE.
* fix(core): propagate parent distinctId/sessionId to delegated agents
Delegated agents (spawned sub-agents, configured agents, teammates) were
built without distinctId and sessionId, so their Langfuse traces had no
userId or sessionId and did not group with the parent user or session.
Thread the host-resolved distinctId through RuntimeBuilderInput and the
root sessionId through the delegated-agent config provider, and copy both
onto the delegated AgentConfig.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* ci(vscode): make combined nightly manual-dispatch only
The PublishNightly environment gained required reviewers, so each cron
run parked on approval, held the workflow's concurrency group, and
silently cancelled every scheduled run queued behind it. 20 consecutive
scheduled nightlies died this way between 2026-07-31 and 2026-08-21;
the only nightlies that shipped in that window were manual dispatches.
Drop the cron rather than leave a trigger that cannot succeed unattended.
* feat(hub): add drain and upgrade commands with replay support (#13468)
* feat(hub): add drain and upgrade commands with replay support
* handles disconnection
* feat(hub): wire bot profiles, drain, and durable event/run-queue into the live transport
Completes the wiring the previous commits' primitives needed:
HubServerTransport gains isDraining(), hub.drain/hub.status/profile.get
command handling, and replayEventsAfter() (backed by the durable event
log), plus the sequence/sinceSequence wire types they depend on in
shared/hub.ts. run-queue-handlers.ts reads the active bot profile's
plugin roots when executing durable runs.
Also adds hub/profiles/: profile.json (identity/rules/plugins) ->
system prompt composition, --profile / CLINE_HUB_BOT_PROFILE
resolution, and the bundled cline-dad profile with its
cline_hub_support read-only diagnostics tool.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Revert "feat(hub): wire bot profiles, drain, and durable event/run-queue into the live transport"
This reverts commit 6696d5d202.
* fix(hub): dedupe replayed events by eventId, not just sequence
HubEventLogStore.append() returns a new envelope stamped with a
sequence rather than mutating the input, so a pending approval
re-issued sequence-less by subscribe() (it predates any durable-log
append) and its later sequence-stamped copy from the durable log are
two different objects carrying the same eventId. The replay-then-live
buffer in browser-websocket.ts only deduped by sequence, so the
sequence-less copy's guard never tripped and it was delivered a second
time when the buffer flushed after replay.
Track delivered eventIds alongside the sequence cursor; eventId
survives the append/stamp round-trip unchanged, so this dedupes the
exact-same logical event regardless of which copy arrives first.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): wire drain, durable event log, and run queue into the live transport
CI on this branch failed bun run build:sdk: browser-websocket.ts,
client/index.ts, and hub-websocket-server.ts (already on this branch)
reference sequence/sinceSequence, HubServerTransport.isDraining(), and
the "hub.drain" command — but the commit that reverted bot profiles
out of this branch also reverted this wiring, since it shared a commit
with the profiles work. That wiring is a hub concern, not a
bot-profiles one; split it back out.
- shared/hub.ts: sequence/sinceSequence types, run.enqueue/run.list/
hub.drain/hub.status/stream.replay capability, command, and event
names. profile.get intentionally excluded — stays bot-profiles-only.
- context.ts: isDraining() on HubTransportContext. botProfile field
intentionally excluded.
- hub-server-transport.ts: eventLog/runQueue fields and start/stop
lifecycle, publish() appends to the durable log, handleCommand cases
for run.enqueue/run.list/hub.drain/hub.status, drain-refusal check,
replayEventsAfter()/lastEventSequence(). startBotProfile()/
startHubSupportTool() and the profile.get case intentionally
excluded.
- run-queue-handlers.ts: added without handleProfileGet (needs
ctx.botProfile, which doesn't exist here).
- hub-upgrades.test.ts: added without its two bot-profile-injection
tests (they need a resolved bot profile to assert against).
Verified bun run build:sdk exits 0 (the exact CI command) and
bunx vitest run src/hub passes (311/312; the one failure is the
same pre-existing environment-timing flake already present before
this change).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): export instance-lock, event-log, and run-queue from the hub barrel
These landed as internal modules only; hub-server-transport.ts and
hub-websocket-server.ts import them by direct path, but nothing
re-exported them from the public @cline/core/hub surface the way
sibling discovery/server modules already are.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): wire the instance lock into the daemon entry point
The singleton lock (discovery/instance-lock.ts) and its consumption in
startHubWebSocketServer/ensureHubWebSocketServer were already on this
branch, but the daemon entry point's own half was not: retrying a bind
when a retiring predecessor still holds the lock, and exiting with a
distinct code (3) instead of the generic fatal path when a live Hub
already owns the data directory. Without this, a daemon racing a
retiring predecessor could fail outright instead of waiting the lock
out, and losing the singleton race looked identical to a crash.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): address drain/upgrade review findings (#13478)
- cline hub upgrade: check idleness at least once (--wait 0 works), reject
non-numeric --wait, and un-drain on every abort path so an aborted
upgrade can never leave the hub refusing new work
- add cline hub drain --off and the off query param to requestHubDrain so
POST /drain?off is reachable from shipped code
- HubEventLogStore/HubRunQueue: WAL journal mode + busy_timeout, and stamp
sequences from lastInsertRowid instead of SELECT MAX(sequence)
- HubInstanceLock.acquire: degrade to an unheld lock when SQLite is
unavailable instead of refusing hub startup; only BUSY/LOCKED still
raises HubLockHeldError
- ensureHubWebSocketServer: retire an unusable discovered hub through the
shared retireDiscoveredHub (busy hubs are attached to, drain precedes
shutdown, discovery cleared only when the hub actually retired)
- replay adapter: advance the cursor past eventId-deduped events, cap
replay pages, stop when the cursor stalls, and drop the dedupe set after
the buffered flush so it cannot grow for the socket lifetime
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(hub): derive the singleton e2e challenger cwd portably
The challenger's working directory was derived by round-tripping the
discovery path through a file: URL and stripping the last pathname
segment. On Windows that yields a POSIX-style '/C:/...' path, which is
not a valid spawn cwd, so the spawn fails ENOENT before the singleton
lock is ever contested and the Windows SDK test job goes red.
The data dir is simply the discovery file's parent: use dirname().
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): stop stored capability lists from silently revoking tool calling for custom models (#13476)
* fix(core): seed tools capability when custom model capabilities are synthesized from boolean flags
For a models.json entry with no explicit capabilities list, toStoredModelInfo
synthesized a capability array purely from boolean convenience flags (e.g.
supportsReasoning: true -> ["reasoning"]). modelSupportsToolCalling fails open
only for a missing or empty list, so the synthesized non-empty list read as an
authoritative denial and silently stripped every tool definition from requests
to custom OpenAI-compatible models (#13463).
Seed "tools" whenever the list was not explicitly authored and the boolean
projections made it non-empty, preserving the fail-open contract. Explicitly
authored capability lists remain authoritative and can still disable tools.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(core): cover stale catalog capability overrides
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: treat stored capability lists as non-authoritative for tool calling
The hasExplicitCapabilities guard still let two producers of tool-less
lists through:
- The VS Code legacy-override migration (legacyModelInfoToOverrides)
persists explicit partial lists like ["prompt-cache"] into models.json
for custom OpenAI-compatible models, which then read as an authoritative
"cannot call tools" and drop every tool - same symptom as #13463.
- Any hand- or UI-authored partial list on a non-catalog model.
Stored entries and user-authored provider metadata have no way to declare
"cannot call tools" (there is no supportsTools field, and every writer
that authors a full list includes "tools"), so seed "tools" into any
non-empty list for a language model. Only generated catalog capabilities
remain authoritative - a genuine no-tools catalog model stays that way -
and non-language models (e.g. image generation) never gain a tools claim.
Also make legacyModelInfoToOverrides write "tools" into the arrays it
fabricates, matching the providers.json migration, so models.json stops
being poisoned for older readers.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): prepare 4.1.13 release
* chore(sdk): release v0.0.78
* chore(cli): release v3.0.57
* fix(core): run hub e2e files serially so daemon timing budgets survive CI contention
singleton.e2e.test.ts (added in #13468) spawns real daemons and runs for
~15s. Vitest's default file parallelism let it run alongside
shutdown.e2e.test.ts, whose assertions are wall-clock bound: discovery
within 10s, exit within 5s, and a 2s shutdown watchdog. On the 2-core
windows-latest runner that contention alone broke those budgets, failing
the shutdown test two different ways across runs — once never observing
discovery, once with the daemon forced to exit before its HTTP 202
flushed (socket hang up). The test passed on Windows before #13468 and
has failed every SDK publish run since.
* chore(desktop): release v0.0.16
* test(sdk): give windows-sensitive suites realistic timeouts
Four consecutive SDK publish runs failed on windows-latest, each on a
different test, all of them plain timeouts: two @cline/shared SQLite
tests at the 5s vitest default, core's bash executor at 10s, and the hub
singleton endpoint test at 10s. The 2-core Windows runner spawns forks
and takes SQLite locks slowly enough to blow those budgets under load.
These timeouts guard against hangs; they are not timing assertions (the
one suite that does assert elapsed time, shutdown.e2e, was fixed by
removing file-level parallelism instead). Raise core to 20s and give
@cline/shared an explicit 15s in place of the inherited 5s default.
* fix(telemetry): emit task.completed from every session teardown path (#13489)
The task.completed fallback lived only inside shutdownSession, but
stopSession/dispose route interactive sessions with a terminal reported
status through releaseSessionRuntime, which never emitted. Truthful
session-status reporting (shipped in 4.1.11) re-routed a large share of
interactive stops onto that branch and silently dropped the event.
Route the emission through a single choke point,
emitTaskCompletedOnTeardown, called from both shutdownSession and
releaseSessionRuntime. The completion criterion no longer reads
session.status: interactive sessions use the recorded final-turn
outcome (lastInteractiveTurnFinishReason), non-interactive sessions
keep the existing input.status === "completed" logic. A new
taskCompletedEmitted flag (also set by the submit_and_exit observer)
enforces exactly one task.completed per session. failSession now
records the errored final turn so a stale "completed" from an earlier
turn can never leak into the teardown emission. Telemetry only; no
user-facing behavior changes.
* chore(vscode): release v4.1.14
* fix(vscode): auto-approve all MCP tool calls when the MCP toggle is on (#13498)
* fix(vscode): honor MCP auto-approve settings for SDK tool calls
The SDK extension required both the global 'Use MCP servers' auto-approve
toggle AND each tool's per-tool autoApprove flag before silently approving
an MCP call, while the legacy extension treated them as either/or. Restore
the legacy OR semantics so toggling MCP auto-approve works again.
Also key toolPolicies by the registered SDK tool name (via
defaultMcpToolNameTransform, now exported from @cline/core) instead of raw
server__tool. Servers whose names contain sanitized characters (e.g.
marketplace names like github.com/user/repo) or exceed 64 chars produced
policy keys that never matched the registered tool, so those MCP tools ran
without any approval gate; the live auto-approve lookup now re-applies the
transform instead of string-splitting the name.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Revert "fix(vscode): honor MCP auto-approve settings for SDK tool calls"
This reverts commit 86c568fbba.
* fix(vscode): auto-approve all MCP tool calls when the MCP toggle is on
The SDK extension only auto-approved an MCP call when the global 'Use MCP
servers' auto-approve toggle AND that tool's per-tool autoApprove flag were
both set, so toggling MCP auto-approve appeared to do nothing and users had
to opt in each tool individually. The toggle alone now governs all MCP
tools; the per-tool flag is no longer consulted.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): release v4.1.15
* fix(cli): remove the $4.99 ClinePass promo copy (#13514)
The $4.99 first-month promo is ending, so the CLI's first-launch "Try ClinePass" dialog should no longer advertise it. Also drops the leftover CLI_PROMO_CODE plumbing, which has been an empty string since the promo-code flow was removed.
* fix(vscode): resolve hook workspace identity from the window, not shared global state (#13352)
* fix(vscode): resolve hook workspace identity from the window, not shared global state
Hook discovery, hook cwd selection, and the workspaceRoots metadata passed
to hook scripts all read the workspaceRoots/primaryRootIndex global state
keys. Global state lives in ~/.cline and is shared by every Cline instance
(all VS Code windows, the CLI, the JetBrains plugin), and nothing writes
these keys anymore, so hooks resolved against whatever project some other
or older instance last recorded. With a second window open on another
project, a workspace's .clinerules/hooks scripts were never discovered.
Resolve workspace roots via a single guarded helper backed by
HostProvider.workspace.getWorkspacePaths() (in-process, window-scoped,
same as refreshHooks): blank paths are filtered, a host-bridge failure
degrades to no workspace roots instead of silently disabling global hooks
or skipping blocking PreToolUse guards, and one resolution is threaded
through hooks-dir discovery, cache misses, cwd selection, and hook input
metadata so they can't disagree (previously up to four host lookups per
hook execution — real gRPC round trips in the standalone host). Roots and
hooks dirs are matched on whole path segments with the longest root
winning, so prefix-sharing or nested workspace roots resolve to the right
project. The adapter creates the runner once per event and skips no-op
runners, making creation the single resolution point; the separate
hasHook/getHookInfo checks are removed. The dead workspaceRoots and
primaryRootIndex state keys are dropped, and the four hand-rolled
HostProvider.workspace test stubs are consolidated into one shared
helper.
* test(vscode): add e2e coverage for workspace-scoped hook discovery
Boots real VS Code with the packaged extension against the workspace
fixture, sends a prompt, and asserts the fixture's UserPromptSubmit hook
was discovered from the open window's workspace, executed with that
workspace root as its cwd, and received the same root in its
workspaceRoots input — the end-to-end contract the hook workspace
identity fix establishes.
* test(vscode): isolate the e2e hook fixture from the shared workspace
The UserPromptSubmit fixture hook lived in the shared e2e workspace, so
every prompt-sending spec executed it (hooksEnabled defaults to true) —
and its cold PowerShell spawn on Windows pushed chat.test.ts past the
5s expect timeout. hooks.test.ts now overrides workspaceDir to a
dedicated workspace-hooks fixture, so only the hooks spec pays the hook
spawn.
* fix(hub): cap hub-events db size so it can't fill the disk (#13516)
* fix(hub): cap hub-events db size so it can't fill the disk
Row/time retention alone didn't bound disk usage: envelopes carrying
full session snapshots reach hundreds of KB each, so retained rows
could total tens of GB, sweeps only ran hourly, and DELETE never
shrinks a SQLite file. Enforce a 64 MiB size budget in prune() (oldest
rows first, VACUUM to return the space), and also prune after every
16 MiB appended so bursts can't outrun the hourly timer.
Fixes#13505
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): tolerate VACUUM failure on a full disk
VACUUM needs scratch space and can fail in exactly the state a
ballooned event log causes. The byte-budget deletes already bound live
data, so swallow the error and let the next sweep retry the reclaim
instead of aborting startup pruning and disabling the durable log.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): count the size budget in UTF-8 bytes, not characters
envelopeJson.length (UTF-16 units) and SQLite LENGTH() (characters)
undercount multibyte text by up to 3x, which could leave a CJK-heavy
log settled above budget and re-running VACUUM every sweep. Use
Buffer.byteLength and LENGTH(CAST(... AS BLOB)) instead.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(sdk): carry root overrides into the Node smoke-test sandbox (#13517)
ci-node-smoke.ts installs the packed SDK tarballs with a plain npm
install in a fresh temp dir, where the repo root package.json overrides
do not apply. When @sap-cloud-sdk 4.9.0 shipped (2026-08-24) it broke
@sap-ai-sdk/ai-api 2.14.0 (via @jerome-benoit/sap-ai-provider in
@cline/llms) with ERR_PACKAGE_PATH_NOT_EXPORTED, failing the smoke step
on every PR even though the root already pins @sap-cloud-sdk/* to 4.6.0.
Copy the root overrides block into the generated sandbox package.json
so the smoke install resolves the same pinned versions as the repo and
future third-party releases cannot break it independently.
* chore(sdk): release v0.0.79
* fix(vscode): don't steal last-used provider from ClinePass on credential refresh (#13520)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): flush the /shutdown 202 before daemon teardown
The /shutdown handler queued teardown on a microtask, which runs before
the event loop's write phase, so the daemon could process.exit() before
the accepted 202 was handed to the socket. Unix masked it (uv_try_write
lands small loopback writes synchronously); Windows has no such fast
path and lost the race regularly — the recurring shutdown.e2e.test.ts
'socket hang up' failures on windows-latest. Start teardown from the
response's write callback instead, with an idempotent 1s fallback so a
client that vanishes mid-write cannot strand the daemon, and send
Connection: close so the client gets a FIN rather than an abort.
Since the flakiness this compensated for is fixed at the source, restore
maxWorkers: 2 for the Windows core suite (serializing it cost ~3 min of
CI per run), and raise the e2e daemon discovery hang guard 10s→30s —
it guards against hangs, not runner speed.
* chore(cli): release v3.0.58
* fix(core): prevent search_codebase from crashing the process on giant single-line files (#13525)
* fix(core): prevent search_codebase from crashing the process on giant single-line files
searchWithRipgrep buffered all of rg's --json stdout into one string. Each
JSON event embeds the full text of the matched line (--max-columns is
ignored in JSON mode), so searching a directory of serialized trace dumps
(single-line multi-hundred-MB JSON files) accumulated gigabytes of stdout
until string concatenation threw RangeError: Out of memory inside the
stream data handler. That throw is outside the tool's try/catch, so it
escalated to an uncaughtException and killed the CLI/hub daemon.
Parse rg's JSON events incrementally line by line, drop events larger
than 256KB, truncate matched/context lines to MAX_LINE_CHARS, and stop
reading once maxResults is reached. The fallback regex scan now skips
files larger than 10MB (reporting the skip count) and truncates its
context lines the same way.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* simplify search_codebase crash fix to a minimal diff
Replace the incremental JSON-event parser with three small guards: stop
buffering rg stdout past 10MB, drop the trailing partial event before
parsing, and slice fallback context lines to MAX_LINE_CHARS. Drops the
fallback file-size skip and skip-count reporting.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): hide per-tool MCP auto-approve checkboxes behind a flag (#13522)
* chore(vscode): remove per-tool MCP auto-approve checkboxes from webview
MCP auto-approval is now governed solely by the global 'Use MCP servers'
toggle; the SDK approval path (shared with the CLI and desktop app) has no
per-tool granularity, so the per-tool and 'Auto-approve all tools'
checkboxes were no-ops that implied control that no longer exists. Remove
them from the MCP settings view and chat tool rows. The autoApprove arrays
in cline_mcp_settings.json and the toggleToolAutoApprove RPC are left
intact for the legacy extension.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): hide per-tool MCP auto-approve checkboxes behind a flag
Keep the checkbox components, handlers, and RPC plumbing intact but gate
rendering behind SHOW_MCP_PER_TOOL_AUTO_APPROVE=false: the SDK approval
path (shared with the CLI and desktop app) is all-or-nothing via the
global 'Use MCP servers' toggle, so the per-tool checkboxes were no-ops.
Flip the flag back on if the SDK gains per-tool approval granularity.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(tools): create new files with the platform-native line ending (#13521)
* fix(tools): use platform-native EOL for new files and preserve CRLF in apply_patch updates
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* simplify to the minimal new-file EOL fix
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* extract shared normalizeNewFileLineEndings helper
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add suggested schedule templates to the desktop Schedules page (#13529)
* Add suggested schedule templates to desktop Schedules page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix unreadable selected text in inputs caused by selection utility conflict
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Restyle Suggested section label as small gray uppercase
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Hide suggested schedule cards that match an existing schedule name
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Disable the agent todo tool and hide the Agenda UI in the desktop app (#13530)
* remove todo tool and Agenda UI, keep schedule-only tasks tool
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: biome formatting fixes
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* restore agenda backend; disable todo kind behind a flag instead of deleting
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* keep agenda automation pump idle while the todo tool is disabled
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* remove todo tool and Agenda UI altogether (revert the disable-flag hybrid)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* restore all agenda code to main state
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* disable agent todo tool and hide Agenda UI behind flags
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add Desktop App and Cloud Platform to bug report issue template (#13532)
* Add Desktop App and Cloud Platform to bug report surfaces
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename Surface Diagnostics field to Diagnostics
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: sidebar navigation cleanup with New/Schedule/Customize rows and dialog-based search (#13533)
* desktop: clean up sidebar navigation chrome
- Give New Task its own full-width labeled row below the logo row
instead of an ambiguous icon next to the agenda toggle
- Wire the New Task row to the home action so starting a new task
clearly takes you home (the logo still works as a fallback)
- Swap back/forward chevrons for browser-style arrow icons and
bump their size
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: sidebar New/Schedule/Customize rows and always-visible search
- Stack New (plus icon), Schedule, and Customize as full-width labeled
rows below the logo; whole row highlights on hover via sidebarItem
- New starts a fresh task (home), Schedule opens Settings > Schedules,
Customize opens the Customizations sections (Plugins first)
- Show the session search bar permanently above the sessions list
instead of hiding it behind a search icon toggle
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: move session search into a dialog behind a logo-row icon
- Replace the inline sidebar search bar with a search icon in the
logo row that opens a cmdk command dialog listing sessions
- Selecting a result opens that session and closes the dialog
- Remove the agenda/tasks toggle the icon replaces, along with the
now-unreachable sidebar Agenda panel (the welcome screen still
surfaces agenda tasks)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: load full session history when the search dialog opens
Addresses Greptile review on #13533: the dialog only searched the
currently loaded history batch, so older unloaded sessions could not
be found. Opening search now kicks off loadAllSessions() (the hook's
purpose-built global-search loader), and the empty state reads
'Searching older sessions...' while more history is streaming in.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Hide Channels and Agents sections from desktop app sidebar (#13527)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Desktop app: organize sidebar sessions into Pinned, Scheduled, and Tasks sections (#13528)
* Add Pinned/Scheduled/Tasks categories to desktop app sidebar
Replace the Schedules and Favorites filter-menu options with visible
collapsible category sections in the session sidebar, and rename the
Favorite action to Pin across the sidebar and sessions view.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Grow full history window when Tasks show-more outpaces loaded tasks
loadMoreSessions treats its argument as a limit on all sessions, but the
Tasks show-more count only tracks Task rows, so once pinned/scheduled
rows pushed the loaded total past the requested count the call no-oped
and clicks went dead. Grow the whole history window via
loadOlderSessions instead, and only when the loaded tasks cannot fill
the next page. Addresses Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Auto-fill the Tasks page instead of fetching once per show-more click
A single 50-session window growth can consist entirely of pinned or
scheduled sessions, leaving a show-more click with no visible Tasks
progress. Replace the one-shot fetch with a page-fill effect that keeps
growing the history window until the requested Tasks page fills or
history runs out. Addresses the follow-up Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Halt page-fill retries after a failed history fetch
A failed fetch leaves the task count and has-more state unchanged,
which are exactly the conditions the page-fill effect fires on, so one
failing request would retry and re-toast forever. Halt the effect after
a failure and let the next explicit show-more click retry. Addresses
the third Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Redesign desktop Model Providers page and split voice input into its own settings page (#13531)
* Redesign desktop Model Providers page and split voice input into its own settings page
- Group providers into Connected / Popular / All with auth-kind hints and
connection status instead of per-row enable toggles
- Show browser sign-in (not an API key field) for OAuth providers, with a
collapsed manual-key escape hatch where supported, plus explicit
Connect / Disconnect / Sign out actions
- Move voice input to a dedicated Settings > Voice page that only offers
connected transcription-capable providers, preselects a default model
(streaming preferred), and stays disabled in the sidebar until a
provider is connected
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Show native tooltip on the disabled Voice settings nav item
Disabled buttons drop pointer events, so the 'connect a model provider'
hint moves to a wrapping span for the browser tooltip to render.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Drop letter avatars and gray provider ids from provider rows and voice chips
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Drop model counts from provider list rows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename provider Connected status to Configured and drop the green styling
A settings entry is configuration, not a live connection; neutral gray
text avoids implying an active link, since the user still picks which
configured provider to use per chat.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Resync provider catalog from disk when a settings save fails
Connect/disconnect/credential edits update the list optimistically; a
failed save now reloads the catalog instead of leaving the optimistic
state (and the view's module cache) claiming a configuration that was
never persisted.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename oauthProvider test fixture to dodge CodeQL name heuristic
CodeQL's clear-text-storage query flags any identifier matching 'oauth'
as a credential source and traced the fixture's provider id into the
favorite-models localStorage write, which stores only provider/model id
strings. Renaming the fixture removes the false-positive source.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Guard catalog reloads against races and resync detail drafts on failed saves
Optimistic provider mutations now bump a generation that discards any
in-flight catalog response, so a failed-save recovery reload can't
overwrite a newer edit with an older disk snapshot. The recovery also
remounts the provider detail panel via a reset token so its local field
drafts reflect the reloaded on-disk state instead of unpersisted edits
or an optimistically cleared disconnect.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix failed-save recovery ordering and retry superseded reloads
Remount the provider detail only after the authoritative catalog reload
lands, so its drafts re-seed from disk state rather than the optimistic
values that failed to persist. When a concurrent edit supersedes the
recovery's in-flight response, retry the reload (bounded) instead of
dropping it, since that edit performs no reload of its own.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): Customize hub, sidebar overhaul, and settings polish (#13538)
* feat(desktop): merge customization pages into a Customize hub with inline marketplace
Replaces the Plugins page and the dedicated Marketplace page with a single
Customize hub. Tabs: Skills, MCP, Plugins, Rules, Hooks, Tools, each with
live counts. Tabs backed by a marketplace catalog render the installed
items followed by an inline browsable Browse section (CLI-hub style), so
installing from the catalog immediately reflects in Installed above.
- Installed cards restyled to mirror the browse-card anatomy: bg-card p-4
containers, absolute top-right xs Uninstall matching Install, truncating
semibold titles, primary-tinted icons, real Badge components instead of
ad-hoc bordered spans, un-indented line-clamped descriptions
- Rules/Hooks/Tools rows brought into the same card language; redundant
intro paragraphs (duplicating the page description) removed; Tools group
headers match the Installed header style with counts
- Marketplace section header renamed to Browse; duplicate 'N results' row
removed (the header count is the single source)
- MCP embedded view now shows the full marketplace instead of
installed-only
* feat(desktop): overhaul sidebar sessions and navigation
Sessions list:
- Sort toggle removed; sessions are always grouped by project, with pinned
sessions leading each group (both subsets ordered by recency). The
Pinned/Scheduled/Tasks category sections and their time-mode paging
machinery (page-fill effect included) are deleted
- Scheduled sessions get an inline clock icon next to the pin position;
pin + clock render together when both apply, and the running/unread
status dot now coexists with them
- One font size (text-sm) across the list: titles, timestamps, project
headers, show-more buttons, empty states. sidebarText needed !text-sm
because the default button size's text-base wins the twMerge conflict
- Gradient fade under the Sessions header once the list scrolls, so rows
fade out instead of hard-clipping
- The session-detail hover card is controlled from the sidebar and closes
on scroll (Radix receives no pointer events while scrolling, so it used
to float over moving content)
- Sidebar min resize width raised 224->260px; the per-project show-more
label truncates so its nowrap text can't force rows to overflow and clip
timestamps at narrow widths
Navigation:
- Customize replaces the Plugins/Marketplace/Hooks/Rules/Tools sidebar
entries; Schedules and Customize are hidden from the expanded settings
nav (their top rows cover them) but stay reachable when collapsed
- The settings gear always opens General instead of resuming the last
section; the Account no-op hover special case is gone
- The New row highlights (aria-current) while the fresh not-yet-started
task page is showing and hands off to the session row once the task
starts; hitting New also focuses the prompt input via a window-event
signal (lib/prompt-input-focus.ts) since the sidebar and composer sit in
distant subtrees
- Fixed the xs button size collapsing any icon-bearing button to 12x12
(leftover has-[>svg]:size-3 from when xs was a micro button) — this was
why Uninstall buttons rendered broken next to Install
* feat(desktop): polish settings pages and chat composer
Models page:
- The provider detail panel is always open: no X button, no empty
no-selection state. It defaults to the first connected provider (falling
back to the first in the catalog), which also removes the layout shift
that happened when the page swapped between full-width and panel
variants on selection
- Fixed the list pane becoming unscrollable while the panel was open:
grid items default to min-size auto, so the pane grew past its track
inside the overflow-hidden grid and its ScrollArea had nothing to
scroll; wrapped it in a min-h-0 min-w-0 cell
- Add Provider opens a Dialog instead of swapping the page
(AddProviderContent gained a dialog variant that renders only the form)
- Embedded inputs (provider search, model search, detail fields) share one
EMBEDDED_INPUT_CLASS stripping the Input component's own border/dark bg
tint/shadow/ring, which rendered as a mismatched inner box; the model
search box uses the same h-9/px-3 frame as the provider search
- Model list flows with the page instead of a max-h capped inner scroller
Other pages:
- Account uses the shared PageFrame/PageHeader: left-aligned, text-3xl
title, Sign Out in the header actions slot
- Desktop notifications is one General section: header row plus the
Event/Notify/Sound matrix nested in a card, so its rows no longer read
as top-level peers of Dark mode; 'Available in the desktop app' label
removed
- Schedule page retitled from Schedules with a real description; Customize
description rewritten
Chat composer:
- The voice dictation button only renders once a voice model is
configured (Settings -> Voice); the unconfigured deep-link state is
gone (prop type kept for an easy restore)
* chore(desktop): release v0.0.17
* fix(desktop): unblock sdk-test lint on the voice-input model picker (#13553)
The model picker renders a radiogroup of styled buttons with role=radio
and aria-checked; biome's useSemanticElements flags the role as an
error, which fails the sdk-test Quality Checks lint for every PR
touching sdk/ or apps/ paths. Suppress with a justification — switching
to input type=radio needs a restyle and belongs to the desktop settings
work.
* fix(vscode): include rich workspace metadata in system prompt (#13518)
* capture richer workspace information for vs code extension
* fix(shared): redact credentials from workspace remotes
* fix(shared): avoid regex backtracking in remote redaction
---------
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
* Hide task costs on vscode when ClinePass is selected (#13515)
* fix: stop showing cost estimates for subscription-billed providers (#13552)
* fix(vscode): stop showing cost estimates for subscription-billed providers
Providers whose usage is covered by a flat-rate subscription (ChatGPT
Plus/Pro via openai-codex, ClinePass) are marked with
metadata.usageCostDisplay = "subscription" in the SDK, and the CLI
already suppresses dollar figures for them. The VS Code host collapsed
that value into "show" before it reached the webview, so the task
header and model pricing rows rendered API-rate cost estimates that
users read as real charges on top of their subscription.
Pass all three usageCostDisplay values ("show" | "hide" |
"subscription") through the catalog listing and render cost only when
the value is "show", matching the CLI's shouldShowCliUsageCost
policy.
* feat(llms): mark Claude Code as a subscription-billed provider
Claude Code is typically authenticated with a Claude Pro/Max
subscription, but its models reuse Anthropic API pricing metadata, so
Cline rendered per-token prices and API-rate cost estimates for usage
that is covered by the subscription. Set usageCostDisplay =
"subscription" on the provider (picked up by the CLI and the VS Code
webview) and suppress the price rows in the Claude Code settings card.
The Claude Code CLI can also run on API-key billing, where a real cost
exists; the provider cannot distinguish the two, so we prefer showing
no number over a misleading one.
* fix(vscode): suppress cost display until provider listings load
While the ListProviders request is in flight (or after it fails), the
usage-cost hook had no listing to consult and fell back to "show",
flashing the API-rate estimate at subscription users on every chat-view
mount — the exact display the previous commit removes. Return
"unknown" whenever listings are absent; consumers already render cost
only for "show", so they suppress it during that window with no
changes. Briefly hiding a real cost is harmless, briefly showing a fake
charge is not.
* fix(desktop): reconcile voice settings after main sync
* test(llms): allow experimental ElevenLabs models
* fix(sdk): preserve canonical media model behavior
* feat(desktop): customize macOS DMG install window (#13563)
* feat(desktop): add Retina DMG background tooling
* feat(desktop): customize the macOS DMG layout
* ci(desktop): validate DMG background assets
* fix(desktop): adjust DMG Applications icon position
* ci(desktop): drop redundant DMG artwork validation from publish workflow
Tauri's beforeBuildCommand already runs dmg:background (with its own
validation) at the start of the build/sign/notarize step, and the
release/beta config overlays do not override the build section, so this
step duplicated work the publish job performs anyway. PR-time coverage
lives in desktop-test.yml.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): sidebar time view, Customize/Marketplace split, and schedule page UX (#13570)
* feat(desktop): split Customize into Installed and Marketplace pages
The Customize hub previously embedded a Browse section inside every tab
that had a catalog. That inlining made each tab long and buried the
catalog. Customize is now the installed inventory only (skills, MCP,
plugins, rules, hooks, tools tabs pass marketplaceVariant="installed"
to the embedded MarketplaceView; McpServersContent grew the same prop),
with an outline Marketplace button in the header.
Browsing moved to a dedicated Marketplace settings section that renders
the previously dead "directory" variant of MarketplaceView: one list
across all catalog types with type-filter chips, wrapping tag chips,
and light rules separating the filter tiers from each other and from
the results. The Clear control now renders inline at the end of the tag
row only while a tag is active, the Updated date is gone, and the
header hosts an Installed button mirroring the one on the Customize
page. Directory subheader copy: "A curated set of plugins, MCP
servers, and skills from the Cline community."
Tag and type chips wrap to new lines instead of scrolling
horizontally.
* feat(desktop): sidebar time view with sections, sort toggle, and scheduled detection
Restores the time-sorted session list as the default sidebar view, with
collapsible Pinned / Scheduled / Tasks sections (headers appear only
once something is pinned or scheduled) and the page-fill effect that
grows the fetched history window until a Show-more click makes visible
progress. Project grouping stays as the alternate mode behind a
one-click sort toggle whose icon reflects the active mode — the old
dropdown cost an extra click for a two-option choice.
Scheduled sessions are detected two ways: the hub-schedule origin
trigger in session metadata, plus a fallback that asks the hub which
session ids belong to schedule executions (list_routine_schedules,
fetched on mount and every two minutes, merged into a rolling set).
The fallback matters because locally executed scheduled runs do not
reliably stamp the trigger into session metadata — a real scheduled
session created today carried only {mode:"user"} provenance. The
scheduled clock icon now leads the row, left of the title; pin and
timestamp stay on the right.
The initial visible page grows from 10 to 30 rows so a tall sidebar
fills instead of stranding a stub of rows over empty space (history
fetches already start at 50).
The expanded sidebar's Customize row now hosts indented Installed and
Marketplace sub-tabs while a customize section is open; the active
sub-tab carries the full selected background while the parent keeps a
subtler one so the two simultaneous highlights read differently.
Also fixes the hover-card flash on click (logo card and session-row
cards): Radix HoverCardContent sits on a DismissableLayer, so a click
on the trigger registers as a pointer-down outside the card and
dismisses it, and the trigger's focus event immediately reopens it.
onPointerDownOutside preventDefault suppresses the dismissal; cards
still close on pointer leave.
* feat(desktop): schedule page row, dialog, and details UX polish
Schedule cards are now click targets: clicking anywhere on a card
outside its controls opens the details dialog (guarded via
closest("button,...") since every inline control, including the Radix
switch, renders a button element), with Enter/Space keyboard support.
The redundant eye button is gone. The remaining edit / run / pause /
delete buttons grow from the 12px icon-sm size to 28px targets with
16px icons, sized consistently with the adjacent enable toggle — the
icons use explicit size-4 classes so the Button base svg rule cannot
shrink them back.
The new/edit dialog gains breathing room between field labels and their
inputs (space-y-2 per field wrapper).
The details dialog no longer scrolls as a whole when the schedule JSON
is long: the dialog is a flex column capped at 85vh, the JSON pre
shrinks to the remaining space (min-h-0) and scrolls internally, and
the Runs tab list scrolls inside the tab the same way.
* feat(desktop): scheduled sessions UX — unified details dialog, run-now handoff, hidden steering, stuck-thinking fix (#13573)
* feat(desktop): merge schedule details into one view and open run-now sessions
The schedule details dialog drops its Overview/Runs tabs: one scrollable
column with the meta grid, the configuration JSON (capped at max-h-64
with internal scroll so it cannot crowd out what follows), and a Runs
section beneath it showing the three most recent runs with a ghost
"Show all N runs" expander (collapsed again whenever a different
schedule's details open). The "Full configuration for this schedule"
subtext is gone; the dialog passes aria-describedby={undefined} so
Radix does not warn about the missing description.
Run now hands you into the session it starts. The trigger command
queues the run and returns before the runner attaches a session id, so
after the toast the handler polls the schedule overview once a second
for up to 15 seconds — which doubles as keeping the page's run status
fresh (refreshSchedules now returns the fetched overview to make that
single-stream) — until the triggered execution reports its session id,
then calls onOpenSession. Guarded so it never auto-navigates after the
user left the page.
* feat(desktop): hide runtime steering messages from transcripts
Scheduled/automation runs inject user-role steering messages each
iteration ("[SYSTEM] This run is not complete until you call
submit_and_exit...", plus a team-obligations variant). The chat view
rendered them as user bubbles, as if the person had typed them — in a
scheduled session the transcript was mostly [SYSTEM] noise.
They are machinery talking to the model, not something the person said
or needs to read, so the transcript now hides them entirely:
MessageBubble renders null for any [SYSTEM]-prefixed user message.
Grouping still treats them as working-row machinery via a single
isSystemSteeringMessage predicate — they collapse into the run's work
span, are never a turn boundary, can never be mistaken for a run's
answer, and never advance the run count even when metadata is missing —
so work-block folding and checkpoint/edit run numbering stay correct.
A finished scheduled session now reads as prompt, work summary, answer.
* fix(desktop): poll history while an attached session's event stream is dead
Opening a scheduled session while (or right after) it runs left the
view stuck on the thinking shimmer until the user switched away and
back. Root cause is in core: the hub daemon executes scheduled runs on
a private LocalRuntimeHost inside createLocalHubScheduleRuntimeHandlers,
while the hub server only projects live events from its own session
host — so session.attach succeeds but no assistant/tool/status events
ever flow. And since multiple hub daemons share cron.db, a run claimed
by a different daemon is invisible to this hub regardless. The proper
core rewiring is tracked as ENG-2474.
Client-side heal that covers every case: while an attached history
session reports a busy status and no chat_event chunk has arrived for
five seconds (and no assistant bubble is mid-stream), poll every three
seconds — re-read canonical history, merged through the same dedupe
path hydration uses, and the session record's status — so the
transcript and the thinking indicator settle in place. Locally driven
turns keep chunks flowing, so the quiet-window guard keeps the fallback
inert there.
* chore(desktop): format workspace selector components
Biome formatting drift that landed on main; picked up by a formatter
pass over components/views/chat.
* fix(desktop): keep stale-stream poll inert during locally driven turns
The fallback poll could fire between a local submit and the model's
first chunk (optimistic user bubble added, stream quiet past the
window, no assistant bubble yet). It then replaced the optimistic
bubble — raw prompt text — with its canonical history twin, which is
stored wrapped in a user_input envelope. The rekey handler that runs
when the stream starts looks for a trailing user bubble matching the
raw prompt, finds only the wrapped copy, and appends a second bubble:
duplicated messages in normal interactive chat.
The poll now stays inert while a local turn is in flight
(turnEpoch !== turnSettledEpoch, or outstanding optimistic user
messages), checked both before polling and again after the snapshot
returns. Hydration marks the turn settled — the mount defaults
(epoch 0, settled -1) otherwise read as an open turn and would keep
the fallback inert forever for the scheduled-session case it exists
for. Applying a polled snapshot also rebuilds the live tool routing
keys, same as hydration, so later tool events update canonical rows
in place instead of appending.
* fix(desktop): keep the working indicator alive for narrating scheduled runs
Watching a scheduled run live: the first tool row appeared, then the
thinking indicator vanished with nothing streaming, and the rest of
the run (final answer, submit_and_exit) only showed up seconds later
in one lump.
inferHydratedChatStatus treats a "running" session record whose
transcript ends on an assistant message as a session that died without
a status flip and reports "completed". That heuristic is right for
stale records, but scheduled/automation models narrate between tool
calls, so a polled snapshot can genuinely end on assistant text
mid-run — the completed flip hid the working indicator, folded the
run early, and disarmed the stale-stream poll (status left the busy
set), dead-ending live updates until an in-flight poll happened to
deliver the finished run.
The heuristic now only applies once the transcript has actually gone
quiet (newest message older than two minutes — comfortably past model
latency plus tool runs). A recently active transcript keeps the
record's "running" verdict, so the indicator stays up and polling
stays armed until the record itself settles.
* fix(desktop): stale-stream poll mirrors the session record instead of inferring
Replaces the previous fix for the vanishing working indicator (the
time-window guard added to inferHydratedChatStatus) with a version
that adds no inference at all: the heuristic is restored to exactly
its long-standing form, and the poll now maps the session record's
status verbatim (mapSessionRecordStatus).
The record is the right authority in the poll's context: the sessions
this fallback serves have a live host maintaining their record, and it
flips to a terminal status when the run ends. Transcript-shape
inference belongs only where it has always lived — hydrating sessions
whose records may be orphaned — and would misread a mid-run snapshot
ending on assistant narration as a finished session, hiding the
working indicator and disarming the poll.
* fix(desktop): address review findings on steering detection and run-now matching
Steering detection additionally requires the injected-message marker
(meta.userRunSpan === 0) beside the [SYSTEM] prefix, so a person's
genuine prompt that happens to start with "[SYSTEM]" stays visible
and turn-counted. The failure direction is deliberate: an unstamped
injected reminder would merely show as a user bubble, while the
content-only check could hide a real prompt.
Run-now only follows the execution id the trigger reply itself named;
the newest-execution-for-this-schedule fallback could open a previous
run's session when the trigger failed to enqueue one.
* fix(desktop): report a failed run-now instead of confirming a start
A trigger reply without an execution means no run was enqueued (the
schedule may have been disabled or deleted since the page loaded). The
handler previously toasted "Run started" regardless and then silently
skipped the session-open polling. It now shows a destructive
"Run not started" toast, refreshes the schedule list so the row
reflects reality, and skips the polling entirely.
* fix(desktop): don't block the main thread on quit while stopping the sidecar (#13566)
Quitting the mac app beach-balled for ~5-7s. The shutdown POST was
built from the ws transport URL (appending /shutdown lands inside the
query string), so the sidecar was never told to exit, and stop() then
polled the child for up to 7s on the main thread - on macOS inside
applicationWillTerminate - before SIGKILLing it.
stop() now sends SIGTERM and returns immediately. The sidecar handles
SIGTERM with the same bounded (5s) graceful shutdown as the /shutdown
endpoint and exits itself, finishing session persistence as an orphan.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): hover trash button on sidebar session rows (#13582)
Each session row shows a trash icon on the right while hovered (or
when the button itself is focused), opening the same delete
confirmation dialog the row's context menu uses. The row is a button
and buttons cannot nest, so the trash is an absolutely positioned
sibling inside a group/row wrapper, overlaid where the timestamp sits:
row hover hides the timestamp, shows the trash, and moves the row's
hover background to the wrapper group so it holds while the pointer is
on the trash itself.
* fix(desktop): install marketplace plugins and MCP servers in-process instead of spawning a cline binary (#13585)
* fix: install marketplace plugins and MCP servers in-process instead of spawning a cline binary
The desktop app sidecar and cline-hub shelled out to 'cline plugin install'
and 'cline mcp install' for marketplace installs. Packaged GUI apps inherit
launchd's minimal PATH on macOS and most desktop users have no cline CLI
installed at all, so installs failed with a red
'Executable not found in $PATH: "cline"' error.
Install via @cline/core's installPlugin/installMcpServer in-process instead,
matching what the VS Code extension already does. Also fix
parseMcpInstallArgs in @cline/core to treat the marketplace catalog's '--'
separator as end-of-options; previously the separator itself became the
stdio command.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop test-injection plumbing from marketplace installers
Call @cline/core's installPlugin directly instead of threading an
installer option through the marketplace entry points; tests stub the
core module instead.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert: keep cline-hub marketplace installs CLI-backed
The hub dashboard is launched via 'cline dashboard', so a CLI is always
present and CLINE_WRAPPER_PATH resolves it; the PATH bug only affects
the desktop app, which does not ship a CLI.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(desktop): release v0.0.18
* chore(vscode): release v4.1.16
* chore(sdk): release v0.0.80
* chore(cli): release v3.0.59
* fix(hub): stop shipping full transcripts inside broadcast hub events (#13587)
* fix(hub): stop shipping full transcripts inside broadcast hub events
Every session.updated (and session.created/detached/run.started) event
embedded the session's ENTIRE message transcript via readCoreSessionSnapshot,
even though no consumer reads snapshot.messages off an event — clients fetch
messages with the session.messages command. For a multi-megabyte transcript
this turns every status flip into megabytes per subscriber, floods the
durable event log, and (until the send-queue backpressure fix lands) lets a
slow subscriber balloon the hub process by one full transcript copy per
event — reported as a 25GB cline process on a 16GB Mac.
Strip snapshot.messages centrally in HubServerTransport.publish() so every
current and future event publisher is covered, the event log stores slim
envelopes, and cursor replay stays byte-identical with live fan-out. All
other snapshot fields (status, usage, model, workspace, checkpoint) are kept,
and command replies are untouched.
* fix(hub): never capture the transcript into event/reply snapshots
Replaces the publish-boundary strip with the real fix: don't build
message-bearing snapshots in the first place. emitSessionSnapshot no longer
re-reads the entire transcript from disk on every status flip, and
readCoreSessionSnapshot no longer reads it for any event or reply — a
snapshot is a state notification (status, usage, model, workspace,
checkpoint); the transcript is fetched via the session.messages command.
Checkpoint-restore snapshots (session-versioning-service) are untouched:
restore replies carry messages in their own dedicated field.
* chore(desktop): release v0.0.19
* chore(sdk): release v0.0.81
* chore(cli): release v3.0.60
* fix(vscode): avoid render crash on malformed api_req payloads in combineApiRequests (#13560)
* fix(vscode): stop pinning DeepSeek model count in catalog smoke test (#13600)
* feat(ui): share agent welcome hero (#13567)
* feat(ui): share agent welcome hero
* test(ui): cover welcome hero pointer states
* refactor(ui): keep welcome hero API minimal
* test(ui): verify welcome hero package assets
* fix(ui): inline welcome hero masks
* fix(tools): preserve a file's own CRLF line endings across apply_patch updates (#13512)
* fix(desktop): keep the window title bar draggable across views (#13572)
* fix(desktop): keep window title bar persistent
* fix(desktop): reserve persistent title bar space
* fix(desktop): polish persistent title bar layout
* Sign Windows CLI binaries with Azure Trusted Signing; surface app-control launch errors (#13021)
* feat(cli): sign Windows binaries with Azure Trusted Signing and surface app-control launch errors
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): use _CLI-suffixed signing profile secret, normalize endpoint, fail loud on partial config
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* Tunnel ProtoBus over the existing Host Bridge (#13218)
* feat(core): tunnel ProtoBus over Host Bridge
* fix(core): harden Host Bridge stream lifecycle
* fix(core): serialize concurrent chunked responses per request
Streaming handlers deliver updates fire-and-forget, so two logical
responses for one request_id can be in flight at once. Chunked payloads
made forwarding non-atomic: each chunk write is an await, so concurrent
forwards could interleave their chunk sequences and the receiver --
which reassembles purely by arrival order -- would splice two payloads
into one. Route all forwards for a request through one promise chain; a
failed write rejects every later forward so a torn payload is never
followed by more chunks.
Rename the lock manager's instanceAddress to instanceOwner: it holds an
opaque per-spawn instance ID on the token path and a listener address
only on the CLI-harness path. Delete the caller-less getInstanceByPort
query that interpreted the owner as an address.
Also: document message_json as a legal wire encoding for small
payloads, close the gRPC client when startup fails, note the
intentional discard of the cancellation confirmation, and add the
proto's trailing newline.
---------
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
* Build and Authenticode-sign a Windows x64 desktop installer in desktop releases (#13607)
* feat(desktop): build and Authenticode-sign a Windows x64 NSIS installer in desktop releases
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): pin OIDC-adjacent actions to commit SHAs in the Windows signing job
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): pin checkout and upload-artifact to commit SHAs in the Windows signing job
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): show agent-created schedules on the Schedules page (#13613)
* fix(desktop): show agent-created schedules on the Schedules page
Schedule hub commands are scoped to the workspace registered by the
connection, but the desktop app's hub client registers the app launch
directory while agent-created schedules live under each chat's own
workspace folder - so they never appeared on the Schedules page.
Grant token-authenticated hub connections (which can already bind any
workspace at registration) explicit cross-workspace schedule access via
an allWorkspaces payload flag, and have the desktop sidecar request it
for routine schedule commands. Workspace-bound clients (local browser
origins) and default CLI behavior stay scoped.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(core): strip allWorkspaces flag from schedule inputs and pin it in the sidecar payload
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make suggested routine template prompts prescriptive about their final output (#13611)
* Make bug hunter routine template prescriptive about its final report
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make remaining routine templates prescriptive about their final output
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Desktop: surface scheduled-task final output — auto-expand submit_and_exit and render its summary as markdown (#13612)
* desktop: auto-expand submit_and_exit and render its summary as markdown
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: render submit summary in full foreground color
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: label the submit row 'Scheduled task completed'
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: label errored submit_and_exit rows as failed
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add tooltips explaining Live and After recording badges on voice input models (#13610)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Remove box shadow from chat message actions row (#13630)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): make the Tauri shell work on Windows (#13632)
- Defer updater installation to the user-initiated restart on Windows:
install() launches the NSIS installer and exits the process immediately,
so the background cycle now downloads only and stages the bytes, and
restart_to_apply_update installs them after stopping the sidecar.
- Spawn child processes (sidecar, git, cmd /C start) with CREATE_NO_WINDOW
so the GUI-subsystem app doesn't pop visible console windows.
- Fall back to USERPROFILE when HOME is unset resolving the MCP settings
path, matching the sidecar's homedir().
- Reap the sidecar after the Windows hard-kill so its exe file lock is
released before the NSIS installer replaces it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): stop watching agenda spec dirs while the todo tool is disabled (#13629)
* fix(core): stop watching agenda spec dirs while the todo tool is disabled
Since #13530 disabled the agent todo tool, the Agenda UI, and the
automation pump, the hub still created fs.watch watchers on the global
agenda specs dir and on every workspace root recorded in the task store
(at startup and on scope access). Nothing consumes the watcher-driven
task events while the feature is off, and the task.* hub commands
already reconcile spec files on demand, so the watchers are pure
overhead - one OS watch handle per known workspace.
Wire watchFiles to AGENDA_TODO_TOOL_ENABLED the same way
automationEnabled is, preserving a host's explicit watchFiles opt-out
for when the flag is turned back on. Schedules are unaffected: the
schedule list has no file watcher and updates through hub commands and
published schedule events.
* fix(core): reconcile external spec edits inside updateTask
With the spec watchers off there is no background reconciliation, so a
task spec edited directly on disk made every same-store task.update fail
the signature check with "task spec changed outside the manager" until
an unrelated task.get or task.list happened to reconcile the scope.
Reconcile the task's scope at the start of updateTask (mirroring what
refreshAndVerifyTaskIntent already does for approve/run), skipping it
when the file reconciler itself is the caller to avoid recursing from
reconcileFileStore. An external edit now surfaces as the store's normal
stale-revision conflict, and a re-read-and-retry succeeds. This also
closes the pre-existing watcher debounce race for updates.
* fix(sdk): don't log out Codex/OCA users when token refresh fails transiently (#13565)
* fix(sdk): don't log out Codex/OCA users when token refresh fails transiently
Port the cline-provider refresh semantics to openai-codex and oca:
a transient refresh failure (network error, timeout, server 5xx) with an
already-expired access token now rethrows instead of returning null.
A null return means the refresh token was REJECTED and re-auth is
required; treating an outage blip as a rejection is what turned it into
a forced 'openai-codex requires re-authentication.' task stop while the
settings UI still showed the user as signed in.
Both providers also emit user.auth_refresh_soft_failure telemetry on
transient failures (the 'prevented logout' counter the cline provider
already has) and attach status/errorCode details to the genuine
invalid_grant logout event.
* refactor: collapse duplicate soft-failure telemetry branches and test
Review feedback: compute tokenExpired once and emit the soft-failure
event once in both providers, then return current credentials or
rethrow. Fold the codex soft-failure telemetry assertions into the
existing still-usable-token test instead of a near-duplicate case.
* fix: make OpenAI Codex (ChatGPT subscription) sign-in fail loudly instead of silently dead-ending (#13537)
* fix: make OpenAI Codex sign-in fail loudly instead of silently dead-ending
When callback port 1455 is already in use (e.g. by the Codex CLI or a
previous pending sign-in), startLocalOAuthServer returns a no-op server
and loginOpenAICodex would open the browser anyway, then dead-end:
the callback could never be received, and in the VS Code extension the
user just saw nothing happen after clicking 'Sign in to OpenAI Codex'.
- loginOpenAICodex now fails fast with an actionable 'port in use'
error before opening the browser, unless the host provides manual
code entry (the CLI's paste fallback keeps working)
- surface OAuth redirect errors (e.g. access_denied) instead of
collapsing them into 'Missing authorization code'
- the extension dedupes concurrent sign-in clicks: a re-click re-opens
the auth page of the pending flow instead of spawning a second flow
that would collide with our own callback server
- browser-open failures now show an error message with the URL to
open manually instead of only logging
- abandoned-flow timeouts no longer surface a confusing 'Missing
authorization code' toast
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop host-side codex login dedupe, keep flow identical to CLI
The SDK owns the failure handling now (fail-fast on an unbindable
callback port), so the extension keeps the exact same simple
loginOpenAICodex call the CLI uses. A second click while a flow is
pending gets the SDK's clear port-in-use error, same as running
'cline auth openai-codex' twice would. Keep only the CLI-parallel
onOpenUrlError surfacing (the CLI prints 'open the URL above
manually'; the extension's equivalent is an error toast with the
URL).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(e2e): cover Codex sign-in callback-port failure and redirect errors
Two driven-VS Code tests for the OpenAI Codex (ChatGPT subscription)
sign-in flow:
- with port 1455 occupied on both loopback families, clicking the
sign-in button surfaces the fail-fast port-in-use toast
- with the port free, the callback server binds and an OAuth redirect
error (access_denied) propagates to a visible error toast
The second test opens a real browser tab to the OpenAI auth page as a
side effect of the genuine sign-in click.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
* feat(core): anchor agent-created schedules in the user's .cline schedules home (#13634)
* feat(core): anchor agent-created schedules in the user's .cline schedules home
Agent-created schedules inherited whichever workspace folder the chat
session happened to run in, scattering user-level routines across chat
and project folders. They were invisible to workspace-scoped listings
elsewhere, tied to folders that may be cleaned up, and each chat's
tasks tool saw a different set when checking for duplicates.
Anchor them in ~/.cline/schedules instead: the hub's scheduled-task
session defaults now resolve to that home (created on demand), so
agent-created schedules live and run in one stable user-level scope.
The tasks tool guidance now tells agents that scheduled sessions run in
the schedules home, so prompts must carry absolute paths to any project
they operate on.
Schedules created explicitly with a workspace (CLI --workspace, desktop
routine wizard) are unchanged, and existing rows keep their current
workspaceRoot - they stay visible through the all-workspaces listing
paths (#13613, #13633).
* test(core): restore any pre-existing CLINE_DIR after the agenda hub test
The test's cleanup deleted CLINE_DIR outright, so an environment that
had it configured would leave later tests in the same worker on the
default storage directory. Save the previous value and restore it.
* test(core): restore CLINE_DIR even when hub test setup throws early
Restoring the override in the try/finally missed failures thrown during
transport construction or start(), before the try was entered. Register
the restore with onTestFinished instead, which runs regardless of where
the test fails.
* fix(desktop): don't show providers as configured without real credentials (#13608)
* fix(desktop): don't show providers as configured without real credentials
The desktop settings marked any provider with a persisted settings entry
as Configured, but legacy VS Code migration and empty saves can seed
entries (e.g. qwen-code, sapaicore) holding only a default model and no
credentials. Move the CLI's isProviderSettingsUsable readiness check into
@cline/core, expose it as a computed 'configured' flag on the provider
catalog, and use it in the desktop's isProviderConnected.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): resync catalog after saves so Configured badge updates live
Optimistic provider mutations can't know the sidecar-computed 'configured'
flag, so after connecting a keyless provider or saving cloud credentials
(e.g. a Vertex project id) the row stayed 'Not configured' until remount.
Silently refetch the catalog after each successful save, guarded by the
existing generation counter so newer edits discard stale responses.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): claim a generation in post-save resync so overlapping refreshes can't apply stale snapshots
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): bump catalog generation on OAuth login success
Every other optimistic provider mutation claims a new generation; the
OAuth success path didn't, so a catalog load or resync still in flight
could arrive late and overwrite the just-connected state.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): resync catalog after OAuth login instead of bare generation bump
The resync claims a new generation (discarding any stale in-flight
response) and its own fetch covers both the new OAuth connection and any
provider saved moments earlier, matching the post-save path.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): refuse checkpoint workspace restore when HEAD moved past the checkpoint (#13626)
* fix(core): refuse checkpoint workspace restore when HEAD moved past the checkpoint
Restoring a checkpoint runs git reset --hard, which moves the current
branch pointer. If commits were made after the checkpoint (by the user
or by the agent), the reset silently knocked them off the branch,
leaving them reachable only through the reflog.
Guard the reset: if HEAD no longer matches the commit the checkpoint
was created on, throw a descriptive error (including how many commits
would be dropped) instead of destroying history. Chat-only restore is
unaffected, and users who really want to discard the commits can reset
the branch manually first.
Fixes#13550
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): close the guard-to-reset race with an atomic ref update
The moved-HEAD guard read HEAD, ran further git commands, then reset
unconditionally, so a commit landing in that window could still be
knocked off the branch. Replace the reset's branch move with git's
native compare-and-swap (git update-ref HEAD <new> <old>), which fails
if HEAD no longer points at the verified commit, and follow with a bare
reset --hard to sync the index and worktree to the already-moved HEAD.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: hide history cost estimates for subscription-billed tasks (#13562)
* fix(vscode): hide history cost estimates for subscription-billed tasks
The task-header fix for subscription providers cannot reach history:
history rows render the stored totalCost (an API-rate estimate) and do
not know which provider ran the task, so the history page printed
$X.XXXX on every row and the recent-task chips in an empty chat view
rendered a $ chip even for subscription-billed tasks.
The SDK session records already persist the provider — the CLI's
history view uses it for exactly this — but the VS Code mappers dropped
it. Map it through both transports (HistoryItem.apiProvider for the
state-pushed taskHistory, TaskItem.api_provider for getTaskHistory) and
suppress the dollar figure per row when that provider's
usageCostDisplay is not "show", via a new useUsageCostVisibility
predicate shared by both surfaces.
Rows without a recorded provider (tasks predating the field, legacy
imports) keep showing the stored value — there is nothing to key
suppression on.
* test(vscode): e2e-verify history cost suppression in real VS Code
Seeds SDK session records (one openai-codex subscription task, one
anthropic usage-billed task) into the isolated CLINE_DIR before the
webview loads, then asserts in a real VS Code instance that both the
recent-task chips and the full history page render the dollar figure
only for the usage-billed task. Covers the two boundaries the unit
tests stub: on-disk records reaching getTaskHistory with provider
populated, and the provider listings delivering the subscription mark
to the webview.
* Fix scheduled tasks disappearing after desktop app updates (#13627)
* Fix hub-managed schedules being wiped by cron reconciliation on hub restart
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Require the virtual hub/schedules path when exempting specs from removal reconciliation
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Treat recorded source mtime as proof a spec is file-backed, closing the hub/schedules spoof gap
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(shared): discover global rules at ~/Cline/Rules (#13614)
The VS Code Rules tab resolves the Documents folder via
'xdg-user-dir DOCUMENTS', which prints bare $HOME when no user-dirs
config exists (WSL/headless installs), so it reads and writes global
rules at ~/Cline/Rules. The SDK's rule search paths only covered
~/Documents/Cline/Rules, so those rules never reached the system prompt.
Add the missing path to the search list.
Fixes#13542
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat: add searchable session history (#13420)
* feat: add searchable session history
Rebased onto main and updated to supersede the sidebar search dialog
from #13533: the sidebar search icon now opens the indexed command bar
(Cmd/Ctrl+P) instead of a sidebar-local cmdk dialog that eagerly loaded
the entire session history via loadAllSessions(). CommandDialog gains a
shouldFilter passthrough so server-ranked FTS hits are displayed as-is.
* fix: harden session history search
* fix: evict failed restoration sessions from search
* fix: preserve deletion when search eviction fails
* fix: address session search review feedback
* fix: preserve search suppression during reconciliation
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* Fix CLI crash when a remote MCP server is offline but enabled (#13639)
Remote (SSE/streamable HTTP) MCP connects run on the session.create
critical path, which the hub caps at 30s. Without a connect budget an
unreachable server spent the full 60s default request timeout (with the
SSE transport stuck in a reconnect loop), stalling session.create past
the hub deadline and tearing the whole session down - the interactive
TUI exited and one-shot runs failed. Stdio servers already have a
bounded initialize budget for exactly this reason; give URL clients the
same treatment with a 10s default connect budget that an explicit
timeout overrides in either direction.
Fixes#13597
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(vscode): prevent E2E worker teardown hangs (#13644)
* test(vscode): capture external URLs in E2E runs
* docs(test): clarify browser capture rationale
* Add a GitHub integration step to the onboarding (#13225)
* Add feature flags to the app
* React to account updates
* Address comments
* Add a GitHub integration step to the onboarding
* validate domain and fix errors on auth
* Hide the step behind a feature flag
* update version
---------
Co-authored-by: John Choi <john.choi@cline.bot>
* fix(ci): stop e2e worker teardown timeouts and deflake hub daemon e2e on Windows (#13646)
* fix(e2e): stop VS Code e2e worker teardown from timing out
The ext-vscode-test-e2e job has been failing on main with 'Worker teardown
timeout of 60000ms exceeded' even though every test passes. Playwright only
reports an Electron app as closed once the process exits AND every holder of
its stdio pipes is gone (ChildProcess 'close' waits on the extra fd3/fd4
pipes Playwright creates for Electron). Any VS Code descendant that outlives
the main process (chrome_crashpad_handler, GLib's 'dconf watch' helper,
xdg-open browser handlers, VS Code 1.135's agent host CLI subprocess that
logs 'unable to kill the process') keeps those pipes open, so app.close()
never resolves and the worker teardown hangs on it until its 60s timeout
fails the job.
Harness fixes, each removing one source of that wedge:
- closeAppForTeardown now SIGKILLs the whole process group (taskkill /T on
Windows) when app.close() times out, instead of only the main pid — and
does so even when the main process already exited, which is exactly the
wedged state. Playwright launches Electron detached, so pid == pgid.
- Launch VS Code with --disable-crash-reporter so no crashpad handler
outlives the app holding the harness pipes.
- Seed the fresh user-data-dir with chat.disableAIFeatures: true so VS
Code's own AI features (rolled out via server-side experiments, so CI
breaks without any repo change) never start their agent host process.
- Drop the page.close() teardown: closing VS Code's last window quits the
whole app, and ElectronApplication.close() on an already-exited app
deadlocks; the app fixture's app.close() closes windows itself while the
app is alive.
- Codex sign-in no longer opens a real external browser under E2E_TEST; the
codex-oauth test drives the OAuth callback itself, and the browser was an
orphaned process holding the harness pipes on the runner.
* fix(core): deflake hub daemon e2e tests on Windows runners
sdk-test on windows-latest fails intermittently in the hub daemon e2e
files:
- shutdown.e2e.test.ts dies with a bare 'Error: socket hang up'. That
message is the ws handshake (http.ClientRequest) failing, not the
/shutdown fetch (an undici failure prints 'TypeError: fetch failed'):
a freshly spawned bun daemon on a loaded 2-core Windows runner
occasionally drops its first accepted connection before writing the
upgrade response. Real hub clients reconnect with backoff, and the test
asserts shutdown behavior rather than first-connection reliability, so
openAuthenticatedSocket now retries transient handshake failures within
a 15s budget.
- singleton.e2e.test.ts times out waiting for daemon discovery: it still
used the 10s hang guard that 0cfc90158 already raised to 30s in
shutdown.e2e.test.ts for the same reason. Use the same 30s guard.
- Raise the e2e testTimeout to 60s so a test that legitimately spawns two
daemons back to back can survive slow-runner startups instead of the
discovery hang guard being cut off by the test timeout.
* feat(desktop): render tool output images as attachments (#13643)
* fix(desktop): render tool output images as attachments
Add support for displaying media returned by tool calls (e.g. screenshots)
as rendered images with expand-to-fullscreen capability instead of raw
base64 text. Introduces an `ImageCarousel` component for navigating
multiple images, propagates the expand handler to tool message blocks,
and extracts/validates output media in tool summaries.
* test: cover multi-image and canonical media extraction in tool output (#13645)
extractOutputMedia and the desktop tool-message rendering path were only
ever exercised with exactly one distinct valid image, and
canonicalInlineMedia (MCP-style type: "media" blocks for audio/video/file)
had zero coverage. Add tests for: multiple distinct images in one tool
result (parser + desktop carousel navigation), inline audio via the
mime_type key spelling, canonical video/file media blocks, and rejection
of an invalid canonical image block.
---------
Co-authored-by: Harrison <harrison@cline.bot>
* chore(desktop): release v0.0.20
* feat(sdk): add discovery boundary ahead of Agent Plugins support (#13017)
* ENG-2490: Propagate session aborts to teammates (#13647)
* fix(core): propagate session abort to teammates
* fix(core): persist aborted teammate tasks as cancelled
* fix(core): settle teammate work on session abort
* fix(core): isolate replacement runs from stale aborts
* refactor(core): narrow teammate task status metadata
---------
Co-authored-by: abeatrix <beatrix@cline.bot>
* fix(llms): use AI SDK 7 Langfuse telemetry (#13651)
* fix(llms): use AI SDK 7 Langfuse telemetry
* test(llms): cover Langfuse runtime context
* chore(llms): built-in model list update 1787907289186 (#13663)
* chore(llms): built-in model list update 1787907289186
Result of `bun run build:models`.
Includes updated model list and fixed formatting issues across codebase.
* test(llms): update GLM reasoning toggle expectation
* test: cover session search fallback on hub timeout and rejection (#13642)
* feat: add searchable session history
Rebased onto main and updated to supersede the sidebar search dialog
from #13533: the sidebar search icon now opens the indexed command bar
(Cmd/Ctrl+P) instead of a sidebar-local cmdk dialog that eagerly loaded
the entire session history via loadAllSessions(). CommandDialog gains a
shouldFilter passthrough so server-ranked FTS hits are displayed as-is.
* fix: harden session history search
* fix: evict failed restoration sessions from search
* fix: preserve deletion when search eviction fails
* fix: address session search review feedback
* fix: preserve search suppression during reconciliation
* test: cover sidecar search fallback on hub timeout and rejection
The existing search_sessions tests only exercised the index-hit and
empty-index-fallback paths with an immediately-resolved hub reply.
Add coverage for the two other realistic Hub-connection failure
modes the fallback is meant to tolerate: the hub call rejecting, and
the hub call hanging past the 750ms withSearchDeadline race.
---------
Co-authored-by: abeatrix <beatrix@cline.bot>
Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
* fix(core): refresh Cline models from live catalog (#13670)
* feat(ui): share attachment drop zone (#13672)
* feat(ui): share attachment drop zone
* fix(ui): cancel disabled attachment drops
* chore(ui): simplify drop zone surface
* chore(ui): release v0.2.0-next.8
* Chore/bump undici mermaid (#13675)
* chore(deps): bump mermaid to 11.16.1 and raise undici floor to 7.29.0
* chore(deps): patch js-yaml and body-parser in the npm-managed subprojects
* fix(llms): make Langfuse tracer detection survive minified release builds (#13680)
* fix(llms): recognize direct tracer providers
* fix(llms): make Langfuse tracer detection survive minified release builds
Release binaries are compiled with minify enabled, which renames classes,
so initializeLangfuseTelemetry's constructor-name guard never matched
"ProxyTracerProvider" and silently returned readiness=false in every
production build (hub log: "creating span processor" followed by
"initialized readiness=false" with no branch message in between). Dev runs
execute unminified source, which is why the same env vars worked there.
Replace every constructor-name comparison with checks that survive
minification: detect the proxy structurally via getDelegate, distinguish a
recording provider from the no-op fallback by its lifecycle methods, and
confirm our NodeTracerProvider registration by object identity. When a
foreign provider already owns the global slot, attach the Langfuse span
processor to it when it accepts processors, and otherwise shut down the
orphaned provider and report the rejection instead of bailing silently.
Verified by bundling the module with Bun minify:true against the real
OpenTelemetry packages: the previous code reproduces readiness=false
(provider class name mangles to "H2"), the new code initializes with
readiness=true.
* fix(vscode): prevent hook spawn failures from crashing the core process (#13422)
* fix(vscode): prevent hook spawn failures from crashing the core process
A hook child-process spawn failure emitted "error" on HookProcess with no
listener registered, which Node's EventEmitter turns into an uncaught
exception - killing the entire cline-core process instead of failing the
one hook open. Guard the emit behind listenerCount so the rejection (which
StdioHookRunner handles) is the only propagation path.
The trigger was a workspace root that no longer exists on disk passed as
the spawn cwd: Node reports a nonexistent cwd as a misleading ENOENT on
the launcher binary ("spawn /bin/sh ENOENT"). Validate cwd existence in
HookProcess right before spawning - falling back to no explicit cwd with
a warning that names the missing directory - and when a spawn still fails
ENOENT because the directory vanished in between, name it in the error
message instead of blaming the shell.
* fix(vscode): fail hooks with a missing working directory instead of relocating them
Running a hook whose assigned cwd no longer exists from the host
process's own working directory would let its relative paths read and
write an unrelated location (e.g. the IDE install directory). Reject
before spawning, with an error naming the missing directory; the runner
reports the hook as failed and the task continues. Also carry pre-spawn
failure messages into HookExecutionError details so the cause is not
reduced to a bare "exited with code 1".
* fix(vscode): thread task id into hook runner creation so execution telemetry fires (#13547)
The SDK hooks adapter created every hook runner without a task id, and
StdioHookRunner gates all captureHookExecution calls on one being set —
so the next variant emitted zero hooks.execution events while discovery
telemetry fired normally. Pass the task id (and tool name for the tool
hooks) at all five factory.create call sites, and pin the threading
with a regression test.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: JasmineLCY <38378321+JasmineLCY@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
Co-authored-by: Max <maxpaulus43@gmail.com>
Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bee <abeatrix@users.noreply.github.com>
Co-authored-by: Renee Huang <100229782+reneehuang1@users.noreply.github.com>
Co-authored-by: Ara <arafat.da.khan@gmail.com>
Co-authored-by: Haley Park <haleypark.design@gmail.com>
Co-authored-by: cline-cloud[bot] <276134852+cline-cloud[bot]@users.noreply.github.com>
Co-authored-by: yzxcj797 <54314860+yzxcj797@users.noreply.github.com>
Co-authored-by: yzxcj797 <yzxcj797@users.noreply.github.com>
Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
Co-authored-by: 𝓜𝓲𝓼𝓼𝓪𝓻𝓲 𝓐𝓱𝓲𝓵 🌿 <143264692+missarii@users.noreply.github.com>
Co-authored-by: Dominic Cooney <dominic.cooney@cline.bot>
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
Co-authored-by: Harrison <harrison@cline.bot>
Co-authored-by: abeatrix <beatrix@cline.bot>
Co-authored-by: TheRealSpencer <32678829+TheRealSpencer@users.noreply.github.com>
* feat(desktop): Composio connectors (Gmail, Google Calendar, GitHub + catalog)
Port of the connectors feature from main (bee/poc-connectors-composio,
PR #13684) onto desktop-experimental, adapted to this branch's inline
marketplace: with no separate Marketplace page here, the Customize >
Connectors tab hosts both the installed view (API key management,
recommended Gmail / Google Calendar / GitHub, connected accounts) and
the browsable connectable-only catalog below it. The Marketplace-page
filter-chip wiring is kept dormant for a cleaner future merge with main.
Feature summary:
- Sidecar management plane (sidecar/composio.ts): key handling with a
COMPOSIO_API_KEY env fallback, OAuth connect (authorize with a
connected-accounts link fallback, custom auth configs preferred),
dashboard reconciliation, and a usage-ranked catalog filtered to
toolkits with Composio-managed credentials or a project auth config.
- Connected toolkits materialize as a generated single-file plugin in
~/.cline/plugins that the Hub loads into new sessions; tools execute
against Composio's REST API with versions pinned at connect time.
- Rows use Install / View; Uninstall lives in the fixed-size detail
dialog; official catalog logos with themed fallbacks.
* fix(desktop): guard stale OAuth finalization and surface plugin sync failures
Review follow-ups on the Composio connectors:
- finalizeToolkitConnection now takes a guard (attempt id + the key/user
the attempt started under) and re-checks it synchronously at write
time, after the awaited tool fetch — a cancel, disconnect, or key
change landing mid-flow drops the stale result instead of resurrecting
a disconnected account or binding an old-project account to a new key.
setComposioApiKey cancels in-flight attempts when the key changes, and
the dashboard reconcile re-reads state before writing and skips
toolkits disconnected after its remote snapshot was taken.
- syncComposioPluginFile now throws on filesystem failure. User-initiated
paths surface it: connect keeps the recorded connection but reports
that new sessions will not see the tools; disconnect and key changes
fail loudly when the plugin file could not be updated. Passive status
reads keep a log-and-continue wrapper.
* fix(vscode): continue the surviving session on resume instead of rebuilding with the original task text (#13175)
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilt the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
The preserved conversation history is the source of truth on resume, so
the fallback prompt now just asks the model to reassess the history and
continue, matching the legacy resume prompt which also never resent the
original task. User-typed text still takes precedence when provided.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): continue the surviving idle session on follow-ups instead of rebuilding
Stopping a turn keeps the session alive, but every idle follow-up (bare
Resume after Stop, and typed follow-ups after a completed turn) tore
that session down and rebuilt it from persisted task history before
sending. Continue the matching idle session in place instead, the same
way the CLI reuses the live session after an abort. Rebuilding from
history now only happens when no live session matches the displayed
task (task opened from history, extension host reload).
A bare resume still needs a prompt to start a turn, so it sends the
neutral [TASK RESUMPTION] prompt (shared with the rebuild fallback and
hidden from the transcript); user-typed content is echoed and sent
as-is. If the send lands while the abort is still settling, the runtime
auto-queues it and drains once the abort completes.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): consolidate follow-up send paths in SdkFollowupCoordinator
Now that idle follow-ups continue the live session in place, the
two-mode sendToActiveSession helper was redundant: its non-queued branch
duplicated continueIdleSession minus the bare-resume prompt. Split it
into a single-purpose queueToActiveSession and fold the idle no-task
send into continueIdleSession, flattening askResponse's decision tree
to: queue onto a running turn, continue a matching live idle session,
rebuild from history, or abandon.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): reuse the existing neutral resumption prompt for bare resumes
Drop the newly invented long resumption wording in favor of the phrase
that already existed as the no-history fallback and that the transcript
hiding logic and test fixtures recognize: '[TASK RESUMPTION] Please
continue where you left off.' The net change to resumeSessionFromTask
against main is now just deleting the branch that resubmitted
historyItem.task as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilds the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
Bare resumes now always use the neutral prompt that already existed as
the no-history fallback; user-typed text still takes precedence. This
matches the legacy resume prompt (responses.taskResumption), which only
ever included user-supplied text as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): hide synthetic prompts from the queued-prompt echo
A send that races a settling abort is auto-queued by the runtime, so a
bare Resume can reach the pending_prompt_submitted echo carrying the
synthetic [TASK RESUMPTION] prompt. Echoing it leaked model-facing text
as a visible user bubble and shifted the visible-user-message ordinals
that edit/regenerate mapping relies on. Filter synthetic prompts with
isSyntheticUserPrompt, keeping user attachments visible (matching
isSyntheticSdkUserMessage semantics).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): preserve LiteLLM input token limits (#13293)
* fix(vscode): preserve LiteLLM input token limits
* fix(vscode): prefer live LiteLLM model metadata
* fix(vscode): generalize private catalog metadata
* test(vscode): preserve llms exports in vscode lm mock
* fix(vscode): point provider signup URLs at their API key pages (#13337)
* fix(vscode): point Mistral signup URL at the general API keys console
The Mistral provider's signup link led to the Codestral console, which
issues Codestral-scoped keys that fail with 401 on api.mistral.ai — the
endpoint the provider actually calls. Point it at the general API keys
page instead.
Fixes#13288
* fix(vscode): deep-link DeepSeek and Fireworks signup URLs to their API key pages
Both pointed at marketing homepages; link straight to the key-creation
pages instead, matching the rest of the registry and the desktop app's
provider-key-urls map.
* fix(ci): always build the legacy bundle from the legacy-extension branch (#13349)
The combined-VSIX workflow took legacy-ref as a free-form dispatch input
with no publish-time validation (next-ref has one: publish requires main).
Any typed ref — a PR merge ref, an unprotected branch — would be built
into the published VSIX by the environment-less build job, and the publish
environment approver only ever sees an opaque prebuilt artifact, so the
approval protected the marketplace PAT but not the shipped bytes.
Remove the input entirely and hardcode the protected legacy-extension
branch, which makes that branch's protection rules load-bearing for
releases. The tested-sha pinning between test-legacy and build is
unchanged. publish-extension skill dispatch command updated to match.
* fix(ci): lock the legacy publish workflow to the legacy-extension branch (#13350)
The branch dispatch input was a free-form string with no validation. Both
jobs checked it out and ran full npm lifecycle scripts from it: the publish
job next to VSCE_PAT/OVSX_PAT (and npm run publish:marketplace executes a
script from that same ref with the PATs in env), and the test job with NO
environment approval at all while inheriting the workflow-level
contents/packages/checks/pull-requests write grants. A dispatch pointing at
e.g. refs/pull/N/head would run outside-contributor code with the
marketplace keys behind one approval, or with a repo-write token behind
none.
Remove the input and hardcode the protected legacy-extension branch, drop
the workflow-level permissions to contents: read, and elevate only the
publish job to contents: write (tag push + GitHub release). The branch
input's default was legacy-extension, so normal publishes are unchanged.
publish-extension skill dispatch command updated to match.
* fix(vscode): SDK remote-config parity — refresh coordination, session gating, and fail-closed opt-out (#13226)
* feat(desktop): native notifications (#13166)
* feat(desktop): native notifications
* macos target
* fix(desktop): isolate macOS dev app identity
* fix(desktop): address notification review feedback
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched (#13310)
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched
Toggling an auto-approve setting while a task is open writes
autoApprovalSettings into the StateManager's task-settings overlay
(updateAutoApprovalSettings -> setTaskSettings). The SDK controller never
cleared that overlay on clearTask/showTaskWithId (the legacy controller
did), so after New Task the stale overlay kept shadowing global settings
in getGlobalSettingsKey(): toggle RPCs were accepted into global state,
but every posted state still carried the overlay's old version, which the
webview rejects as not newer - the auto-approve checkboxes froze forever.
Restore legacy parity in SdkTaskControlCoordinator: drop the overlay
(persisting pending writes first) in clearTask() and before installing a
different task's proxy in showTaskWithId().
Fixes#13260
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* changeset
* test(vscode): add end-to-end regression test for auto-approve freeze after New Task
Wires the real StateManager, the real updateAutoApprovalSettings handler,
and the real SdkTaskControlCoordinator.clearTask() together with the
webview's version gate modeled on ExtensionStateContext, pinning the
end-to-end invariant behind #13260: checkbox toggles must keep reaching
the webview after a mid-task toggle followed by New Task. Verified the
test fails when the clearTaskSettings() call is removed from clearTask().
* fix implicit any in regression test
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): show provider web-search support under the settings toggle (#13328)
* feat(desktop): show provider web-search support under the settings toggle
The global Web search toggle silently does nothing unless the session's
provider offers native web search, which made the setting read as if it
worked with any provider. The desktop General settings row now explains
that only providers with built-in web search honor it, and shows a live
status line: which connected providers are ready to use it (no extra
setup needed), or an amber warning with a link to the Models section
when none of them support it.
Support is resolved in the webview via a new providerOffersModelTool
helper in @cline/llms (browser export), sharing the same builtin-manifest
source of truth as the runtime's supportsModelTool attachment check.
* fix(desktop): address review — refetch web-search status on catalog invalidation, clarify per-model support
Greptile P2: the one-time catalog fetch could race an in-flight provider
save and show stale status; the row now refetches when the provider
catalog cache is invalidated (fired after saves complete).
Greptile P1: the ready line implied every model on the provider works;
Vertex excludes Claude routes, so the copy now scopes the promise to
models that support it.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat(ui/desktop): collapse finished runs into a work summary and remove hover-state dead space (#13315)
* feat(ui): add WorkActivity collapsed-run summary and float message actions as a pill
WorkActivity/WorkActivityTrigger/WorkActivityContent fold a finished agent
run's working rows (tool calls, thinking traces, narration) behind a single
"Worked for 4m 12s · 14 tool calls" disclosure built on the shared animated
disclosure primitives, with formatWorkActivityLabel/formatWorkDuration
exported for consumers.
Message hover actions no longer rely on the transcript reserving blank space
below each message: the action row is now a self-backed pill (border,
blurred background, shadow) that floats over whatever follows, so
conversations can pack rows tightly without hover chrome colliding with the
next message.
* feat(desktop): collapse finished runs into a work summary and tighten chat spacing
collapseCompletedWork post-processes the grouped transcript: once a run ends
on assistant text with no further tool calls, its working rows fold into one
expandable WorkActivity row while the final answer stays visible. Runs are
delimited by user messages; the trailing run only collapses when the session
has stopped running and actually produced an answer, so live streams and
cancelled/failed tails keep their rows. Assistant messages carrying images
or media are treated as deliverables and never collapse.
The conversation list gap drops from gap-8 to gap-4 now that hover actions
are self-backed pills that need no reserved space, and user messages add
their own top margin so turn boundaries stay visually distinct.
* refactor(ui/desktop): work summary label wording, flat expansion, stable in-run rhythm
Feedback round on #13315:
- Label reads "Worked for 4m 12s and made 14 tool calls" instead of joining
with a dot; without a duration it falls back to "Made N tool calls".
- Expanded work rows render at transcript level — no rail or extra indent —
since tool rows and thinking traces already carry their own nesting when
expanded. The work content keeps the tight working-row rhythm.
- Live working rows (thinking traces + tool calls) now group into a 'run'
render item with the same tight 0.25rem rhythm, so there is no oversized
gap under a "Thought for Ns" row and every row keeps its exact position
when the finished run folds into the work summary. A trailing
answer-in-progress stays outside the group at transcript level, and pure
prose spans keep normal spacing.
- The transient "Thinking..." indicator moves inside the transcript column
and mirrors a trigger row's geometry, so the first real row replaces it in
place with no jump.
* style(ui/desktop): hover-pill metrics, right-pointing work chevron, scroll and spacing fixes
Another feedback round on #13315:
- Hover action pill: +2px internal padding, a trailing inset after the
timestamp (it sat flush against the pill border), and more clearance
between the message content and the pill (2px -> 6px; the hover bridge
grows to match).
- The work summary chevron points right while collapsed and continues
counterclockwise to point up when expanded.
- Conversation bottom padding drops pb-20 -> pb-8: the composer sits below
the scroller, so the padding only needs to clear a pinned action pill.
- Sending a message scrolls back to the bottom even if the reader had
scrolled up (new AutoScrollOnSend on the user-message count, which ignores
optimistic-bubble re-keying; @cline/ui now exports useConversation for
this).
- An assistant answer directly under its run's working rows pulls itself
0.5rem closer than the full transcript gap.
* style(desktop): leave a visible gap between a pinned action pill and the composer
pb-8 exactly matched the pill's ~40px footprint, so the last row's hover
actions sat flush against the composer top; pb-12 restores ~8px of daylight.
* style(desktop): widen the gap between the pinned action pill and the composer to ~24px
pb-12 left only ~8px of daylight under the pill; pb-16 reads comfortable
without reverting to pb-20's dead space.
* fix(desktop): keep the thinking indicator at the working-row offset mid-run
The indicator matched a trigger row's geometry but sat a full transcript gap
(1rem) below the last working row, while the tool/thinking row replacing it
joins the tight run group at 0.25rem — a visible upward jump. When the last
transcript item is working rows (or streamed assistant output), the
indicator now pulls up to the same tight offset; only at the start of a run,
under the user message, does it keep the normal gap.
* style(ui): calm the hover actions surface per team feedback
Borderless rectangle instead of the bordered pill: radius drops to
var(--radius), the side padding goes entirely (the icon buttons carry their
own hit areas), and the vertical padding halves. Blurred background and
shadow stay so it remains legible over following content.
* feat(ui/desktop): full-band hover reveal and iOS-style disclosure easing
The hover actions only appeared while the pointer was inside the message
box itself. The invisible bridge under each message now spans the full
height of the band the floating actions occupy (full row width), so
hovering anywhere in that strip reveals them. Sibling row types
(.cline-chat-tool, .cline-chat-work, and the desktop's run/tool groups)
become position: relative so they paint above the bridge — their own
content keeps its hover and clicks, and the bridge only wins in the band's
genuinely empty space.
All expandable rows (work summary, tool panels, thinking) open and close on
a 240ms symmetric ease-in-out cubic-bezier instead of the 60ms snap, with
chevron rotation on the same curve. Reduced-motion still disables both.
* revert(ui/desktop): drop the full-band hover reveal; quicken disclosure easing to 180ms
The full-band hover bridge (and the position: relative changes that made it
safe) is reverted per feedback — back to the narrow bridge that only spans
the gap under the message. The iOS-style ease-in-out on disclosures stays
but speeds up from 240ms to 180ms.
* fix(ui): recover live tool diffs that mount as a blank pierre skeleton
Live-streamed edit rows could show an empty diff for the whole run, with the
diff only appearing after the collapsed work row was expanded (fresh mount).
Root cause, confirmed by driving a live session and inspecting the element:
React StrictMode double-invokes @pierre/diffs' ref callback; the first
instance's async highlight work aborts on its immediate cleanup, and the
second instance adopts the abandoned half-rendered shadow tree as if it were
complete prerendered output — zero height, no code, no theme stylesheet,
permanently. A rendered diff always carries style[data-theme-css] in its
shadow root, so ToolFileDiff now checks for it shortly after mount and
remounts FileDiff (bounded attempts) when missing; the fresh host element
takes the normal render path and recovers within ~400ms. Verified live: the
diff now renders during the run.
* fix(desktop): keep interrupted runs expanded even with partial trailing text
The trailing-run collapse gated on 'ended with assistant text', which
misread a Stop that landed mid-answer as a finished run and folded the tool
calls the user wants to inspect. The gate is now the terminal status itself:
only completed (or restored-idle) sessions collapse the trailing run;
cancelled/failed/error tails keep their rows regardless of partial text.
(Greptile P1 on #13315 — matches the PR's stated rule.)
* feat(ui): share the markdown pipeline, chat polish, and ThinkingBlock across products (#13323)
* feat(ui): share the markdown pipeline, chat polish CSS, and ThinkingBlock
The desktop app and the cloud dashboard both consume @cline/ui yet rendered
assistant output differently, because Markdown policy and the thinking-trace
row lived app-side. This moves the shareable parts into the package:
- components/markdown (new export): the lazy Shiki code highlighter (GitHub
light/dark, pinned language set) and agentMarkdownControls — the standard
Streamdown configuration. streamdown/shiki/@shikijs/* become optional peer
dependencies, mirroring @pierre/diffs.
- components/markdown.css: the desktop's chat polish moves in — chat-scale
headings, outside list markers, single quiet code blocks with a
hover-revealed copy control, table cards. Kept unlayered so it beats
Streamdown's layered Tailwind utilities without !important.
- ThinkingBlock + formatThoughtLabel in agent-chat: the standard thinking
row (brain icon, Thinking/Thought-for-Ns label, streaming shimmer, rail
presentation, capped scrollable body). The shimmer and the
reasoning-hover-suppression rule move into agent-chat.css; triggers gain
the color transition the desktop applied locally.
Version bumps to 0.2.0-next.5 for the dashboard to pick up.
* refactor(desktop): consume shared markdown and thinking primitives from @cline/ui
The local Shiki highlighter, Streamdown controls, chat markdown polish CSS,
streaming-title shimmer, and reasoning hover-suppression rule are deleted in
favor of the @cline/ui versions (the highlighter test moves to the package's
suite). ReasoningBlock becomes a thin wrapper that hands MemoizedMarkdown to
the shared ThinkingBlock, and formatThoughtLabel re-exports from the package
so grouping code and tests keep their import path.
globals.css now imports @cline/ui/components/markdown.css (unlayered, so the
polish keeps beating Streamdown's layered utilities); the app keeps only what
is genuinely app-specific: link/image policy in markdown.tsx, selectability
rules, accent palettes, and the view-enter transition.
* style(ui/desktop): make thinking-trace prose legible
Thinking body text rendered too faint: plain muted-foreground plus the
desktop's font-thin weight. The shared thinking content now leans 75% of the
way back toward the body text color (still slightly de-emphasized), and the
desktop drops the thin font weight.
* ci(ui-publish): build @cline/shared before ui typecheck (#13354)
@cline/ui's generated-media imports @cline/shared/browser, which resolves to
shared's dist output. The build-shared step sat after typecheck/test/build,
so the first ui-publish dispatch since #13025 failed at Typecheck UI with
TS2307. Move the step to right after install.
* fix: run_commands object form without args routes through the shell instead of failing with ENOENT (#13336)
* fix: run_commands object form without args routes through the shell
The structured { command, args? } form of run_commands was always spawned
directly with shell: false. When a model emitted a full command line in
command with no args (e.g. { command: "echo hello" }), spawn failed with
ENOENT for any command containing a space, breaking command execution for
the whole session.
Direct exec now only applies when a non-empty args list is provided; the
object form without args is routed through getShellInvocation like the
string form. Schema descriptions are tightened so models put arguments in
args instead of embedding them in command.
Fixes#13279
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: trim structured-command schema descriptions
The union schema is only used for lenient validation of input the model
already sent; its descriptions never reach a model prompt. Keep them
short instead of restating executor behavior.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: simplify direct-exec comment in shell executor
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert: keep original structured-command schema description
The description never reaches a model prompt and the executor now handles
both shapes, so the wording change was cosmetic noise.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: gate direct exec on args key presence, not array length
Review feedback: an explicit empty args array is intentionally structured
input and stays direct exec; only an object with no args key is treated
as a full shell command line. Matches the key-presence rule already used
by the VS Code host's formatCommandForTerminal. Also replaces the
empty-args shell test (which was PowerShell-incompatible) with a test
pinning the direct-exec contract.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: normalize Gemini custom base URLs for legacy host-root values (#13329)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* docs: add GLM-5.3 to ClinePass models and reference pricing (#13357)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): stream run command output (#13179)
* feat(desktop): stream run command output
* fix(sdk): clean up detached command logs
* fix(sdk): reap detached logs after hub restarts
* fix(sdk): preserve live detached command logs
* fix(desktop): harden live command progress
* fix(sdk): recover detached logs for local hosts
* fix(desktop): reconcile command output tool rows
* fix(sdk): retain logs for surviving commands
* fix(core): prevent PID reuse from retaining detached logs
* fix(core): preserve detached logs on probe failures
* fix(core): retain detached logs during probe outages
* fix(desktop): resolve leftover merge conflict in messages projection test
Combine both sides of the assertion: main's incremented per-block
createdAt projection and this branch's toolCallId/hookEventName meta.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): make TUI dialog colors follow theme changes live (#13355)
* fix(cli): make TUI dialog colors follow theme changes live
Dialog content previously read the static palette constant, so open
dialogs (including the theme picker itself) kept the default dark-blue
accents while scrolling through theme previews. Add getDialogPalette /
useDialogPalette, which resolve dialog colors from the active theme's
dialog accents and re-render on every theme change, and migrate all
dialog-rendered components to it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(cli): derive dialog panel background from the active theme
Dark themes now lift their own background one OKLAB step for the dialog
surface, so panels keep the theme's hue instead of the library's fixed
#262626. DialogThemeSync pushes the surface into the dialog container
for new dialogs and repaints open panels, so the surface also follows
live theme previews. Light themes keep the neutral dark panel to match
the dark accent fallback and the light-on-dark dialog text.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: skill slash commands load via the skills tool instead of expanding into the user message (#13327)
* fix(desktop): show typed slash command instead of expanded skill markdown
The sidecar expands /skill and /workflow tokens into their instructions
before dispatching, so the runtime's persisted transcript only contains the
expanded text. After a turn (and when reopening a session) the webview
re-hydrates from that history and rendered the whole SKILL.md body as the
user's message; queue events echoing the expanded prompt could also add a
second user bubble, and fresh sessions were titled with the markdown's first
line. The CLI never shows this because its TUI keeps the typed text in its
own transcript and only sends the expanded prompt to the model.
Mirror that separation inside the desktop sidecar's display boundaries:
- history projection (readSessionMessages) inverts user text that starts
with a configured command's instructions back to '/name remainder',
which also repairs sessions recorded before this fix
- queue snapshots and chat_queued_prompt_start events echo the typed
prompt recorded at expansion time, so the webview's optimistic-bubble
re-key matches again
- an untitled session sent an expanded prompt gets titled from the typed
command instead of the instructions' first line
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't overwrite a mid-turn rename with the typed-command title
The untitled check ran before dispatch, so renaming a fresh slash-command
session while its first turn was running got clobbered by the post-turn
typed-command title. Re-check at write time and only replace a missing title
or the one the runtime auto-derived from the expanded prompt.
Also documents the inherent prefix-inversion ambiguity flagged in review:
text hand-typed with a command's exact instructions persists byte-identically
to that command's expansion, so stored history alone cannot distinguish them.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop expanding skill commands; let the skills tool load them
Pasting the skill body into the prompt is why the transcript could ever show
it: the desktop webview re-hydrates from the runtime's persisted history, so
whatever the sidecar splices into the user message renders as if the user
typed it. The runtime already registers the skills tool, whose description
requires the model to invoke it whenever the user references a slash command
— so send the typed /skill text through and let the tool deliver the
instructions as a tool result (previously they arrived twice: pasted and via
the tool). The persisted user message, session title, and queue entries are
then simply the typed command, which deletes the typed-prompt registry, the
queue event/snapshot rewriting, and the title machinery from the previous
approach.
Workflows are not served by the skills tool and keep textual expansion, so
the read-time display inverter stays: it collapses expanded workflow prompts
— and skill prompts persisted before this change — back to the typed
/command in the history projection.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): option to keep skill slash commands typed for the skills tool
resolveRuntimeSlashCommandFromWatcher (and the hub snapshot proxy) accept
expandSkillCommands: hosts whose sessions register the skills tool pass
false so the typed /skill goes through and the model loads the instructions
as a tool result, keeping the persisted transcript as what the user typed.
Workflows always expand — the tool does not serve them. isSkillsToolAvailable
exposes the catalog check hosts use to decide (yolo preset and the skills
tool toggle leave textual expansion as the only delivery path).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): skill slash commands load via the skills tool instead of expanding
The TUI user-command wrap and buildUserInputMessage now keep a typed /skill
as-is when the session's mode/toggles register the skills tool, matching the
desktop app; workflows keep expanding, and yolo (zen) keeps expanding skills
because its preset has no skills tool. This also fixes CLI resume/history
surfaces showing the skill body: the persisted user message is now the typed
command.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): keep configured skill slash commands typed for the skills tool
expandSlashCommands no longer splices a configured skill's instructions into
the model text; the SDK session's skills tool delivers them as a tool result
(previously they arrived twice). Builtin pseudo-skills like /deep-planning
are not served by that tool and keep expanding, as do workflows.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): use the shared skill-expansion option in the sidecar
Replaces the sidecar's workflow-detection dance with core's
expandSkillCommands option and gates on isSkillsToolAvailable, restoring
textual expansion where the tool is missing (yolo mode or the skills tool
toggle) — a gap in the previous desktop-only change.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): drop the display inverter for expanded transcripts
Accepted trade-off to keep the change minimal: sessions recorded before
skills switched to the skills tool, workflow sends (deprecated), and
yolo-mode skill sends persist expanded instructions and now render that text
as-is instead of being collapsed back to the typed /command at projection
time.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Use fixed selection chevron in account dialog to match other dialogs (#13364)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): align system prompt with session mode (#13361)
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(desktop): finalize queued turns on chat_done with canonical history reconcile (#13330)
Turns that settle through the event stream (queued prompts, including the
first prompt of a fresh session) resolve their send() RPC early, so nothing
cleared the streaming shimmer or reconciled live-streamed content against
the persisted transcript at turn end. A turn whose deltas were incomplete
stayed visually streaming forever and only healed when a later non-queued
send rehydrated history.
chat_done (and chat_session_ended / the queue-drain double check) now clears
the active assistant streaming id and schedules a short-delayed
read_session_messages + applyCanonicalHistory, guarded by turn epoch,
session id, and in-flight send submissions so it never clobbers a newer
turn or duplicates the blocking send path's own finalization.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(desktop): release v0.0.14
* fix(clients): filter non-chat models from chat pickers (#13317)
* fix(clients): filter non-chat models from chat pickers
* fix(clients): align chat model eligibility
* fix(desktop): strip user_input envelope when copying a user message (#13369)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bee <abeatrix@users.noreply.github.com>
* docs: show DeepSeek V4 peak and off-peak pricing (#13312)
* docs: update DeepSeek V4 average pricing
* docs: show DeepSeek peak and off-peak pricing
* docs: add GLM-5.3 reference pricing (same as GLM-5.2)
* docs: add GLM-5.3 to ClinePass models table
* fix(llms): display billed gateway cost (#13385)
* fix(shared): run PowerShell commands with fail-fast error semantics (#13358)
* fix(shared): run PowerShell commands with fail-fast error semantics
The run_commands PowerShell wrapper never set $ErrorActionPreference, so
the default 'Continue' applied: a pipeline erroring per item (e.g. a
malformed Where-Object over Get-ChildItem -Recurse) emitted one error
record per enumerated file - tens of thousands of stderr records on
large trees, looking like a hang - and could still resolve as SUCCESS
with exit 0.
Prepend $ErrorActionPreference='Stop'; to the script content executed
by the ScriptBlock so the first error terminates the command with a
non-zero exit and a single error message. Concatenated on the same line
as the user command so error line numbers stay unshifted.
Fixes#13285
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(shared): set the fail-fast preference in the bootstrap scope
Setting $ErrorActionPreference='Stop' by string-prepending it into the
scriptblock source displaced a leading param(...) from its mandatory
first-statement position, so scripts beginning with a param block failed
with CommandNotFoundException. Preference variables are dynamically
scoped, so setting Stop in the -Command bootstrap gives the invoked
scriptblock identical fail-fast semantics while keeping the user script
byte-identical (param works, error positions unshifted) and drops the
doubled-quote escaping.
* docs(shared): document the fail-fast tradeoffs in the PowerShell wrapper
Stop promotes every non-terminating error, not only per-item pipeline
floods: partial-result commands (recursive listings over access-denied
junctions) now stop at their first error, and Windows PowerShell 5.1
turns in-script stderr redirection of succeeding native commands fatal.
State this in the wrapper comment as a deliberate tradeoff, with the
GitHub Actions precedent and the per-command opt-outs.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
* ci: stop over-long changelogs from silently dropping release Slack posts (#12955)
Slack section blocks reject text longer than 3000 characters. The Slack
action logs that rejection as ##[error] but does not fail the step, so an
over-long changelog drops the release announcement while the run stays
green — cline@3.0.50 (3272 chars) published to npm, tagged, and cut a
GitHub release with no Slack post and nothing red to notice.
Every publish workflow pasted the changelog section verbatim into one
section block, so all six were exposed; the SDK, desktop, and extension
sections were only 150-350 chars under the ceiling.
Add a slack_content output alongside content: unchanged when the section
fits, otherwise trimmed on a line boundary with a link to the full
release notes. Only the Slack payload uses it — GitHub release bodies and
the desktop updater manifest still get the whole section.
* ci: tidy workflow cache config and job permissions (#13403)
Publish workflows now always do clean npm installs (no dependency
cache in their test gates), the e2e workflow's cache keys are
exact-match only, and the e2e job drops an id-token permission it
never used.
* Rename desktop app from "Cline Code" to "Cline" (#13401)
* Rename desktop app from Cline Code to Cline
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Format touched Rust test assertions
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): surface provider-executed tool activity as observational events (#13300)
* fix(llms): surface provider-executed tool activity as observational events
Provider-executed tool parts (e.g. every tool the Claude Code CLI runs
inside its own session) were dropped by the model-tool guard added for
web search: only declared model tools were re-emitted, everything else
hit continue with nothing yielded. Those sessions modified the workspace
with no tool activity in runtime events, transcripts, or the UI.
Route all providerExecuted parts onto the observational path instead:
emit execution-tagged tool-call-delta and tool-result events, matched by
tool-call ID for providers that omit the flag on the result half. They
stay out of AgentRuntime's execution/approval loop, and the runtime
already persists them as modelToolActivities and projects them for
display.
The AgentModelEvent tool-result variant widens toolName from
ModelToolName to string to carry the provider's own tool names.
* fix(agents): keep turns that are only provider-executed tool activity
A turn consisting solely of observational tool activity has an empty
assistant content array - the activity lives in message metadata, since
projecting it into content would replay tool_use blocks the model never
gets results for. The empty-content guard threw on such turns, erroring
the run and losing the activity from the transcript. Count model-tool
activity as content for the emptiness check (error finishes still
throw); replay stays safe through the codec's empty-content placeholder.
Also drop the trailing text delta from one gateway test so the tool-only
stream shape stays covered end to end.
* feat: allow agents to create scheduled tasks (#13331)
* feat(core, desktop): add durable todo agenda
* fix(desktop): secure todo approvals and track tool usage
* fix(desktop): clean up failed approval delivery
* fix(desktop): authenticate approval connections
* fix(desktop): cancel approvals on broadcast failure
* fix(desktop): authenticate development approvals
* fix(desktop): harden development approvals
* test(core): make task paths cross-platform
* fix(desktop): serialize approval readiness
* refactor(core): unify todo and schedule tools
* feat(core): distinguish user todos from agent suggestions
* fix(core): hide tasks tool in yolo mode
* fix(core): enforce schedule workspace scope
* fix(core): bind schedule scope to hub connection
* fix(core): establish task scope at hub startup
* fix(core): scope task automation by workspace
* test(core): normalize workspace path expectations
* test(core): serialize Windows CI workers
* fix(core): reject unregistered schedule authority
* fix(desktop): guard task execution commands
* fix(core): avoid polynomial regex in mention parsing
* fix(core): address schedule tool review feedback
* fix(core): bind websocket clients to hub workspace
* fix(core): flatten tasks tool input schema
* fix(core): authorize multi-workspace hub clients
* test(core): type hub transport authority mock
* fix(cli): register a workspace client for remote schedule commands (#13398)
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(desktop): treat ClinePass as OAuth-managed in the chat credential gate (#13404)
* fix(desktop): treat ClinePass as OAuth-managed in chat credential gate
ClinePass shares the Cline account OAuth credentials (its auth handler
stores under the "cline" provider), so the webview never sees a plain
API key for it. The chat pre-flight check only exempted cline/oca/
openai-codex, so switching to ClinePass while signed in via OAuth
blocked with "Missing API key" even though the sidecar resolves the
stored access token fine (which is why the CLI worked).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style: format helpers.test.ts with biome
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(ui): stack code block lines when streamdown lineNumbers is off (#13412)
streamdown renders each Shiki token line as a bare inline span with no
newline text between non-empty lines, and only applies its block line
class when lineNumbers is on. With lineNumbers off (the desktop app's
config) every multi-line fenced block collapsed into one run-on line.
Make the direct line spans under code-block-body display: block in the
shared markdown.css; empty lines keep their height via their lone "\n"
child under white-space: pre.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): work summary undercounts wall time when pre-tool thinking attaches to the answer (#13413)
* fix(desktop): anchor work summary duration on the answer row, not attached pre-tool reasoning
The collapsed 'Worked for Xs' row undercounted wall time whenever a turn's
assistant message contained thinking + tool_use with no narration text: the
canonical projection emitted the reasoning-only row after the tool row (both
stamped before the tool executed), the webview attached that row to the final
answer, and collapseCompletedWork used the answer's earliest attached
reasoning timestamp as the end anchor - excluding the entire tool execution
(e.g. 'Worked for 5s' for a turn with an 8s command).
- webview: end the work span at the answer row's own timestamp, clamped to
the last collapsed row so a fallback answer bubble with a synthetic early
timestamp cannot shrink the duration either
- sidecar: flush pending thinking before a tool_use row so rehydrated
transcripts keep the live-stream order (thinking before its tool call) and
pre-tool reasoning no longer rides on the next answer
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep interleaved thinking between the tool calls it separates
Address Greptile review: when one assistant message interleaves thinking
between multiple tool_use blocks, each reasoning segment now projects at its
own position (attached to a text row from its own segment when present,
otherwise as its own row) instead of merging into the first reasoning row,
which displayed later thinking before a tool call it actually followed.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): remove settings gear hover state while Account screen is open (#13408)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't show "No sessions found" while session history is still loading (#13414)
* fix(desktop): don't show 'No sessions found' while session history is still loading
Replace the isLoadingHistory flag with hasLoadedHistory, set only once the
backend has actually answered a list_discovered_sessions request. The sidebar
and Sessions view now keep their loading state until that first definitive
response, so the empty-state copy can no longer appear while history is still
being fetched (or while a failed fetch is being retried).
Also retry a failed initial fetch on the 2s event cadence instead of stranding
the UI until the 12s periodic poll, which is what stretched the misleading
empty state to ~10 seconds after a webview reload when the websocket lost the
race with the page load.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop history fast-retry from re-arming after hook unmount
A failed initial fetch that settles after the hook unmounted could schedule a
new retry timer after cleanup had already cleared the refs, leaving the
abandoned hook polling the backend every 2s. Guard scheduleRefresh with a
disposed ref set by the mount effect's cleanup.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix @ file mentions breaking on paths with spaces (#13391)
* Fix @ file mentions breaking on paths with spaces
Quote mentions generated by getFileMentionFromPath (Add to Cline /
Fix / Explain / Improve commands) when the relative path contains
spaces, so the mention regex no longer truncates the path at the
first space. Also quote the path part of workspace-prefixed mentions
(workspace:/path with spaces) inserted from the @ context menu, which
previously bypassed quoting because the value does not start with '/'.
Fixes#13338
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix import ordering in mentions test (biome organize imports)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reduce fix to minimal scope
Revert the webview quoting refactor and extra tests; keep only the
getFileMentionFromPath quoting fix with a single regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Normalize mention paths to posix separators for Windows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix code actions failing with "command not found" on VS Code 1.134 (#13402)
* Fix @ file mentions breaking on paths with spaces
Quote mentions generated by getFileMentionFromPath (Add to Cline /
Fix / Explain / Improve commands) when the relative path contains
spaces, so the mention regex no longer truncates the path at the
first space. Also quote the path part of workspace-prefixed mentions
(workspace:/path with spaces) inserted from the @ context menu, which
previously bypassed quoting because the value does not start with '/'.
Fixes#13338
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix import ordering in mentions test (biome organize imports)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reduce fix to minimal scope
Revert the webview quoting refactor and extra tests; keep only the
getFileMentionFromPath quoting fix with a single regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Normalize mention paths to posix separators for Windows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix code actions failing with 'command not found' on VS Code 1.134
Code action commands carried arguments (expandedRange, diagnostics),
which routes them through VS Code's CommandsConverter cache. VS Code
1.134 disposes the cached entries before the clicked action executes,
so every lightbulb action failed with 'Actual command not found,
wanted to execute cline.addToChat'.
Drop the arguments so the command id is passed through directly, and
recover the context in the handler instead: getContextForCommand now
expands an empty selection by 3 surrounding lines (matching the old
provider behavior) and gathers document diagnostics intersecting the
range when none are passed explicitly.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Scope gathered diagnostics to the selection/cursor
Match the old CodeActionContext.diagnostics behavior: only include
diagnostics intersecting the range the action was requested for, not
the surrounding lines the text gets expanded to.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Desktop: unify Plugins, MCP, and Skills into one Plugins hub with a dedicated Marketplace page (#13411)
* Unify desktop plugins, apps, MCP, and skills into one Plugins hub with a Browse directory mode
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Open the marketplace directory as a modal over the Plugins hub instead of swapping the page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename directory to Marketplace: Browse Marketplace button, Marketplace modal title with icon, search placeholder
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix search input focus ring clipped by the Marketplace modal scroll container
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Address Greptile review: keep selected tag chip visible when its count drops to zero, and remount installed tab when a marketplace install completes after the modal closed
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Track marketplace modal mutation flag in a ref so a close click racing a queued render cannot skip the inventory remount
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make Marketplace its own settings page under Customizations and restore Channels as a standalone page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Remove icon from Marketplace page header for consistency with other settings pages
* Notify mounted inventory views when the marketplace invalidates the cache so late install completions refresh the Plugins hub
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop/ui): recommended and free model tiers in the composer model selector (#13410)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK (#13415)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): recommended-feed badges and descriptions in provider settings (#13416)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* feat(desktop): recommended-feed badges and descriptions in provider settings
Review suggestion on #13410: the provider settings page has room for
more model detail than the composer's picker. The cline/cline-pass
provider cards now refresh their model list through
list_provider_models (the catalog snapshot deliberately skips the
recommended-feed overlay so the startup catalog fetch never blocks on
the feed) and render Recommended/Free tier badges plus feed tags (NEW)
next to the model name, with the model description underneath. The
refreshed list also surfaces the live entries instead of the bundled
snapshot.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): scope the settings featured model list to its provider and revision
The fetched featured list was unscoped component state: switching
between cline and cline-pass reused the component instance, so the
previous provider's models stayed visible while the new request was
pending (or forever, when it failed), and the retained copy shadowed
later provider.modelList updates — adding a second custom model
submitted the stale list as the complete configuration and dropped the
first addition.
The fetched list now only applies to the provider and modelList
revision it was fetched for (falling back to the catalog snapshot
otherwise and refetching on membership changes), and add-model submits
the union of the displayed and configured ids so an update can never
silently unconfigure existing entries.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(ui): update packed-Tailwind smoke contract for the picker's max-h-64 (#13421)
The ui-publish smoke check pins a set of Tailwind candidates the packed
sources must emit; #13410 grew the SearchCombobox options list from
max-h-56 to max-h-64, so the publish run failed on the stale candidate.
All other pinned candidates verified against the current sources.
* feat(desktop): refresh app icons and branding (#13400)
* ci(vscode): upload E2E failure recordings from the right path (#13427)
The job sets working-directory: apps/vscode, but that default applies to run
steps only, not to `uses:` steps. Since #10961 moved the extension under apps/
and added that default, the artifact path has resolved against the repo root,
matched nothing, and every failing run logged "No files were found with the
provided path: test-results/playwright/" instead of uploading recordings.
Widen to test-results/ so Playwright's error-context snapshots ship alongside
the videos.
* fix(hooks): deliver tool hook contextModification to the model (#13297)
* fix(hooks): deliver tool hook contextModification to the model
On the next engine, a tool_call (PreToolUse) hook's contextModification
was parsed into HookControl.context and then silently dropped: the
runtime beforeTool/afterTool result contract had no channel for
injecting conversation context. Legacy consumed it (ToolExecutor /
ToolHookUtils pushed <hook_context> blocks into the next user turn), so
this was a regression of documented behavior.
- Add appendContext to AgentBeforeToolResult/AgentAfterToolResult.
- AgentRuntime collects appendContext across hooks during an
iteration's tool executions and appends one <hook_context> user
message after the tool results, keeping tool-result parts contiguous.
- Map HookControl.context into appendContext in both subprocess hook
layers (skipped when the hook cancels, matching legacy, where the
message doubled as the error).
- Truncate injected context at 50KB per hook output, matching legacy.
- Concatenate appendContext across merged hook layers.
tool_result (PostToolUse) hooks still run detached with stdout ignored;
making them blocking so their context can be collected is a follow-up.
Ref: https://linear.app/cline-bot/issue/CLINE-2987
* fix(hooks): stamp tool identity on injected hook context blocks
Contexts are batched into one message after the tool results, and
parallel tool execution collects them in completion order, so position
alone cannot attribute a block to its tool call. Add tool_name and
tool_call_id attributes to each <hook_context> block.
* fix(hooks): sanitize hook context block markup
Attribute values (tool_name, tool_call_id) are stripped of quote/angle
characters and embedded </hook_context> closers in hook output are
neutralized, so neither provider-supplied ids nor hook text can corrupt
or spoof a block's stamped identity.
* fix(hooks): neutralize forged opening hook_context tags in hook output
The previous sanitization only neutralized closing tags, so hook output
could still open a forged <hook_context> block claiming another tool's
identity. Escape both opening and closing embedded tags with one rule.
* fix(hooks): hide injected hook context from user-facing transcripts
Stamp the injected hook-context user message with displayRole 'system'
(the compaction-summary convention) so it reaches the model but does
not render as a user bubble in live or replayed transcripts. Without
this, resuming a session showed the raw <hook_context> block as if the
user had typed it.
* fix(hooks): neutralize case-variant embedded hook_context tags
The tag-neutralization regex was case-sensitive, so hook output could
still smuggle a forged tag as <HOOK_CONTEXT>. Match case-insensitively.
* fix(vscode): map PreToolUse contextModification into runtime appendContext
The extension's hooks adapter bridged file hooks into the SDK runtime
but forwarded only cancel/errorMessage, so a PreToolUse hook's
contextModification never reached the model. Map it into the runtime's
appendContext channel; HookFactory already truncates it at 50KB.
* fix(vscode): hide hook-injected context from replayed transcripts
Live sessions never rendered the injected <hook_context> user message,
but session reload replayed it as a user bubble (and post-resume turns
kept doing so). Treat these messages as synthetic in the user-message
mapping: honor the displayRole 'system' stamp the runtime sets, with a
text-prefix guard for paths where metadata is unavailable. This also
keeps edit/regenerate ordinal mapping aligned with visible bubbles.
* fix(hooks): run file hooks through exactly one layer per host
The VS Code extension registered two independent hook execution layers:
its own hooks adapter (config.hooks) and the SDK core's file-hook
extension from the runtime bootstrap. When both discover the same hook
files, every hook executes twice per event — and with context injection
wired, each contextModification would be injected twice.
Add a 'hooks' runtime config extension kind (in the default set, so the
CLI keeps core file hooks unchanged) and gate the bootstrap's file-hook
extension on it. The extension excludes 'hooks' at session start, so
its adapter — which also provides the hook status UI and the
hooksEnabled setting — is its single execution path.
* fix(vscode): discover hooks from the session workspace, not only global state
Hook discovery read workspaceRoots from global state shared across
every Cline instance, so another window repointing it made workspace
hooks silently stop being discovered. With the extension's adapter now
the single hook execution layer, that meant no hooks at all.
HookFactory takes an optional sessionWorkspaceRoot and unions that
root's .clinerules/hooks into discovery (and into cwd resolution), fed
from the session config's cwd. Shared-state discovery still works, so
behavior in the single-window case is unchanged.
* fix(hooks): keep sanitized hook attribute values distinguishable
Replacing every markup delimiter with the same underscore could
collapse two tool call ids that differ only by such a character into
identical stamps. Escape each delimiter with a distinct token instead.
* fix(hooks): make hook attribute sanitization injective
Escaping the underscore itself turns the attribute escaping into a
uniquely decodable code, so no two distinct tool call ids can collapse
to the same sanitized stamp (previously an id containing a literal
escape token could collide with an id containing the delimiter).
* fix(vscode): reconstruct hook status rows when replaying transcripts
hook_status messages are emitted live but never persisted, so reloading
a session dropped every hook row. The injected <hook_context> blocks
carry the hook source and tool name, so the replay translator now
rebuilds a completed hook status row from each block. The injection is
also no longer treated as a user turn boundary, so the final turn's
completion retag is unaffected by it.
* fix(hooks): collect PostToolUse hook output and honor its control (#13298)
* fix(hooks): collect PostToolUse hook output and honor its control
tool_result (PostToolUse) hooks ran fire-and-forget with stdout
ignored, so their entire JSON output — contextModification and cancel —
was discarded. Legacy awaited PostToolUse, injected its
contextModification into the conversation, and honored cancel.
- Run tool_result hook commands blocking (same 120s default timeout as
tool_call) in both the hook-config-file layer and the agent-hook
subprocess layer.
- Map their output: cancel stops the run with the hook's error message
as the reason; otherwise context is injected via afterTool
appendContext.
This restores legacy blocking semantics: tool results now wait for
tool_result hooks, but only in sessions that have one configured.
Ref: https://linear.app/cline-bot/issue/CLINE-2987
* fix(hooks): bound tool_result hook wait and isolate cancel reason
Address review findings:
- The agent-hook subprocess layer forwarded an unset timeoutMs
unchanged, so a tool hook command that never exits would block the
agent indefinitely. Default both tool_call and tool_result to the
120s bound the hook-config-file layer already used.
- A cancelling hook's error message was folded into the same context
field as other hooks' injectable context, so merging controls could
leak unrelated hook context into the cancellation reason. Carry it as
a separate cancelReason, and surface it as the stop reason for
beforeTool cancels too.
* fix(hooks): prefer errorMessage as a cancelling hook's stop reason
When a cancelling hook returns both contextModification and
errorMessage, the context-first parse precedence made the injectable
context the cancel reason and discarded the actual error. Parse the two
fields separately: errorMessage wins as the cancel reason (matching
legacy), and a lone errorMessage still folds into injectable context
for non-cancelling hooks as before.
* fix(vscode): honor PostToolUse hook cancel and contextModification
The adapter awaited PostToolUse hooks but discarded their output
entirely. Map cancel to a stop control (with errorMessage as the
reason) and contextModification into the runtime appendContext channel,
matching the PreToolUse mapping and legacy semantics.
* fix(hooks): whitespace-only errorMessage no longer suppresses the cancel reason
A cancelling hook returning meaningful context alongside a blank
errorMessage lost both: the parsers selected the whitespace as the
reason and the result mappers trimmed it away. Require a non-blank
errorMessage before it wins, so context serves as the fallback reason.
Apply the same fallback in the extension adapter's stop mapping.
* fix(core): stop Windows CI worker crashes from the agenda spec watcher (#13428)
* fix(core): watch agenda task specs via the resolved long path
fs.watch on a path with 8.3 short components (e.g. C:\Users\RUNNER~1
temp dirs) trips a libuv assertion in fs-event.c on Windows and aborts
the whole process. Since the agenda task manager landed, every hub
server test spins up its spec watcher on such a path on hosted Windows
runners, killing the vitest worker and failing the sdk-test Windows job
on every branch. Resolve the specs dir with realpathSync.native before
watching so libuv only ever sees the long form.
* test(ui): stub ResizeObserver for @pierre/diffs in tool-diff tests
jsdom does not implement ResizeObserver, so every ToolFileDiff render
logged a ReferenceError from @pierre/diffs to stderr. Tests still
passed; this just silences the noise the same way the constructable
stylesheet shim does.
* fix(core): skip the agenda spec watcher when the dir does not resolve
Falling back to the raw path on realpath failure would reintroduce the
Windows short-path abort; log and go without the watcher instead.
* fix(vscode): honor the classic truncation range when migrating legacy tasks (#13419)
Classic Cline truncated long conversations by omitting an index range of
api_conversation_history from every API request (keep the first
user-assistant pair, drop everything through the range end, strip
orphaned tool_results from the first kept message). The range was
persisted on the history item while the full history stayed on disk.
legacyApiHistoryToSdkMessages ignored conversationHistoryDeletedRange
and converted the entire file, so resuming a migrated long task handed
the SDK an untruncated working context that could exceed the model's
context window by millions of tokens - every request failed with
'prompt is too long' and every compaction restarted from the full
history (#12996, confirmed by the reporter: the task was migrated from
an older version and broke after a restart, with each compaction
starting from ~3M tokens).
The migration now replays exactly what the classic extension sent:
slice out the deleted range and drop orphaned tool_results, mirroring
ContextManager.getTruncatedMessages (see origin/main). Malformed ranges
fall back to the full history (previous behavior).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): show the diff edit view for multi-line edits in CRLF files (#13417)
The edit preview computed proposed content with an exact old_text match, but
the SDK executor normalizes old/new text to the file's own line endings before
matching (#12305) - reads strip CR, so models emit LF-only text even for CRLF
files. Any multi-line old_text in a CRLF file therefore failed the preview's
match: the diff edit view silently never opened while the executor applied the
edit. Single-line edits (no line break in old_text) were unaffected, which is
why the diff view appeared to trigger inconsistently.
Mirror the executor's EOL normalization (and its literal $-sequence insertion)
in the preview computation.
Fixes#13296
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): report truthful session status so desktop checkpoint restore stops wedging (#13418)
* fix(core): keep hub session status truthful across queue-drained turns
Queue-drained turns settle only through the event stream, but the hub
runtime host mistranslated their lifecycle in two ways:
- session.updated events carrying only a snapshot (persistence updates)
defaulted the projected status to "running". When one trailed the
final idle update after a turn, clients that track busy state from
status events (the desktop sidecar's workspace restore gate) stayed
busy forever. Use the snapshot's real status and emit nothing when
neither source reports one.
- the per-run agent.done dedup was only reset by run.started, which the
daemon-side queue drain never publishes, so a drained turn's done was
swallowed as a duplicate of the previous turn's. Reset the dedup on
session.pending_prompt_submitted, and suppress stale run.completed
events that land inside a drained turn's window so they can neither
emit a phantom done nor consume the drained turn's dedup slot.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(desktop): cover restore unlock after an event-settled queued turn
Exports the sidecar's core-session event handler so the queued-turn
lifecycle (busy via status events, cleared by the done agent event,
restore allowed afterwards) is testable end-to-end.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): start interactive sessions without a prompt as idle
The runtime host reported every new session as "running" until its
first turn ended. Interactive hosts (the desktop app) start sessions
with no prompt and dispatch turns through separate send calls, so a
created-but-never-prompted session stayed "running" forever — wedging
clients that gate workspace operations (checkpoint restore, message
edit) on active turns.
Interactive no-prompt starts now begin idle, start emits the session's
actual status (resumed sessions no longer masquerade as running), and
markTurn* transitions keep tracking in-memory status for lazily
persisted sessions so the first turn still reports running -> idle.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style: format hub-runtime-host test filter
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop the drained-turn done bookkeeping, keep the minimal fix
The stuck restore is fully explained by the two status defects (fabricated
"running" from snapshot-only session.updated events, and never-prompted
interactive sessions reporting "running"). The done-dedup machinery for
queue-drained turns addressed a separate cosmetic gap (queued turns emit no
chat_done, pre-existing) and required fragile run-window heuristics, so it
is removed to keep this change reviewable. Sidecar test now settles the
queued turn through the status event, matching the shipped mechanism.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* docs(sdk): document the truthful session-status contract
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(deps): update Langfuse packages and bump app versions (#13443)
* chore(deps): update Langfuse packages and bump app versions
Update @langfuse/otel to v5.10.1 and add @langfuse/vercel-ai-sdk v5.9.1 for improved observability with Vercel AI SDK.
Bump versions for @cline/code to 0.0.14 and @cline/ui to 0.2.0-next.6, updated via bun.lock.
Other Changes:
Added optional userId to AgentRuntimeConfig.
Propagated userId, sessionId, conversationId, runId, iteration, provider, and model context into AI SDK telemetry.
Added AI SDK 7 runtimeContext with explicit includeRuntimeContext.
Added stable OTEL_SERVICE_NAME=cline-sdk.
Added runtime metadata assertions in agent tests.
* add taskId
* Revert "add taskId"
This reverts commit f20d31d96d.
* docs: simplify Open Cline step in installing guide (#13405)
* docs: remove duplicate GLM-5.3 rows in ClinePass tables (#13449)
Co-authored-by: cline-cloud[bot] <276134852+cline-cloud[bot]@users.noreply.github.com>
* chore(sdk): release v0.0.76
* chore(cli): release v3.0.56
* docs(cli): scope the v3.0.56 release notes to CLI-visible changes
* feat(desktop): interactive welcome hero graphic (#13399)
* feat(desktop): add interactive welcome hero
* feat(desktop): support composable welcome hero variants
* feat(desktop): reskin first-run onboarding (#13441)
* refactor: centralize client tool availability (#13451)
* chore(sdk): release v0.0.77
* docs(cli): drop the tasks tool from the v3.0.56 notes, it is desktop-only
* chore(vscode): prepare 4.1.11 release
* chore(desktop): release v0.0.15
* fix(vscode): remote config MCP settings (#13466)
* fix(vscode): enforce enterprise MCP controls on the Customize marketplace
The unified Customize marketplace replaced the old MCP marketplace
without carrying over enterprise remote-config enforcement: the catalog
RPC returned every MCP entry and installs were never policy-checked,
so orgs with mcpMarketplaceEnabled=false or an allowedMCPServers
allowlist saw (and could install) all marketplace MCP servers.
- Filter MCP entries out of getMarketplaceCatalog when the marketplace
is disabled, and restrict entries to the allowlist when configured
(matching entry id, display name, installed server name, or source
repo URL, mirroring legacy GitHub-URL allowlist ids)
- Reject installMarketplaceEntry requests that violate the policy
- Map the published catalog's repo/homepage fields onto
sourceUrl/homepageUrl so URL-based allowlists can match
- Update the enterprise MCP server controls docs
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: simplify MCP marketplace policy enforcement
Fold the policy check into marketplace-helpers, drop the dedicated
test suite, and trim the docs edit to the strictly necessary line.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Treat an empty preserved capability list as unspecified when seeding tools (#13465)
* Treat an empty preserved capability list as unspecified when seeding tools
toSdkModelInfo guarded the tools seeding with a strict
preservedCapabilities === undefined check, but modelHasCapability —
the runtime's own reader — treats undefined AND length === 0 as
"unspecified". A custom OpenAI-Compatible model whose stored
capabilities field is a defined-but-empty array (a config carried over
from before the field existed, or one round-tripped through a boundary
that defaults it to []) skipped the seeding; the first boolean
projection to run afterwards (e.g. supportsReasoning) then populated
the array, the runtime gate read the non-empty, tool-less list as
authoritative, and every tool definition was silently dropped from the
session (#13463).
The guard now covers the empty array too, matching the reader's
unspecified semantics.
* test: satisfy the store's isModelInfo gate so the empty-capabilities case actually reaches knownModels
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: yzxcj797 <yzxcj797@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): prepare 4.1.12 release
* Add feature flags to the desktop app (#13289)
* Add feature flags to the app
* React to account updates
* Address comments
* use a per-app file
* fix: propagate Langfuse session telemetry (#13473)
* fix telemetry session propagation
* feat telemetry client version metadata
* fix(core): address Langfuse review feedback — hub client identity + delegated agent session grouping (#13475)
* fix(core): rebuild hub session client identity from request headers
Hub-backed sessions do not transport extensionContext (it is local-only),
so the daemon's runtime built traces without the clientName/clientVersion
metadata even though the hub client bakes X-CLIENT-TYPE / X-CLIENT-VERSION
into the session's provider headers. Reconstruct extensionContext.client
from those headers during local runtime bootstrap so hub-backed Langfuse
traces carry the same client identity as local runtimes, and the daemon's
header re-resolution stops clobbering the original X-CLIENT-TYPE.
* fix(core): propagate parent distinctId/sessionId to delegated agents
Delegated agents (spawned sub-agents, configured agents, teammates) were
built without distinctId and sessionId, so their Langfuse traces had no
userId or sessionId and did not group with the parent user or session.
Thread the host-resolved distinctId through RuntimeBuilderInput and the
root sessionId through the delegated-agent config provider, and copy both
onto the delegated AgentConfig.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* ci(vscode): make combined nightly manual-dispatch only
The PublishNightly environment gained required reviewers, so each cron
run parked on approval, held the workflow's concurrency group, and
silently cancelled every scheduled run queued behind it. 20 consecutive
scheduled nightlies died this way between 2026-07-31 and 2026-08-21;
the only nightlies that shipped in that window were manual dispatches.
Drop the cron rather than leave a trigger that cannot succeed unattended.
* feat(hub): add drain and upgrade commands with replay support (#13468)
* feat(hub): add drain and upgrade commands with replay support
* handles disconnection
* feat(hub): wire bot profiles, drain, and durable event/run-queue into the live transport
Completes the wiring the previous commits' primitives needed:
HubServerTransport gains isDraining(), hub.drain/hub.status/profile.get
command handling, and replayEventsAfter() (backed by the durable event
log), plus the sequence/sinceSequence wire types they depend on in
shared/hub.ts. run-queue-handlers.ts reads the active bot profile's
plugin roots when executing durable runs.
Also adds hub/profiles/: profile.json (identity/rules/plugins) ->
system prompt composition, --profile / CLINE_HUB_BOT_PROFILE
resolution, and the bundled cline-dad profile with its
cline_hub_support read-only diagnostics tool.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Revert "feat(hub): wire bot profiles, drain, and durable event/run-queue into the live transport"
This reverts commit 6696d5d202.
* fix(hub): dedupe replayed events by eventId, not just sequence
HubEventLogStore.append() returns a new envelope stamped with a
sequence rather than mutating the input, so a pending approval
re-issued sequence-less by subscribe() (it predates any durable-log
append) and its later sequence-stamped copy from the durable log are
two different objects carrying the same eventId. The replay-then-live
buffer in browser-websocket.ts only deduped by sequence, so the
sequence-less copy's guard never tripped and it was delivered a second
time when the buffer flushed after replay.
Track delivered eventIds alongside the sequence cursor; eventId
survives the append/stamp round-trip unchanged, so this dedupes the
exact-same logical event regardless of which copy arrives first.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): wire drain, durable event log, and run queue into the live transport
CI on this branch failed bun run build:sdk: browser-websocket.ts,
client/index.ts, and hub-websocket-server.ts (already on this branch)
reference sequence/sinceSequence, HubServerTransport.isDraining(), and
the "hub.drain" command — but the commit that reverted bot profiles
out of this branch also reverted this wiring, since it shared a commit
with the profiles work. That wiring is a hub concern, not a
bot-profiles one; split it back out.
- shared/hub.ts: sequence/sinceSequence types, run.enqueue/run.list/
hub.drain/hub.status/stream.replay capability, command, and event
names. profile.get intentionally excluded — stays bot-profiles-only.
- context.ts: isDraining() on HubTransportContext. botProfile field
intentionally excluded.
- hub-server-transport.ts: eventLog/runQueue fields and start/stop
lifecycle, publish() appends to the durable log, handleCommand cases
for run.enqueue/run.list/hub.drain/hub.status, drain-refusal check,
replayEventsAfter()/lastEventSequence(). startBotProfile()/
startHubSupportTool() and the profile.get case intentionally
excluded.
- run-queue-handlers.ts: added without handleProfileGet (needs
ctx.botProfile, which doesn't exist here).
- hub-upgrades.test.ts: added without its two bot-profile-injection
tests (they need a resolved bot profile to assert against).
Verified bun run build:sdk exits 0 (the exact CI command) and
bunx vitest run src/hub passes (311/312; the one failure is the
same pre-existing environment-timing flake already present before
this change).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): export instance-lock, event-log, and run-queue from the hub barrel
These landed as internal modules only; hub-server-transport.ts and
hub-websocket-server.ts import them by direct path, but nothing
re-exported them from the public @cline/core/hub surface the way
sibling discovery/server modules already are.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): wire the instance lock into the daemon entry point
The singleton lock (discovery/instance-lock.ts) and its consumption in
startHubWebSocketServer/ensureHubWebSocketServer were already on this
branch, but the daemon entry point's own half was not: retrying a bind
when a retiring predecessor still holds the lock, and exiting with a
distinct code (3) instead of the generic fatal path when a live Hub
already owns the data directory. Without this, a daemon racing a
retiring predecessor could fail outright instead of waiting the lock
out, and losing the singleton race looked identical to a crash.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(hub): address drain/upgrade review findings (#13478)
- cline hub upgrade: check idleness at least once (--wait 0 works), reject
non-numeric --wait, and un-drain on every abort path so an aborted
upgrade can never leave the hub refusing new work
- add cline hub drain --off and the off query param to requestHubDrain so
POST /drain?off is reachable from shipped code
- HubEventLogStore/HubRunQueue: WAL journal mode + busy_timeout, and stamp
sequences from lastInsertRowid instead of SELECT MAX(sequence)
- HubInstanceLock.acquire: degrade to an unheld lock when SQLite is
unavailable instead of refusing hub startup; only BUSY/LOCKED still
raises HubLockHeldError
- ensureHubWebSocketServer: retire an unusable discovered hub through the
shared retireDiscoveredHub (busy hubs are attached to, drain precedes
shutdown, discovery cleared only when the hub actually retired)
- replay adapter: advance the cursor past eventId-deduped events, cap
replay pages, stop when the cursor stalls, and drop the dedupe set after
the buffered flush so it cannot grow for the socket lifetime
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(hub): derive the singleton e2e challenger cwd portably
The challenger's working directory was derived by round-tripping the
discovery path through a file: URL and stripping the last pathname
segment. On Windows that yields a POSIX-style '/C:/...' path, which is
not a valid spawn cwd, so the spawn fails ENOENT before the singleton
lock is ever contested and the Windows SDK test job goes red.
The data dir is simply the discovery file's parent: use dirname().
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): stop stored capability lists from silently revoking tool calling for custom models (#13476)
* fix(core): seed tools capability when custom model capabilities are synthesized from boolean flags
For a models.json entry with no explicit capabilities list, toStoredModelInfo
synthesized a capability array purely from boolean convenience flags (e.g.
supportsReasoning: true -> ["reasoning"]). modelSupportsToolCalling fails open
only for a missing or empty list, so the synthesized non-empty list read as an
authoritative denial and silently stripped every tool definition from requests
to custom OpenAI-compatible models (#13463).
Seed "tools" whenever the list was not explicitly authored and the boolean
projections made it non-empty, preserving the fail-open contract. Explicitly
authored capability lists remain authoritative and can still disable tools.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(core): cover stale catalog capability overrides
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: treat stored capability lists as non-authoritative for tool calling
The hasExplicitCapabilities guard still let two producers of tool-less
lists through:
- The VS Code legacy-override migration (legacyModelInfoToOverrides)
persists explicit partial lists like ["prompt-cache"] into models.json
for custom OpenAI-compatible models, which then read as an authoritative
"cannot call tools" and drop every tool - same symptom as #13463.
- Any hand- or UI-authored partial list on a non-catalog model.
Stored entries and user-authored provider metadata have no way to declare
"cannot call tools" (there is no supportsTools field, and every writer
that authors a full list includes "tools"), so seed "tools" into any
non-empty list for a language model. Only generated catalog capabilities
remain authoritative - a genuine no-tools catalog model stays that way -
and non-language models (e.g. image generation) never gain a tools claim.
Also make legacyModelInfoToOverrides write "tools" into the arrays it
fabricates, matching the providers.json migration, so models.json stops
being poisoned for older readers.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): prepare 4.1.13 release
* chore(sdk): release v0.0.78
* chore(cli): release v3.0.57
* fix(core): run hub e2e files serially so daemon timing budgets survive CI contention
singleton.e2e.test.ts (added in #13468) spawns real daemons and runs for
~15s. Vitest's default file parallelism let it run alongside
shutdown.e2e.test.ts, whose assertions are wall-clock bound: discovery
within 10s, exit within 5s, and a 2s shutdown watchdog. On the 2-core
windows-latest runner that contention alone broke those budgets, failing
the shutdown test two different ways across runs — once never observing
discovery, once with the daemon forced to exit before its HTTP 202
flushed (socket hang up). The test passed on Windows before #13468 and
has failed every SDK publish run since.
* chore(desktop): release v0.0.16
* test(sdk): give windows-sensitive suites realistic timeouts
Four consecutive SDK publish runs failed on windows-latest, each on a
different test, all of them plain timeouts: two @cline/shared SQLite
tests at the 5s vitest default, core's bash executor at 10s, and the hub
singleton endpoint test at 10s. The 2-core Windows runner spawns forks
and takes SQLite locks slowly enough to blow those budgets under load.
These timeouts guard against hangs; they are not timing assertions (the
one suite that does assert elapsed time, shutdown.e2e, was fixed by
removing file-level parallelism instead). Raise core to 20s and give
@cline/shared an explicit 15s in place of the inherited 5s default.
* fix(telemetry): emit task.completed from every session teardown path (#13489)
The task.completed fallback lived only inside shutdownSession, but
stopSession/dispose route interactive sessions with a terminal reported
status through releaseSessionRuntime, which never emitted. Truthful
session-status reporting (shipped in 4.1.11) re-routed a large share of
interactive stops onto that branch and silently dropped the event.
Route the emission through a single choke point,
emitTaskCompletedOnTeardown, called from both shutdownSession and
releaseSessionRuntime. The completion criterion no longer reads
session.status: interactive sessions use the recorded final-turn
outcome (lastInteractiveTurnFinishReason), non-interactive sessions
keep the existing input.status === "completed" logic. A new
taskCompletedEmitted flag (also set by the submit_and_exit observer)
enforces exactly one task.completed per session. failSession now
records the errored final turn so a stale "completed" from an earlier
turn can never leak into the teardown emission. Telemetry only; no
user-facing behavior changes.
* chore(vscode): release v4.1.14
* fix(vscode): auto-approve all MCP tool calls when the MCP toggle is on (#13498)
* fix(vscode): honor MCP auto-approve settings for SDK tool calls
The SDK extension required both the global 'Use MCP servers' auto-approve
toggle AND each tool's per-tool autoApprove flag before silently approving
an MCP call, while the legacy extension treated them as either/or. Restore
the legacy OR semantics so toggling MCP auto-approve works again.
Also key toolPolicies by the registered SDK tool name (via
defaultMcpToolNameTransform, now exported from @cline/core) instead of raw
server__tool. Servers whose names contain sanitized characters (e.g.
marketplace names like github.com/user/repo) or exceed 64 chars produced
policy keys that never matched the registered tool, so those MCP tools ran
without any approval gate; the live auto-approve lookup now re-applies the
transform instead of string-splitting the name.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Revert "fix(vscode): honor MCP auto-approve settings for SDK tool calls"
This reverts commit 86c568fbba.
* fix(vscode): auto-approve all MCP tool calls when the MCP toggle is on
The SDK extension only auto-approved an MCP call when the global 'Use MCP
servers' auto-approve toggle AND that tool's per-tool autoApprove flag were
both set, so toggling MCP auto-approve appeared to do nothing and users had
to opt in each tool individually. The toggle alone now governs all MCP
tools; the per-tool flag is no longer consulted.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): release v4.1.15
* fix(cli): remove the $4.99 ClinePass promo copy (#13514)
The $4.99 first-month promo is ending, so the CLI's first-launch "Try ClinePass" dialog should no longer advertise it. Also drops the leftover CLI_PROMO_CODE plumbing, which has been an empty string since the promo-code flow was removed.
* fix(vscode): resolve hook workspace identity from the window, not shared global state (#13352)
* fix(vscode): resolve hook workspace identity from the window, not shared global state
Hook discovery, hook cwd selection, and the workspaceRoots metadata passed
to hook scripts all read the workspaceRoots/primaryRootIndex global state
keys. Global state lives in ~/.cline and is shared by every Cline instance
(all VS Code windows, the CLI, the JetBrains plugin), and nothing writes
these keys anymore, so hooks resolved against whatever project some other
or older instance last recorded. With a second window open on another
project, a workspace's .clinerules/hooks scripts were never discovered.
Resolve workspace roots via a single guarded helper backed by
HostProvider.workspace.getWorkspacePaths() (in-process, window-scoped,
same as refreshHooks): blank paths are filtered, a host-bridge failure
degrades to no workspace roots instead of silently disabling global hooks
or skipping blocking PreToolUse guards, and one resolution is threaded
through hooks-dir discovery, cache misses, cwd selection, and hook input
metadata so they can't disagree (previously up to four host lookups per
hook execution — real gRPC round trips in the standalone host). Roots and
hooks dirs are matched on whole path segments with the longest root
winning, so prefix-sharing or nested workspace roots resolve to the right
project. The adapter creates the runner once per event and skips no-op
runners, making creation the single resolution point; the separate
hasHook/getHookInfo checks are removed. The dead workspaceRoots and
primaryRootIndex state keys are dropped, and the four hand-rolled
HostProvider.workspace test stubs are consolidated into one shared
helper.
* test(vscode): add e2e coverage for workspace-scoped hook discovery
Boots real VS Code with the packaged extension against the workspace
fixture, sends a prompt, and asserts the fixture's UserPromptSubmit hook
was discovered from the open window's workspace, executed with that
workspace root as its cwd, and received the same root in its
workspaceRoots input — the end-to-end contract the hook workspace
identity fix establishes.
* test(vscode): isolate the e2e hook fixture from the shared workspace
The UserPromptSubmit fixture hook lived in the shared e2e workspace, so
every prompt-sending spec executed it (hooksEnabled defaults to true) —
and its cold PowerShell spawn on Windows pushed chat.test.ts past the
5s expect timeout. hooks.test.ts now overrides workspaceDir to a
dedicated workspace-hooks fixture, so only the hooks spec pays the hook
spawn.
* fix(hub): cap hub-events db size so it can't fill the disk (#13516)
* fix(hub): cap hub-events db size so it can't fill the disk
Row/time retention alone didn't bound disk usage: envelopes carrying
full session snapshots reach hundreds of KB each, so retained rows
could total tens of GB, sweeps only ran hourly, and DELETE never
shrinks a SQLite file. Enforce a 64 MiB size budget in prune() (oldest
rows first, VACUUM to return the space), and also prune after every
16 MiB appended so bursts can't outrun the hourly timer.
Fixes#13505
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): tolerate VACUUM failure on a full disk
VACUUM needs scratch space and can fail in exactly the state a
ballooned event log causes. The byte-budget deletes already bound live
data, so swallow the error and let the next sweep retry the reclaim
instead of aborting startup pruning and disabling the durable log.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): count the size budget in UTF-8 bytes, not characters
envelopeJson.length (UTF-16 units) and SQLite LENGTH() (characters)
undercount multibyte text by up to 3x, which could leave a CJK-heavy
log settled above budget and re-running VACUUM every sweep. Use
Buffer.byteLength and LENGTH(CAST(... AS BLOB)) instead.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(sdk): carry root overrides into the Node smoke-test sandbox (#13517)
ci-node-smoke.ts installs the packed SDK tarballs with a plain npm
install in a fresh temp dir, where the repo root package.json overrides
do not apply. When @sap-cloud-sdk 4.9.0 shipped (2026-08-24) it broke
@sap-ai-sdk/ai-api 2.14.0 (via @jerome-benoit/sap-ai-provider in
@cline/llms) with ERR_PACKAGE_PATH_NOT_EXPORTED, failing the smoke step
on every PR even though the root already pins @sap-cloud-sdk/* to 4.6.0.
Copy the root overrides block into the generated sandbox package.json
so the smoke install resolves the same pinned versions as the repo and
future third-party releases cannot break it independently.
* chore(sdk): release v0.0.79
* fix(vscode): don't steal last-used provider from ClinePass on credential refresh (#13520)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(hub): flush the /shutdown 202 before daemon teardown
The /shutdown handler queued teardown on a microtask, which runs before
the event loop's write phase, so the daemon could process.exit() before
the accepted 202 was handed to the socket. Unix masked it (uv_try_write
lands small loopback writes synchronously); Windows has no such fast
path and lost the race regularly — the recurring shutdown.e2e.test.ts
'socket hang up' failures on windows-latest. Start teardown from the
response's write callback instead, with an idempotent 1s fallback so a
client that vanishes mid-write cannot strand the daemon, and send
Connection: close so the client gets a FIN rather than an abort.
Since the flakiness this compensated for is fixed at the source, restore
maxWorkers: 2 for the Windows core suite (serializing it cost ~3 min of
CI per run), and raise the e2e daemon discovery hang guard 10s→30s —
it guards against hangs, not runner speed.
* chore(cli): release v3.0.58
* fix(core): prevent search_codebase from crashing the process on giant single-line files (#13525)
* fix(core): prevent search_codebase from crashing the process on giant single-line files
searchWithRipgrep buffered all of rg's --json stdout into one string. Each
JSON event embeds the full text of the matched line (--max-columns is
ignored in JSON mode), so searching a directory of serialized trace dumps
(single-line multi-hundred-MB JSON files) accumulated gigabytes of stdout
until string concatenation threw RangeError: Out of memory inside the
stream data handler. That throw is outside the tool's try/catch, so it
escalated to an uncaughtException and killed the CLI/hub daemon.
Parse rg's JSON events incrementally line by line, drop events larger
than 256KB, truncate matched/context lines to MAX_LINE_CHARS, and stop
reading once maxResults is reached. The fallback regex scan now skips
files larger than 10MB (reporting the skip count) and truncates its
context lines the same way.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* simplify search_codebase crash fix to a minimal diff
Replace the incremental JSON-event parser with three small guards: stop
buffering rg stdout past 10MB, drop the trailing partial event before
parsing, and slice fallback context lines to MAX_LINE_CHARS. Drops the
fallback file-size skip and skip-count reporting.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): hide per-tool MCP auto-approve checkboxes behind a flag (#13522)
* chore(vscode): remove per-tool MCP auto-approve checkboxes from webview
MCP auto-approval is now governed solely by the global 'Use MCP servers'
toggle; the SDK approval path (shared with the CLI and desktop app) has no
per-tool granularity, so the per-tool and 'Auto-approve all tools'
checkboxes were no-ops that implied control that no longer exists. Remove
them from the MCP settings view and chat tool rows. The autoApprove arrays
in cline_mcp_settings.json and the toggleToolAutoApprove RPC are left
intact for the legacy extension.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(vscode): hide per-tool MCP auto-approve checkboxes behind a flag
Keep the checkbox components, handlers, and RPC plumbing intact but gate
rendering behind SHOW_MCP_PER_TOOL_AUTO_APPROVE=false: the SDK approval
path (shared with the CLI and desktop app) is all-or-nothing via the
global 'Use MCP servers' toggle, so the per-tool checkboxes were no-ops.
Flip the flag back on if the SDK gains per-tool approval granularity.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(tools): create new files with the platform-native line ending (#13521)
* fix(tools): use platform-native EOL for new files and preserve CRLF in apply_patch updates
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* simplify to the minimal new-file EOL fix
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* extract shared normalizeNewFileLineEndings helper
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add suggested schedule templates to the desktop Schedules page (#13529)
* Add suggested schedule templates to desktop Schedules page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix unreadable selected text in inputs caused by selection utility conflict
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Restyle Suggested section label as small gray uppercase
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Hide suggested schedule cards that match an existing schedule name
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Disable the agent todo tool and hide the Agenda UI in the desktop app (#13530)
* remove todo tool and Agenda UI, keep schedule-only tasks tool
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: biome formatting fixes
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* restore agenda backend; disable todo kind behind a flag instead of deleting
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* keep agenda automation pump idle while the todo tool is disabled
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* remove todo tool and Agenda UI altogether (revert the disable-flag hybrid)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* restore all agenda code to main state
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* disable agent todo tool and hide Agenda UI behind flags
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add Desktop App and Cloud Platform to bug report issue template (#13532)
* Add Desktop App and Cloud Platform to bug report surfaces
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename Surface Diagnostics field to Diagnostics
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: sidebar navigation cleanup with New/Schedule/Customize rows and dialog-based search (#13533)
* desktop: clean up sidebar navigation chrome
- Give New Task its own full-width labeled row below the logo row
instead of an ambiguous icon next to the agenda toggle
- Wire the New Task row to the home action so starting a new task
clearly takes you home (the logo still works as a fallback)
- Swap back/forward chevrons for browser-style arrow icons and
bump their size
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: sidebar New/Schedule/Customize rows and always-visible search
- Stack New (plus icon), Schedule, and Customize as full-width labeled
rows below the logo; whole row highlights on hover via sidebarItem
- New starts a fresh task (home), Schedule opens Settings > Schedules,
Customize opens the Customizations sections (Plugins first)
- Show the session search bar permanently above the sessions list
instead of hiding it behind a search icon toggle
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: move session search into a dialog behind a logo-row icon
- Replace the inline sidebar search bar with a search icon in the
logo row that opens a cmdk command dialog listing sessions
- Selecting a result opens that session and closes the dialog
- Remove the agenda/tasks toggle the icon replaces, along with the
now-unreachable sidebar Agenda panel (the welcome screen still
surfaces agenda tasks)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: load full session history when the search dialog opens
Addresses Greptile review on #13533: the dialog only searched the
currently loaded history batch, so older unloaded sessions could not
be found. Opening search now kicks off loadAllSessions() (the hook's
purpose-built global-search loader), and the empty state reads
'Searching older sessions...' while more history is streaming in.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Hide Channels and Agents sections from desktop app sidebar (#13527)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Desktop app: organize sidebar sessions into Pinned, Scheduled, and Tasks sections (#13528)
* Add Pinned/Scheduled/Tasks categories to desktop app sidebar
Replace the Schedules and Favorites filter-menu options with visible
collapsible category sections in the session sidebar, and rename the
Favorite action to Pin across the sidebar and sessions view.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Grow full history window when Tasks show-more outpaces loaded tasks
loadMoreSessions treats its argument as a limit on all sessions, but the
Tasks show-more count only tracks Task rows, so once pinned/scheduled
rows pushed the loaded total past the requested count the call no-oped
and clicks went dead. Grow the whole history window via
loadOlderSessions instead, and only when the loaded tasks cannot fill
the next page. Addresses Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Auto-fill the Tasks page instead of fetching once per show-more click
A single 50-session window growth can consist entirely of pinned or
scheduled sessions, leaving a show-more click with no visible Tasks
progress. Replace the one-shot fetch with a page-fill effect that keeps
growing the history window until the requested Tasks page fills or
history runs out. Addresses the follow-up Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Halt page-fill retries after a failed history fetch
A failed fetch leaves the task count and has-more state unchanged,
which are exactly the conditions the page-fill effect fires on, so one
failing request would retry and re-toast forever. Halt the effect after
a failure and let the next explicit show-more click retry. Addresses
the third Greptile review on #13528.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Redesign desktop Model Providers page and split voice input into its own settings page (#13531)
* Redesign desktop Model Providers page and split voice input into its own settings page
- Group providers into Connected / Popular / All with auth-kind hints and
connection status instead of per-row enable toggles
- Show browser sign-in (not an API key field) for OAuth providers, with a
collapsed manual-key escape hatch where supported, plus explicit
Connect / Disconnect / Sign out actions
- Move voice input to a dedicated Settings > Voice page that only offers
connected transcription-capable providers, preselects a default model
(streaming preferred), and stays disabled in the sidebar until a
provider is connected
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Show native tooltip on the disabled Voice settings nav item
Disabled buttons drop pointer events, so the 'connect a model provider'
hint moves to a wrapping span for the browser tooltip to render.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Drop letter avatars and gray provider ids from provider rows and voice chips
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Drop model counts from provider list rows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename provider Connected status to Configured and drop the green styling
A settings entry is configuration, not a live connection; neutral gray
text avoids implying an active link, since the user still picks which
configured provider to use per chat.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Resync provider catalog from disk when a settings save fails
Connect/disconnect/credential edits update the list optimistically; a
failed save now reloads the catalog instead of leaving the optimistic
state (and the view's module cache) claiming a configuration that was
never persisted.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename oauthProvider test fixture to dodge CodeQL name heuristic
CodeQL's clear-text-storage query flags any identifier matching 'oauth'
as a credential source and traced the fixture's provider id into the
favorite-models localStorage write, which stores only provider/model id
strings. Renaming the fixture removes the false-positive source.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Guard catalog reloads against races and resync detail drafts on failed saves
Optimistic provider mutations now bump a generation that discards any
in-flight catalog response, so a failed-save recovery reload can't
overwrite a newer edit with an older disk snapshot. The recovery also
remounts the provider detail panel via a reset token so its local field
drafts reflect the reloaded on-disk state instead of unpersisted edits
or an optimistically cleared disconnect.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix failed-save recovery ordering and retry superseded reloads
Remount the provider detail only after the authoritative catalog reload
lands, so its drafts re-seed from disk state rather than the optimistic
values that failed to persist. When a concurrent edit supersedes the
recovery's in-flight response, retry the reload (bounded) instead of
dropping it, since that edit performs no reload of its own.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): Customize hub, sidebar overhaul, and settings polish (#13538)
* feat(desktop): merge customization pages into a Customize hub with inline marketplace
Replaces the Plugins page and the dedicated Marketplace page with a single
Customize hub. Tabs: Skills, MCP, Plugins, Rules, Hooks, Tools, each with
live counts. Tabs backed by a marketplace catalog render the installed
items followed by an inline browsable Browse section (CLI-hub style), so
installing from the catalog immediately reflects in Installed above.
- Installed cards restyled to mirror the browse-card anatomy: bg-card p-4
containers, absolute top-right xs Uninstall matching Install, truncating
semibold titles, primary-tinted icons, real Badge components instead of
ad-hoc bordered spans, un-indented line-clamped descriptions
- Rules/Hooks/Tools rows brought into the same card language; redundant
intro paragraphs (duplicating the page description) removed; Tools group
headers match the Installed header style with counts
- Marketplace section header renamed to Browse; duplicate 'N results' row
removed (the header count is the single source)
- MCP embedded view now shows the full marketplace instead of
installed-only
* feat(desktop): overhaul sidebar sessions and navigation
Sessions list:
- Sort toggle removed; sessions are always grouped by project, with pinned
sessions leading each group (both subsets ordered by recency). The
Pinned/Scheduled/Tasks category sections and their time-mode paging
machinery (page-fill effect included) are deleted
- Scheduled sessions get an inline clock icon next to the pin position;
pin + clock render together when both apply, and the running/unread
status dot now coexists with them
- One font size (text-sm) across the list: titles, timestamps, project
headers, show-more buttons, empty states. sidebarText needed !text-sm
because the default button size's text-base wins the twMerge conflict
- Gradient fade under the Sessions header once the list scrolls, so rows
fade out instead of hard-clipping
- The session-detail hover card is controlled from the sidebar and closes
on scroll (Radix receives no pointer events while scrolling, so it used
to float over moving content)
- Sidebar min resize width raised 224->260px; the per-project show-more
label truncates so its nowrap text can't force rows to overflow and clip
timestamps at narrow widths
Navigation:
- Customize replaces the Plugins/Marketplace/Hooks/Rules/Tools sidebar
entries; Schedules and Customize are hidden from the expanded settings
nav (their top rows cover them) but stay reachable when collapsed
- The settings gear always opens General instead of resuming the last
section; the Account no-op hover special case is gone
- The New row highlights (aria-current) while the fresh not-yet-started
task page is showing and hands off to the session row once the task
starts; hitting New also focuses the prompt input via a window-event
signal (lib/prompt-input-focus.ts) since the sidebar and composer sit in
distant subtrees
- Fixed the xs button size collapsing any icon-bearing button to 12x12
(leftover has-[>svg]:size-3 from when xs was a micro button) — this was
why Uninstall buttons rendered broken next to Install
* feat(desktop): polish settings pages and chat composer
Models page:
- The provider detail panel is always open: no X button, no empty
no-selection state. It defaults to the first connected provider (falling
back to the first in the catalog), which also removes the layout shift
that happened when the page swapped between full-width and panel
variants on selection
- Fixed the list pane becoming unscrollable while the panel was open:
grid items default to min-size auto, so the pane grew past its track
inside the overflow-hidden grid and its ScrollArea had nothing to
scroll; wrapped it in a min-h-0 min-w-0 cell
- Add Provider opens a Dialog instead of swapping the page
(AddProviderContent gained a dialog variant that renders only the form)
- Embedded inputs (provider search, model search, detail fields) share one
EMBEDDED_INPUT_CLASS stripping the Input component's own border/dark bg
tint/shadow/ring, which rendered as a mismatched inner box; the model
search box uses the same h-9/px-3 frame as the provider search
- Model list flows with the page instead of a max-h capped inner scroller
Other pages:
- Account uses the shared PageFrame/PageHeader: left-aligned, text-3xl
title, Sign Out in the header actions slot
- Desktop notifications is one General section: header row plus the
Event/Notify/Sound matrix nested in a card, so its rows no longer read
as top-level peers of Dark mode; 'Available in the desktop app' label
removed
- Schedule page retitled from Schedules with a real description; Customize
description rewritten
Chat composer:
- The voice dictation button only renders once a voice model is
configured (Settings -> Voice); the unconfigured deep-link state is
gone (prop type kept for an easy restore)
* chore(desktop): release v0.0.17
* fix(desktop): unblock sdk-test lint on the voice-input model picker (#13553)
The model picker renders a radiogroup of styled buttons with role=radio
and aria-checked; biome's useSemanticElements flags the role as an
error, which fails the sdk-test Quality Checks lint for every PR
touching sdk/ or apps/ paths. Suppress with a justification — switching
to input type=radio needs a restyle and belongs to the desktop settings
work.
* fix(vscode): include rich workspace metadata in system prompt (#13518)
* capture richer workspace information for vs code extension
* fix(shared): redact credentials from workspace remotes
* fix(shared): avoid regex backtracking in remote redaction
---------
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
* Hide task costs on vscode when ClinePass is selected (#13515)
* fix: stop showing cost estimates for subscription-billed providers (#13552)
* fix(vscode): stop showing cost estimates for subscription-billed providers
Providers whose usage is covered by a flat-rate subscription (ChatGPT
Plus/Pro via openai-codex, ClinePass) are marked with
metadata.usageCostDisplay = "subscription" in the SDK, and the CLI
already suppresses dollar figures for them. The VS Code host collapsed
that value into "show" before it reached the webview, so the task
header and model pricing rows rendered API-rate cost estimates that
users read as real charges on top of their subscription.
Pass all three usageCostDisplay values ("show" | "hide" |
"subscription") through the catalog listing and render cost only when
the value is "show", matching the CLI's shouldShowCliUsageCost
policy.
* feat(llms): mark Claude Code as a subscription-billed provider
Claude Code is typically authenticated with a Claude Pro/Max
subscription, but its models reuse Anthropic API pricing metadata, so
Cline rendered per-token prices and API-rate cost estimates for usage
that is covered by the subscription. Set usageCostDisplay =
"subscription" on the provider (picked up by the CLI and the VS Code
webview) and suppress the price rows in the Claude Code settings card.
The Claude Code CLI can also run on API-key billing, where a real cost
exists; the provider cannot distinguish the two, so we prefer showing
no number over a misleading one.
* fix(vscode): suppress cost display until provider listings load
While the ListProviders request is in flight (or after it fails), the
usage-cost hook had no listing to consult and fell back to "show",
flashing the API-rate estimate at subscription users on every chat-view
mount — the exact display the previous commit removes. Return
"unknown" whenever listings are absent; consumers already render cost
only for "show", so they suppress it during that window with no
changes. Briefly hiding a real cost is harmless, briefly showing a fake
charge is not.
* fix(desktop): reconcile voice settings after main sync
* test(llms): allow experimental ElevenLabs models
* fix(sdk): preserve canonical media model behavior
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: JasmineLCY <38378321+JasmineLCY@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
Co-authored-by: Max <maxpaulus43@gmail.com>
Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bee <abeatrix@users.noreply.github.com>
Co-authored-by: Renee Huang <100229782+reneehuang1@users.noreply.github.com>
Co-authored-by: Ara <arafat.da.khan@gmail.com>
Co-authored-by: Haley Park <haleypark.design@gmail.com>
Co-authored-by: cline-cloud[bot] <276134852+cline-cloud[bot]@users.noreply.github.com>
Co-authored-by: yzxcj797 <54314860+yzxcj797@users.noreply.github.com>
Co-authored-by: yzxcj797 <yzxcj797@users.noreply.github.com>
Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
* fix(vscode): continue the surviving session on resume instead of rebuilding with the original task text (#13175)
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilt the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
The preserved conversation history is the source of truth on resume, so
the fallback prompt now just asks the model to reassess the history and
continue, matching the legacy resume prompt which also never resent the
original task. User-typed text still takes precedence when provided.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): continue the surviving idle session on follow-ups instead of rebuilding
Stopping a turn keeps the session alive, but every idle follow-up (bare
Resume after Stop, and typed follow-ups after a completed turn) tore
that session down and rebuilt it from persisted task history before
sending. Continue the matching idle session in place instead, the same
way the CLI reuses the live session after an abort. Rebuilding from
history now only happens when no live session matches the displayed
task (task opened from history, extension host reload).
A bare resume still needs a prompt to start a turn, so it sends the
neutral [TASK RESUMPTION] prompt (shared with the rebuild fallback and
hidden from the transcript); user-typed content is echoed and sent
as-is. If the send lands while the abort is still settling, the runtime
auto-queues it and drains once the abort completes.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): consolidate follow-up send paths in SdkFollowupCoordinator
Now that idle follow-ups continue the live session in place, the
two-mode sendToActiveSession helper was redundant: its non-queued branch
duplicated continueIdleSession minus the bare-resume prompt. Split it
into a single-purpose queueToActiveSession and fold the idle no-task
send into continueIdleSession, flattening askResponse's decision tree
to: queue onto a running turn, continue a matching live idle session,
rebuild from history, or abandon.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): reuse the existing neutral resumption prompt for bare resumes
Drop the newly invented long resumption wording in favor of the phrase
that already existed as the no-history fallback and that the transcript
hiding logic and test fixtures recognize: '[TASK RESUMPTION] Please
continue where you left off.' The net change to resumeSessionFromTask
against main is now just deleting the branch that resubmitted
historyItem.task as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilds the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
Bare resumes now always use the neutral prompt that already existed as
the no-history fallback; user-typed text still takes precedence. This
matches the legacy resume prompt (responses.taskResumption), which only
ever included user-supplied text as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): hide synthetic prompts from the queued-prompt echo
A send that races a settling abort is auto-queued by the runtime, so a
bare Resume can reach the pending_prompt_submitted echo carrying the
synthetic [TASK RESUMPTION] prompt. Echoing it leaked model-facing text
as a visible user bubble and shifted the visible-user-message ordinals
that edit/regenerate mapping relies on. Filter synthetic prompts with
isSyntheticUserPrompt, keeping user attachments visible (matching
isSyntheticSdkUserMessage semantics).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): preserve LiteLLM input token limits (#13293)
* fix(vscode): preserve LiteLLM input token limits
* fix(vscode): prefer live LiteLLM model metadata
* fix(vscode): generalize private catalog metadata
* test(vscode): preserve llms exports in vscode lm mock
* fix(vscode): point provider signup URLs at their API key pages (#13337)
* fix(vscode): point Mistral signup URL at the general API keys console
The Mistral provider's signup link led to the Codestral console, which
issues Codestral-scoped keys that fail with 401 on api.mistral.ai — the
endpoint the provider actually calls. Point it at the general API keys
page instead.
Fixes#13288
* fix(vscode): deep-link DeepSeek and Fireworks signup URLs to their API key pages
Both pointed at marketing homepages; link straight to the key-creation
pages instead, matching the rest of the registry and the desktop app's
provider-key-urls map.
* fix(ci): always build the legacy bundle from the legacy-extension branch (#13349)
The combined-VSIX workflow took legacy-ref as a free-form dispatch input
with no publish-time validation (next-ref has one: publish requires main).
Any typed ref — a PR merge ref, an unprotected branch — would be built
into the published VSIX by the environment-less build job, and the publish
environment approver only ever sees an opaque prebuilt artifact, so the
approval protected the marketplace PAT but not the shipped bytes.
Remove the input entirely and hardcode the protected legacy-extension
branch, which makes that branch's protection rules load-bearing for
releases. The tested-sha pinning between test-legacy and build is
unchanged. publish-extension skill dispatch command updated to match.
* fix(ci): lock the legacy publish workflow to the legacy-extension branch (#13350)
The branch dispatch input was a free-form string with no validation. Both
jobs checked it out and ran full npm lifecycle scripts from it: the publish
job next to VSCE_PAT/OVSX_PAT (and npm run publish:marketplace executes a
script from that same ref with the PATs in env), and the test job with NO
environment approval at all while inheriting the workflow-level
contents/packages/checks/pull-requests write grants. A dispatch pointing at
e.g. refs/pull/N/head would run outside-contributor code with the
marketplace keys behind one approval, or with a repo-write token behind
none.
Remove the input and hardcode the protected legacy-extension branch, drop
the workflow-level permissions to contents: read, and elevate only the
publish job to contents: write (tag push + GitHub release). The branch
input's default was legacy-extension, so normal publishes are unchanged.
publish-extension skill dispatch command updated to match.
* fix(vscode): SDK remote-config parity — refresh coordination, session gating, and fail-closed opt-out (#13226)
* feat(desktop): native notifications (#13166)
* feat(desktop): native notifications
* macos target
* fix(desktop): isolate macOS dev app identity
* fix(desktop): address notification review feedback
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched (#13310)
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched
Toggling an auto-approve setting while a task is open writes
autoApprovalSettings into the StateManager's task-settings overlay
(updateAutoApprovalSettings -> setTaskSettings). The SDK controller never
cleared that overlay on clearTask/showTaskWithId (the legacy controller
did), so after New Task the stale overlay kept shadowing global settings
in getGlobalSettingsKey(): toggle RPCs were accepted into global state,
but every posted state still carried the overlay's old version, which the
webview rejects as not newer - the auto-approve checkboxes froze forever.
Restore legacy parity in SdkTaskControlCoordinator: drop the overlay
(persisting pending writes first) in clearTask() and before installing a
different task's proxy in showTaskWithId().
Fixes#13260
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* changeset
* test(vscode): add end-to-end regression test for auto-approve freeze after New Task
Wires the real StateManager, the real updateAutoApprovalSettings handler,
and the real SdkTaskControlCoordinator.clearTask() together with the
webview's version gate modeled on ExtensionStateContext, pinning the
end-to-end invariant behind #13260: checkbox toggles must keep reaching
the webview after a mid-task toggle followed by New Task. Verified the
test fails when the clearTaskSettings() call is removed from clearTask().
* fix implicit any in regression test
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): show provider web-search support under the settings toggle (#13328)
* feat(desktop): show provider web-search support under the settings toggle
The global Web search toggle silently does nothing unless the session's
provider offers native web search, which made the setting read as if it
worked with any provider. The desktop General settings row now explains
that only providers with built-in web search honor it, and shows a live
status line: which connected providers are ready to use it (no extra
setup needed), or an amber warning with a link to the Models section
when none of them support it.
Support is resolved in the webview via a new providerOffersModelTool
helper in @cline/llms (browser export), sharing the same builtin-manifest
source of truth as the runtime's supportsModelTool attachment check.
* fix(desktop): address review — refetch web-search status on catalog invalidation, clarify per-model support
Greptile P2: the one-time catalog fetch could race an in-flight provider
save and show stale status; the row now refetches when the provider
catalog cache is invalidated (fired after saves complete).
Greptile P1: the ready line implied every model on the provider works;
Vertex excludes Claude routes, so the copy now scopes the promise to
models that support it.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat(ui/desktop): collapse finished runs into a work summary and remove hover-state dead space (#13315)
* feat(ui): add WorkActivity collapsed-run summary and float message actions as a pill
WorkActivity/WorkActivityTrigger/WorkActivityContent fold a finished agent
run's working rows (tool calls, thinking traces, narration) behind a single
"Worked for 4m 12s · 14 tool calls" disclosure built on the shared animated
disclosure primitives, with formatWorkActivityLabel/formatWorkDuration
exported for consumers.
Message hover actions no longer rely on the transcript reserving blank space
below each message: the action row is now a self-backed pill (border,
blurred background, shadow) that floats over whatever follows, so
conversations can pack rows tightly without hover chrome colliding with the
next message.
* feat(desktop): collapse finished runs into a work summary and tighten chat spacing
collapseCompletedWork post-processes the grouped transcript: once a run ends
on assistant text with no further tool calls, its working rows fold into one
expandable WorkActivity row while the final answer stays visible. Runs are
delimited by user messages; the trailing run only collapses when the session
has stopped running and actually produced an answer, so live streams and
cancelled/failed tails keep their rows. Assistant messages carrying images
or media are treated as deliverables and never collapse.
The conversation list gap drops from gap-8 to gap-4 now that hover actions
are self-backed pills that need no reserved space, and user messages add
their own top margin so turn boundaries stay visually distinct.
* refactor(ui/desktop): work summary label wording, flat expansion, stable in-run rhythm
Feedback round on #13315:
- Label reads "Worked for 4m 12s and made 14 tool calls" instead of joining
with a dot; without a duration it falls back to "Made N tool calls".
- Expanded work rows render at transcript level — no rail or extra indent —
since tool rows and thinking traces already carry their own nesting when
expanded. The work content keeps the tight working-row rhythm.
- Live working rows (thinking traces + tool calls) now group into a 'run'
render item with the same tight 0.25rem rhythm, so there is no oversized
gap under a "Thought for Ns" row and every row keeps its exact position
when the finished run folds into the work summary. A trailing
answer-in-progress stays outside the group at transcript level, and pure
prose spans keep normal spacing.
- The transient "Thinking..." indicator moves inside the transcript column
and mirrors a trigger row's geometry, so the first real row replaces it in
place with no jump.
* style(ui/desktop): hover-pill metrics, right-pointing work chevron, scroll and spacing fixes
Another feedback round on #13315:
- Hover action pill: +2px internal padding, a trailing inset after the
timestamp (it sat flush against the pill border), and more clearance
between the message content and the pill (2px -> 6px; the hover bridge
grows to match).
- The work summary chevron points right while collapsed and continues
counterclockwise to point up when expanded.
- Conversation bottom padding drops pb-20 -> pb-8: the composer sits below
the scroller, so the padding only needs to clear a pinned action pill.
- Sending a message scrolls back to the bottom even if the reader had
scrolled up (new AutoScrollOnSend on the user-message count, which ignores
optimistic-bubble re-keying; @cline/ui now exports useConversation for
this).
- An assistant answer directly under its run's working rows pulls itself
0.5rem closer than the full transcript gap.
* style(desktop): leave a visible gap between a pinned action pill and the composer
pb-8 exactly matched the pill's ~40px footprint, so the last row's hover
actions sat flush against the composer top; pb-12 restores ~8px of daylight.
* style(desktop): widen the gap between the pinned action pill and the composer to ~24px
pb-12 left only ~8px of daylight under the pill; pb-16 reads comfortable
without reverting to pb-20's dead space.
* fix(desktop): keep the thinking indicator at the working-row offset mid-run
The indicator matched a trigger row's geometry but sat a full transcript gap
(1rem) below the last working row, while the tool/thinking row replacing it
joins the tight run group at 0.25rem — a visible upward jump. When the last
transcript item is working rows (or streamed assistant output), the
indicator now pulls up to the same tight offset; only at the start of a run,
under the user message, does it keep the normal gap.
* style(ui): calm the hover actions surface per team feedback
Borderless rectangle instead of the bordered pill: radius drops to
var(--radius), the side padding goes entirely (the icon buttons carry their
own hit areas), and the vertical padding halves. Blurred background and
shadow stay so it remains legible over following content.
* feat(ui/desktop): full-band hover reveal and iOS-style disclosure easing
The hover actions only appeared while the pointer was inside the message
box itself. The invisible bridge under each message now spans the full
height of the band the floating actions occupy (full row width), so
hovering anywhere in that strip reveals them. Sibling row types
(.cline-chat-tool, .cline-chat-work, and the desktop's run/tool groups)
become position: relative so they paint above the bridge — their own
content keeps its hover and clicks, and the bridge only wins in the band's
genuinely empty space.
All expandable rows (work summary, tool panels, thinking) open and close on
a 240ms symmetric ease-in-out cubic-bezier instead of the 60ms snap, with
chevron rotation on the same curve. Reduced-motion still disables both.
* revert(ui/desktop): drop the full-band hover reveal; quicken disclosure easing to 180ms
The full-band hover bridge (and the position: relative changes that made it
safe) is reverted per feedback — back to the narrow bridge that only spans
the gap under the message. The iOS-style ease-in-out on disclosures stays
but speeds up from 240ms to 180ms.
* fix(ui): recover live tool diffs that mount as a blank pierre skeleton
Live-streamed edit rows could show an empty diff for the whole run, with the
diff only appearing after the collapsed work row was expanded (fresh mount).
Root cause, confirmed by driving a live session and inspecting the element:
React StrictMode double-invokes @pierre/diffs' ref callback; the first
instance's async highlight work aborts on its immediate cleanup, and the
second instance adopts the abandoned half-rendered shadow tree as if it were
complete prerendered output — zero height, no code, no theme stylesheet,
permanently. A rendered diff always carries style[data-theme-css] in its
shadow root, so ToolFileDiff now checks for it shortly after mount and
remounts FileDiff (bounded attempts) when missing; the fresh host element
takes the normal render path and recovers within ~400ms. Verified live: the
diff now renders during the run.
* fix(desktop): keep interrupted runs expanded even with partial trailing text
The trailing-run collapse gated on 'ended with assistant text', which
misread a Stop that landed mid-answer as a finished run and folded the tool
calls the user wants to inspect. The gate is now the terminal status itself:
only completed (or restored-idle) sessions collapse the trailing run;
cancelled/failed/error tails keep their rows regardless of partial text.
(Greptile P1 on #13315 — matches the PR's stated rule.)
* feat(ui): share the markdown pipeline, chat polish, and ThinkingBlock across products (#13323)
* feat(ui): share the markdown pipeline, chat polish CSS, and ThinkingBlock
The desktop app and the cloud dashboard both consume @cline/ui yet rendered
assistant output differently, because Markdown policy and the thinking-trace
row lived app-side. This moves the shareable parts into the package:
- components/markdown (new export): the lazy Shiki code highlighter (GitHub
light/dark, pinned language set) and agentMarkdownControls — the standard
Streamdown configuration. streamdown/shiki/@shikijs/* become optional peer
dependencies, mirroring @pierre/diffs.
- components/markdown.css: the desktop's chat polish moves in — chat-scale
headings, outside list markers, single quiet code blocks with a
hover-revealed copy control, table cards. Kept unlayered so it beats
Streamdown's layered Tailwind utilities without !important.
- ThinkingBlock + formatThoughtLabel in agent-chat: the standard thinking
row (brain icon, Thinking/Thought-for-Ns label, streaming shimmer, rail
presentation, capped scrollable body). The shimmer and the
reasoning-hover-suppression rule move into agent-chat.css; triggers gain
the color transition the desktop applied locally.
Version bumps to 0.2.0-next.5 for the dashboard to pick up.
* refactor(desktop): consume shared markdown and thinking primitives from @cline/ui
The local Shiki highlighter, Streamdown controls, chat markdown polish CSS,
streaming-title shimmer, and reasoning hover-suppression rule are deleted in
favor of the @cline/ui versions (the highlighter test moves to the package's
suite). ReasoningBlock becomes a thin wrapper that hands MemoizedMarkdown to
the shared ThinkingBlock, and formatThoughtLabel re-exports from the package
so grouping code and tests keep their import path.
globals.css now imports @cline/ui/components/markdown.css (unlayered, so the
polish keeps beating Streamdown's layered utilities); the app keeps only what
is genuinely app-specific: link/image policy in markdown.tsx, selectability
rules, accent palettes, and the view-enter transition.
* style(ui/desktop): make thinking-trace prose legible
Thinking body text rendered too faint: plain muted-foreground plus the
desktop's font-thin weight. The shared thinking content now leans 75% of the
way back toward the body text color (still slightly de-emphasized), and the
desktop drops the thin font weight.
* ci(ui-publish): build @cline/shared before ui typecheck (#13354)
@cline/ui's generated-media imports @cline/shared/browser, which resolves to
shared's dist output. The build-shared step sat after typecheck/test/build,
so the first ui-publish dispatch since #13025 failed at Typecheck UI with
TS2307. Move the step to right after install.
* fix: run_commands object form without args routes through the shell instead of failing with ENOENT (#13336)
* fix: run_commands object form without args routes through the shell
The structured { command, args? } form of run_commands was always spawned
directly with shell: false. When a model emitted a full command line in
command with no args (e.g. { command: "echo hello" }), spawn failed with
ENOENT for any command containing a space, breaking command execution for
the whole session.
Direct exec now only applies when a non-empty args list is provided; the
object form without args is routed through getShellInvocation like the
string form. Schema descriptions are tightened so models put arguments in
args instead of embedding them in command.
Fixes#13279
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: trim structured-command schema descriptions
The union schema is only used for lenient validation of input the model
already sent; its descriptions never reach a model prompt. Keep them
short instead of restating executor behavior.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: simplify direct-exec comment in shell executor
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert: keep original structured-command schema description
The description never reaches a model prompt and the executor now handles
both shapes, so the wording change was cosmetic noise.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: gate direct exec on args key presence, not array length
Review feedback: an explicit empty args array is intentionally structured
input and stays direct exec; only an object with no args key is treated
as a full shell command line. Matches the key-presence rule already used
by the VS Code host's formatCommandForTerminal. Also replaces the
empty-args shell test (which was PowerShell-incompatible) with a test
pinning the direct-exec contract.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: normalize Gemini custom base URLs for legacy host-root values (#13329)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* docs: add GLM-5.3 to ClinePass models and reference pricing (#13357)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): stream run command output (#13179)
* feat(desktop): stream run command output
* fix(sdk): clean up detached command logs
* fix(sdk): reap detached logs after hub restarts
* fix(sdk): preserve live detached command logs
* fix(desktop): harden live command progress
* fix(sdk): recover detached logs for local hosts
* fix(desktop): reconcile command output tool rows
* fix(sdk): retain logs for surviving commands
* fix(core): prevent PID reuse from retaining detached logs
* fix(core): preserve detached logs on probe failures
* fix(core): retain detached logs during probe outages
* fix(desktop): resolve leftover merge conflict in messages projection test
Combine both sides of the assertion: main's incremented per-block
createdAt projection and this branch's toolCallId/hookEventName meta.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): make TUI dialog colors follow theme changes live (#13355)
* fix(cli): make TUI dialog colors follow theme changes live
Dialog content previously read the static palette constant, so open
dialogs (including the theme picker itself) kept the default dark-blue
accents while scrolling through theme previews. Add getDialogPalette /
useDialogPalette, which resolve dialog colors from the active theme's
dialog accents and re-render on every theme change, and migrate all
dialog-rendered components to it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(cli): derive dialog panel background from the active theme
Dark themes now lift their own background one OKLAB step for the dialog
surface, so panels keep the theme's hue instead of the library's fixed
#262626. DialogThemeSync pushes the surface into the dialog container
for new dialogs and repaints open panels, so the surface also follows
live theme previews. Light themes keep the neutral dark panel to match
the dark accent fallback and the light-on-dark dialog text.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: skill slash commands load via the skills tool instead of expanding into the user message (#13327)
* fix(desktop): show typed slash command instead of expanded skill markdown
The sidecar expands /skill and /workflow tokens into their instructions
before dispatching, so the runtime's persisted transcript only contains the
expanded text. After a turn (and when reopening a session) the webview
re-hydrates from that history and rendered the whole SKILL.md body as the
user's message; queue events echoing the expanded prompt could also add a
second user bubble, and fresh sessions were titled with the markdown's first
line. The CLI never shows this because its TUI keeps the typed text in its
own transcript and only sends the expanded prompt to the model.
Mirror that separation inside the desktop sidecar's display boundaries:
- history projection (readSessionMessages) inverts user text that starts
with a configured command's instructions back to '/name remainder',
which also repairs sessions recorded before this fix
- queue snapshots and chat_queued_prompt_start events echo the typed
prompt recorded at expansion time, so the webview's optimistic-bubble
re-key matches again
- an untitled session sent an expanded prompt gets titled from the typed
command instead of the instructions' first line
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't overwrite a mid-turn rename with the typed-command title
The untitled check ran before dispatch, so renaming a fresh slash-command
session while its first turn was running got clobbered by the post-turn
typed-command title. Re-check at write time and only replace a missing title
or the one the runtime auto-derived from the expanded prompt.
Also documents the inherent prefix-inversion ambiguity flagged in review:
text hand-typed with a command's exact instructions persists byte-identically
to that command's expansion, so stored history alone cannot distinguish them.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop expanding skill commands; let the skills tool load them
Pasting the skill body into the prompt is why the transcript could ever show
it: the desktop webview re-hydrates from the runtime's persisted history, so
whatever the sidecar splices into the user message renders as if the user
typed it. The runtime already registers the skills tool, whose description
requires the model to invoke it whenever the user references a slash command
— so send the typed /skill text through and let the tool deliver the
instructions as a tool result (previously they arrived twice: pasted and via
the tool). The persisted user message, session title, and queue entries are
then simply the typed command, which deletes the typed-prompt registry, the
queue event/snapshot rewriting, and the title machinery from the previous
approach.
Workflows are not served by the skills tool and keep textual expansion, so
the read-time display inverter stays: it collapses expanded workflow prompts
— and skill prompts persisted before this change — back to the typed
/command in the history projection.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): option to keep skill slash commands typed for the skills tool
resolveRuntimeSlashCommandFromWatcher (and the hub snapshot proxy) accept
expandSkillCommands: hosts whose sessions register the skills tool pass
false so the typed /skill goes through and the model loads the instructions
as a tool result, keeping the persisted transcript as what the user typed.
Workflows always expand — the tool does not serve them. isSkillsToolAvailable
exposes the catalog check hosts use to decide (yolo preset and the skills
tool toggle leave textual expansion as the only delivery path).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): skill slash commands load via the skills tool instead of expanding
The TUI user-command wrap and buildUserInputMessage now keep a typed /skill
as-is when the session's mode/toggles register the skills tool, matching the
desktop app; workflows keep expanding, and yolo (zen) keeps expanding skills
because its preset has no skills tool. This also fixes CLI resume/history
surfaces showing the skill body: the persisted user message is now the typed
command.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): keep configured skill slash commands typed for the skills tool
expandSlashCommands no longer splices a configured skill's instructions into
the model text; the SDK session's skills tool delivers them as a tool result
(previously they arrived twice). Builtin pseudo-skills like /deep-planning
are not served by that tool and keep expanding, as do workflows.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): use the shared skill-expansion option in the sidecar
Replaces the sidecar's workflow-detection dance with core's
expandSkillCommands option and gates on isSkillsToolAvailable, restoring
textual expansion where the tool is missing (yolo mode or the skills tool
toggle) — a gap in the previous desktop-only change.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): drop the display inverter for expanded transcripts
Accepted trade-off to keep the change minimal: sessions recorded before
skills switched to the skills tool, workflow sends (deprecated), and
yolo-mode skill sends persist expanded instructions and now render that text
as-is instead of being collapsed back to the typed /command at projection
time.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Use fixed selection chevron in account dialog to match other dialogs (#13364)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): align system prompt with session mode (#13361)
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(desktop): finalize queued turns on chat_done with canonical history reconcile (#13330)
Turns that settle through the event stream (queued prompts, including the
first prompt of a fresh session) resolve their send() RPC early, so nothing
cleared the streaming shimmer or reconciled live-streamed content against
the persisted transcript at turn end. A turn whose deltas were incomplete
stayed visually streaming forever and only healed when a later non-queued
send rehydrated history.
chat_done (and chat_session_ended / the queue-drain double check) now clears
the active assistant streaming id and schedules a short-delayed
read_session_messages + applyCanonicalHistory, guarded by turn epoch,
session id, and in-flight send submissions so it never clobbers a newer
turn or duplicates the blocking send path's own finalization.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(desktop): release v0.0.14
* fix(clients): filter non-chat models from chat pickers (#13317)
* fix(clients): filter non-chat models from chat pickers
* fix(clients): align chat model eligibility
* fix(desktop): strip user_input envelope when copying a user message (#13369)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bee <abeatrix@users.noreply.github.com>
* docs: show DeepSeek V4 peak and off-peak pricing (#13312)
* docs: update DeepSeek V4 average pricing
* docs: show DeepSeek peak and off-peak pricing
* docs: add GLM-5.3 reference pricing (same as GLM-5.2)
* docs: add GLM-5.3 to ClinePass models table
* fix(llms): display billed gateway cost (#13385)
* fix(shared): run PowerShell commands with fail-fast error semantics (#13358)
* fix(shared): run PowerShell commands with fail-fast error semantics
The run_commands PowerShell wrapper never set $ErrorActionPreference, so
the default 'Continue' applied: a pipeline erroring per item (e.g. a
malformed Where-Object over Get-ChildItem -Recurse) emitted one error
record per enumerated file - tens of thousands of stderr records on
large trees, looking like a hang - and could still resolve as SUCCESS
with exit 0.
Prepend $ErrorActionPreference='Stop'; to the script content executed
by the ScriptBlock so the first error terminates the command with a
non-zero exit and a single error message. Concatenated on the same line
as the user command so error line numbers stay unshifted.
Fixes#13285
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(shared): set the fail-fast preference in the bootstrap scope
Setting $ErrorActionPreference='Stop' by string-prepending it into the
scriptblock source displaced a leading param(...) from its mandatory
first-statement position, so scripts beginning with a param block failed
with CommandNotFoundException. Preference variables are dynamically
scoped, so setting Stop in the -Command bootstrap gives the invoked
scriptblock identical fail-fast semantics while keeping the user script
byte-identical (param works, error positions unshifted) and drops the
doubled-quote escaping.
* docs(shared): document the fail-fast tradeoffs in the PowerShell wrapper
Stop promotes every non-terminating error, not only per-item pipeline
floods: partial-result commands (recursive listings over access-denied
junctions) now stop at their first error, and Windows PowerShell 5.1
turns in-script stderr redirection of succeeding native commands fatal.
State this in the wrapper comment as a deliberate tradeoff, with the
GitHub Actions precedent and the per-command opt-outs.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
* ci: stop over-long changelogs from silently dropping release Slack posts (#12955)
Slack section blocks reject text longer than 3000 characters. The Slack
action logs that rejection as ##[error] but does not fail the step, so an
over-long changelog drops the release announcement while the run stays
green — cline@3.0.50 (3272 chars) published to npm, tagged, and cut a
GitHub release with no Slack post and nothing red to notice.
Every publish workflow pasted the changelog section verbatim into one
section block, so all six were exposed; the SDK, desktop, and extension
sections were only 150-350 chars under the ceiling.
Add a slack_content output alongside content: unchanged when the section
fits, otherwise trimmed on a line boundary with a link to the full
release notes. Only the Slack payload uses it — GitHub release bodies and
the desktop updater manifest still get the whole section.
* ci: tidy workflow cache config and job permissions (#13403)
Publish workflows now always do clean npm installs (no dependency
cache in their test gates), the e2e workflow's cache keys are
exact-match only, and the e2e job drops an id-token permission it
never used.
* Rename desktop app from "Cline Code" to "Cline" (#13401)
* Rename desktop app from Cline Code to Cline
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Format touched Rust test assertions
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): surface provider-executed tool activity as observational events (#13300)
* fix(llms): surface provider-executed tool activity as observational events
Provider-executed tool parts (e.g. every tool the Claude Code CLI runs
inside its own session) were dropped by the model-tool guard added for
web search: only declared model tools were re-emitted, everything else
hit continue with nothing yielded. Those sessions modified the workspace
with no tool activity in runtime events, transcripts, or the UI.
Route all providerExecuted parts onto the observational path instead:
emit execution-tagged tool-call-delta and tool-result events, matched by
tool-call ID for providers that omit the flag on the result half. They
stay out of AgentRuntime's execution/approval loop, and the runtime
already persists them as modelToolActivities and projects them for
display.
The AgentModelEvent tool-result variant widens toolName from
ModelToolName to string to carry the provider's own tool names.
* fix(agents): keep turns that are only provider-executed tool activity
A turn consisting solely of observational tool activity has an empty
assistant content array - the activity lives in message metadata, since
projecting it into content would replay tool_use blocks the model never
gets results for. The empty-content guard threw on such turns, erroring
the run and losing the activity from the transcript. Count model-tool
activity as content for the emptiness check (error finishes still
throw); replay stays safe through the codec's empty-content placeholder.
Also drop the trailing text delta from one gateway test so the tool-only
stream shape stays covered end to end.
* feat: allow agents to create scheduled tasks (#13331)
* feat(core, desktop): add durable todo agenda
* fix(desktop): secure todo approvals and track tool usage
* fix(desktop): clean up failed approval delivery
* fix(desktop): authenticate approval connections
* fix(desktop): cancel approvals on broadcast failure
* fix(desktop): authenticate development approvals
* fix(desktop): harden development approvals
* test(core): make task paths cross-platform
* fix(desktop): serialize approval readiness
* refactor(core): unify todo and schedule tools
* feat(core): distinguish user todos from agent suggestions
* fix(core): hide tasks tool in yolo mode
* fix(core): enforce schedule workspace scope
* fix(core): bind schedule scope to hub connection
* fix(core): establish task scope at hub startup
* fix(core): scope task automation by workspace
* test(core): normalize workspace path expectations
* test(core): serialize Windows CI workers
* fix(core): reject unregistered schedule authority
* fix(desktop): guard task execution commands
* fix(core): avoid polynomial regex in mention parsing
* fix(core): address schedule tool review feedback
* fix(core): bind websocket clients to hub workspace
* fix(core): flatten tasks tool input schema
* fix(core): authorize multi-workspace hub clients
* test(core): type hub transport authority mock
* fix(cli): register a workspace client for remote schedule commands (#13398)
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(desktop): treat ClinePass as OAuth-managed in the chat credential gate (#13404)
* fix(desktop): treat ClinePass as OAuth-managed in chat credential gate
ClinePass shares the Cline account OAuth credentials (its auth handler
stores under the "cline" provider), so the webview never sees a plain
API key for it. The chat pre-flight check only exempted cline/oca/
openai-codex, so switching to ClinePass while signed in via OAuth
blocked with "Missing API key" even though the sidecar resolves the
stored access token fine (which is why the CLI worked).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style: format helpers.test.ts with biome
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(ui): stack code block lines when streamdown lineNumbers is off (#13412)
streamdown renders each Shiki token line as a bare inline span with no
newline text between non-empty lines, and only applies its block line
class when lineNumbers is on. With lineNumbers off (the desktop app's
config) every multi-line fenced block collapsed into one run-on line.
Make the direct line spans under code-block-body display: block in the
shared markdown.css; empty lines keep their height via their lone "\n"
child under white-space: pre.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): work summary undercounts wall time when pre-tool thinking attaches to the answer (#13413)
* fix(desktop): anchor work summary duration on the answer row, not attached pre-tool reasoning
The collapsed 'Worked for Xs' row undercounted wall time whenever a turn's
assistant message contained thinking + tool_use with no narration text: the
canonical projection emitted the reasoning-only row after the tool row (both
stamped before the tool executed), the webview attached that row to the final
answer, and collapseCompletedWork used the answer's earliest attached
reasoning timestamp as the end anchor - excluding the entire tool execution
(e.g. 'Worked for 5s' for a turn with an 8s command).
- webview: end the work span at the answer row's own timestamp, clamped to
the last collapsed row so a fallback answer bubble with a synthetic early
timestamp cannot shrink the duration either
- sidecar: flush pending thinking before a tool_use row so rehydrated
transcripts keep the live-stream order (thinking before its tool call) and
pre-tool reasoning no longer rides on the next answer
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep interleaved thinking between the tool calls it separates
Address Greptile review: when one assistant message interleaves thinking
between multiple tool_use blocks, each reasoning segment now projects at its
own position (attached to a text row from its own segment when present,
otherwise as its own row) instead of merging into the first reasoning row,
which displayed later thinking before a tool call it actually followed.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): remove settings gear hover state while Account screen is open (#13408)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't show "No sessions found" while session history is still loading (#13414)
* fix(desktop): don't show 'No sessions found' while session history is still loading
Replace the isLoadingHistory flag with hasLoadedHistory, set only once the
backend has actually answered a list_discovered_sessions request. The sidebar
and Sessions view now keep their loading state until that first definitive
response, so the empty-state copy can no longer appear while history is still
being fetched (or while a failed fetch is being retried).
Also retry a failed initial fetch on the 2s event cadence instead of stranding
the UI until the 12s periodic poll, which is what stretched the misleading
empty state to ~10 seconds after a webview reload when the websocket lost the
race with the page load.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop history fast-retry from re-arming after hook unmount
A failed initial fetch that settles after the hook unmounted could schedule a
new retry timer after cleanup had already cleared the refs, leaving the
abandoned hook polling the backend every 2s. Guard scheduleRefresh with a
disposed ref set by the mount effect's cleanup.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix @ file mentions breaking on paths with spaces (#13391)
* Fix @ file mentions breaking on paths with spaces
Quote mentions generated by getFileMentionFromPath (Add to Cline /
Fix / Explain / Improve commands) when the relative path contains
spaces, so the mention regex no longer truncates the path at the
first space. Also quote the path part of workspace-prefixed mentions
(workspace:/path with spaces) inserted from the @ context menu, which
previously bypassed quoting because the value does not start with '/'.
Fixes#13338
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix import ordering in mentions test (biome organize imports)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reduce fix to minimal scope
Revert the webview quoting refactor and extra tests; keep only the
getFileMentionFromPath quoting fix with a single regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Normalize mention paths to posix separators for Windows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix code actions failing with "command not found" on VS Code 1.134 (#13402)
* Fix @ file mentions breaking on paths with spaces
Quote mentions generated by getFileMentionFromPath (Add to Cline /
Fix / Explain / Improve commands) when the relative path contains
spaces, so the mention regex no longer truncates the path at the
first space. Also quote the path part of workspace-prefixed mentions
(workspace:/path with spaces) inserted from the @ context menu, which
previously bypassed quoting because the value does not start with '/'.
Fixes#13338
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix import ordering in mentions test (biome organize imports)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reduce fix to minimal scope
Revert the webview quoting refactor and extra tests; keep only the
getFileMentionFromPath quoting fix with a single regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Normalize mention paths to posix separators for Windows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix code actions failing with 'command not found' on VS Code 1.134
Code action commands carried arguments (expandedRange, diagnostics),
which routes them through VS Code's CommandsConverter cache. VS Code
1.134 disposes the cached entries before the clicked action executes,
so every lightbulb action failed with 'Actual command not found,
wanted to execute cline.addToChat'.
Drop the arguments so the command id is passed through directly, and
recover the context in the handler instead: getContextForCommand now
expands an empty selection by 3 surrounding lines (matching the old
provider behavior) and gathers document diagnostics intersecting the
range when none are passed explicitly.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Scope gathered diagnostics to the selection/cursor
Match the old CodeActionContext.diagnostics behavior: only include
diagnostics intersecting the range the action was requested for, not
the surrounding lines the text gets expanded to.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Desktop: unify Plugins, MCP, and Skills into one Plugins hub with a dedicated Marketplace page (#13411)
* Unify desktop plugins, apps, MCP, and skills into one Plugins hub with a Browse directory mode
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Open the marketplace directory as a modal over the Plugins hub instead of swapping the page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename directory to Marketplace: Browse Marketplace button, Marketplace modal title with icon, search placeholder
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix search input focus ring clipped by the Marketplace modal scroll container
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Address Greptile review: keep selected tag chip visible when its count drops to zero, and remount installed tab when a marketplace install completes after the modal closed
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Track marketplace modal mutation flag in a ref so a close click racing a queued render cannot skip the inventory remount
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make Marketplace its own settings page under Customizations and restore Channels as a standalone page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Remove icon from Marketplace page header for consistency with other settings pages
* Notify mounted inventory views when the marketplace invalidates the cache so late install completions refresh the Plugins hub
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop/ui): recommended and free model tiers in the composer model selector (#13410)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK (#13415)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): recommended-feed badges and descriptions in provider settings (#13416)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* feat(desktop): recommended-feed badges and descriptions in provider settings
Review suggestion on #13410: the provider settings page has room for
more model detail than the composer's picker. The cline/cline-pass
provider cards now refresh their model list through
list_provider_models (the catalog snapshot deliberately skips the
recommended-feed overlay so the startup catalog fetch never blocks on
the feed) and render Recommended/Free tier badges plus feed tags (NEW)
next to the model name, with the model description underneath. The
refreshed list also surfaces the live entries instead of the bundled
snapshot.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): scope the settings featured model list to its provider and revision
The fetched featured list was unscoped component state: switching
between cline and cline-pass reused the component instance, so the
previous provider's models stayed visible while the new request was
pending (or forever, when it failed), and the retained copy shadowed
later provider.modelList updates — adding a second custom model
submitted the stale list as the complete configuration and dropped the
first addition.
The fetched list now only applies to the provider and modelList
revision it was fetched for (falling back to the catalog snapshot
otherwise and refetching on membership changes), and add-model submits
the union of the displayed and configured ids so an update can never
silently unconfigure existing entries.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(ui): update packed-Tailwind smoke contract for the picker's max-h-64 (#13421)
The ui-publish smoke check pins a set of Tailwind candidates the packed
sources must emit; #13410 grew the SearchCombobox options list from
max-h-56 to max-h-64, so the publish run failed on the stale candidate.
All other pinned candidates verified against the current sources.
* feat(desktop): refresh app icons and branding (#13400)
* ci(vscode): upload E2E failure recordings from the right path (#13427)
The job sets working-directory: apps/vscode, but that default applies to run
steps only, not to `uses:` steps. Since #10961 moved the extension under apps/
and added that default, the artifact path has resolved against the repo root,
matched nothing, and every failing run logged "No files were found with the
provided path: test-results/playwright/" instead of uploading recordings.
Widen to test-results/ so Playwright's error-context snapshots ship alongside
the videos.
* fix(hooks): deliver tool hook contextModification to the model (#13297)
* fix(hooks): deliver tool hook contextModification to the model
On the next engine, a tool_call (PreToolUse) hook's contextModification
was parsed into HookControl.context and then silently dropped: the
runtime beforeTool/afterTool result contract had no channel for
injecting conversation context. Legacy consumed it (ToolExecutor /
ToolHookUtils pushed <hook_context> blocks into the next user turn), so
this was a regression of documented behavior.
- Add appendContext to AgentBeforeToolResult/AgentAfterToolResult.
- AgentRuntime collects appendContext across hooks during an
iteration's tool executions and appends one <hook_context> user
message after the tool results, keeping tool-result parts contiguous.
- Map HookControl.context into appendContext in both subprocess hook
layers (skipped when the hook cancels, matching legacy, where the
message doubled as the error).
- Truncate injected context at 50KB per hook output, matching legacy.
- Concatenate appendContext across merged hook layers.
tool_result (PostToolUse) hooks still run detached with stdout ignored;
making them blocking so their context can be collected is a follow-up.
Ref: https://linear.app/cline-bot/issue/CLINE-2987
* fix(hooks): stamp tool identity on injected hook context blocks
Contexts are batched into one message after the tool results, and
parallel tool execution collects them in completion order, so position
alone cannot attribute a block to its tool call. Add tool_name and
tool_call_id attributes to each <hook_context> block.
* fix(hooks): sanitize hook context block markup
Attribute values (tool_name, tool_call_id) are stripped of quote/angle
characters and embedded </hook_context> closers in hook output are
neutralized, so neither provider-supplied ids nor hook text can corrupt
or spoof a block's stamped identity.
* fix(hooks): neutralize forged opening hook_context tags in hook output
The previous sanitization only neutralized closing tags, so hook output
could still open a forged <hook_context> block claiming another tool's
identity. Escape both opening and closing embedded tags with one rule.
* fix(hooks): hide injected hook context from user-facing transcripts
Stamp the injected hook-context user message with displayRole 'system'
(the compaction-summary convention) so it reaches the model but does
not render as a user bubble in live or replayed transcripts. Without
this, resuming a session showed the raw <hook_context> block as if the
user had typed it.
* fix(hooks): neutralize case-variant embedded hook_context tags
The tag-neutralization regex was case-sensitive, so hook output could
still smuggle a forged tag as <HOOK_CONTEXT>. Match case-insensitively.
* fix(vscode): map PreToolUse contextModification into runtime appendContext
The extension's hooks adapter bridged file hooks into the SDK runtime
but forwarded only cancel/errorMessage, so a PreToolUse hook's
contextModification never reached the model. Map it into the runtime's
appendContext channel; HookFactory already truncates it at 50KB.
* fix(vscode): hide hook-injected context from replayed transcripts
Live sessions never rendered the injected <hook_context> user message,
but session reload replayed it as a user bubble (and post-resume turns
kept doing so). Treat these messages as synthetic in the user-message
mapping: honor the displayRole 'system' stamp the runtime sets, with a
text-prefix guard for paths where metadata is unavailable. This also
keeps edit/regenerate ordinal mapping aligned with visible bubbles.
* fix(hooks): run file hooks through exactly one layer per host
The VS Code extension registered two independent hook execution layers:
its own hooks adapter (config.hooks) and the SDK core's file-hook
extension from the runtime bootstrap. When both discover the same hook
files, every hook executes twice per event — and with context injection
wired, each contextModification would be injected twice.
Add a 'hooks' runtime config extension kind (in the default set, so the
CLI keeps core file hooks unchanged) and gate the bootstrap's file-hook
extension on it. The extension excludes 'hooks' at session start, so
its adapter — which also provides the hook status UI and the
hooksEnabled setting — is its single execution path.
* fix(vscode): discover hooks from the session workspace, not only global state
Hook discovery read workspaceRoots from global state shared across
every Cline instance, so another window repointing it made workspace
hooks silently stop being discovered. With the extension's adapter now
the single hook execution layer, that meant no hooks at all.
HookFactory takes an optional sessionWorkspaceRoot and unions that
root's .clinerules/hooks into discovery (and into cwd resolution), fed
from the session config's cwd. Shared-state discovery still works, so
behavior in the single-window case is unchanged.
* fix(hooks): keep sanitized hook attribute values distinguishable
Replacing every markup delimiter with the same underscore could
collapse two tool call ids that differ only by such a character into
identical stamps. Escape each delimiter with a distinct token instead.
* fix(hooks): make hook attribute sanitization injective
Escaping the underscore itself turns the attribute escaping into a
uniquely decodable code, so no two distinct tool call ids can collapse
to the same sanitized stamp (previously an id containing a literal
escape token could collide with an id containing the delimiter).
* fix(vscode): reconstruct hook status rows when replaying transcripts
hook_status messages are emitted live but never persisted, so reloading
a session dropped every hook row. The injected <hook_context> blocks
carry the hook source and tool name, so the replay translator now
rebuilds a completed hook status row from each block. The injection is
also no longer treated as a user turn boundary, so the final turn's
completion retag is unaffected by it.
* fix(hooks): collect PostToolUse hook output and honor its control (#13298)
* fix(hooks): collect PostToolUse hook output and honor its control
tool_result (PostToolUse) hooks ran fire-and-forget with stdout
ignored, so their entire JSON output — contextModification and cancel —
was discarded. Legacy awaited PostToolUse, injected its
contextModification into the conversation, and honored cancel.
- Run tool_result hook commands blocking (same 120s default timeout as
tool_call) in both the hook-config-file layer and the agent-hook
subprocess layer.
- Map their output: cancel stops the run with the hook's error message
as the reason; otherwise context is injected via afterTool
appendContext.
This restores legacy blocking semantics: tool results now wait for
tool_result hooks, but only in sessions that have one configured.
Ref: https://linear.app/cline-bot/issue/CLINE-2987
* fix(hooks): bound tool_result hook wait and isolate cancel reason
Address review findings:
- The agent-hook subprocess layer forwarded an unset timeoutMs
unchanged, so a tool hook command that never exits would block the
agent indefinitely. Default both tool_call and tool_result to the
120s bound the hook-config-file layer already used.
- A cancelling hook's error message was folded into the same context
field as other hooks' injectable context, so merging controls could
leak unrelated hook context into the cancellation reason. Carry it as
a separate cancelReason, and surface it as the stop reason for
beforeTool cancels too.
* fix(hooks): prefer errorMessage as a cancelling hook's stop reason
When a cancelling hook returns both contextModification and
errorMessage, the context-first parse precedence made the injectable
context the cancel reason and discarded the actual error. Parse the two
fields separately: errorMessage wins as the cancel reason (matching
legacy), and a lone errorMessage still folds into injectable context
for non-cancelling hooks as before.
* fix(vscode): honor PostToolUse hook cancel and contextModification
The adapter awaited PostToolUse hooks but discarded their output
entirely. Map cancel to a stop control (with errorMessage as the
reason) and contextModification into the runtime appendContext channel,
matching the PreToolUse mapping and legacy semantics.
* fix(hooks): whitespace-only errorMessage no longer suppresses the cancel reason
A cancelling hook returning meaningful context alongside a blank
errorMessage lost both: the parsers selected the whitespace as the
reason and the result mappers trimmed it away. Require a non-blank
errorMessage before it wins, so context serves as the fallback reason.
Apply the same fallback in the extension adapter's stop mapping.
* fix(core): stop Windows CI worker crashes from the agenda spec watcher (#13428)
* fix(core): watch agenda task specs via the resolved long path
fs.watch on a path with 8.3 short components (e.g. C:\Users\RUNNER~1
temp dirs) trips a libuv assertion in fs-event.c on Windows and aborts
the whole process. Since the agenda task manager landed, every hub
server test spins up its spec watcher on such a path on hosted Windows
runners, killing the vitest worker and failing the sdk-test Windows job
on every branch. Resolve the specs dir with realpathSync.native before
watching so libuv only ever sees the long form.
* test(ui): stub ResizeObserver for @pierre/diffs in tool-diff tests
jsdom does not implement ResizeObserver, so every ToolFileDiff render
logged a ReferenceError from @pierre/diffs to stderr. Tests still
passed; this just silences the noise the same way the constructable
stylesheet shim does.
* fix(core): skip the agenda spec watcher when the dir does not resolve
Falling back to the raw path on realpath failure would reintroduce the
Windows short-path abort; log and go without the watcher instead.
* fix(vscode): honor the classic truncation range when migrating legacy tasks (#13419)
Classic Cline truncated long conversations by omitting an index range of
api_conversation_history from every API request (keep the first
user-assistant pair, drop everything through the range end, strip
orphaned tool_results from the first kept message). The range was
persisted on the history item while the full history stayed on disk.
legacyApiHistoryToSdkMessages ignored conversationHistoryDeletedRange
and converted the entire file, so resuming a migrated long task handed
the SDK an untruncated working context that could exceed the model's
context window by millions of tokens - every request failed with
'prompt is too long' and every compaction restarted from the full
history (#12996, confirmed by the reporter: the task was migrated from
an older version and broke after a restart, with each compaction
starting from ~3M tokens).
The migration now replays exactly what the classic extension sent:
slice out the deleted range and drop orphaned tool_results, mirroring
ContextManager.getTruncatedMessages (see origin/main). Malformed ranges
fall back to the full history (previous behavior).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): show the diff edit view for multi-line edits in CRLF files (#13417)
The edit preview computed proposed content with an exact old_text match, but
the SDK executor normalizes old/new text to the file's own line endings before
matching (#12305) - reads strip CR, so models emit LF-only text even for CRLF
files. Any multi-line old_text in a CRLF file therefore failed the preview's
match: the diff edit view silently never opened while the executor applied the
edit. Single-line edits (no line break in old_text) were unaffected, which is
why the diff view appeared to trigger inconsistently.
Mirror the executor's EOL normalization (and its literal $-sequence insertion)
in the preview computation.
Fixes#13296
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): report truthful session status so desktop checkpoint restore stops wedging (#13418)
* fix(core): keep hub session status truthful across queue-drained turns
Queue-drained turns settle only through the event stream, but the hub
runtime host mistranslated their lifecycle in two ways:
- session.updated events carrying only a snapshot (persistence updates)
defaulted the projected status to "running". When one trailed the
final idle update after a turn, clients that track busy state from
status events (the desktop sidecar's workspace restore gate) stayed
busy forever. Use the snapshot's real status and emit nothing when
neither source reports one.
- the per-run agent.done dedup was only reset by run.started, which the
daemon-side queue drain never publishes, so a drained turn's done was
swallowed as a duplicate of the previous turn's. Reset the dedup on
session.pending_prompt_submitted, and suppress stale run.completed
events that land inside a drained turn's window so they can neither
emit a phantom done nor consume the drained turn's dedup slot.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(desktop): cover restore unlock after an event-settled queued turn
Exports the sidecar's core-session event handler so the queued-turn
lifecycle (busy via status events, cleared by the done agent event,
restore allowed afterwards) is testable end-to-end.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): start interactive sessions without a prompt as idle
The runtime host reported every new session as "running" until its
first turn ended. Interactive hosts (the desktop app) start sessions
with no prompt and dispatch turns through separate send calls, so a
created-but-never-prompted session stayed "running" forever — wedging
clients that gate workspace operations (checkpoint restore, message
edit) on active turns.
Interactive no-prompt starts now begin idle, start emits the session's
actual status (resumed sessions no longer masquerade as running), and
markTurn* transitions keep tracking in-memory status for lazily
persisted sessions so the first turn still reports running -> idle.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style: format hub-runtime-host test filter
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor: drop the drained-turn done bookkeeping, keep the minimal fix
The stuck restore is fully explained by the two status defects (fabricated
"running" from snapshot-only session.updated events, and never-prompted
interactive sessions reporting "running"). The done-dedup machinery for
queue-drained turns addressed a separate cosmetic gap (queued turns emit no
chat_done, pre-existing) and required fragile run-window heuristics, so it
is removed to keep this change reviewable. Sidecar test now settles the
queued turn through the status event, matching the shipped mechanism.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* docs(sdk): document the truthful session-status contract
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(deps): update Langfuse packages and bump app versions (#13443)
* chore(deps): update Langfuse packages and bump app versions
Update @langfuse/otel to v5.10.1 and add @langfuse/vercel-ai-sdk v5.9.1 for improved observability with Vercel AI SDK.
Bump versions for @cline/code to 0.0.14 and @cline/ui to 0.2.0-next.6, updated via bun.lock.
Other Changes:
Added optional userId to AgentRuntimeConfig.
Propagated userId, sessionId, conversationId, runId, iteration, provider, and model context into AI SDK telemetry.
Added AI SDK 7 runtimeContext with explicit includeRuntimeContext.
Added stable OTEL_SERVICE_NAME=cline-sdk.
Added runtime metadata assertions in agent tests.
* add taskId
* Revert "add taskId"
This reverts commit f20d31d96d.
* docs: simplify Open Cline step in installing guide (#13405)
* docs: remove duplicate GLM-5.3 rows in ClinePass tables (#13449)
Co-authored-by: cline-cloud[bot] <276134852+cline-cloud[bot]@users.noreply.github.com>
* chore(sdk): release v0.0.76
* chore(cli): release v3.0.56
* docs(cli): scope the v3.0.56 release notes to CLI-visible changes
* feat(desktop): interactive welcome hero graphic (#13399)
* feat(desktop): add interactive welcome hero
* feat(desktop): support composable welcome hero variants
* feat(desktop): reskin first-run onboarding (#13441)
* refactor: centralize client tool availability (#13451)
* chore(sdk): release v0.0.77
* docs(cli): drop the tasks tool from the v3.0.56 notes, it is desktop-only
* chore(vscode): prepare 4.1.11 release
* chore(desktop): release v0.0.15
* fix(llms): restore pinned media models atop the regenerated catalog
The desktop-experimental media/voice models were hand-pinned into
catalog.generated.ts (17e8c0cbc9) rather than produced by the generator,
so taking main's regeneration dropped every audio/video/TTS/realtime
entry the beta voice features select from. Re-add the 105 pinned media
models on top of main's fresh snapshot. Follow-up: move the pinned set
into a generator overlay so regeneration stops erasing it.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: JasmineLCY <38378321+JasmineLCY@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
Co-authored-by: Max <maxpaulus43@gmail.com>
Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bee <abeatrix@users.noreply.github.com>
Co-authored-by: Renee Huang <100229782+reneehuang1@users.noreply.github.com>
Co-authored-by: Ara <arafat.da.khan@gmail.com>
Co-authored-by: Haley Park <haleypark.design@gmail.com>
Co-authored-by: cline-cloud[bot] <276134852+cline-cloud[bot]@users.noreply.github.com>
* fix(vscode): continue the surviving session on resume instead of rebuilding with the original task text (#13175)
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilt the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
The preserved conversation history is the source of truth on resume, so
the fallback prompt now just asks the model to reassess the history and
continue, matching the legacy resume prompt which also never resent the
original task. User-typed text still takes precedence when provided.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): continue the surviving idle session on follow-ups instead of rebuilding
Stopping a turn keeps the session alive, but every idle follow-up (bare
Resume after Stop, and typed follow-ups after a completed turn) tore
that session down and rebuilt it from persisted task history before
sending. Continue the matching idle session in place instead, the same
way the CLI reuses the live session after an abort. Rebuilding from
history now only happens when no live session matches the displayed
task (task opened from history, extension host reload).
A bare resume still needs a prompt to start a turn, so it sends the
neutral [TASK RESUMPTION] prompt (shared with the rebuild fallback and
hidden from the transcript); user-typed content is echoed and sent
as-is. If the send lands while the abort is still settling, the runtime
auto-queues it and drains once the abort completes.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): consolidate follow-up send paths in SdkFollowupCoordinator
Now that idle follow-ups continue the live session in place, the
two-mode sendToActiveSession helper was redundant: its non-queued branch
duplicated continueIdleSession minus the bare-resume prompt. Split it
into a single-purpose queueToActiveSession and fold the idle no-task
send into continueIdleSession, flattening askResponse's decision tree
to: queue onto a running turn, continue a matching live idle session,
rebuild from history, or abandon.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): reuse the existing neutral resumption prompt for bare resumes
Drop the newly invented long resumption wording in favor of the phrase
that already existed as the no-history fallback and that the transcript
hiding logic and test fixtures recognize: '[TASK RESUMPTION] Please
continue where you left off.' The net change to resumeSessionFromTask
against main is now just deleting the branch that resubmitted
historyItem.task as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilds the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
Bare resumes now always use the neutral prompt that already existed as
the no-history fallback; user-typed text still takes precedence. This
matches the legacy resume prompt (responses.taskResumption), which only
ever included user-supplied text as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): hide synthetic prompts from the queued-prompt echo
A send that races a settling abort is auto-queued by the runtime, so a
bare Resume can reach the pending_prompt_submitted echo carrying the
synthetic [TASK RESUMPTION] prompt. Echoing it leaked model-facing text
as a visible user bubble and shifted the visible-user-message ordinals
that edit/regenerate mapping relies on. Filter synthetic prompts with
isSyntheticUserPrompt, keeping user attachments visible (matching
isSyntheticSdkUserMessage semantics).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): preserve LiteLLM input token limits (#13293)
* fix(vscode): preserve LiteLLM input token limits
* fix(vscode): prefer live LiteLLM model metadata
* fix(vscode): generalize private catalog metadata
* test(vscode): preserve llms exports in vscode lm mock
* fix(vscode): point provider signup URLs at their API key pages (#13337)
* fix(vscode): point Mistral signup URL at the general API keys console
The Mistral provider's signup link led to the Codestral console, which
issues Codestral-scoped keys that fail with 401 on api.mistral.ai — the
endpoint the provider actually calls. Point it at the general API keys
page instead.
Fixes#13288
* fix(vscode): deep-link DeepSeek and Fireworks signup URLs to their API key pages
Both pointed at marketing homepages; link straight to the key-creation
pages instead, matching the rest of the registry and the desktop app's
provider-key-urls map.
* fix(ci): always build the legacy bundle from the legacy-extension branch (#13349)
The combined-VSIX workflow took legacy-ref as a free-form dispatch input
with no publish-time validation (next-ref has one: publish requires main).
Any typed ref — a PR merge ref, an unprotected branch — would be built
into the published VSIX by the environment-less build job, and the publish
environment approver only ever sees an opaque prebuilt artifact, so the
approval protected the marketplace PAT but not the shipped bytes.
Remove the input entirely and hardcode the protected legacy-extension
branch, which makes that branch's protection rules load-bearing for
releases. The tested-sha pinning between test-legacy and build is
unchanged. publish-extension skill dispatch command updated to match.
* fix(ci): lock the legacy publish workflow to the legacy-extension branch (#13350)
The branch dispatch input was a free-form string with no validation. Both
jobs checked it out and ran full npm lifecycle scripts from it: the publish
job next to VSCE_PAT/OVSX_PAT (and npm run publish:marketplace executes a
script from that same ref with the PATs in env), and the test job with NO
environment approval at all while inheriting the workflow-level
contents/packages/checks/pull-requests write grants. A dispatch pointing at
e.g. refs/pull/N/head would run outside-contributor code with the
marketplace keys behind one approval, or with a repo-write token behind
none.
Remove the input and hardcode the protected legacy-extension branch, drop
the workflow-level permissions to contents: read, and elevate only the
publish job to contents: write (tag push + GitHub release). The branch
input's default was legacy-extension, so normal publishes are unchanged.
publish-extension skill dispatch command updated to match.
* fix(vscode): SDK remote-config parity — refresh coordination, session gating, and fail-closed opt-out (#13226)
* feat(desktop): native notifications (#13166)
* feat(desktop): native notifications
* macos target
* fix(desktop): isolate macOS dev app identity
* fix(desktop): address notification review feedback
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched (#13310)
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched
Toggling an auto-approve setting while a task is open writes
autoApprovalSettings into the StateManager's task-settings overlay
(updateAutoApprovalSettings -> setTaskSettings). The SDK controller never
cleared that overlay on clearTask/showTaskWithId (the legacy controller
did), so after New Task the stale overlay kept shadowing global settings
in getGlobalSettingsKey(): toggle RPCs were accepted into global state,
but every posted state still carried the overlay's old version, which the
webview rejects as not newer - the auto-approve checkboxes froze forever.
Restore legacy parity in SdkTaskControlCoordinator: drop the overlay
(persisting pending writes first) in clearTask() and before installing a
different task's proxy in showTaskWithId().
Fixes#13260
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* changeset
* test(vscode): add end-to-end regression test for auto-approve freeze after New Task
Wires the real StateManager, the real updateAutoApprovalSettings handler,
and the real SdkTaskControlCoordinator.clearTask() together with the
webview's version gate modeled on ExtensionStateContext, pinning the
end-to-end invariant behind #13260: checkbox toggles must keep reaching
the webview after a mid-task toggle followed by New Task. Verified the
test fails when the clearTaskSettings() call is removed from clearTask().
* fix implicit any in regression test
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): show provider web-search support under the settings toggle (#13328)
* feat(desktop): show provider web-search support under the settings toggle
The global Web search toggle silently does nothing unless the session's
provider offers native web search, which made the setting read as if it
worked with any provider. The desktop General settings row now explains
that only providers with built-in web search honor it, and shows a live
status line: which connected providers are ready to use it (no extra
setup needed), or an amber warning with a link to the Models section
when none of them support it.
Support is resolved in the webview via a new providerOffersModelTool
helper in @cline/llms (browser export), sharing the same builtin-manifest
source of truth as the runtime's supportsModelTool attachment check.
* fix(desktop): address review — refetch web-search status on catalog invalidation, clarify per-model support
Greptile P2: the one-time catalog fetch could race an in-flight provider
save and show stale status; the row now refetches when the provider
catalog cache is invalidated (fired after saves complete).
Greptile P1: the ready line implied every model on the provider works;
Vertex excludes Claude routes, so the copy now scopes the promise to
models that support it.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat(ui/desktop): collapse finished runs into a work summary and remove hover-state dead space (#13315)
* feat(ui): add WorkActivity collapsed-run summary and float message actions as a pill
WorkActivity/WorkActivityTrigger/WorkActivityContent fold a finished agent
run's working rows (tool calls, thinking traces, narration) behind a single
"Worked for 4m 12s · 14 tool calls" disclosure built on the shared animated
disclosure primitives, with formatWorkActivityLabel/formatWorkDuration
exported for consumers.
Message hover actions no longer rely on the transcript reserving blank space
below each message: the action row is now a self-backed pill (border,
blurred background, shadow) that floats over whatever follows, so
conversations can pack rows tightly without hover chrome colliding with the
next message.
* feat(desktop): collapse finished runs into a work summary and tighten chat spacing
collapseCompletedWork post-processes the grouped transcript: once a run ends
on assistant text with no further tool calls, its working rows fold into one
expandable WorkActivity row while the final answer stays visible. Runs are
delimited by user messages; the trailing run only collapses when the session
has stopped running and actually produced an answer, so live streams and
cancelled/failed tails keep their rows. Assistant messages carrying images
or media are treated as deliverables and never collapse.
The conversation list gap drops from gap-8 to gap-4 now that hover actions
are self-backed pills that need no reserved space, and user messages add
their own top margin so turn boundaries stay visually distinct.
* refactor(ui/desktop): work summary label wording, flat expansion, stable in-run rhythm
Feedback round on #13315:
- Label reads "Worked for 4m 12s and made 14 tool calls" instead of joining
with a dot; without a duration it falls back to "Made N tool calls".
- Expanded work rows render at transcript level — no rail or extra indent —
since tool rows and thinking traces already carry their own nesting when
expanded. The work content keeps the tight working-row rhythm.
- Live working rows (thinking traces + tool calls) now group into a 'run'
render item with the same tight 0.25rem rhythm, so there is no oversized
gap under a "Thought for Ns" row and every row keeps its exact position
when the finished run folds into the work summary. A trailing
answer-in-progress stays outside the group at transcript level, and pure
prose spans keep normal spacing.
- The transient "Thinking..." indicator moves inside the transcript column
and mirrors a trigger row's geometry, so the first real row replaces it in
place with no jump.
* style(ui/desktop): hover-pill metrics, right-pointing work chevron, scroll and spacing fixes
Another feedback round on #13315:
- Hover action pill: +2px internal padding, a trailing inset after the
timestamp (it sat flush against the pill border), and more clearance
between the message content and the pill (2px -> 6px; the hover bridge
grows to match).
- The work summary chevron points right while collapsed and continues
counterclockwise to point up when expanded.
- Conversation bottom padding drops pb-20 -> pb-8: the composer sits below
the scroller, so the padding only needs to clear a pinned action pill.
- Sending a message scrolls back to the bottom even if the reader had
scrolled up (new AutoScrollOnSend on the user-message count, which ignores
optimistic-bubble re-keying; @cline/ui now exports useConversation for
this).
- An assistant answer directly under its run's working rows pulls itself
0.5rem closer than the full transcript gap.
* style(desktop): leave a visible gap between a pinned action pill and the composer
pb-8 exactly matched the pill's ~40px footprint, so the last row's hover
actions sat flush against the composer top; pb-12 restores ~8px of daylight.
* style(desktop): widen the gap between the pinned action pill and the composer to ~24px
pb-12 left only ~8px of daylight under the pill; pb-16 reads comfortable
without reverting to pb-20's dead space.
* fix(desktop): keep the thinking indicator at the working-row offset mid-run
The indicator matched a trigger row's geometry but sat a full transcript gap
(1rem) below the last working row, while the tool/thinking row replacing it
joins the tight run group at 0.25rem — a visible upward jump. When the last
transcript item is working rows (or streamed assistant output), the
indicator now pulls up to the same tight offset; only at the start of a run,
under the user message, does it keep the normal gap.
* style(ui): calm the hover actions surface per team feedback
Borderless rectangle instead of the bordered pill: radius drops to
var(--radius), the side padding goes entirely (the icon buttons carry their
own hit areas), and the vertical padding halves. Blurred background and
shadow stay so it remains legible over following content.
* feat(ui/desktop): full-band hover reveal and iOS-style disclosure easing
The hover actions only appeared while the pointer was inside the message
box itself. The invisible bridge under each message now spans the full
height of the band the floating actions occupy (full row width), so
hovering anywhere in that strip reveals them. Sibling row types
(.cline-chat-tool, .cline-chat-work, and the desktop's run/tool groups)
become position: relative so they paint above the bridge — their own
content keeps its hover and clicks, and the bridge only wins in the band's
genuinely empty space.
All expandable rows (work summary, tool panels, thinking) open and close on
a 240ms symmetric ease-in-out cubic-bezier instead of the 60ms snap, with
chevron rotation on the same curve. Reduced-motion still disables both.
* revert(ui/desktop): drop the full-band hover reveal; quicken disclosure easing to 180ms
The full-band hover bridge (and the position: relative changes that made it
safe) is reverted per feedback — back to the narrow bridge that only spans
the gap under the message. The iOS-style ease-in-out on disclosures stays
but speeds up from 240ms to 180ms.
* fix(ui): recover live tool diffs that mount as a blank pierre skeleton
Live-streamed edit rows could show an empty diff for the whole run, with the
diff only appearing after the collapsed work row was expanded (fresh mount).
Root cause, confirmed by driving a live session and inspecting the element:
React StrictMode double-invokes @pierre/diffs' ref callback; the first
instance's async highlight work aborts on its immediate cleanup, and the
second instance adopts the abandoned half-rendered shadow tree as if it were
complete prerendered output — zero height, no code, no theme stylesheet,
permanently. A rendered diff always carries style[data-theme-css] in its
shadow root, so ToolFileDiff now checks for it shortly after mount and
remounts FileDiff (bounded attempts) when missing; the fresh host element
takes the normal render path and recovers within ~400ms. Verified live: the
diff now renders during the run.
* fix(desktop): keep interrupted runs expanded even with partial trailing text
The trailing-run collapse gated on 'ended with assistant text', which
misread a Stop that landed mid-answer as a finished run and folded the tool
calls the user wants to inspect. The gate is now the terminal status itself:
only completed (or restored-idle) sessions collapse the trailing run;
cancelled/failed/error tails keep their rows regardless of partial text.
(Greptile P1 on #13315 — matches the PR's stated rule.)
* feat(ui): share the markdown pipeline, chat polish, and ThinkingBlock across products (#13323)
* feat(ui): share the markdown pipeline, chat polish CSS, and ThinkingBlock
The desktop app and the cloud dashboard both consume @cline/ui yet rendered
assistant output differently, because Markdown policy and the thinking-trace
row lived app-side. This moves the shareable parts into the package:
- components/markdown (new export): the lazy Shiki code highlighter (GitHub
light/dark, pinned language set) and agentMarkdownControls — the standard
Streamdown configuration. streamdown/shiki/@shikijs/* become optional peer
dependencies, mirroring @pierre/diffs.
- components/markdown.css: the desktop's chat polish moves in — chat-scale
headings, outside list markers, single quiet code blocks with a
hover-revealed copy control, table cards. Kept unlayered so it beats
Streamdown's layered Tailwind utilities without !important.
- ThinkingBlock + formatThoughtLabel in agent-chat: the standard thinking
row (brain icon, Thinking/Thought-for-Ns label, streaming shimmer, rail
presentation, capped scrollable body). The shimmer and the
reasoning-hover-suppression rule move into agent-chat.css; triggers gain
the color transition the desktop applied locally.
Version bumps to 0.2.0-next.5 for the dashboard to pick up.
* refactor(desktop): consume shared markdown and thinking primitives from @cline/ui
The local Shiki highlighter, Streamdown controls, chat markdown polish CSS,
streaming-title shimmer, and reasoning hover-suppression rule are deleted in
favor of the @cline/ui versions (the highlighter test moves to the package's
suite). ReasoningBlock becomes a thin wrapper that hands MemoizedMarkdown to
the shared ThinkingBlock, and formatThoughtLabel re-exports from the package
so grouping code and tests keep their import path.
globals.css now imports @cline/ui/components/markdown.css (unlayered, so the
polish keeps beating Streamdown's layered utilities); the app keeps only what
is genuinely app-specific: link/image policy in markdown.tsx, selectability
rules, accent palettes, and the view-enter transition.
* style(ui/desktop): make thinking-trace prose legible
Thinking body text rendered too faint: plain muted-foreground plus the
desktop's font-thin weight. The shared thinking content now leans 75% of the
way back toward the body text color (still slightly de-emphasized), and the
desktop drops the thin font weight.
* ci(ui-publish): build @cline/shared before ui typecheck (#13354)
@cline/ui's generated-media imports @cline/shared/browser, which resolves to
shared's dist output. The build-shared step sat after typecheck/test/build,
so the first ui-publish dispatch since #13025 failed at Typecheck UI with
TS2307. Move the step to right after install.
* fix: run_commands object form without args routes through the shell instead of failing with ENOENT (#13336)
* fix: run_commands object form without args routes through the shell
The structured { command, args? } form of run_commands was always spawned
directly with shell: false. When a model emitted a full command line in
command with no args (e.g. { command: "echo hello" }), spawn failed with
ENOENT for any command containing a space, breaking command execution for
the whole session.
Direct exec now only applies when a non-empty args list is provided; the
object form without args is routed through getShellInvocation like the
string form. Schema descriptions are tightened so models put arguments in
args instead of embedding them in command.
Fixes#13279
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: trim structured-command schema descriptions
The union schema is only used for lenient validation of input the model
already sent; its descriptions never reach a model prompt. Keep them
short instead of restating executor behavior.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: simplify direct-exec comment in shell executor
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert: keep original structured-command schema description
The description never reaches a model prompt and the executor now handles
both shapes, so the wording change was cosmetic noise.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: gate direct exec on args key presence, not array length
Review feedback: an explicit empty args array is intentionally structured
input and stays direct exec; only an object with no args key is treated
as a full shell command line. Matches the key-presence rule already used
by the VS Code host's formatCommandForTerminal. Also replaces the
empty-args shell test (which was PowerShell-incompatible) with a test
pinning the direct-exec contract.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: normalize Gemini custom base URLs for legacy host-root values (#13329)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* docs: add GLM-5.3 to ClinePass models and reference pricing (#13357)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): stream run command output (#13179)
* feat(desktop): stream run command output
* fix(sdk): clean up detached command logs
* fix(sdk): reap detached logs after hub restarts
* fix(sdk): preserve live detached command logs
* fix(desktop): harden live command progress
* fix(sdk): recover detached logs for local hosts
* fix(desktop): reconcile command output tool rows
* fix(sdk): retain logs for surviving commands
* fix(core): prevent PID reuse from retaining detached logs
* fix(core): preserve detached logs on probe failures
* fix(core): retain detached logs during probe outages
* fix(desktop): resolve leftover merge conflict in messages projection test
Combine both sides of the assertion: main's incremented per-block
createdAt projection and this branch's toolCallId/hookEventName meta.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): make TUI dialog colors follow theme changes live (#13355)
* fix(cli): make TUI dialog colors follow theme changes live
Dialog content previously read the static palette constant, so open
dialogs (including the theme picker itself) kept the default dark-blue
accents while scrolling through theme previews. Add getDialogPalette /
useDialogPalette, which resolve dialog colors from the active theme's
dialog accents and re-render on every theme change, and migrate all
dialog-rendered components to it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(cli): derive dialog panel background from the active theme
Dark themes now lift their own background one OKLAB step for the dialog
surface, so panels keep the theme's hue instead of the library's fixed
#262626. DialogThemeSync pushes the surface into the dialog container
for new dialogs and repaints open panels, so the surface also follows
live theme previews. Light themes keep the neutral dark panel to match
the dark accent fallback and the light-on-dark dialog text.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: skill slash commands load via the skills tool instead of expanding into the user message (#13327)
* fix(desktop): show typed slash command instead of expanded skill markdown
The sidecar expands /skill and /workflow tokens into their instructions
before dispatching, so the runtime's persisted transcript only contains the
expanded text. After a turn (and when reopening a session) the webview
re-hydrates from that history and rendered the whole SKILL.md body as the
user's message; queue events echoing the expanded prompt could also add a
second user bubble, and fresh sessions were titled with the markdown's first
line. The CLI never shows this because its TUI keeps the typed text in its
own transcript and only sends the expanded prompt to the model.
Mirror that separation inside the desktop sidecar's display boundaries:
- history projection (readSessionMessages) inverts user text that starts
with a configured command's instructions back to '/name remainder',
which also repairs sessions recorded before this fix
- queue snapshots and chat_queued_prompt_start events echo the typed
prompt recorded at expansion time, so the webview's optimistic-bubble
re-key matches again
- an untitled session sent an expanded prompt gets titled from the typed
command instead of the instructions' first line
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't overwrite a mid-turn rename with the typed-command title
The untitled check ran before dispatch, so renaming a fresh slash-command
session while its first turn was running got clobbered by the post-turn
typed-command title. Re-check at write time and only replace a missing title
or the one the runtime auto-derived from the expanded prompt.
Also documents the inherent prefix-inversion ambiguity flagged in review:
text hand-typed with a command's exact instructions persists byte-identically
to that command's expansion, so stored history alone cannot distinguish them.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop expanding skill commands; let the skills tool load them
Pasting the skill body into the prompt is why the transcript could ever show
it: the desktop webview re-hydrates from the runtime's persisted history, so
whatever the sidecar splices into the user message renders as if the user
typed it. The runtime already registers the skills tool, whose description
requires the model to invoke it whenever the user references a slash command
— so send the typed /skill text through and let the tool deliver the
instructions as a tool result (previously they arrived twice: pasted and via
the tool). The persisted user message, session title, and queue entries are
then simply the typed command, which deletes the typed-prompt registry, the
queue event/snapshot rewriting, and the title machinery from the previous
approach.
Workflows are not served by the skills tool and keep textual expansion, so
the read-time display inverter stays: it collapses expanded workflow prompts
— and skill prompts persisted before this change — back to the typed
/command in the history projection.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): option to keep skill slash commands typed for the skills tool
resolveRuntimeSlashCommandFromWatcher (and the hub snapshot proxy) accept
expandSkillCommands: hosts whose sessions register the skills tool pass
false so the typed /skill goes through and the model loads the instructions
as a tool result, keeping the persisted transcript as what the user typed.
Workflows always expand — the tool does not serve them. isSkillsToolAvailable
exposes the catalog check hosts use to decide (yolo preset and the skills
tool toggle leave textual expansion as the only delivery path).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): skill slash commands load via the skills tool instead of expanding
The TUI user-command wrap and buildUserInputMessage now keep a typed /skill
as-is when the session's mode/toggles register the skills tool, matching the
desktop app; workflows keep expanding, and yolo (zen) keeps expanding skills
because its preset has no skills tool. This also fixes CLI resume/history
surfaces showing the skill body: the persisted user message is now the typed
command.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): keep configured skill slash commands typed for the skills tool
expandSlashCommands no longer splices a configured skill's instructions into
the model text; the SDK session's skills tool delivers them as a tool result
(previously they arrived twice). Builtin pseudo-skills like /deep-planning
are not served by that tool and keep expanding, as do workflows.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): use the shared skill-expansion option in the sidecar
Replaces the sidecar's workflow-detection dance with core's
expandSkillCommands option and gates on isSkillsToolAvailable, restoring
textual expansion where the tool is missing (yolo mode or the skills tool
toggle) — a gap in the previous desktop-only change.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): drop the display inverter for expanded transcripts
Accepted trade-off to keep the change minimal: sessions recorded before
skills switched to the skills tool, workflow sends (deprecated), and
yolo-mode skill sends persist expanded instructions and now render that text
as-is instead of being collapsed back to the typed /command at projection
time.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Use fixed selection chevron in account dialog to match other dialogs (#13364)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): align system prompt with session mode (#13361)
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(desktop): finalize queued turns on chat_done with canonical history reconcile (#13330)
Turns that settle through the event stream (queued prompts, including the
first prompt of a fresh session) resolve their send() RPC early, so nothing
cleared the streaming shimmer or reconciled live-streamed content against
the persisted transcript at turn end. A turn whose deltas were incomplete
stayed visually streaming forever and only healed when a later non-queued
send rehydrated history.
chat_done (and chat_session_ended / the queue-drain double check) now clears
the active assistant streaming id and schedules a short-delayed
read_session_messages + applyCanonicalHistory, guarded by turn epoch,
session id, and in-flight send submissions so it never clobbers a newer
turn or duplicates the blocking send path's own finalization.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(desktop): release v0.0.14
* fix(clients): filter non-chat models from chat pickers (#13317)
* fix(clients): filter non-chat models from chat pickers
* fix(clients): align chat model eligibility
* fix(desktop): strip user_input envelope when copying a user message (#13369)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bee <abeatrix@users.noreply.github.com>
* docs: show DeepSeek V4 peak and off-peak pricing (#13312)
* docs: update DeepSeek V4 average pricing
* docs: show DeepSeek peak and off-peak pricing
* docs: add GLM-5.3 reference pricing (same as GLM-5.2)
* docs: add GLM-5.3 to ClinePass models table
* fix(llms): display billed gateway cost (#13385)
* fix(shared): run PowerShell commands with fail-fast error semantics (#13358)
* fix(shared): run PowerShell commands with fail-fast error semantics
The run_commands PowerShell wrapper never set $ErrorActionPreference, so
the default 'Continue' applied: a pipeline erroring per item (e.g. a
malformed Where-Object over Get-ChildItem -Recurse) emitted one error
record per enumerated file - tens of thousands of stderr records on
large trees, looking like a hang - and could still resolve as SUCCESS
with exit 0.
Prepend $ErrorActionPreference='Stop'; to the script content executed
by the ScriptBlock so the first error terminates the command with a
non-zero exit and a single error message. Concatenated on the same line
as the user command so error line numbers stay unshifted.
Fixes#13285
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(shared): set the fail-fast preference in the bootstrap scope
Setting $ErrorActionPreference='Stop' by string-prepending it into the
scriptblock source displaced a leading param(...) from its mandatory
first-statement position, so scripts beginning with a param block failed
with CommandNotFoundException. Preference variables are dynamically
scoped, so setting Stop in the -Command bootstrap gives the invoked
scriptblock identical fail-fast semantics while keeping the user script
byte-identical (param works, error positions unshifted) and drops the
doubled-quote escaping.
* docs(shared): document the fail-fast tradeoffs in the PowerShell wrapper
Stop promotes every non-terminating error, not only per-item pipeline
floods: partial-result commands (recursive listings over access-denied
junctions) now stop at their first error, and Windows PowerShell 5.1
turns in-script stderr redirection of succeeding native commands fatal.
State this in the wrapper comment as a deliberate tradeoff, with the
GitHub Actions precedent and the per-command opt-outs.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
* ci: stop over-long changelogs from silently dropping release Slack posts (#12955)
Slack section blocks reject text longer than 3000 characters. The Slack
action logs that rejection as ##[error] but does not fail the step, so an
over-long changelog drops the release announcement while the run stays
green — cline@3.0.50 (3272 chars) published to npm, tagged, and cut a
GitHub release with no Slack post and nothing red to notice.
Every publish workflow pasted the changelog section verbatim into one
section block, so all six were exposed; the SDK, desktop, and extension
sections were only 150-350 chars under the ceiling.
Add a slack_content output alongside content: unchanged when the section
fits, otherwise trimmed on a line boundary with a link to the full
release notes. Only the Slack payload uses it — GitHub release bodies and
the desktop updater manifest still get the whole section.
* ci: tidy workflow cache config and job permissions (#13403)
Publish workflows now always do clean npm installs (no dependency
cache in their test gates), the e2e workflow's cache keys are
exact-match only, and the e2e job drops an id-token permission it
never used.
* Rename desktop app from "Cline Code" to "Cline" (#13401)
* Rename desktop app from Cline Code to Cline
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Format touched Rust test assertions
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): surface provider-executed tool activity as observational events (#13300)
* fix(llms): surface provider-executed tool activity as observational events
Provider-executed tool parts (e.g. every tool the Claude Code CLI runs
inside its own session) were dropped by the model-tool guard added for
web search: only declared model tools were re-emitted, everything else
hit continue with nothing yielded. Those sessions modified the workspace
with no tool activity in runtime events, transcripts, or the UI.
Route all providerExecuted parts onto the observational path instead:
emit execution-tagged tool-call-delta and tool-result events, matched by
tool-call ID for providers that omit the flag on the result half. They
stay out of AgentRuntime's execution/approval loop, and the runtime
already persists them as modelToolActivities and projects them for
display.
The AgentModelEvent tool-result variant widens toolName from
ModelToolName to string to carry the provider's own tool names.
* fix(agents): keep turns that are only provider-executed tool activity
A turn consisting solely of observational tool activity has an empty
assistant content array - the activity lives in message metadata, since
projecting it into content would replay tool_use blocks the model never
gets results for. The empty-content guard threw on such turns, erroring
the run and losing the activity from the transcript. Count model-tool
activity as content for the emptiness check (error finishes still
throw); replay stays safe through the codec's empty-content placeholder.
Also drop the trailing text delta from one gateway test so the tool-only
stream shape stays covered end to end.
* feat: allow agents to create scheduled tasks (#13331)
* feat(core, desktop): add durable todo agenda
* fix(desktop): secure todo approvals and track tool usage
* fix(desktop): clean up failed approval delivery
* fix(desktop): authenticate approval connections
* fix(desktop): cancel approvals on broadcast failure
* fix(desktop): authenticate development approvals
* fix(desktop): harden development approvals
* test(core): make task paths cross-platform
* fix(desktop): serialize approval readiness
* refactor(core): unify todo and schedule tools
* feat(core): distinguish user todos from agent suggestions
* fix(core): hide tasks tool in yolo mode
* fix(core): enforce schedule workspace scope
* fix(core): bind schedule scope to hub connection
* fix(core): establish task scope at hub startup
* fix(core): scope task automation by workspace
* test(core): normalize workspace path expectations
* test(core): serialize Windows CI workers
* fix(core): reject unregistered schedule authority
* fix(desktop): guard task execution commands
* fix(core): avoid polynomial regex in mention parsing
* fix(core): address schedule tool review feedback
* fix(core): bind websocket clients to hub workspace
* fix(core): flatten tasks tool input schema
* fix(core): authorize multi-workspace hub clients
* test(core): type hub transport authority mock
* fix(cli): register a workspace client for remote schedule commands (#13398)
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(desktop): treat ClinePass as OAuth-managed in the chat credential gate (#13404)
* fix(desktop): treat ClinePass as OAuth-managed in chat credential gate
ClinePass shares the Cline account OAuth credentials (its auth handler
stores under the "cline" provider), so the webview never sees a plain
API key for it. The chat pre-flight check only exempted cline/oca/
openai-codex, so switching to ClinePass while signed in via OAuth
blocked with "Missing API key" even though the sidecar resolves the
stored access token fine (which is why the CLI worked).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* style: format helpers.test.ts with biome
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(ui): stack code block lines when streamdown lineNumbers is off (#13412)
streamdown renders each Shiki token line as a bare inline span with no
newline text between non-empty lines, and only applies its block line
class when lineNumbers is on. With lineNumbers off (the desktop app's
config) every multi-line fenced block collapsed into one run-on line.
Make the direct line spans under code-block-body display: block in the
shared markdown.css; empty lines keep their height via their lone "\n"
child under white-space: pre.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): work summary undercounts wall time when pre-tool thinking attaches to the answer (#13413)
* fix(desktop): anchor work summary duration on the answer row, not attached pre-tool reasoning
The collapsed 'Worked for Xs' row undercounted wall time whenever a turn's
assistant message contained thinking + tool_use with no narration text: the
canonical projection emitted the reasoning-only row after the tool row (both
stamped before the tool executed), the webview attached that row to the final
answer, and collapseCompletedWork used the answer's earliest attached
reasoning timestamp as the end anchor - excluding the entire tool execution
(e.g. 'Worked for 5s' for a turn with an 8s command).
- webview: end the work span at the answer row's own timestamp, clamped to
the last collapsed row so a fallback answer bubble with a synthetic early
timestamp cannot shrink the duration either
- sidecar: flush pending thinking before a tool_use row so rehydrated
transcripts keep the live-stream order (thinking before its tool call) and
pre-tool reasoning no longer rides on the next answer
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep interleaved thinking between the tool calls it separates
Address Greptile review: when one assistant message interleaves thinking
between multiple tool_use blocks, each reasoning segment now projects at its
own position (attached to a text row from its own segment when present,
otherwise as its own row) instead of merging into the first reasoning row,
which displayed later thinking before a tool call it actually followed.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): remove settings gear hover state while Account screen is open (#13408)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't show "No sessions found" while session history is still loading (#13414)
* fix(desktop): don't show 'No sessions found' while session history is still loading
Replace the isLoadingHistory flag with hasLoadedHistory, set only once the
backend has actually answered a list_discovered_sessions request. The sidebar
and Sessions view now keep their loading state until that first definitive
response, so the empty-state copy can no longer appear while history is still
being fetched (or while a failed fetch is being retried).
Also retry a failed initial fetch on the 2s event cadence instead of stranding
the UI until the 12s periodic poll, which is what stretched the misleading
empty state to ~10 seconds after a webview reload when the websocket lost the
race with the page load.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop history fast-retry from re-arming after hook unmount
A failed initial fetch that settles after the hook unmounted could schedule a
new retry timer after cleanup had already cleared the refs, leaving the
abandoned hook polling the backend every 2s. Guard scheduleRefresh with a
disposed ref set by the mount effect's cleanup.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix @ file mentions breaking on paths with spaces (#13391)
* Fix @ file mentions breaking on paths with spaces
Quote mentions generated by getFileMentionFromPath (Add to Cline /
Fix / Explain / Improve commands) when the relative path contains
spaces, so the mention regex no longer truncates the path at the
first space. Also quote the path part of workspace-prefixed mentions
(workspace:/path with spaces) inserted from the @ context menu, which
previously bypassed quoting because the value does not start with '/'.
Fixes#13338
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix import ordering in mentions test (biome organize imports)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reduce fix to minimal scope
Revert the webview quoting refactor and extra tests; keep only the
getFileMentionFromPath quoting fix with a single regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Normalize mention paths to posix separators for Windows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix code actions failing with "command not found" on VS Code 1.134 (#13402)
* Fix @ file mentions breaking on paths with spaces
Quote mentions generated by getFileMentionFromPath (Add to Cline /
Fix / Explain / Improve commands) when the relative path contains
spaces, so the mention regex no longer truncates the path at the
first space. Also quote the path part of workspace-prefixed mentions
(workspace:/path with spaces) inserted from the @ context menu, which
previously bypassed quoting because the value does not start with '/'.
Fixes#13338
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix import ordering in mentions test (biome organize imports)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Reduce fix to minimal scope
Revert the webview quoting refactor and extra tests; keep only the
getFileMentionFromPath quoting fix with a single regression test.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Normalize mention paths to posix separators for Windows
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix code actions failing with 'command not found' on VS Code 1.134
Code action commands carried arguments (expandedRange, diagnostics),
which routes them through VS Code's CommandsConverter cache. VS Code
1.134 disposes the cached entries before the clicked action executes,
so every lightbulb action failed with 'Actual command not found,
wanted to execute cline.addToChat'.
Drop the arguments so the command id is passed through directly, and
recover the context in the handler instead: getContextForCommand now
expands an empty selection by 3 surrounding lines (matching the old
provider behavior) and gathers document diagnostics intersecting the
range when none are passed explicitly.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Scope gathered diagnostics to the selection/cursor
Match the old CodeActionContext.diagnostics behavior: only include
diagnostics intersecting the range the action was requested for, not
the surrounding lines the text gets expanded to.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Desktop: unify Plugins, MCP, and Skills into one Plugins hub with a dedicated Marketplace page (#13411)
* Unify desktop plugins, apps, MCP, and skills into one Plugins hub with a Browse directory mode
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Open the marketplace directory as a modal over the Plugins hub instead of swapping the page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Rename directory to Marketplace: Browse Marketplace button, Marketplace modal title with icon, search placeholder
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Fix search input focus ring clipped by the Marketplace modal scroll container
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Address Greptile review: keep selected tag chip visible when its count drops to zero, and remount installed tab when a marketplace install completes after the modal closed
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Track marketplace modal mutation flag in a ref so a close click racing a queued render cannot skip the inventory remount
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Make Marketplace its own settings page under Customizations and restore Channels as a standalone page
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Remove icon from Marketplace page header for consistency with other settings pages
* Notify mounted inventory views when the marketplace invalidates the cache so late install completions refresh the Plugins hub
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop/ui): recommended and free model tiers in the composer model selector (#13410)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK (#13415)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): recommended-feed badges and descriptions in provider settings (#13416)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* feat(desktop): recommended-feed badges and descriptions in provider settings
Review suggestion on #13410: the provider settings page has room for
more model detail than the composer's picker. The cline/cline-pass
provider cards now refresh their model list through
list_provider_models (the catalog snapshot deliberately skips the
recommended-feed overlay so the startup catalog fetch never blocks on
the feed) and render Recommended/Free tier badges plus feed tags (NEW)
next to the model name, with the model description underneath. The
refreshed list also surfaces the live entries instead of the bundled
snapshot.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): scope the settings featured model list to its provider and revision
The fetched featured list was unscoped component state: switching
between cline and cline-pass reused the component instance, so the
previous provider's models stayed visible while the new request was
pending (or forever, when it failed), and the retained copy shadowed
later provider.modelList updates — adding a second custom model
submitted the stale list as the complete configuration and dropped the
first addition.
The fetched list now only applies to the provider and modelList
revision it was fetched for (falling back to the catalog snapshot
otherwise and refetching on membership changes), and add-model submits
the union of the displayed and configured ids so an update can never
silently unconfigure existing entries.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(ui): update packed-Tailwind smoke contract for the picker's max-h-64 (#13421)
The ui-publish smoke check pins a set of Tailwind candidates the packed
sources must emit; #13410 grew the SearchCombobox options list from
max-h-56 to max-h-64, so the publish run failed on the stale candidate.
All other pinned candidates verified against the current sources.
* chore(desktop): cut 0.0.15-beta.1
* chore(desktop): reconcile Cargo.lock with merged dependencies
* feat(desktop): refresh app icons and branding (#13400)
* chore(desktop): restore ai dependency and refresh lockfile
* chore(desktop): align beta cloud copy with the Cline rename
* chore(desktop): merge refreshed app icons (#13400) and dedupe changelog
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: JasmineLCY <38378321+JasmineLCY@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
Co-authored-by: Max <maxpaulus43@gmail.com>
Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bee <abeatrix@users.noreply.github.com>
Co-authored-by: Renee Huang <100229782+reneehuang1@users.noreply.github.com>
Co-authored-by: Ara <arafat.da.khan@gmail.com>
Co-authored-by: Haley Park <haleypark.design@gmail.com>
* fix(vscode): continue the surviving session on resume instead of rebuilding with the original task text (#13175)
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilt the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
The preserved conversation history is the source of truth on resume, so
the fallback prompt now just asks the model to reassess the history and
continue, matching the legacy resume prompt which also never resent the
original task. User-typed text still takes precedence when provided.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): continue the surviving idle session on follow-ups instead of rebuilding
Stopping a turn keeps the session alive, but every idle follow-up (bare
Resume after Stop, and typed follow-ups after a completed turn) tore
that session down and rebuilt it from persisted task history before
sending. Continue the matching idle session in place instead, the same
way the CLI reuses the live session after an abort. Rebuilding from
history now only happens when no live session matches the displayed
task (task opened from history, extension host reload).
A bare resume still needs a prompt to start a turn, so it sends the
neutral [TASK RESUMPTION] prompt (shared with the rebuild fallback and
hidden from the transcript); user-typed content is echoed and sent
as-is. If the send lands while the abort is still settling, the runtime
auto-queues it and drains once the abort completes.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): consolidate follow-up send paths in SdkFollowupCoordinator
Now that idle follow-ups continue the live session in place, the
two-mode sendToActiveSession helper was redundant: its non-queued branch
duplicated continueIdleSession minus the bare-resume prompt. Split it
into a single-purpose queueToActiveSession and fold the idle no-task
send into continueIdleSession, flattening askResponse's decision tree
to: queue onto a running turn, continue a matching live idle session,
rebuild from history, or abandon.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(vscode): reuse the existing neutral resumption prompt for bare resumes
Drop the newly invented long resumption wording in favor of the phrase
that already existed as the no-history fallback and that the transcript
hiding logic and test fixtures recognize: '[TASK RESUMPTION] Please
continue where you left off.' The net change to resumeSessionFromTask
against main is now just deleting the branch that resubmitted
historyItem.task as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): stop resubmitting the original task text on bare resume (#12975)
A bare Resume after Stop rebuilds the session from task history and
injected historyItem.task into the resumption prompt as 'New
instructions from the user'. The model treated the already-completed
original request as fresh instructions and re-executed it (e.g. re-ran
all terminal commands after stopping a queued follow-up turn).
Bare resumes now always use the neutral prompt that already existed as
the no-history fallback; user-typed text still takes precedence. This
matches the legacy resume prompt (responses.taskResumption), which only
ever included user-supplied text as new instructions.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): hide synthetic prompts from the queued-prompt echo
A send that races a settling abort is auto-queued by the runtime, so a
bare Resume can reach the pending_prompt_submitted echo carrying the
synthetic [TASK RESUMPTION] prompt. Echoing it leaked model-facing text
as a visible user bubble and shifted the visible-user-message ordinals
that edit/regenerate mapping relies on. Filter synthetic prompts with
isSyntheticUserPrompt, keeping user attachments visible (matching
isSyntheticSdkUserMessage semantics).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): preserve LiteLLM input token limits (#13293)
* fix(vscode): preserve LiteLLM input token limits
* fix(vscode): prefer live LiteLLM model metadata
* fix(vscode): generalize private catalog metadata
* test(vscode): preserve llms exports in vscode lm mock
* fix(vscode): point provider signup URLs at their API key pages (#13337)
* fix(vscode): point Mistral signup URL at the general API keys console
The Mistral provider's signup link led to the Codestral console, which
issues Codestral-scoped keys that fail with 401 on api.mistral.ai — the
endpoint the provider actually calls. Point it at the general API keys
page instead.
Fixes#13288
* fix(vscode): deep-link DeepSeek and Fireworks signup URLs to their API key pages
Both pointed at marketing homepages; link straight to the key-creation
pages instead, matching the rest of the registry and the desktop app's
provider-key-urls map.
* fix(ci): always build the legacy bundle from the legacy-extension branch (#13349)
The combined-VSIX workflow took legacy-ref as a free-form dispatch input
with no publish-time validation (next-ref has one: publish requires main).
Any typed ref — a PR merge ref, an unprotected branch — would be built
into the published VSIX by the environment-less build job, and the publish
environment approver only ever sees an opaque prebuilt artifact, so the
approval protected the marketplace PAT but not the shipped bytes.
Remove the input entirely and hardcode the protected legacy-extension
branch, which makes that branch's protection rules load-bearing for
releases. The tested-sha pinning between test-legacy and build is
unchanged. publish-extension skill dispatch command updated to match.
* fix(ci): lock the legacy publish workflow to the legacy-extension branch (#13350)
The branch dispatch input was a free-form string with no validation. Both
jobs checked it out and ran full npm lifecycle scripts from it: the publish
job next to VSCE_PAT/OVSX_PAT (and npm run publish:marketplace executes a
script from that same ref with the PATs in env), and the test job with NO
environment approval at all while inheriting the workflow-level
contents/packages/checks/pull-requests write grants. A dispatch pointing at
e.g. refs/pull/N/head would run outside-contributor code with the
marketplace keys behind one approval, or with a repo-write token behind
none.
Remove the input and hardcode the protected legacy-extension branch, drop
the workflow-level permissions to contents: read, and elevate only the
publish job to contents: write (tag push + GitHub release). The branch
input's default was legacy-extension, so normal publishes are unchanged.
publish-extension skill dispatch command updated to match.
* fix(vscode): SDK remote-config parity — refresh coordination, session gating, and fail-closed opt-out (#13226)
* feat(desktop): native notifications (#13166)
* feat(desktop): native notifications
* macos target
* fix(desktop): isolate macOS dev app identity
* fix(desktop): address notification review feedback
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched (#13310)
* fix(vscode): clear task-scoped settings overlay when task view is cleared or switched
Toggling an auto-approve setting while a task is open writes
autoApprovalSettings into the StateManager's task-settings overlay
(updateAutoApprovalSettings -> setTaskSettings). The SDK controller never
cleared that overlay on clearTask/showTaskWithId (the legacy controller
did), so after New Task the stale overlay kept shadowing global settings
in getGlobalSettingsKey(): toggle RPCs were accepted into global state,
but every posted state still carried the overlay's old version, which the
webview rejects as not newer - the auto-approve checkboxes froze forever.
Restore legacy parity in SdkTaskControlCoordinator: drop the overlay
(persisting pending writes first) in clearTask() and before installing a
different task's proxy in showTaskWithId().
Fixes#13260
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* changeset
* test(vscode): add end-to-end regression test for auto-approve freeze after New Task
Wires the real StateManager, the real updateAutoApprovalSettings handler,
and the real SdkTaskControlCoordinator.clearTask() together with the
webview's version gate modeled on ExtensionStateContext, pinning the
end-to-end invariant behind #13260: checkbox toggles must keep reaching
the webview after a mid-task toggle followed by New Task. Verified the
test fails when the clearTaskSettings() call is removed from clearTask().
* fix implicit any in regression test
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): show provider web-search support under the settings toggle (#13328)
* feat(desktop): show provider web-search support under the settings toggle
The global Web search toggle silently does nothing unless the session's
provider offers native web search, which made the setting read as if it
worked with any provider. The desktop General settings row now explains
that only providers with built-in web search honor it, and shows a live
status line: which connected providers are ready to use it (no extra
setup needed), or an amber warning with a link to the Models section
when none of them support it.
Support is resolved in the webview via a new providerOffersModelTool
helper in @cline/llms (browser export), sharing the same builtin-manifest
source of truth as the runtime's supportsModelTool attachment check.
* fix(desktop): address review — refetch web-search status on catalog invalidation, clarify per-model support
Greptile P2: the one-time catalog fetch could race an in-flight provider
save and show stale status; the row now refetches when the provider
catalog cache is invalidated (fired after saves complete).
Greptile P1: the ready line implied every model on the provider works;
Vertex excludes Claude routes, so the copy now scopes the promise to
models that support it.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat(ui/desktop): collapse finished runs into a work summary and remove hover-state dead space (#13315)
* feat(ui): add WorkActivity collapsed-run summary and float message actions as a pill
WorkActivity/WorkActivityTrigger/WorkActivityContent fold a finished agent
run's working rows (tool calls, thinking traces, narration) behind a single
"Worked for 4m 12s · 14 tool calls" disclosure built on the shared animated
disclosure primitives, with formatWorkActivityLabel/formatWorkDuration
exported for consumers.
Message hover actions no longer rely on the transcript reserving blank space
below each message: the action row is now a self-backed pill (border,
blurred background, shadow) that floats over whatever follows, so
conversations can pack rows tightly without hover chrome colliding with the
next message.
* feat(desktop): collapse finished runs into a work summary and tighten chat spacing
collapseCompletedWork post-processes the grouped transcript: once a run ends
on assistant text with no further tool calls, its working rows fold into one
expandable WorkActivity row while the final answer stays visible. Runs are
delimited by user messages; the trailing run only collapses when the session
has stopped running and actually produced an answer, so live streams and
cancelled/failed tails keep their rows. Assistant messages carrying images
or media are treated as deliverables and never collapse.
The conversation list gap drops from gap-8 to gap-4 now that hover actions
are self-backed pills that need no reserved space, and user messages add
their own top margin so turn boundaries stay visually distinct.
* refactor(ui/desktop): work summary label wording, flat expansion, stable in-run rhythm
Feedback round on #13315:
- Label reads "Worked for 4m 12s and made 14 tool calls" instead of joining
with a dot; without a duration it falls back to "Made N tool calls".
- Expanded work rows render at transcript level — no rail or extra indent —
since tool rows and thinking traces already carry their own nesting when
expanded. The work content keeps the tight working-row rhythm.
- Live working rows (thinking traces + tool calls) now group into a 'run'
render item with the same tight 0.25rem rhythm, so there is no oversized
gap under a "Thought for Ns" row and every row keeps its exact position
when the finished run folds into the work summary. A trailing
answer-in-progress stays outside the group at transcript level, and pure
prose spans keep normal spacing.
- The transient "Thinking..." indicator moves inside the transcript column
and mirrors a trigger row's geometry, so the first real row replaces it in
place with no jump.
* style(ui/desktop): hover-pill metrics, right-pointing work chevron, scroll and spacing fixes
Another feedback round on #13315:
- Hover action pill: +2px internal padding, a trailing inset after the
timestamp (it sat flush against the pill border), and more clearance
between the message content and the pill (2px -> 6px; the hover bridge
grows to match).
- The work summary chevron points right while collapsed and continues
counterclockwise to point up when expanded.
- Conversation bottom padding drops pb-20 -> pb-8: the composer sits below
the scroller, so the padding only needs to clear a pinned action pill.
- Sending a message scrolls back to the bottom even if the reader had
scrolled up (new AutoScrollOnSend on the user-message count, which ignores
optimistic-bubble re-keying; @cline/ui now exports useConversation for
this).
- An assistant answer directly under its run's working rows pulls itself
0.5rem closer than the full transcript gap.
* style(desktop): leave a visible gap between a pinned action pill and the composer
pb-8 exactly matched the pill's ~40px footprint, so the last row's hover
actions sat flush against the composer top; pb-12 restores ~8px of daylight.
* style(desktop): widen the gap between the pinned action pill and the composer to ~24px
pb-12 left only ~8px of daylight under the pill; pb-16 reads comfortable
without reverting to pb-20's dead space.
* fix(desktop): keep the thinking indicator at the working-row offset mid-run
The indicator matched a trigger row's geometry but sat a full transcript gap
(1rem) below the last working row, while the tool/thinking row replacing it
joins the tight run group at 0.25rem — a visible upward jump. When the last
transcript item is working rows (or streamed assistant output), the
indicator now pulls up to the same tight offset; only at the start of a run,
under the user message, does it keep the normal gap.
* style(ui): calm the hover actions surface per team feedback
Borderless rectangle instead of the bordered pill: radius drops to
var(--radius), the side padding goes entirely (the icon buttons carry their
own hit areas), and the vertical padding halves. Blurred background and
shadow stay so it remains legible over following content.
* feat(ui/desktop): full-band hover reveal and iOS-style disclosure easing
The hover actions only appeared while the pointer was inside the message
box itself. The invisible bridge under each message now spans the full
height of the band the floating actions occupy (full row width), so
hovering anywhere in that strip reveals them. Sibling row types
(.cline-chat-tool, .cline-chat-work, and the desktop's run/tool groups)
become position: relative so they paint above the bridge — their own
content keeps its hover and clicks, and the bridge only wins in the band's
genuinely empty space.
All expandable rows (work summary, tool panels, thinking) open and close on
a 240ms symmetric ease-in-out cubic-bezier instead of the 60ms snap, with
chevron rotation on the same curve. Reduced-motion still disables both.
* revert(ui/desktop): drop the full-band hover reveal; quicken disclosure easing to 180ms
The full-band hover bridge (and the position: relative changes that made it
safe) is reverted per feedback — back to the narrow bridge that only spans
the gap under the message. The iOS-style ease-in-out on disclosures stays
but speeds up from 240ms to 180ms.
* fix(ui): recover live tool diffs that mount as a blank pierre skeleton
Live-streamed edit rows could show an empty diff for the whole run, with the
diff only appearing after the collapsed work row was expanded (fresh mount).
Root cause, confirmed by driving a live session and inspecting the element:
React StrictMode double-invokes @pierre/diffs' ref callback; the first
instance's async highlight work aborts on its immediate cleanup, and the
second instance adopts the abandoned half-rendered shadow tree as if it were
complete prerendered output — zero height, no code, no theme stylesheet,
permanently. A rendered diff always carries style[data-theme-css] in its
shadow root, so ToolFileDiff now checks for it shortly after mount and
remounts FileDiff (bounded attempts) when missing; the fresh host element
takes the normal render path and recovers within ~400ms. Verified live: the
diff now renders during the run.
* fix(desktop): keep interrupted runs expanded even with partial trailing text
The trailing-run collapse gated on 'ended with assistant text', which
misread a Stop that landed mid-answer as a finished run and folded the tool
calls the user wants to inspect. The gate is now the terminal status itself:
only completed (or restored-idle) sessions collapse the trailing run;
cancelled/failed/error tails keep their rows regardless of partial text.
(Greptile P1 on #13315 — matches the PR's stated rule.)
* feat(ui): share the markdown pipeline, chat polish, and ThinkingBlock across products (#13323)
* feat(ui): share the markdown pipeline, chat polish CSS, and ThinkingBlock
The desktop app and the cloud dashboard both consume @cline/ui yet rendered
assistant output differently, because Markdown policy and the thinking-trace
row lived app-side. This moves the shareable parts into the package:
- components/markdown (new export): the lazy Shiki code highlighter (GitHub
light/dark, pinned language set) and agentMarkdownControls — the standard
Streamdown configuration. streamdown/shiki/@shikijs/* become optional peer
dependencies, mirroring @pierre/diffs.
- components/markdown.css: the desktop's chat polish moves in — chat-scale
headings, outside list markers, single quiet code blocks with a
hover-revealed copy control, table cards. Kept unlayered so it beats
Streamdown's layered Tailwind utilities without !important.
- ThinkingBlock + formatThoughtLabel in agent-chat: the standard thinking
row (brain icon, Thinking/Thought-for-Ns label, streaming shimmer, rail
presentation, capped scrollable body). The shimmer and the
reasoning-hover-suppression rule move into agent-chat.css; triggers gain
the color transition the desktop applied locally.
Version bumps to 0.2.0-next.5 for the dashboard to pick up.
* refactor(desktop): consume shared markdown and thinking primitives from @cline/ui
The local Shiki highlighter, Streamdown controls, chat markdown polish CSS,
streaming-title shimmer, and reasoning hover-suppression rule are deleted in
favor of the @cline/ui versions (the highlighter test moves to the package's
suite). ReasoningBlock becomes a thin wrapper that hands MemoizedMarkdown to
the shared ThinkingBlock, and formatThoughtLabel re-exports from the package
so grouping code and tests keep their import path.
globals.css now imports @cline/ui/components/markdown.css (unlayered, so the
polish keeps beating Streamdown's layered utilities); the app keeps only what
is genuinely app-specific: link/image policy in markdown.tsx, selectability
rules, accent palettes, and the view-enter transition.
* style(ui/desktop): make thinking-trace prose legible
Thinking body text rendered too faint: plain muted-foreground plus the
desktop's font-thin weight. The shared thinking content now leans 75% of the
way back toward the body text color (still slightly de-emphasized), and the
desktop drops the thin font weight.
* ci(ui-publish): build @cline/shared before ui typecheck (#13354)
@cline/ui's generated-media imports @cline/shared/browser, which resolves to
shared's dist output. The build-shared step sat after typecheck/test/build,
so the first ui-publish dispatch since #13025 failed at Typecheck UI with
TS2307. Move the step to right after install.
* fix: run_commands object form without args routes through the shell instead of failing with ENOENT (#13336)
* fix: run_commands object form without args routes through the shell
The structured { command, args? } form of run_commands was always spawned
directly with shell: false. When a model emitted a full command line in
command with no args (e.g. { command: "echo hello" }), spawn failed with
ENOENT for any command containing a space, breaking command execution for
the whole session.
Direct exec now only applies when a non-empty args list is provided; the
object form without args is routed through getShellInvocation like the
string form. Schema descriptions are tightened so models put arguments in
args instead of embedding them in command.
Fixes#13279
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: trim structured-command schema descriptions
The union schema is only used for lenient validation of input the model
already sent; its descriptions never reach a model prompt. Keep them
short instead of restating executor behavior.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: simplify direct-exec comment in shell executor
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* revert: keep original structured-command schema description
The description never reaches a model prompt and the executor now handles
both shapes, so the wording change was cosmetic noise.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: gate direct exec on args key presence, not array length
Review feedback: an explicit empty args array is intentionally structured
input and stays direct exec; only an object with no args key is treated
as a full shell command line. Matches the key-presence rule already used
by the VS Code host's formatCommandForTerminal. Also replaces the
empty-args shell test (which was PowerShell-incompatible) with a test
pinning the direct-exec contract.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: normalize Gemini custom base URLs for legacy host-root values (#13329)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* docs: add GLM-5.3 to ClinePass models and reference pricing (#13357)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): stream run command output (#13179)
* feat(desktop): stream run command output
* fix(sdk): clean up detached command logs
* fix(sdk): reap detached logs after hub restarts
* fix(sdk): preserve live detached command logs
* fix(desktop): harden live command progress
* fix(sdk): recover detached logs for local hosts
* fix(desktop): reconcile command output tool rows
* fix(sdk): retain logs for surviving commands
* fix(core): prevent PID reuse from retaining detached logs
* fix(core): preserve detached logs on probe failures
* fix(core): retain detached logs during probe outages
* fix(desktop): resolve leftover merge conflict in messages projection test
Combine both sides of the assertion: main's incremented per-block
createdAt projection and this branch's toolCallId/hookEventName meta.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): make TUI dialog colors follow theme changes live (#13355)
* fix(cli): make TUI dialog colors follow theme changes live
Dialog content previously read the static palette constant, so open
dialogs (including the theme picker itself) kept the default dark-blue
accents while scrolling through theme previews. Add getDialogPalette /
useDialogPalette, which resolve dialog colors from the active theme's
dialog accents and re-render on every theme change, and migrate all
dialog-rendered components to it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(cli): derive dialog panel background from the active theme
Dark themes now lift their own background one OKLAB step for the dialog
surface, so panels keep the theme's hue instead of the library's fixed
#262626. DialogThemeSync pushes the surface into the dialog container
for new dialogs and repaints open panels, so the surface also follows
live theme previews. Light themes keep the neutral dark panel to match
the dark accent fallback and the light-on-dark dialog text.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: skill slash commands load via the skills tool instead of expanding into the user message (#13327)
* fix(desktop): show typed slash command instead of expanded skill markdown
The sidecar expands /skill and /workflow tokens into their instructions
before dispatching, so the runtime's persisted transcript only contains the
expanded text. After a turn (and when reopening a session) the webview
re-hydrates from that history and rendered the whole SKILL.md body as the
user's message; queue events echoing the expanded prompt could also add a
second user bubble, and fresh sessions were titled with the markdown's first
line. The CLI never shows this because its TUI keeps the typed text in its
own transcript and only sends the expanded prompt to the model.
Mirror that separation inside the desktop sidecar's display boundaries:
- history projection (readSessionMessages) inverts user text that starts
with a configured command's instructions back to '/name remainder',
which also repairs sessions recorded before this fix
- queue snapshots and chat_queued_prompt_start events echo the typed
prompt recorded at expansion time, so the webview's optimistic-bubble
re-key matches again
- an untitled session sent an expanded prompt gets titled from the typed
command instead of the instructions' first line
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): don't overwrite a mid-turn rename with the typed-command title
The untitled check ran before dispatch, so renaming a fresh slash-command
session while its first turn was running got clobbered by the post-turn
typed-command title. Re-check at write time and only replace a missing title
or the one the runtime auto-derived from the expanded prompt.
Also documents the inherent prefix-inversion ambiguity flagged in review:
text hand-typed with a command's exact instructions persists byte-identically
to that command's expansion, so stored history alone cannot distinguish them.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): stop expanding skill commands; let the skills tool load them
Pasting the skill body into the prompt is why the transcript could ever show
it: the desktop webview re-hydrates from the runtime's persisted history, so
whatever the sidecar splices into the user message renders as if the user
typed it. The runtime already registers the skills tool, whose description
requires the model to invoke it whenever the user references a slash command
— so send the typed /skill text through and let the tool deliver the
instructions as a tool result (previously they arrived twice: pasted and via
the tool). The persisted user message, session title, and queue entries are
then simply the typed command, which deletes the typed-prompt registry, the
queue event/snapshot rewriting, and the title machinery from the previous
approach.
Workflows are not served by the skills tool and keep textual expansion, so
the read-time display inverter stays: it collapses expanded workflow prompts
— and skill prompts persisted before this change — back to the typed
/command in the history projection.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): option to keep skill slash commands typed for the skills tool
resolveRuntimeSlashCommandFromWatcher (and the hub snapshot proxy) accept
expandSkillCommands: hosts whose sessions register the skills tool pass
false so the typed /skill goes through and the model loads the instructions
as a tool result, keeping the persisted transcript as what the user typed.
Workflows always expand — the tool does not serve them. isSkillsToolAvailable
exposes the catalog check hosts use to decide (yolo preset and the skills
tool toggle leave textual expansion as the only delivery path).
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): skill slash commands load via the skills tool instead of expanding
The TUI user-command wrap and buildUserInputMessage now keep a typed /skill
as-is when the session's mode/toggles register the skills tool, matching the
desktop app; workflows keep expanding, and yolo (zen) keeps expanding skills
because its preset has no skills tool. This also fixes CLI resume/history
surfaces showing the skill body: the persisted user message is now the typed
command.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): keep configured skill slash commands typed for the skills tool
expandSlashCommands no longer splices a configured skill's instructions into
the model text; the SDK session's skills tool delivers them as a tool result
(previously they arrived twice). Builtin pseudo-skills like /deep-planning
are not served by that tool and keep expanding, as do workflows.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): use the shared skill-expansion option in the sidecar
Replaces the sidecar's workflow-detection dance with core's
expandSkillCommands option and gates on isSkillsToolAvailable, restoring
textual expansion where the tool is missing (yolo mode or the skills tool
toggle) — a gap in the previous desktop-only change.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(desktop): drop the display inverter for expanded transcripts
Accepted trade-off to keep the change minimal: sessions recorded before
skills switched to the skills tool, workflow sends (deprecated), and
yolo-mode skill sends persist expanded instructions and now render that text
as-is instead of being collapsed back to the typed /command at projection
time.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Use fixed selection chevron in account dialog to match other dialogs (#13364)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): align system prompt with session mode (#13361)
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(desktop): finalize queued turns on chat_done with canonical history reconcile (#13330)
Turns that settle through the event stream (queued prompts, including the
first prompt of a fresh session) resolve their send() RPC early, so nothing
cleared the streaming shimmer or reconciled live-streamed content against
the persisted transcript at turn end. A turn whose deltas were incomplete
stayed visually streaming forever and only healed when a later non-queued
send rehydrated history.
chat_done (and chat_session_ended / the queue-drain double check) now clears
the active assistant streaming id and schedules a short-delayed
read_session_messages + applyCanonicalHistory, guarded by turn epoch,
session id, and in-flight send submissions so it never clobbers a newer
turn or duplicates the blocking send path's own finalization.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(desktop): release v0.0.14
* fix(clients): filter non-chat models from chat pickers (#13317)
* fix(clients): filter non-chat models from chat pickers
* fix(clients): align chat model eligibility
* fix(desktop): strip user_input envelope when copying a user message (#13369)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bee <abeatrix@users.noreply.github.com>
* test(vscode): stabilize code action activation
* test(vscode): activate code action by keyboard
* test(vscode): decouple action discovery from invocation
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: JasmineLCY <38378321+JasmineLCY@users.noreply.github.com>
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
Co-authored-by: Max <maxpaulus43@gmail.com>
Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bee <abeatrix@users.noreply.github.com>
Waiting on rendered text could pass in a stale window between the search
clearing and the base reload effect running, letting the scroll fire a
stale observer whose captured request key mismatched. Drop the captured
observer callback before clearing and wait for the effect to recreate it,
which only happens after the post-clear base list applied.
No behavior changes; review-readiness cleanup of the cloud sessions diff:
- one cloudRepositoryLabel helper in webview/lib/cloud-repositories.ts
replaces four copies of the owner/repo label parser (sidecar placeholder
title, repository picker, composer context label, provisioning phases)
- the cloud-provisioning- placeholder id prefix moves behind
isCloudProvisioningSessionId, shared by the sidecar that mints the ids
and the webview affordance gates that check them
- the two identical cloud status mappers in use-chat-session collapse into
mapCloudRuntimeStatus in chat-session/helpers.ts
- attach() and attachExpired() share one attachResultPayload builder
instead of duplicating the reply literal
- settings-view drops the commented-out PostHog lookup block in favor of a
short pointer comment
- welcome-chat derives its fallback connect URL from the environment
config instead of hardcoding production
- refresh the stale claim-set comment in create-recovery to describe the
post-fix wait-all semantics
- humanize cloud error envelopes in the sync-failed banner and the
rehydration fetch fallback (and scope the fallback to the active session)
- align the recovered-send status mapper with the rehydrated handler:
cover cancelled, and leave unknown statuses alone instead of flipping a
running turn to completed
- disable Delete on provisioning placeholders in the sidebar and sessions
view; the sidecar always rejects it until the create settles
- surface the CLINE_CODE_CLOUD_AGENTS override in Settings when it makes
the toggle diverge from effective behavior, and load the settings
sections concurrently
- gate the slash-command menu to local sessions like @-mentions; the
sandbox cannot resolve local skills/workflows
- stop the model selector from silently 'correcting' a locked cloud
session's model when its id is missing from the local catalog
- reap connections whose session vanished from a successful list (deleted
remotely or re-scoped): they otherwise redial the dead proxy every ~5s
forever, with a REST list per attempt, until app restart
- clear desktop-visible state synchronously at the start of dispose() so
a manager rebuilt mid-dispose (account/credential change) cannot have
its fresh liveSessions/pendingApprovals entries deleted from under it
- report rehydrated failed runs with the same chat_session_ended reason
(error) the live run.failed path uses
The cloud error envelope travels in Error.message and is authenticated by
string prefix only, so error strings a session pod controls (hub command
replies pass through verbatim) could spoof a github_not_connected envelope
whose connectUrl pointed anywhere. The webview rendered that as a trusted
looking Connect GitHub button and open_external_url validates protocol,
not origin. Drop connectUrls whose origin is not a known Cline app base
URL before they reach the action button.
The stale-selection guard compared repoUrl against a repositoryUrls
snapshot refreshed only on mount, account-id change, focus, or the
onboarding poll (which stops in ready status). An in-app org switch
refreshed none of those, so picking a repository from the new scope's
correctly filtered picker got immediately wiped against the old scope's
list. Route the picker's own loads through the same request-id-guarded
snapshot application, and re-check setup on the sidecar's
cloud_sessions_changed broadcast.
Recovery previously waited only for earlier identical peers, so an
earlier create failing fast (any request_failed, including an instant
5xx) could adopt a later in-flight POST's listed session and hand two
composers the same sandbox. Branchless and branch-specific creates also
hashed to different claim keys while the branchless recovery filter
ignores branch, allowing cross-key adoption with no ordering at all.
- key in-flight peers by repo/model/org (branch excluded) so
branchless recoveries see branch-specific peers
- settle each create's peer entry when its POST settles (never after
recovery), then make recovery wait for every other in-flight peer in
both directions, re-snapshotting until stable; waits cannot cycle
- gate recovery on timeout/5xx/no-status failures: a fast 4xx never
provisioned anything, and recovering on one risks adopting an
identical-config session created by another device on the account
A cloud create returns a server-assigned session id, but the optimistic
user bubble kept the client-planned id. mergeCloudSnapshotWithLive drops
other-session messages before consulting the optimistic map, so the first
prompt's bubble silently lost its retention semantics: a lagging snapshot
could merge to a transcript with no user prompt, and a failed first send
lost its bubble on the next rehydration.
Also pins the previously untested merge behaviors: the reflected-prompt
budget (zero-budget retention and one-consumption-per-new-copy) and
error-bubble preservation on the unmatched-live drop path.
loadMore reset loadingMore only when the request key still matched. Typing
a search character while a page fetch was in flight changed the key, so
the stale fetch never released the flag and pagination was dead for the
rest of the welcome screen's life (the observer effect and loadMore both
short-circuit on loadingMore). Only one page fetch can be in flight, so
the reset can be unconditional.
The post-registration continuation was the one mutation window not guarded
by the connect generation: a close() landing after the register reply
resolved but before the continuation ran would mark a closed client
registered. That stale flag then made a later failed registration skip
closing its socket, leaving a permanently unregistered zombie connection
that isConnected() reported healthy.
- generation-guard the continuation so a superseded attempt closes its
socket and rejects instead of touching shared state
- drop the registered-flag condition from the connect() catch guard; the
socket identity check alone decides ownership and cannot be poisoned
- keep a stale attempt's late timeout/error/close handlers from clobbering
lastCloseError and sawSocketClose for a newer attempt
- stop close() from wiping the real connect failure cause when no socket
was ever opened
A session is listed the moment the server starts provisioning it, minutes
before its successful POST returns. Timeout recovery now waits for every
earlier identical in-flight create to record its claim before adopting a
listed candidate; later peers wait on earlier ones only, so waits cannot
cycle. Regression test covers the slow-success/fast-failure overlap.
- Emit cloud_session_provisioning_failed from the sidecar and render a
terminal error pane in an open placeholder thread instead of an
infinite provisioning spinner.
- Cloud-aware delete confirmations in the sidebar and sessions view (the
action destroys the remote workspace, not just local history).
- Humanize cloud rename failures instead of showing the raw envelope.
- Preserve UI error bubbles through cloud rehydration merges; ignore
unknown snapshot statuses instead of flipping a running turn to done.
- Clear a stale repository selection when the account can no longer
access it so the send gate re-engages.
- Migrate cloud optimistic bookkeeping across queued-prompt re-keys,
clear cloud refs on reset, session-scope the cloud merge, fix the
impure provisioning-phase updater, gate rename on provisioning
placeholder rows, and stop advertising local-only mentions/commands in
cloud composer placeholders.
- Reap connections whose sandbox expired (attach, sidebar poll, and
reconnect-failure paths) so dead sessions stop reconnect-looping and
spamming sync-failure events; sync failures now notify on transition
only.
- Tombstone sessions mid-delete so a concurrent attach/send cannot dial a
fresh connection that outlives the delete; treat remotely-gone sessions
(404/410) as deletable locally.
- Guard disposed connections against resurrection by late reconnect timers
and approval responses; purge approvals stored during failed connection
setup.
- Leave cloud approvals pending on app shutdown instead of denying tool
calls on pods that outlive the app.
- Use a fresh auth token (with fallback) for create-timeout recovery;
normalize list rows so one malformed record cannot crash discovery;
widen the recovery clock-skew window now that claims prevent
double-adoption.
- Drop the queue-shrink 'prompt started' inference on the hub path (the
hub emits explicit submitted events; a shrink can also mean removal).
- Reset the transcript baseline on reconnect; answer pendingPrompts with
[] for sessions with no inner session instead of throwing.
- Use core's canonical getProviderAuthHandler("cline") for the persisted
token fallback instead of a hand-rolled prefix heuristic that could
corrupt unprefixed API keys; drop the dead test-only reset export.
- Reset the cloud session manager and broadcast cloud_sessions_changed
after a cline OAuth login, and broadcast on the save_provider_settings
(sign-out) reset, so the sidebar re-scopes immediately.
- Log cloud discovery failures instead of silently emptying the sidebar.
- Atomic write-then-rename for the desktop settings file.
- Share the repository/branch wire types between sidecar and webview.
- Add command-layer tests for the settings/flag commands; refresh the
stale sidecar ARCHITECTURE.md; delete an orphaned comment.
Review findings: bound resolveConnectionHeaders with the connect timeout so
a hung token refresh cannot pin connect() and every deduped caller forever;
record resolver failures in lastCloseError so getConnectionError() reports
the real cause; add a connect-generation token so close() during header
resolution cannot leave a doomed attempt satisfying the next connect();
stop header-auth clients from inheriting registry tokens for loopback URLs;
use the shared extractSessionId in approval.list_pending. Desktop: make the
onboarding poll read status from a ref instead of running side effects in a
state updater, and re-check GitHub connectivity when the account changes.
Reverts the SDK feature-flags service/provider changes and barrel exports to
main, and strips the desktop sidecar's PostHog-backed flag service (context
targeting, cache file, refresh/dispose lifecycle). isCloudAgentsEnabled() is
now just the env override plus the Settings toggle, and get_feature_flags
answers synchronously.
Cloud sessions are gated by the explicit Settings toggle now, so the SDK no
longer registers the unused PostHog flag. The Settings row is wrapped in a
visibility gate that is hard-wired on, with the future flag lookup left
commented out until the flag actually exists in PostHog.
- applyQueueSnapshot now rejects replies without a prompts array instead of
publishing an authoritative empty queue from the pending/update/remove
command paths.
- Timeout-recovery candidate selection and claiming now happen in one
synchronous helper so the claim can never be separated from the check,
and the regression test exercises truly concurrent create requests.
An unsuccessful or malformed session.pending_prompts reply during
rehydration no longer publishes an empty queue or discards buffered queue
events; the newest buffered queue snapshot is replayed instead.
- Keep the newest buffered queue snapshot when rehydration's queue fetch
fails instead of silently dropping queued/steered prompts (Greptile P1).
- Claim recovered/created session ids per process so overlapping identical
create requests cannot adopt the same record and orphan a sandbox
(Greptile P1).
- Use crypto.randomUUID() for provisioning placeholder ids (CodeQL
insecure-randomness alerts).
Resolves conflicts with #13028 (native-feel polish and render-path
performance): keep dynamic view imports and the memoized headerDiff from
main while preserving the cloud-session behaviors from this branch (Cloud
icon import, Connect GitHub error-action button in the chat error banner,
and hiding the diff header for cloud sessions).
Rename was already supported by the sidecar and offered in the sidebar and
chat header; the sessions view context menu was the odd one out. Includes
biome format fixes picked up in touched files.
When the cloud composer cannot start a session yet (signed out, GitHub not
connected, or the GitHub App has no repository access) replace the composer
with an onboarding panel that explains cloud sessions, walks through the
dashboard hand-off with visual steps, and auto-detects completion via polling
and window-focus refetches. Adds a teaching hint under the ready composer.
Cloud sessions are in preview, so replace the remote rollout flag with an
opt-in toggle in Settings -> General, persisted in a desktop-owned settings
file (kept out of global-settings.json so older CLI writers cannot strip it).
The CLINE_CODE_CLOUD_AGENTS env override still wins for development. Toggling
broadcasts feature_flags_changed so open composers react without a restart.
The sidebar now shows exactly the active scope's cloud sessions (personal
or the active organization — matching the dashboard), instead of merging
both. On account/organization switch the sidecar broadcasts
cloud_sessions_changed so the sidebar re-scopes immediately rather than
on the next poll; the cloud manager reset already discards the org cache
and connections.
The session registry is now upsert-only: a session opened under another
scope stays routable (send/abort keep working) when the server-side
active org drifts mid-run, even though it leaves the visible list.
Display truth stays lastListedSessions (active scope only).
Known behavior: after a full account/org SWITCH (manager reset), stale
threads from the previous scope report session-not-found on cold reopen —
the list no longer shows them, so this is reachable only via stale
webview state.
Implements the agreed convergence design (mirrors experiment/mobile-app):
subscribe → buffer → attach → snapshot (messages/status/queue) → install →
replay unreflected events → live. Single-flight with one queued rerun;
failed snapshots never become an authoritative empty transcript;
segment-scoped substring supersession in the sidecar; multiset count-delta
optimistic reconciliation with first-hydrate gating in the webview.
Review-round fixes on top of the sync implementation:
- Recovery baseline advances on delivered sends — a lost duplicate prompt
can no longer be falsely confirmed by an earlier identical delivery.
- Prompt occurrence matching normalizes the pod's <user_input> wrapper
(real transcripts never matched raw prompts; tests used unwrapped
fixtures, so recovery was inert in production).
- Streamed-text trim symmetry so whitespace cannot defeat supersession
and duplicate an entire already-persisted reply on reconnect.
- Buffered queue snapshots are dropped during replay (always older than
the synced queue; replaying could regress it and double-bubble).
- approval.list_pending advertised in HUB_CAPABILITIES (capability-gated
clients could never discover it) and the sidecar's approvals refresh
never wipes observed state unless the reply provably carries the list.
- Safety tests: replay-when-not-contained, whitespace supersession,
queue-snapshot drop, wrapped-prompt recovery, baseline advance.
Tracked follow-ups (not in this change): approval.respond and event
delivery remain unscoped hub-wide (scoping naively would break the
desktop's second approvals client); sessionId is mandatory here vs
optional on the mobile branch.
From a three-lens adversarial review of the branch:
Sidecar (cloud-sessions)
- BLOCKER: remove the connections-map entry when inner-session creation
fails — the poisoned entry returned a disposed client whose event
subscription was gone, silently streaming nothing for every later send.
- Single-flight inner-session creation: concurrent sends could fork two
inner sessions on the pod, permanently dropping one run's events.
- Cache the active-organization lookup (60s, successes only) — the
sidebar poll was making two authed REST calls per tick.
- delete() now drains an in-flight connect (zombie-connection race).
- Cold-cache expiry surfaces the clean session_expired envelope on
send/read paths, not a raw WS upgrade failure.
- A provisioned-but-connect-failed create no longer reports failure for
a live, billed sandbox; connect happens on demand instead.
- Guard against empty 2xx create responses (raw TypeError before).
SDK (hub client)
- Socket-identity guards in every connect-attempt cleanup path: a stale
attempt's late timeout/error/close can no longer clobber a newer
in-flight attempt's socket or dedupe state.
Webview
- Placeholder→real swap keeps the placeholder thread when opening the
real session fails (was: deleted it and dumped the user on a blank
fallback thread).
- Reset executionTarget to local when the cloud flag flips off on a
fresh thread (was: permanently stranded cloud-gated composer).
- inferStatusFromMessages preserves 'provisioning' (the hydration pass
was clobbering the sidebar state to idle within a second).
- Humanize cloud error envelopes in the delete toast and rename failure
(rename previously had no catch at all).
Regression tests: poisoned-connection recovery, inner-session
single-flight.
The placeholder → real-session swap mounts a fresh thread whose hydration
briefly showed the skeleton between the provisioning row and the
conversation. An empty cloud session mid-hydration now shows the same
compact row ("Opening session...") so the loading treatment never
changes shape.
From live dogfooding of cloud agent sessions:
Billing & sessions
- Bill the user's ACTIVE organization (server-side active flag, cached
resolver; personal fallback) instead of always personal credits, and
list both personal and org-scoped sessions.
- Auto-title sessions from the first prompt; support rename via REST.
- Optional branch passthrough (picker + create body + recovery match).
- Forward autoApproveTools into cloud session creation.
Provisioning experience
- Sidebar placeholder while the synchronous create provisions (REST list
cannot see the session yet), pulsing status dot, instant list nudge.
- Unified compact loading row (shared cycling phase line) for both the
originating thread and the placeholder pane; phases advance once and
hold rather than looping.
- cloud_session_provisioned event swaps placeholder threads to the real
session when the sandbox is ready.
- Opening a placeholder is benign (loading state), reads return empty,
only mutating actions error.
Correctness
- Surface run.failed error payloads in the chat (silent-failure fix; the
raw CLOUD_SESSION_ERROR envelope can no longer reach the screen).
- Emit chat_session_status only on real status changes (pods stream
periodic snapshots — every visited session was marked unread forever).
- expiredAt is a TTL deadline, not an end time; display uses createdAt
(backend bumps updatedAt on every WS connect).
- provisioning is a first-class SessionHistoryStatus (the normalizer was
collapsing it to idle).
- The new-prompt hero requires a thread WITHOUT a history session —
fixes every flash-of-intro-screen path for existing sessions.
- Archived-history fallback only replaces a live failure when a snapshot
actually exists (404 = null, not empty).
Plus GitHub repository/branch pickers, org-scoped integration URLs,
thinking-effort passthrough, and feature-flag targeting by account id.
Fix auto-approve checkboxes freezing after "New Task": clear the task-scoped settings overlay when the task view is cleared or switched, so stale task settings no longer shadow global settings
@@ -7,7 +7,7 @@ description: Use when preparing, tagging, and publishing an apps/cli npm release
Use this skill when the user asks to release the CLI, publish `cline`, bump the CLI version, draft release notes, create a `cli-vX.Y.Z` tag, or trigger the CLI publish workflow.
The CLI is npm-only. Do not add alternate distribution or signing steps.
The CLI is npm-only. Do not add alternate distribution channels. Windows binaries are Authenticode-signed automatically by the publish workflow via Azure Trusted Signing (see the `.github/actions/sign-windows-cli` composite action and "Windows code signing" in `apps/cli/DISTRIBUTION.md`); if the signing secrets are not configured the workflow warns and publishes unsigned binaries. Local publishes (`bun release cli`) do not sign — prefer the GitHub Actions publish path for releases users run on Windows.
> Working directory: run every command below from the repository root. Paths and scripts (e.g. `apps/cli/package.json`, `sdk/packages/`, `bun release cli`, `bun run version`) are written relative to the repo root.
description:Use when preparing, tagging, and publishing a Cline Code desktop app (apps/examples/desktop-app) release — stable (desktop-vX.Y.Z from main) or beta (desktop-vX.Y.Z-beta.N from desktop-experimental, shipped as the side-by-side "Cline Code Beta" app). Guides changelog drafting, version bumps in package.json + tauri.conf.json, tagging, and the desktop-publish GitHub workflow that builds, signs, notarizes, and updates the per-channel auto-update feed.
description:Use when preparing, tagging, and publishing a Cline desktop app (apps/examples/desktop-app) release — stable (desktop-vX.Y.Z from main) or beta (desktop-vX.Y.Z-beta.N from desktop-experimental, shipped as the side-by-side "Cline Beta" app). Guides changelog drafting, version bumps in package.json + tauri.conf.json, tagging, and the desktop-publish GitHub workflow that builds, signs, notarizes, and updates the per-channel auto-update feed.
---
# Desktop App Release
Use this skill when the user asks to release the desktop app, publish Cline Code, cut a desktop beta, bump the desktop version, create a `desktop-vX.Y.Z` (or `desktop-vX.Y.Z-beta.N`) tag, or trigger the desktop publish workflow.
Use this skill when the user asks to release the desktop app, publish the Cline desktop app, cut a desktop beta, bump the desktop version, create a `desktop-vX.Y.Z` (or `desktop-vX.Y.Z-beta.N`) tag, or trigger the desktop publish workflow.
> Working directory: run every command below from the repository root.
Desktop releases are macOS-only today (a single signed + notarized universal DMG that runs natively on both Apple Silicon and Intel) and are built entirely in GitHub Actions — there is no local publish path. Installed apps discover new releases automatically through the Tauri updater, so publishing a release is what ships the update to every existing user **on that channel**.
Desktop releases ship two platforms, built entirely in GitHub Actions — there is no local publish path. macOS: a single signed + notarized universal DMG that runs natively on both Apple Silicon and Intel. Windows: an Authenticode-signed NSIS installer (`<Product>_<version>_x64-setup.exe`), signed via Azure Trusted Signing in the `build-windows` job (jsign through Tauri's `signCommand`, see `apps/examples/desktop-app/scripts/tauri-sign-windows.ps1`; requires the repo-level `AZURE_*` secrets including `AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_DESKTOP`, plus a `PublishDesktop`-environment federated credential on the `cline-cli-signing` Entra app). Installed apps discover new releases automatically through the Tauri updater, so publishing a release is what ships the update to every existing user **on that channel**.
## Release contract
- Two channels, one workflow (`channel` input on `desktop-publish.yml`):
- **stable** — tag `desktop-vX.Y.Z` (no suffix; the workflow rejects prerelease suffixes on this channel), cut from `main`, feeds the rolling `desktop-latest` release, ships as "Cline Code".
- **beta** — tag `desktop-vX.Y.Z-beta.N`, cut from `desktop-experimental`, feeds the rolling `desktop-beta` release, ships as "Cline Code Beta" (separate bundle identifier `bot.cline.app.beta`; installs side by side with stable). Built with the extra `src-tauri/tauri.beta.conf.json` overlay. Process background: `apps/examples/desktop-app/EXPERIMENTAL.md`.
- **stable** — tag `desktop-vX.Y.Z` (no suffix; the workflow rejects prerelease suffixes on this channel), cut from `main`, feeds the rolling `desktop-latest` release, ships as "Cline".
- **beta** — tag `desktop-vX.Y.Z-beta.N`, cut from `desktop-experimental`, feeds the rolling `desktop-beta` release, ships as "Cline Beta" (separate bundle identifier `bot.cline.app.beta`; installs side by side with stable). Built with the extra `src-tauri/tauri.beta.conf.json` overlay. Process background: `apps/examples/desktop-app/EXPERIMENTAL.md`.
- Version sources (must match each other and the tag): `apps/examples/desktop-app/package.json` and `apps/examples/desktop-app/src-tauri/tauri.conf.json`. (`src-tauri/Cargo.toml` has its own version but `tauri.conf.json` overrides it; no need to touch it.)
- Beta versions are prereleases of the **next** stable: stable `0.0.13` → betas `0.0.14-beta.1`, `-beta.2`, … Once a stable ≥ the beta base ships, the next beta bumps its base (`0.0.15-beta.1`).
- Release prep includes approved release notes, the version bumps, and an `apps/examples/desktop-app/CHANGELOG.md` update — committed on `main` for stable, on `desktop-experimental` for beta.
- Publish path: `.github/workflows/desktop-publish.yml` (workflow_dispatch, requires the tag to exist, point at the checked-out commit, and be reachable from the channel's branch — `origin/main` for stable, `origin/desktop-experimental` for beta).
- **Both channels dispatch from `main`.** This is a security invariant, not a convenience: the run executes `main`'s workflow copy and only the checkout points at the tag, so the signing-secret gates (the `github.ref == main` check and the PublishDesktop environment's main-only deployment-branch policy) hold for beta too. Never add `desktop-experimental` to the PublishDesktop deployment-branch policy.
- The workflow creates the tag's GitHub release (universal DMG + updater artifact + `latest.json`; marked prerelease for beta) and refreshes the channel's rolling feed release, which is the static auto-update feed every installed app on that channel polls. Never delete the `desktop-latest` or `desktop-beta` release or tag.
- The workflow creates the tag's GitHub release (universal DMG + macOS updater artifact + Windows NSIS installer with its updater signature +`latest.json`; marked prerelease for beta) and refreshes the channel's rolling feed release, which is the static auto-update feed every installed app on that channel polls. Never delete the `desktop-latest` or `desktop-beta` release or tag.
- The changelog's `## <version>` section (exact-match, not "topmost") is extracted verbatim into the GitHub release body, the Slack announcement, and the updater manifest notes.
- Always ask before pushing commits or tags.
@@ -120,7 +120,7 @@ gh api repos/cline/cline/actions/runs/<run-id>/pending_deployments \
Nothing after `validate` runs — and no signing key is readable — until then.
The workflow builds one universal macOS bundle (`tauri build --target universal-apple-darwin` lipos the aarch64 + x86_64 Rust binaries; the Bun sidecar is lipo'd by `build-sidecar-bin.ts`; beta adds the `tauri.beta.conf.json` overlay), verifies every Mach-O in the bundle carries both slices and that the compiled binary embeds exactly its own channel's feed URL, signs with the Developer ID certificate, notarizes with the App Store Connect API key, signs the updater artifact with the Tauri updater key, creates the GitHub release (prerelease for beta), refreshes the channel's feed (`desktop-latest/latest.json` or `desktop-beta/latest.json`), and posts to Slack. Notarization typically adds 2–10 minutes.
The workflow builds one universal macOS bundle (`tauri build --target universal-apple-darwin` lipos the aarch64 + x86_64 Rust binaries; the Bun sidecar is lipo'd by `build-sidecar-bin.ts`; beta adds the `tauri.beta.conf.json` overlay), verifies every Mach-O in the bundle carries both slices and that the compiled binary embeds exactly its own channel's feed URL, signs with the Developer ID certificate, notarizes with the App Store Connect API key, and signs the updater artifact with the Tauri updater key. In parallel, `build-windows` builds the x64 NSIS installer on a Windows runner, Authenticode-signs every binary via Azure Trusted Signing (Tauri `signCommand` -> `scripts/tauri-sign-windows.ps1`), runs the same feed-endpoint and telemetry guardrails, and verifies the shipped installer with `Get-AuthenticodeSignature`. The release job then creates the GitHub release (prerelease for beta), refreshes the channel's feed (`desktop-latest/latest.json` or `desktop-beta/latest.json`), and posts to Slack. Notarization typically adds 2–10 minutes.
If the workflow fails on missing credentials, see "Publish secrets (one-time setup)" below.
curl -sL https://github.com/cline/cline/releases/download/desktop-beta/latest.json | head -30 # beta
```
The `version` field must be the new release and both `darwin-aarch64` and `darwin-x86_64` entries must point at the same new universal `.app.tar.gz` asset under the release tag (each slice of the fat binary requests its own arch key at runtime, so both keys serve the one artifact). Installed apps on that channel — including older per-arch installs — pick the update up on next launch or within 2 hours.
The `version` field must be the new release; both `darwin-aarch64` and `darwin-x86_64` entries must point at the same new universal `.app.tar.gz` asset under the release tag (each slice of the fat binary requests its own arch key at runtime, so both keys serve the one artifact), and the `windows-x86_64` entry must point at the new `*_x64-setup.exe` asset. Installed apps on that channel — including older per-arch installs — pick the update up on next launch or within 2 hours.
After a **beta** publish, also confirm the stable feed was not touched: `desktop-latest/latest.json` must still serve the previous stable version. (The workflow guards this fail-closed, but it is cheap to verify and catastrophic to miss — the updater comparator is a plain semver "newer than", so a beta manifest on `desktop-latest` would auto-update every stable install onto the beta.)
# (the branch is hardcoded to legacy-extension in the workflow; it is
# deliberately not an input)
```
npm test suite runs ungated; the publish job waits on the `Publish` environment. This workflow derives + pushes the `v<version>` tag itself and creates the GitHub release — no manual tagging. Publishes to Marketplace **and** Open VSX. The branch is the npm codebase: use `npm`, never `bun`, and expect the old monolith layout (`apps/vscode/src/core/...`).
Paste the "About" diagnostics for your Cline surface. This captures the IDE build, runtime, and host details we need.
Paste the diagnostics for your Cline surface. This captures the build, runtime, and host details we need.
- VSCode Extension: open `Help → About` (Windows/Linux) or `Code → About Visual Studio Code` (macOS), then copy the info.
- JetBrains Plugin: open `Help → About` (Windows/Linux) or `<IDE name> → About` (macOS), then click `Copy` to grab build, runtime, OS, memory, and cores.
- CLI: there is no About dialog. Run `cline --version` and paste the output.
placeholder:Paste the copied About info or `cline --version` output here.
- Desktop App: paste the app version from the Settings view.
- Cloud Platform: paste your browser name and version, plus the page URL where the issue occurred.
placeholder:Paste the copied About info, `cline --version` output, or browser/app details here.
for var in AZURE_CLIENT_ID AZURE_TENANT_ID AZURE_SUBSCRIPTION_ID SIGNING_ENDPOINT SIGNING_ACCOUNT SIGNING_PROFILE; do
if [ -z "${!var}" ]; then
missing+=("$var")
else
set_count=$((set_count + 1))
fi
done
if [ "${#missing[@]}" -eq 0 ]; then
echo "Azure Trusted Signing is configured; Windows binaries will be signed."
echo "enabled=true" >> "$GITHUB_OUTPUT"
elif [ "$set_count" -eq 0 ]; then
echo "::warning::Azure Trusted Signing is not configured; publishing UNSIGNED Windows binaries. Set the AZURE_* and AZURE_TRUSTED_SIGNING_* repository secrets to enable signing."
echo "enabled=false" >> "$GITHUB_OUTPUT"
else
# Partial configuration is almost certainly a typo'd or renamed
# secret. Fail loudly instead of silently publishing unsigned.
echo "::error::Azure Trusted Signing is PARTIALLY configured; refusing to publish. Missing: ${missing[*]}"
--notes "Rolling release backing the beta desktop app auto-updater. The latest.json asset points at the newest desktop-vX.Y.Z-beta.N release. Only beta installs poll this feed; stable installs use desktop-latest. Do not delete." \
--latest=false \
--prerelease \
--target "$(git rev-parse HEAD)"
else
gh release create "$FEED" \
--title "Cline Code desktop (auto-update feed)" \
--title "Cline desktop (auto-update feed)" \
--notes "Rolling release backing the desktop app auto-updater. The latest.json asset points at the newest desktop-vX.Y.Z release. Do not delete." \
Everything here lands through the SDK bundle, so it applies to windows running that bundle.
### Fixed
- Cost estimates are no longer shown for providers billed by a flat-rate subscription (ClinePass, ChatGPT via Codex, and Claude Code). The task header and model pricing rows rendered API-rate dollar figures that read as real charges on top of the subscription, including a flash of them on every chat-view mount while provider listings were loading.
- Signing back in no longer moves your last-used provider off ClinePass on credential refresh.
- Hooks now resolve their workspace from the VS Code window instead of shared global state in `~/.cline`. With a second window open on another project, a workspace's `.clinerules/hooks` scripts were never discovered, and hook cwd and the workspace paths passed to hook scripts resolved against whatever project some other or older Cline instance last recorded.
- New files are now created with your platform's native line endings.
- Fixed the codebase search tool crashing on files containing a single enormous line.
- Credentials embedded in git remote URLs are now redacted from the workspace information sent to the model, which also now carries richer workspace metadata.
- Installing an MCP server from the marketplace no longer misreads the catalog's `--` separator as part of the server command.
- The hub's event log can no longer grow until it fills your disk.
### Changed
- The per-tool MCP auto-approve checkboxes are hidden. MCP auto-approval is governed solely by the global "Use MCP servers" toggle — the per-tool checkboxes were no-ops that implied granularity the approval path does not have.
## [4.1.15]
Everything here lands through the SDK bundle, so it applies to windows running that bundle.
### Fixed
- Auto-approve every MCP tool call while the "Use MCP servers" toggle is on. The toggle only took effect on tools that had also been opted in individually, so turning it on appeared to do nothing; it now governs all MCP tools on its own.
## [4.1.14]
Everything here lands through the SDK bundle, so it applies to windows running that bundle.
### Added
- Refresh the built-in model catalog. New entries include Claude Fable 5, Grok 4.6 on Vertex, several DeepSeek V4 Flash variants (including the vision preview), MiMo v2.5, Qwen3.8 27B, Gemma 4 26B, LongCat 2.0, Nemotron 3.5 Lightning, and Thinking Machines' Inkling models.
### Fixed
- Restore task completion telemetry for interactive sessions. A share of interactive stops routed through a teardown path that never reported completion after 4.1.11 changed how session status is tracked; every session now reports it exactly once.
## [4.1.13]
Everything here lands through the SDK bundle, so it applies to windows running that bundle.
### Fixed
- Restore tool calling for custom OpenAI-Compatible models whose capability list was inferred from convenience flags like `supportsReasoning`. The inferred list read as an authoritative denial and stripped every tool from the request; an explicitly authored capability list still decides.
- Keep Hub-backed sessions intact across a Hub restart or upgrade. Clients replay the events they missed while disconnected, and the same event is no longer delivered twice when the replay and live streams overlap.
- Carry session and client identity into Langfuse traces for Hub-backed and delegated-agent runs, which previously arrived without their session grouping or client version.
## [4.1.12]
Everything here lands through the SDK bundle, so it applies to windows running that bundle.
### Fixed
- Enforce enterprise MCP controls on the Customize marketplace. MCP entries are now hidden when remote config disables the marketplace, and limited to `allowedMCPServers` when an allowlist is configured.
- Restore tool calling for custom OpenAI-Compatible models whose stored capability list was empty.
## [4.1.11]
Everything here lands through the SDK bundle, so it applies to windows running that bundle — except the last section, which is a legacy-bundle fix.
### Added
- Let models that support it generate images during a task. Generated images render inline in the conversation.
### Fixed
- Fix code actions failing with "command not found" on VS Code 1.134.
- Fix `@` file mentions breaking on paths that contain spaces.
- Show the diff edit view for multi-line edits in files with CRLF line endings.
- Continue the surviving session when resuming a task, instead of rebuilding it from the original task text.
- Clear the task-scoped settings overlay when the task view is cleared or switched, so one task's overrides no longer leak into the next.
- Honor the classic truncation range when migrating legacy tasks.
- Preserve LiteLLM input token limits instead of overwriting them with catalog values.
- Restore custom base URLs for Gemini, and normalize legacy host-root values so they keep working.
- Point provider signup links at each provider's API key page instead of a generic landing page.
- Load skill slash commands through the skills tool instead of pasting their instructions into your message, which previously delivered them twice.
- Stop offering image, voice, and other non-chat models in chat model pickers.
- Deliver a `PreToolUse` hook's `contextModification` to the model again, and wait for `PostToolUse` hooks so their output and `cancel` control are honored.
- Show tool activity a provider runs itself — every tool the Claude Code provider executes inside its own session — instead of dropping it from the conversation.
- Fix `run_commands` failing with ENOENT when a structured command carried a full command line with no arguments.
- Run PowerShell commands fail-fast, so a pipeline erroring per item stops at the first error instead of flooding output and still reporting success.
- Keep remote configuration in step with the SDK: coordinated refreshes, session gating, and a fail-closed opt-out.
### Changed
- Show the billed cost for Cline gateway usage.
- Refresh the model catalog, which adds AMD, Arcee, Echo, Jalapeno, Kosmik, LLM Gateway, RunInfra, and SCNet as providers and updates model lists, pricing, and per-provider default models across the board.
### Fixed (legacy bundle)
- Only treat an Anthropic `invalid_request_error` as a context-overflow when its message says so. An unrelated invalid request (bad tool schema, oversized image, unknown model id) no longer triggers context-overflow recovery.
## [4.1.10]
Everything in this release lands through the SDK bundle, so it applies to windows running that bundle and not the legacy one. The legacy bundle is unchanged from 4.1.9.
- Fixed the background hub process ballooning in memory during long sessions — session status updates were broadcasting a full copy of the conversation transcript to every connected client, which on a large task could grow the process to tens of gigabytes. Upgrading retires the running hub so the fix takes effect on the next command
- New files are now created with your platform's native line endings
- Fixed the codebase search tool crashing on files that contain a single enormous line
- Cost estimates are no longer shown for Claude Code. Its usage is typically covered by a Claude Pro/Max subscription, but its models reuse Anthropic API pricing, so Cline was showing charges you were not being billed
- Credentials embedded in git remote URLs are now redacted from the workspace information sent to the model
- Installing an MCP server no longer misreads a `--` separator in the install arguments as part of the server command
- Refreshed the model catalog. Adds seven providers (Agnes AI, Aixy, IteraCompute, LLM Tech, NeoSmith, Pendra, and Standard Compute) and updates model lists and pricing across providers. The resolved default model changes for ClinePass (now GLM 5.3), Z.ai, Hugging Face, evroc, LLM Gateway, NanoGPT, and Weights & Biases, so if you use one of those without pinning a model you will get a different default
## 3.0.58
- The first-launch "Try ClinePass" dialog no longer advertises the $4.99 first-month promo, which is ending
- The hub's event log is now capped at 64 MiB on disk. Events carrying full session snapshots could previously grow the log to tens of gigabytes on a long-running hub, since deleting rows never shrinks the file. Oldest events are dropped first and the space is returned, and pruning runs on volume as well as on a timer
- Refreshed the model catalog. Adds two providers (AgentRouter and Opper) and updates model lists and pricing across providers. The resolved default model changes for Aki.io and NanoGPT, so if you use one of those without pinning a model you will get a different default
## 3.0.57
- Added `cline hub drain`, which stops a hub from accepting new mutating work while it finishes what it is already running, and `cline hub drain --off` to lift it
- Added `cline hub upgrade`, which drains the hub, waits for it to go idle, stops it, and starts a fresh one on the current build. An aborted upgrade lifts the drain again, so the hub is never left refusing work
- Sessions now survive a hub restart. A reconnecting client replays the events it missed while disconnected, deduped by event id so nothing is delivered twice
- Fixed tool calling being silently disabled for custom OpenAI-Compatible models whose capability list was inferred from convenience flags like `supportsReasoning`. The inferred list read as an authoritative denial and stripped every tool from the request
- Langfuse traces now carry session and client identity for hub-backed and delegated-agent runs, instead of arriving without their session grouping or client version
- Refreshed the model catalog, which updates model lists and pricing across providers and changes the resolved default model for several of them (DeepSeek, Crof, CrossModel, Eden AI, Kilo, and NanoGPT)
## 3.0.56
- Models that support image generation can now produce media during a turn. The TUI saves each generated file to a temporary path and prints it so you can open it with your usual tools, HTML session exports embed images inline, and ACP clients receive generated images as image content
- Skill slash commands now load through the skills tool instead of expanding into your message. History and resume show the `/command` you typed instead of the whole skill body, and the instructions reach the model once instead of twice. Workflows still expand, as does zen mode, whose preset has no skills tool
- Image, voice, and other non-chat models are no longer offered in the onboarding and model pickers or ACP model listings, and are rejected for `--model`
- Fixed TUI dialog colors not following theme changes live
- Fixed the account dialog's selection chevron so it matches the other dialogs
- Fixed provider-executed tool activity — every tool the Claude Code provider runs inside its own session — being dropped instead of shown as a tool card
- Fixed `PreToolUse` hook `contextModification` never reaching the model, and `PostToolUse` hooks running fire-and-forget with their output and `cancel` control discarded
- Fixed `run_commands` failing with ENOENT when a structured command carried a full command line with no `args`
- PowerShell commands now fail fast on the first error instead of emitting an error record per enumerated item and still reporting success
- Fixed Gemini custom base URLs configured as a host root
- Fixed `cline schedule` commands against a remote hub, which now register a workspace client so they are authorized under the new workspace-scoped schedule rules
- Usage now displays the billed gateway cost
- Refreshed the model catalog, which adds AMD, Arcee, Echo, Jalapeno, Kosmik, LLM Gateway, RunInfra, and SCNet as providers and updates model lists, pricing, and per-provider default models across the board
## 3.0.55
- Auto-updates no longer install while a CLI is attached to the Hub. The update is recorded at startup and installed on exit, once the Hub confirms nothing else is attached, so a background update can no longer swap the package out from under a live session and kill it with `Hub connection closed (code=1006)`. `cline update` still installs immediately and now tells you the update applies on next start
@@ -270,6 +270,9 @@ The postinstall script runs in diverse environments (CI, Docker, restricted perm
### Windows
Windows binaries are `.exe` files. The build script appends `.exe` to the output filename on Windows targets. The resolver handles this. npm on Windows generates `.cmd` shims for bin entries automatically.
### Windows code signing
Windows application control (Smart App Control, WDAC, AppLocker) blocks unsigned executables at launch, regardless of how they were installed — npm distribution gets no exemption ([#12934](https://github.com/cline/cline/issues/12934)). The publish workflow Authenticode-signs `cli-windows-x64/bin/cline.exe` and `cli-windows-arm64/bin/cline.exe` with Azure Trusted Signing before publishing, via the `.github/actions/sign-windows-cli` composite action. Signing runs on the Linux publish runner using [jsign](https://ebourg.github.io/jsign/) (`--storetype TRUSTEDSIGNING`) with an OIDC-federated Entra app, then verifies the signature chain with `osslsigncode` against the Microsoft Identity Verification Root CA 2020. If all `AZURE_*` / `AZURE_TRUSTED_SIGNING_*` repository secrets are absent, the action logs a warning and the release ships unsigned rather than failing; if only some resolve (a typo'd or renamed secret), the release fails loudly instead. The certificate profile secret is suffixed `_CLI` because the desktop app will later get its own profile; the other five secrets are shared. Note that signing bun-compiled executables requires Bun >= 1.2.23 (earlier versions located the embedded bundle relative to the end of the file, which signing corrupts).
### File permissions
Compiled binaries need to be executable (`chmod 755`). The build script sets this after copying. The postinstall also sets permissions on the cached binary. Some npm packaging steps can strip permissions, so both handle this defensively.
// biome-ignore lint/correctness/useExhaustiveDependencies: dialogCount re-runs the sync when a dialog opens, covering dialogs opened before the container-level option applied (see docblock).
- Beta: Composio connectors now register tools directly in the packaged desktop runtime for eligible internal accounts, with safer OAuth revocation and more resilient connect, disconnect, and reconciliation behavior.
- Web search is enabled by default for new desktop sessions.
- Includes all stable desktop improvements through 0.0.21, including the two-pane Marketplace explorer, reliable cancellation of child agents and teammates, full-composer attachment drops, live Cline model catalog refreshes, and clearer provider authentication errors.
## 0.0.21
- Marketplace is now a two-pane explorer: a browsable list on the left and full catalog metadata for the selected item on the right, with category tag filters that collapse behind a "more" toggle
- Stopping a session now actually stops everything it started. Stop stays available while child agents are running, and an abort propagates to delegated subagents and to teammates instead of leaving orphaned work running in the background; cancelled teammate tasks now persist as cancelled
- Fixed the ask-a-question tool's option text overflowing instead of wrapping
- You can now drop file attachments anywhere over the chat input, not just on the small attach target
- Cline provider models now refresh from the live catalog, so newly released models show up without waiting for an app update
- Provider 401/403 responses are now classified as authentication errors rather than generic request failures, so a bad or missing API key is distinguishable from a real provider outage
- Fixed Langfuse tracing never initializing in release builds — the minified bundle broke tracer detection, so telemetry worked in dev and silently did nothing in the shipped app. Also updated for AI SDK 7's telemetry API
- Refreshed the model catalog. Adds TokenGo and Volcengine Ark, and updates model lists, pricing, and the resolved default model for ~36 providers (including Hugging Face, Mistral, OpenRouter, Together, NanoGPT, Requesty, Baseten, Cloudflare Workers AI, and DigitalOcean) — if you use one of those without pinning a model, you will get a different default
## 0.0.21-beta.2
- Beta: hand off local sessions to Cline Cloud and continue working from cloud workspaces, with recovery for interrupted transfers and preservation of the prompt, attachments, and session state.
- Beta: choose between local, SSH remote, and Cloud environments from the desktop app, with the experimental realtime voice and avatar overlay experiences included.
- Beta: the GitHub onboarding step is available behind the `code-onboarding-github` feature flag and remains disabled by default.
- Includes all stable desktop improvements through 0.0.20, including the Windows release, full-history session search, scheduled-task fixes, inline tool-result images, and the latest provider and Marketplace updates.
## 0.0.20
- Cline Desktop now ships on Windows: releases include a code-signed x64 installer, and installed apps auto-update on the same feed macOS does
- Windows shell fixes: background processes (the sidecar, git) no longer pop visible console windows; updates now download in the background and install when you restart the app; the MCP settings path falls back to `USERPROFILE` when `HOME` is unset
- Tool results that return images — screenshots from browser or MCP tools — now render as inline images you can click to expand, with a carousel for stepping through multiple images, instead of raw base64 text
- Session search now covers your full indexed history. The sidebar search icon opens the command bar (Cmd/Ctrl+P) with server-ranked results, instead of a sidebar-local dialog that first loaded every session into memory
- Onboarding has a new GitHub integration step
- Fixed scheduled tasks disappearing after the app updated — hub-managed schedules were being wiped by cron reconciliation on restart
- Agent-created schedules now live in one user-level home (`~/.cline/schedules`) instead of being scattered across whichever chat folder created them, and they now appear on the Schedules page
- A finished scheduled session now surfaces its final answer: the completing step auto-expands, is labeled "Scheduled task completed" (or failed), and its summary renders as markdown
- Suggested routine templates now ask for a specific final report, so a scheduled run ends with something readable
- Providers no longer show as "Configured" on the strength of a leftover settings entry with no real credentials, and the badge now updates live after connecting or saving credentials instead of waiting for a remount
- Fixed OpenAI Codex (ChatGPT subscription) sign-in silently dead-ending when callback port 1455 was already in use — it now fails immediately with an actionable error, and OAuth redirect errors surface instead of a confusing "Missing authorization code"
- Codex and OCA sign-ins are no longer dropped when a token refresh hits a transient network failure or server error
- Checkpoint restore now refuses to reset your workspace when commits were made after the checkpoint, instead of silently knocking them off the branch
- Fixed an enabled-but-offline remote MCP server stalling session startup until the session was torn down
- Global rules stored at `~/Cline/Rules` are now discovered (previously only `~/Documents/Cline/Rules`), fixing rules that never reached the model on WSL and headless installs
- `apply_patch` now preserves a file's own CRLF line endings
- The window title bar stays draggable across every view
- Voice input's Live and After recording badges now have tooltips explaining them
- Removed the box shadow from the chat message actions row
- The hub no longer watches agenda spec directories while the todo tool is disabled, dropping an OS watch handle per known workspace
## 0.0.19
- Fixed the background Cline process ballooning in memory during long sessions — session status updates were carrying a full copy of the conversation transcript to every connected client, which on a multi-megabyte task could grow the process to tens of gigabytes. Status updates now carry only state (status, usage, model, workspace, checkpoint); the transcript is fetched on demand
- Refreshed the model catalog. Adds seven providers (Agnes AI, Aixy, IteraCompute, LLM Tech, NeoSmith, Pendra, and Standard Compute) and updates model lists and pricing across providers. The resolved default model changes for ClinePass (now GLM 5.3), Z.ai, Hugging Face, evroc, LLM Gateway, NanoGPT, and Weights & Biases, so if you use one of those without pinning a model you will get a different default
## 0.0.18
- The sidebar is time-sorted again by default, with collapsible Pinned / Scheduled / Tasks sections and a one-click toggle to switch to project grouping (the old dropdown is gone). Scheduled sessions are marked with a clock icon, and the list starts taller and grows to fill the sidebar instead of stranding rows over empty space
- Session rows now show a trash button on hover for quick deletion, with the same confirmation the row's context menu uses
- Customize is now your installed inventory only. Browsing moved to a dedicated Marketplace page — one list across plugins, MCP servers, and skills with type-filter and tag chips — and the two pages link to each other from their headers and from sidebar sub-tabs
- Schedule cards are now click targets: clicking a card anywhere outside its controls opens its details, the redundant eye button is gone, and the edit / run / pause / delete buttons are large enough to hit
- Schedule details are one scrollable view instead of Overview/Runs tabs, showing the meta grid, the configuration, and the most recent runs with a "Show all N runs" expander
- "Run now" now hands you into the session it starts
- Scheduled and automation runs no longer render their internal `[SYSTEM]` steering messages as if you had typed them — a finished scheduled session reads as prompt, work summary, answer
- Fixed opening a scheduled session while it runs leaving it stuck on the thinking shimmer until you switched away and back
- Fixed installing plugins and MCP servers from the Marketplace failing with `Executable not found in $PATH: "cline"` — installs now run in-process and no longer require a Cline CLI on your machine
- Fixed quitting the app beach-balling for several seconds
- Cost estimates are no longer shown for subscription-billed providers (ClinePass, ChatGPT via Codex, and Claude Code), where an API-rate dollar figure read as a real charge on top of your subscription
- Fixed hover cards flashing closed and reopening when clicked
- The macOS DMG install window now has custom Cline artwork and layout
- Credentials embedded in git remote URLs are now redacted from the workspace information sent to the model
## 0.0.17
- Plugins, MCP, Skills, Rules, Hooks, and Tools are now one Customize hub with tabbed sections and live counts. Catalog-backed tabs show what you have installed followed by an inline Browse section, so installing something from the catalog immediately appears above — the separate Marketplace page is gone
- Redesigned the Models page: providers are grouped into Connected, Popular, and All with their auth kind and configuration status instead of per-row toggles. OAuth providers now offer a browser sign-in rather than an API key field, with a collapsed manual-key escape hatch where supported, and explicit Connect / Disconnect / Sign out actions
- Voice input moved to its own Settings → Voice page that only offers connected transcription-capable providers and preselects a default model. The composer's microphone button now appears only once a voice model is configured
- Sidebar sessions are always grouped by project, with pinned sessions leading each group and scheduled sessions marked by an inline clock. The Favorite action is now called Pin
- New, Schedule, and Customize each got their own labeled row below the logo. New starts a fresh task and puts your cursor straight in the composer
- Session search moved into a dialog behind the search icon in the logo row, and it now searches your full history instead of only the sessions already loaded in the sidebar
- Added suggested schedule templates to the Schedule page
- Add Provider opens a dialog instead of swapping out the page
- Desktop notifications are now a single section under General, so the Event/Notify/Sound matrix no longer reads as a peer of settings like Dark mode
- The agent's todo tool and the Agenda panel have been removed; scheduled tasks are unaffected
- Fixed the provider list being unscrollable while a provider detail panel was open
- Fixed a failed settings save leaving the Models page claiming a provider configuration that was never written to disk
- Fixed Uninstall buttons collapsing to a broken square next to Install
- Fixed unreadable selected text inside input fields
- New files are now created with your platform's native line endings
- Fixed the codebase search tool crashing the app on files containing a single enormous line
- The hub's event log can no longer grow until it fills your disk
## 0.0.16
- The agent can now be handed off between Hub instances without losing work: a Hub that is restarting refuses new work while it finishes what it is running, and the app replays anything it missed while disconnected instead of dropping it
- Fixed tool calling being silently disabled for custom OpenAI-Compatible models whose capability list was inferred from convenience flags like `supportsReasoning`
- Refreshed the model catalog, which updates model lists and pricing across providers and changes the resolved default model for several of them (DeepSeek, Crof, CrossModel, Eden AI, Kilo, and NanoGPT)
- The app now honors server-side feature flags, refreshing them when your account changes
## 0.0.16-beta.1
- Beta: your typed prompt is no longer lost when a cloud handoff starts — the live draft carries into the handoff and is restored if it fails.
- Beta: fixed a closed model popover blocking clicks in the composer.
- Beta: cleaned up visual regressions in provider settings, notifications, and the avatar overlay from the previous sync.
- Includes the 0.0.16-track main updates: the redesigned first-run onboarding with an interactive welcome graphic, centralized tool availability, hook fixes (PostToolUse output and context changes now reach the model), and the fix for checkpoint restore staying locked after queued turns.
## 0.0.15
- The app is now called Cline, renamed from Cline Code. Your settings, sessions, and credentials carry over untouched — only the name and icon change
- Refreshed app icons and branding
- Reskinned the first-run onboarding, with an interactive welcome graphic
- Plugins, MCP servers, and Skills are now one Plugins hub with a dedicated Marketplace page
- The composer's model selector now leads with Recommended and Free tiers (Subscribed and Free on ClinePass), labeled by display name with descriptions, instead of an alphabetized list of raw model ids. Provider settings show the same badges and descriptions
- Agents can now create and manage durable todos and one-time or recurring schedules
- Fixed checkpoint restore wedging permanently. Sessions that were never prompted — and persistence-only updates — reported a bogus "running" status, so anything gated on an active turn stayed blocked forever
- Fixed "No sessions found" flashing while session history was still loading
- Fixed the work summary undercounting elapsed time when thinking before a tool call attached to the answer instead of the run
- Fixed the settings gear keeping its hover state while the Account screen is open
- Fixed ClinePass not being recognized as OAuth-managed in the chat credential gate, which asked for credentials it already had
- Fixed copying a user message bringing along its internal envelope
- Fixed multi-line code blocks collapsing onto a single line
- Image, voice, and other non-chat models are no longer offered in chat model pickers
- Fixed `PreToolUse` hook `contextModification` never reaching the model, and `PostToolUse` hook output and `cancel` control being discarded
- Fixed provider-executed tool activity — every tool the Claude Code provider runs inside its own session — being dropped instead of shown
- PowerShell commands now fail fast on the first error instead of flooding output and still reporting success
- Usage now displays the billed gateway cost
- Refreshed the model catalog, which adds AMD, Arcee, Echo, Jalapeno, Kosmik, LLM Gateway, RunInfra, and SCNet as providers and updates model lists, pricing, and per-provider default models across the board
## 0.0.15-beta.1
- Beta: hand off a local session to Cline Cloud with `/handoff` — the conversation, attached images, and an optional follow-up command move to a cloud workspace that keeps working after you close the app. A preflight confirms the repository, branch, and commit are pushed; the composer shows handoff progress and finishes with a receipt linking to the cloud session.
- Beta: Cloud now lives in the existing Local / Remote environment menu. It is selectable when the Cloud sessions feature flag is on and shows as "Coming soon" otherwise. The separate Local / Cloud toggle is gone; repository and branch controls are unchanged.
- If a handoff is interrupted — the app restarts, the network drops, or the branch moves mid-transfer — reopening the session recovers or cleanly retries it, and your typed draft and attachments are restored.
- Includes the 0.0.14 stable release and everything on main since: the unified Plugins hub with a Marketplace page, recommended and free model tiers in the model picker, scheduled tasks for agents, and the app rename to "Cline" (this beta is now "Cline Beta").
## 0.0.14
- The app now posts native macOS notifications when a task finishes or needs your input, so you can leave Cline working in the background. Configure them under Settings → Notifications.
- Voice input: dictate into the composer with the microphone button and your speech is transcribed as you talk, using the provider and model you have configured.
- Commands stream their output into the transcript as they run instead of appearing all at once when the command exits. Output keeps its terminal colors, is scrollable without being yanked back to the bottom, and a long-running command can be sent to the background with "Proceed while running" so the agent moves on while it finishes.
- Models that support image generation can now produce images during a task, and they render inline in the transcript.
- Finished agent runs collapse into a single "Worked for 4m 12s and made 14 tool calls" summary you can expand, so the final answer stays in view instead of being buried under the working rows.
- Reasoning traces and tool rows now open and close with an animation instead of snapping, and respect your reduced-motion setting.
- Redesigned the question card the agent shows when it needs a decision: options are selected explicitly and submitted with a button, multiple-choice questions are supported, and there are arrow-key and A–Z shortcuts. Internal request IDs, iteration counts, and timestamps no longer appear on the card.
- The Web search toggle in Settings now explains that only providers with built-in web search honor it, and shows which of your connected providers are ready to use it — or warns you, with a link to Models, when none of them are.
- Refreshed assistant markdown — chat-scaled headings, quieter code blocks with a hover copy button, and table cards — now rendered through the same pipeline as the rest of Cline, so the desktop app and the cloud dashboard finally look alike.
- Message hover actions float over the transcript instead of reserving blank space under every message, so conversations pack more tightly.
- Restyled session hover cards: they open immediately, drop the duplicated ID and updated time, and no longer animate as you move down the list.
- There is now a separate "Cline Code Beta" app that installs side by side with this one and tracks the experimental branch. It identifies itself as beta in the sidebar, Settings, window title, and tray, so you always know which build you are in.
- Fixed turns that settle through the event stream — queued prompts, and the first prompt of a fresh session — staying stuck on the streaming shimmer with no final output, healing only when you sent another message. The transcript now reconciles against the saved history as soon as the turn ends.
- Fixed sessions being given the Yolo-mode system prompt whenever auto-approve was on, even though the runtime was started in Act mode. Auto-approval is now an independent tool policy and no longer changes the advertised mode.
- `/skill` and `/workflow` commands no longer dump the whole skill body into the chat as your message. Your typed command stays as typed, the model loads the instructions through the skills tool, and sessions are no longer titled with the first line of a skill's markdown. Sessions saved before this fix render compactly too.
- Fixed command execution breaking for an entire session when a model emitted a full command line with no separate arguments — anything containing a space failed with `ENOENT`.
- Restoring a checkpoint now trims the saved transcript too, so the chat no longer keeps showing turns whose file changes were just reverted.
- Gemini custom base URLs work again, including host-root values saved before the SDK migration and proxy roots like `http://localhost:4000/gemini`, which were silently missing the API version segment and 404ing.
- LiteLLM input token limits reported by the server are preserved instead of being replaced with a 128K default.
- Fixed misaligned columns in the Usage table, and added a See More link to the full usage dashboard.
- Fixed routine dialog dropdowns not responding to mouse clicks.
## 0.0.14-beta.1
- First beta release. Cline Code Beta installs side by side with the stable app so you can compare the two, and updates automatically from its own beta channel — stable installs are unaffected.
- Cloud sessions (preview): run sessions in Cline's cloud straight from the desktop app. Connect GitHub during onboarding, pick a repository and branch, and hand sessions off between devices — transcripts, approvals, and queued prompts stay in sync, and you can rename cloud sessions and switch models mid-session. Turn it on with the Cloud sessions toggle in Settings.
- Avatar overlay (preview): a floating desktop companion that reacts to what your sessions are doing.
- Onboarding now includes a GitHub integration step.
- Early proof of concept for running sessions in SSH remote environments.
- Includes everything from the upcoming stable release: microphone voice input in the composer, model-driven image generation, redesigned question prompts, animated reasoning and tool disclosures, and session list polish.
- `<sessionId>.messages.json` is expected to contain ordered messages plus assistant `modelInfo` and `metrics` (including cache token fields when provided by the model runtime).
- `<sessionId>.hooks.jsonl` is observability/debug telemetry and should not be required for normal history replay/export flows.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.