* fix(cli): render MCP tool result text instead of escaped JSON in TUI
MCP tools return {content: [{type: "text", text}]} which
extractFullOutputText JSON-stringified, escaping newlines into one giant
line that word-wrapped across the whole terminal and never triggered the
line-based collapse. Extract the text parts with real newlines so the
existing collapse works.
Fixes#13038
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): keep placeholders for non-text blocks in mixed MCP results
Addresses Greptile review on #13066: text-only filtering silently
dropped image/resource/audio blocks from mixed MCP content. Render them
as [type] placeholders instead.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): surface non-text MCP block metadata in TUI output
Extract embedded resource text, and include resource/resource_link URIs
and image/audio mime types in placeholders so expanded mixed MCP
results keep identifying metadata.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
The stdio MCP client gave servers without a configured `timeout` only
1.5 seconds to answer initialize before killing the process, so
slow-starting servers (e.g. Oracle SQLcl's JVM-based `sql -mcp`) could
never load and were silently skipped at session start.
Raise the default connect budget to 30s, in line with the startup
budget other MCP clients allow. A configured `timeout` still overrides
it in either direction, dead commands still fail fast through the spawn
error/exit path, and the newline -> Content-Length framing fallback is
unchanged.
Fixes#13035
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(desktop): route /team prompts through core runtime
Rewrite desktop `/team` commands as structured user command blocks before sending them to the core runtime. Validate task input and respect the globally disabled Teams tool setting.
Remove legacy agent spawn and team enablement flags from session configuration, and add coverage for prompt rewriting and disabled-tool behavior.
* fix(desktop): preserve team tool defaults
* fix(desktop): display queued /team prompts as their slash form
Queued prompts are stored in their runtime form, so a queued /team
command showed its raw <user_command> envelope in the prompt queue chip
and edit textarea. Fold queue items through formatDisplayUserInput for
display; saving an edit re-resolves the slash form through the sidecar,
so the round trip is lossless.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(hub): align builtin tool catalog flags with the desktop sidecar
The desktop sidecar pins enableSpawnAgent/enableAgentTeams when listing
the builtin tool catalog; the hub's parallel listing did not, so the two
would drift if the preset defaults ever change. Pin the same flags in
the hub and cross-reference the two call sites.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(desktop): drop inert enableSpawn/enableTeams config leftovers
buildCoreSessionConfig no longer reads these keys, so remove the dead
schema fields, default-config initializers, and chat-test payload
entries. The chat-session regression test still sends them on purpose
to prove legacy flags cannot override the runtime's tool presets.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): reject /team when the mode's tool preset disables teams
The /team guard only checked the global disabled-tools setting, but the
runtime resolves tool availability from the mode's preset, so a preset
without team tools (yolo) would still send the model a spawn-a-team
instruction it cannot act on. Resolve the teams catalog entry for the
session's mode and reject /team when it is unavailable, mirroring the
runtime's own availability logic.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Toggling Plan/Act while a turn was streaming or waiting on a tool approval
aborted the turn but left the TurnStateTracker on its last live phase: the
aborted session's done event is fenced off as stale once the rebuild
unsubscribes it, so nothing ever settled the phase. The webview then kept
rendering that phase forever - an eternal Thinking spinner with the input
disabled (aborted while streaming), or dead Approve/Run Command buttons wired
to an approval that clearPending had already denied (aborted while awaiting
approval). Users experienced this as 'switched to act mode and nothing
happened / it never wrote the files'.
Mirror cancelTask: after aborting the turn for the mode change, append a
resume_task ask row and set the phase to resumable, so the footer offers
Resume Task with the input enabled in the new mode.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: native-feel polish and render-path performance fixes
- Suppress the WebView browser context menu on app chrome (keep it for
editable fields and active text selections)
- Make UI chrome unselectable app-wide; opt chat messages, markdown,
code, diffs, and error banners back into text selection
- Contain overscroll so inner scrollers don't rubber-band the window
- Lazy-load Settings/Sessions/Onboarding/Diff views out of the entry chunk
- Memoize ChatInputBar and AgentHeader; stabilize their props in the chat
pane so stream flushes only re-render the affected message bubble
- Stop refocusing the composer textarea on every keystroke (caret flicker)
- Cache slash commands across menu opens (stale-while-revalidate)
- Avoid rebuilding reversed message arrays and ask-question JSX per render
- Drop core info/debug console logging on the streaming hot path behind a
cline:debug-logs opt-in; remove leftover [webview:delete] debug logs
- SearchCombobox (provider/model picker): Escape closes and restores focus
- Remove unused @vercel/analytics, recharts, embla-carousel deps and the
unused chart/carousel UI components
* desktop: surface failed-turn errors instead of leaving the chat blank
On a failed run the runtime reports its error string in result.text.
The webview rendered that as an assistant bubble, which the canonical
history rehydration then wiped (the failed turn is never persisted),
so provider errors like a retired model id left the user staring at a
silently empty chat. Route failed-turn text to a persistent error-role
message added after rehydration instead.
* desktop: fade the welcome/conversation swap instead of hard-cutting
Sending the first message replaced the hero layout with the message
grid in a single commit, which read as a white flash. A 180ms enter
animation now plays when either side becomes visible; disabled under
prefers-reduced-motion.
* desktop: render new-chat panes instantly from the last catalog load
Clicking + remounts ChatThreadPane, which refused to render until the
provider catalog (a large fetch) and workspace list resolved again —
about a second of blank pane plus boot spinner on every new chat.
Seed remounts from a module-level snapshot of the last successful
load; the mount effect still refreshes both in the background.
* desktop: invalidate the provider-catalog snapshot with the cache
Seeding remounted chat panes from the last catalog load left a window
where a pane created right after a credential change could act on the
old keys. The snapshot now lives in the catalog module and is dropped
by invalidateProviderCatalogCache(), so credential edits force the
next remount to wait for fresh data.
* fix(cli): harden tool input/output formatters against malformed payloads
Tool inputs cross the model/tool boundary and may not match their
TypeScript annotations (e.g. run_commands with { command: null }).
truncate() called str.replace() on such values, crashing the TUI with
'.replace is not a function' and making persisted sessions containing
the payload non-resumable, since hydration replays the same input
through formatToolInput().
Normalize untrusted values at the formatting boundary: truncate() now
accepts unknown and safely stringifies null/undefined/objects (including
circular structures and throwing toJSON), formatStructuredCommand no
longer returns non-string commands verbatim, and fetch_web_content
request summaries tolerate malformed entries.
Fixes#13036
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): keep valid empty-string args in structured command summaries
Greptile review: filtering normalized args by truthiness also dropped
genuine empty-string argv entries, so summaries could show a different
argument list than the one executed. Filter only nullish entries before
normalization instead.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): fall back to session cwd/Desktop for @-mention search in empty windows
* fix(vscode): use the shared chat workspace as the no-folder fallback root
* fix(desktop): treat signed-out state as a typed result instead of a command error
* fix(desktop): sign out when the organization balance fetch reports the typed signed-out result
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: retrigger checks after runner outage
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(ui): introduce Cline-owned semantic color system
* refactor(desktop): adopt shared semantic theme roles
* refactor(ui): set 15px root and recalibrate xs/sm type scale
Scale rem steps so xs/sm stay 12/13px visually, and slightly lift dark-mode neutral-4.
* refactor(ui): align SearchCombobox with package type and hover tokens
Use host-safe cline-ui utilities and keep option font inheritance from CSS.
* fix(ui): use standard stroke-2 utility on approval spinner
* refactor(desktop): modernize shared UI primitives for Tailwind v4
Replace legacy arbitrary/has selectors with current utility syntax.
* refactor(desktop): bump chat chrome typography to text-sm
Keep composer controls and pickers on the shared sm type step.
* refactor(desktop): use max-w-344 for page frame content width
* chore(desktop): disable Next.js dev indicators
* chore: ignore desktop-app Cursor settings
* docs(pr): add before/after screenshots for #12941
* chore: retrigger checks
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* desktop: fix silent turn failures, message duplication, and stuck composer; add first-run setup guidance
Findings from two full computer-use UX audits of the desktop app:
- Surface failed turns in the transcript: queued turns (incl. the first
prompt of a fresh session) only signal errors via chat_done, which the
UI previously ignored - sending a message with no credentials failed
in complete silence. Failed turns now show an error message enriched
with the latest core error log and a pointer to Settings -> Models.
- Fix duplicated user messages: a live send's optimistic user message
was materialized a second time by the runtime's queued-prompt-start
event.
- Fix composer stuck on 'Agent is working...': drop prompts from the
local queue snapshot when they start, emit a fresh queue snapshot from
the sidecar on pending_prompt_submitted, and double-check the server
queue on turn completion.
- Add a 'Connect a model' notice on the welcome screen when no provider
has credentials, with actions to reopen onboarding at the connect step
or jump to model settings; it reacts live to credential changes.
- Add 'Get an API key' links for popular providers in onboarding and
Settings -> Models (the catalog docUrl is never populated), and link
the Cline dashboard from the Cline API key form.
- Explain what Cline is on the onboarding welcome step.
- Make the stop button visible (was 8px with no padding) and support
Esc to stop; add Cmd/Ctrl+N (new session) and Cmd/Ctrl+, (settings).
- Remove leftover [webview:delete] console.error debug logging that
surfaced an error badge after deleting a session.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: remove remaining delete debug logging in session history hook
The sidebar right-click delete path had the same leftover [webview:delete]
console.error instrumentation, which made the Next dev-mode issues badge
appear after every deletion.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: fix Biome a11y error in WelcomeSetupNotice
biome's lint/a11y/useSemanticElements errors on role="status" divs;
use the semantic <output> element (implicit status role) instead. This
was failing the repo's 'bun run lint'.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: count structured-config and keyless providers as connected
The welcome setup notice previously only recognized apiKey/OAuth
credentials, so users running Bedrock/Vertex (structured configValues)
or a deliberately enabled keyless local endpoint (e.g. Ollama) were
nagged to connect a model they already use. isProviderConnected now
also counts an enabled provider whose required config fields are all
filled, or an enabled provider that has no API-key field at all.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* desktop: keep re-key eligible when chat_done lands in the same batch as its prompt start
When a turn fails fast, chat_queued_prompt_start and chat_done can be
dispatched in one React batch. Clearing the outstanding-optimistic-
bubble registry synchronously in the chat_done handler ran before the
re-key updater enqueued by the prompt-start event, so the optimistic
bubble was appended a second time instead of re-keyed. Clear the
registry inside a state updater so it executes in event order after
the re-key. Caught by the queued-turn-failure regression test.
* desktop: make the queued-prompt re-key updater idempotent under StrictMode
React StrictMode double-invokes state updaters in dev. The
chat_queued_prompt_start re-key updater consumed the optimistic
bubble's id from outstandingOptimisticUserIdsRef on its first run, so
the second run against the same prev found no eligible candidate and
appended the same user message a second time (and, without a promptId,
makeId() minted a different id per invocation). Hoist the message id
out of the updater and remember which optimistic bubble each queued
message id re-keyed so a re-run reaches the identical result. The memo
resets alongside the outstanding set (error state, reset, hydration).
Root-caused with runtime instrumentation: the duplicate only appeared
on turns that exercised the queue-drain re-key path, and hydration
later collapsed it to one message because the duplicate never existed
in persisted state.
* desktop: preserve failure messages across post-send canonical hydration
Persisted history never contains UI-only error bubbles, so the two
post-send read_session_messages replacements in sendPrompt wiped the
failure explanation appended from chat_done ~40ms after it rendered
(confirmed with runtime instrumentation). Re-append the active
session's error messages after the canonical history. Includes a
regression test reproducing the chat_done-error-then-RPC-resolution
race.
* desktop: don't let an optional API-key field veto a connected provider
Greptile P1 follow-up: Bedrock's catalog entry carries an optional
apiKey field ('Optional Bedrock bearer token') alongside IAM/profile
authentication, and keyless local endpoints can also surface one — so
treating the mere presence of an apiKey field as proof of disconnection
kept nagging configured users. An enabled provider (the user
deliberately persisted settings for it) now counts as connected unless
a required config field is unmet; auth may legitimately live outside
the catalog (IAM, env vars, local endpoints). Brand-new users have no
enabled providers, so the first-run notice still shows for them.
* desktop: tighten credential-error guidance and stop re-pinning stale failure bubbles
* desktop: invalidate the shared provider catalog after settings OAuth login
Greptile P1 follow-up: runOAuthProviderLogin only updated the settings
view's local provider state, so the shared catalog cache and its
invalidation subscribers (the composer selector and the welcome
screen's 'Connect a model' notice) kept reporting the provider as
disconnected until an unrelated invalidation or a pane remount. Notify
the shared cache on successful OAuth login, like the account view and
the API-key save path already do.
* desktop: clear the remembered core error on turn end, reset, and hydration
Greptile flagged that turn-start events are the only thing clearing
lastCoreErrorBySessionRef, and websocket events are not replayed: a
transport interruption that drops a turn's start event lets a later
detail-less failure resurrect an earlier turn's error. The remembered
error belongs to exactly one turn, so clear it whenever a turn ends
(chat_done, any reason) as well as on reset() and history hydration.
Regression test covers the dropped-start-event sequence.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(vscode): explain when a free model promotion ends
Once a free promotion ends, the cline-free/ model is removed from the
catalog and the backend answers 'model not found' to requests against it.
The CLI has shown a dedicated 'Free model promotion ended' banner for this
since #12593; the extension instead rewrote the answer into generic
model-not-found guidance with no model-picker offramp.
Detect the case in the host where the active model id is known
(reshapeErrorForWebview, fed by a new MessageTranslatorState model-id
source), stamp the payload with a cline_free_promotion_ended code, and
render a dedicated card in the webview with a button into the model
picker. Classification is gated on the cline-free/ prefix so ordinary
model-not-found errors keep their generic path, and it runs before the
auth branch since the 404 status falls inside the generic auth range.
* fix(vscode): prefer the live task model over session-start metadata
A mid-task model-only switch updates the running session's model in place
(updateActiveSessionModel) and refreshes the task API shim, but never
touches the session's startConfig/manifest. Preferring the session-start
snapshot could therefore misclassify after such a switch: a genuine
retired-model 404 would miss the promotion-ended card, and the reverse
switch could show it for the wrong model. Provider switches restart the
session, so both sources agree there; the shim starts as "unknown"
(filtered out), so fresh sessions still resolve through start metadata.
* fix(desktop): dedupe chat_queued_prompt_start emitted for the same prompt
PendingPromptService.drain() emits a pending_prompts snapshot (head
removed) and a pending_prompt_submitted event back-to-back for the same
prompt. The sidecar translated both into chat_queued_prompt_start, so
the webview rendered the user's message twice until the chat was
re-hydrated from history. Track the last announced prompt id per live
session and emit the start chunk once.
* fix(desktop): re-key optimistic user bubble when the runtime queues the prompt
The send path renders an optimistic user bubble for prompts dispatched
while the session is idle, keyed by a random id. When the runtime
routes that prompt through its pending queue (e.g. during session
startup), the queued-prompt-start event appended a second bubble under
queued_user_<promptId> — the same message rendered twice until the
chat was re-hydrated from history. Re-key the trailing optimistic
bubble to the event's id instead of appending.
* fix(desktop): re-key only outstanding optimistic bubbles on queued prompt start
Review follow-up: matching by content alone could swallow a new queued
prompt that repeats the text of a message left at the transcript tail
by an earlier cancelled/failed turn. Track in-flight optimistic bubble
ids explicitly (registered on optimistic append; cleared on re-key,
turn end, error, and history hydration) and only re-key those.
* fix(core): don't count plan-mode guard-blocked commands as model mistakes
The plan-mode command guard (#12906) rejects file-editing run_commands
calls with a tool error. The orchestrator counted that error as a failed
tool call, so a turn whose only tool call was guard-blocked fed the
MistakeTracker, which emits a recoverable "error" AgentEvent
("1 tool call(s) failed: [run_commands] ...").
Hosts render that event as a failed turn. In the VS Code extension the
turn ended in the "error" phase (Retry / Start New Task footer), the
final plan text was never retagged to plan_completion_result, and
toggling to Act therefore rebuilt the session without the auto-continue
send - the toggle appeared to do nothing and the presented plan was
never acted on. In the CLI TUI the same event flipped the footer to
idle mid-turn.
A guard rejection is deliberate session policy, not a model mistake:
the run continues and the model is expected to fold the change into
its plan. Tag the guard error with a stable marker sentence, expose
isPlanModeBlockedCommandError, and skip the failed-tool bookkeeping for
matching results so no mistake is recorded and no error event is
emitted. Repeated blocked attempts are still bounded by loop detection
and maxIterations.
* docs(core): flag plan-mode guard error string matching for typed skip channel
FIXME on isPlanModeBlockedCommandError: recognizing guard rejections by
sniffing the error text is brittle. The intended replacement is a typed
skipSource/skipCode on the tool-finished runtime event so the
orchestrator (and the VS Code approval-denial suppression) can identify
skipped tools structurally instead of via string matching.
* Revert core mistake-counting change for plan-guard blocks
A model attempting a file-editing command in plan mode is disobeying
its instructions - that IS a model mistake, and the MistakeTracker
should keep counting it (it is the brake that stops weak models from
flailing at blocked commands indefinitely). The real bug is host-side:
a recoverable mid-turn mistake must not kill a turn that afterwards
completes with a presented plan. The follow-up commit fixes that in
the hosts instead.
* fix(vscode,cli): treat recoverable agent errors as in-run notices, not turn outcomes
The MistakeTracker emits a recoverable error event for every recorded
mistake while the run continues - e.g. a plan-mode guard-blocked
run_commands call as the turn's only tool call. Both hosts treated any
error event as terminal:
- The VS Code translator cleared the pending completion retag, set
errorSeen (turn phase "error": Retry / Start New Task footer), marked
the turn complete, and rendered the error recovery UI. A plan turn
that recovered from the mistake and completed cleanly therefore never
produced plan_completion_result, so togglePlanActMode's planPresented
check failed and switching to act mode rebuilt the session without
the auto-continue send - the toggle appeared to do nothing.
- The CLI TUI flipped isRunning/isStreaming to idle mid-turn, so the
footer lied about the still-running turn.
Recoverable errors are informational: the turn's outcome is decided by
how it actually ends (done/error). VS Code now logs them and keeps them
out of the chat (the tool failure is already shown inline on its tool
row, and provider-failure telemetry already ignores recoverable events
for the same reason); the CLI keeps its running state and surfaces them
only in verbose mode, as it already did for display. Genuine run
failures carry recoverable: false and keep the existing error UI.
* fix(vscode): show cwd-relative tool paths in the chat view
The SDK message translator copied the model's absolute file paths straight
into the ClineSayTool messages, so chat cards like "Cline wants to read this
file" showed full absolute paths. Relativize them against the task's cwd for
display (classic getReadablePath behavior: relative inside the cwd, basename
for the cwd itself, absolute when outside), including apply_patch's
"*** Update File:" markers which DiffEditRow parses for its headers.
Also restores the readFile card's click-to-open target by setting content to
the absolute path, matching the classic extension.
* refactor: apply display-path relativization as a single ClineSayTool transform
Instead of threading cwd through every case of sdkToolToClineSayTool, leave
the tool mapping untouched and apply one toDisplaySayTool transform (with a
filesystem-path tool whitelist) at the points where tool cards are emitted.
Same behavior, much smaller footprint; MCP/unknown tools keep their exact
prior behavior.
* fix: keep absolute readFile open-target untouched on Windows; match '..' as whole segment
path.resolve(cwd, absPath) rewrites a drive-less absolute path onto the
current drive on Windows, breaking the readFile card's click-to-open target
(and the tests asserting it). Guard with path.isAbsolute instead.
Also match '..' only as a whole path segment in toDisplayPath so an in-cwd
entry literally named '..config' is not misclassified as outside the cwd
(greptile P1).
* fix: keep Desktop-fallback paths absolute; relativize '*** Move to:' destinations
When VS Code has no workspace open, getWorkspaceRoot() falls back to the
Desktop; classic getReadablePath deliberately keeps full absolute paths in
that case so the user can see where operations occur. Restore that guard in
toDisplayPath.
Also enroll PATCH_MARKERS.MOVE in relativizePatchPaths so a rename renders
both source and destination relative (covers the split-patch path too).
* fix(desktop): show skills in slash command menu
* fix(core): normalize runtime slash command names
* fix(core): disambiguate colliding slash commands
* fix(core): preserve same-kind slash commands
* fix(core): stabilize colliding slash command aliases
* fix(core): avoid slow runtime command regex
* fix(core): remove quadratic hyphen trim
* fix(core): prefer skills over workflows on slash command collisions
Workflows are effectively deprecated in favor of skills, so when a
workflow's normalized name collides with a skill the skill now owns the
token and the workflow is dropped. This removes the collision
qualification machinery (-skill/-workflow/-hash aliases), which silently
renamed established CLI and VS Code command tokens, and removes the
duplicate-token throw that sat in the CLI send path, the hub snapshot
capability, and the desktop list_user_instruction_configs command.
Same-kind collisions resolve to the first entry of the deterministic
(name, id) sort, stable across discovery order.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(vscode): resolve typed workflow filenames through record ids
Name normalization broke the legacy /my-workflow.md fallback for
workflows renamed via frontmatter: the configured record name (e.g.
"Ship It") no longer compares equal to the normalized command token
("ship-it"), so a typed filename stopped expanding. Match the discovered
record to its runtime command by the stable record id instead, keeping
the canonical-name comparison as a fallback for callers that pass
records without ids.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): normalize snapshot names in hub slash command proxy
The hub-side proxy normalizes the typed token but compared it against
snapshot command names verbatim. Snapshots served by older clients carry
raw configured names (e.g. "Ship It"), which could previously exact-match
typed input and would now never match. Normalize both sides of the
comparison so mixed-version hub setups keep resolving.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): expand slash commands in the sidecar send path
Selecting a skill or workflow from the desktop slash menu inserted the
token but the sidecar dispatched it verbatim, so the model received
literal text like '/publish-ui write docs' instead of the configured
instructions. handleSend now expands a leading runtime slash command via
the core user-instruction service before dispatch (mirroring the CLI's
buildUserInputMessage), keeping the raw token as the session's display
prompt. Built-in webview commands (/fork, /team) and unknown tokens pass
through unchanged, and discovery failures fall back to the raw prompt.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): expand slash commands when editing queued prompts
Editing a pending prompt stored the raw slash token, which the runtime
later delivered to the model unexpanded — only the initial send path
went through expandRuntimeSlashCommand. handleUpdatePendingPrompt now
expands a leading skill/workflow token before persisting the update,
matching the enqueue behavior in handleSend.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): preserve Unicode letters in slash command tokens
Normalization stripped all non-ASCII characters, so a skill named 发布
got an unrelated generated token while typing /发布 could never resolve —
a regression from pre-normalization behavior where the exact name
matched. Keep Unicode letters and numbers in normalized tokens and only
collapse whitespace and symbol runs into hyphens.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* Fix installed plugins all displaying as "index" in the desktop app
Hoist getPluginDisplayName (nearest-ancestor package.json name with
basename fallback) into @cline/shared storage paths, re-export it via
@cline/core, and replace the duplicated copies in cline-hub, the CLI
TUI, and VS Code marketplace helpers. Fix the desktop sidecar and
'cline config plugins', which still named plugins by entry-file
basename, so package-backed installs showed up as "index".
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Use shared getPluginDisplayName in desktop sidecar after #12933
Merging main brought in PR #12933, which fixed the desktop plugin
naming with another local copy of the helper. Drop that copy in favor
of the shared @cline/shared implementation this branch introduces, and
remove the node:path imports it needed.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
The webview posts an optimistic say:'task' message carrying the user's
images/files, and only clears it once an identical authoritative message
arrives from the extension. emitInitialTaskMessage omitted attachments,
so the optimistic copy was never confirmed and withPendingUserMessage
kept re-injecting the old task into the transcript even after New Task
cleared it - leaving the chat permanently stuck on the previous task.
- Include images/files on the authoritative initial task message so the
optimistic pending copy is confirmed and cleared as designed.
- Defensively drop any unconfirmed optimistic message in startNewTask so
an explicit New Task click always yields a clean slate.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): track external git branch changes in the TUI status bar
The branch shown below the prompt was read once at startup and only
refreshed after an agent turn, so checkouts made from another terminal
or an editor left the TUI showing a stale branch (#12911).
Watch the repo's git dir for HEAD changes (git replaces HEAD via
rename, so a directory watch is used) and refresh the status bar
immediately, with a slow 5s poll as a fallback for filesystems where
fs.watch is unreliable. State updates are skipped when nothing changed
to avoid needless re-renders.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(cli): replace subprocess polling with stat-based HEAD backstop
Drop the unconditional 5s git-subprocess poll from useRepoStatus. The
fs.watch directory watcher stays for instant updates where the runtime
delivers HEAD events, but Bun on Linux drops them, so add fs.watchFile
on the HEAD file as the backstop: one in-process stat() every 2s that
only triggers git subprocesses when HEAD actually changed. Verified in
the Bun-run TUI that external checkouts show up within ~2s.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(cli): simplify HEAD watching to a single fs.watchFile
Drop the fs.watch directory watcher (Bun on Linux never delivers its
HEAD events, making it dead weight on the runtime the CLI ships on) and
the debounce it required. watchGitHead now just stat-watches the single
.git/HEAD file via fs.watchFile, which survives git's rename-based HEAD
updates and works on network mounts.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(cli): use a plain 5s poll for repo status
Remove the HEAD watcher entirely per review preference for minimal
code: root.tsx now just polls readRepoStatus every 5 seconds, skipping
state updates (via isSameRepoStatus) when nothing changed so idle ticks
don't re-render the app.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): skip repo status poll ticks while a read is in flight
Bounds concurrent git subprocesses when a read exceeds the 5s interval
(slow git on huge repos) and prevents an older completion from
overwriting newer status. Addresses Greptile review feedback.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
The copy/fork (and user copy/edit/restore) action row was pulled up 8px
(-translate-y-2), which made the icons collide with the descenders of the
message's last line of text. Reduce the raise to 4px (-translate-y-1) so the
actions sit with a small, deliberate gap under the message content while
still hugging the message closely enough to read as attached to it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Remove model-initiated plan-to-act switching from the VS Code extension
Match the legacy extension: the model can no longer call switch_to_act_mode
to move itself from plan mode to act mode. The user must flip the Plan/Act
toggle manually. The CLI keeps the tool and its prompt unchanged.
- Stop registering the switch_to_act_mode extra tool in plan-mode sessions
and drop the pending-mode-change queue, beforeModel stop hook, and idle
apply path that existed only for the tool-initiated switch.
- Add a planModeSwitchTool option to buildClineSystemPrompt (default true,
CLI output unchanged) and a PLAN_MODE_INSTRUCTIONS_MANUAL_SWITCH variant
that directs the model to ask the user to toggle to Act mode instead of
calling a tool it does not have; the extension passes false.
- The user-driven toggle path (togglePlanActModeProto), including
auto-continue when a completed plan is presented, is unchanged.
* Use a generic completing-tool name in translator retag test
Review feedback: submit_and_exit is a yolo-mode tool and does not exist
in plan/act sessions. The test exercises tool-agnostic translator
behavior, so use a neutral example name and clarify the comment.
* fix(cli): claim connector instance before socket connect
Prevent racing foreground or detached connector launches from
both opening socket-mode with the same bot token by exclusively
claiming the state file via tryClaimConnectorStateFile before
connecting, and exit with CONNECT_ALREADY_RUNNING_EXIT_CODE when
another live instance already holds the claim.
* serializes stale-generation replacement without a removable mutex
* lint
* fix
* base
* fix(cli): keep pre-claim Slack state files manageable
State files written by CLI versions that predate connector claiming have
no claimId, so requiring it in readConnectorState made a live legacy
connector invisible: stop deleted its state without stopping it, which
let the next connect open a second socket-mode connection with the same
bot token. Treat claimId as optional metadata; claiming itself never
relied on the validator.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore(telemetry): remove duplicate capture defs owned by @cline/core
The cline-core bundle layer (apps/vscode, which also compiles into the
cline-core.js that JetBrains runs) still carries event constants and
public capture methods inherited from the legacy architecture. Where
@cline/core now emits the same event, the bundle-layer twin is a second
capture path on the same telemetry service — which is how the
task.provider_api_error double-emission happened (cline/cline#12820,
follow-up to the removals in cline/cline#12818).
Removes 20 such methods and the EVENTS constants only they referenced:
- 15 whose signal @cline/core emits today (task lifecycle, tokens, tool
and skill usage, auth start/success/failure, opt-out, workspace init,
and summarize_task, which core replaced with task.compaction_*).
- 3 obsolete on both architecture lines: captureModelSelected (its
model_selected signal survives as an action on
captureOnboardingProgress), captureRulesMenuOpened, captureHostEvent.
- 2 whose trigger moved into core/sdk, so this layer can no longer
observe them and re-wiring here would be wrong:
captureWorkspacePathResolved (core already owns workspace.path_resolved)
and captureGeminiApiPerformance (providers live in core; generic
provider-timing events supersede it).
Deliberately NOT removed: capture methods with no caller here but a live
caller on legacy-extension. Those emit signals originating in this bundle
(webview UI, VS Code storage, host terminal, checkpoints, focus chain,
legacy-task migration), so core cannot emit them and the missing piece is
a call site on this line, not a redundant definition. They are the
SDK-parity backlog and are flagged as such in the file.
Verified against the JetBrains plugin repo: it references none of these
methods or event names, and no proto surface changes.
Also drops the unused TokenUsage interface, the taskTurnCounts and
taskToolCallCounts maps (only deleted methods wrote to them), and EVENTS
constants that were already orphaned before this change.
Tests that only exercised a removed method are gone; tests that used one
merely as a vehicle for provider/metadata assertions now use a surviving
method, so that coverage is preserved.
* fix(telemetry): keep agent identity on events dispatched after session teardown
A small share of task.tool_used events (~285 of 229k over 48h on
extension_variant=next) arrive without any agentId/agentKind/isSubagent
attributes. Root cause: AgentEventBridge.dispatchAgentEvent resolves
identity solely from the live-session map (AgentEvent metadata never
carries agentId in practice), and session teardown deletes the map entry
before the agent's run fully drains — dispose/stopSession paths can skip
or fail agent.shutdown() without aborting first. Late events from the
still-draining run then hit the session-map miss branch, which passed no
identity at all, so buildTelemetryAgentIdentity returned undefined and
the event was emitted bare.
Fix: snapshot the identity stamped on each session's events while the
session is registered (bounded FIFO map) and reuse it on a session-map
miss. Purely additive — no event is added, removed, or renamed; the
live-session and sub-agent paths emit byte-identical properties.
* feat(sdk): add session initiation mode and lazy session persistence
- Introduce top-level `StartSessionInput.mode` (`user`, `automation`, `subagent`, `team`) alongside `source`, so persisted history records both the client surface and how the session began; missing mode defaults to `user`.
- Make root-session persistence lazy: starting a runtime allocates the session ID in memory without creating a database row, manifest, or messages artifact. The first accepted user turn persists that same ID, so closing a runtime before any user turn leaves no empty history entry, and persistence never allocates a replacement ID for unknown sessions.
- Require automation runtime adapters to explicitly persist `mode: "automation"` for every run.
- Document the provenance model in `sdk/ARCHITECTURE.md`, update the VS Code session factory comment, and add tests for the automation runtime handlers.
* fix(sdk): persist automation trigger source as session provenance
The runtime adapters stopped writing the cron request source into the
session row when source became the client surface, which silently
dropped the spec-defined trigger label. Record it as
sessionHistoryOrigin.trigger instead, surface it in the messages-file
origin, and sort the new history-origin import in HubRuntimeHost.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(ollama): use native AI SDK provider
* fix(ollama): patch ollama-ai-provider-v2 wire contracts and lock them with real-provider tests
The pinned ollama-ai-provider-v2@4.0.1 breaks four native Ollama wire
contracts (review findings on #12892). Patch the package via Bun
patchedDependencies:
- omit think from the request when no reasoning setting resolves,
instead of forcing think: false (lets the server default apply)
- surface mid-stream {"error": ...} objects as error stream parts with
an error finish reason, instead of dropping them before a clean finish
- serialize attachment-only user turns as string content (""), not []
- include the documented tool_name field on tool result messages
Add ollama.wire.test.ts exercising doStream through the vendor module
against the real (patched) package with a stubbed fetch, asserting on
the actual /api/chat request bodies and parsed stream so regressions in
the dependency's request converter or stream parser are caught.
* fix ollama model list refresh
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix: prevent duplicate connector launches during doctor/connect
Mark connectors as starting before the hub daemon spawns so autostart
skips in-flight instances, and improve doctor process filtering with
container-aware namespace/cgroup checks plus detached log rotation.
* Update apps/cli/src/connectors/common.ts
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
* feat(hub): supervise connector processes
* feat(connectors): enable tools by default, and stop replaying the Slack greeting
Tools were on by default only for Telegram (via --no-tools); Slack, Discord,
Linear, Google Chat and WhatsApp all required an explicit --enable-tools. All
six now default to tools on and opt out with --no-tools.
--enable-tools still parses everywhere, including Telegram which never accepted
it, so deployed scripts, systemd units and persisted autostart arguments keep
working. Passing both resolves to the safer answer: --no-tools wins. This also
affects hub/webview starts, which never emitted a tools flag and so ran those
five connectors with tools off.
Slack no longer posts the "Connected to Cline." first-contact message. It was
gated on per-thread welcomeSentAt, so a connector restart or a cleared history
made the next user message look like first contact and replayed the greeting.
The host mechanism is unchanged and the other adapters still greet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(connectors): recover a thread whose session is wedged mid-run
A connector thread keeps a long-lived mapping to a hub session. When that
session's runtime still had a run in flight and no abort had been requested,
every message in the thread came back as "SessionRuntime.shutdown called while a
run is in progress" instead of an answer, and stayed that way until someone
cleared the binding by hand. Observed on the Cline Mom Slack bot after a stack
restart.
The connector host already recovers from a session the hub no longer knows
about: it forgets the mapping and replays the turn once against a fresh session.
This widens the trigger from "session not found" to "session cannot serve
another turn" via isUnusableSessionError, so a wedged runtime takes the same
path.
The shutdown error now carries a stable code (SessionRunInProgressError,
session_run_in_progress) so callers can recognise it structurally. The predicate
also matches on message, because an error reaching a connector has crossed the
hub's JSON boundary and arrives as a bare message - and because a host commonly
runs a hub and CLI of different versions. Ordinary run failures still propagate
untouched: replacing the session on those would hide real errors and drop the
conversation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(connectors): serialise turns that share a session
Answering "what happens if I message the bot in another thread while it is still
replying": channel threads were already independent, but DMs were not.
findBindingForThread deliberately reuses one binding — and therefore one runtime
session — for every message in a DM channel, so a DM stays one continuous
conversation. The turn queue, though, was keyed by thread id, and a DM thread id
carries the message timestamp. Two messages in flight in the same DM therefore
got two independent queues and ran concurrently against a single session, which
fails with "shutdown called while a run is in progress" or interleaves two
conversations in one session history.
The queue key now follows the same identity rule as the binding lookup, via
resolveThreadTurnQueueKey next to findBindingForThread so the two cannot drift.
DM messages queue behind each other on the shared session; channel threads keep
their own key and still run in parallel. Applied to all six adapters, which all
had the same mismatch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(core): abort an in-flight run before tearing its session down
Where the Slack bot's "plugin-sandbox process exited (code=null, signal=SIGTERM)"
came from, and its "shutdown called while a run is in progress" sibling: both are
one event, a session released while a run was still going.
stopSession aborts the agent first "so shutdown can proceed", but callers that
reach shutdownSession or releaseSessionRuntime another way did not - hub
dispose() on a restart being the one that hurt. Without an abort the runtime
refuses to shut down, that error is rethrown from the cleanup, and the plugin
sandbox is SIGTERMed while tool calls are still pending, so those calls reject
with "plugin-sandbox process exited". A connector turn awaiting the run reports
whichever surfaced first instead of answering.
Both paths now abort and let the run drain before shutting the agent, runtime and
sandbox down, guarded on session.aborting so callers that already aborted do not
abort twice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(connectors): stop announcing "Steering current task."
Every follow-up sent while the bot was replying added an acknowledgement line to
the thread, and the wording overstated what happens: the host treats delivery
"steer" the same as "queue", enqueuing the prompt for the session rather than
injecting it into the loop already running. The follow-up is now handed over
silently.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(core): retire a dead supervised entry before replacing it
A start arriving while an instance sat in backoff left the old entry's
restart timer live. The timer closes over the old entry object, so when
it fired it spawned a second process for the same (channel, instanceId)
- untracked by the supervisor's map, so invisible to list() and
unreachable by stop() - two connectors holding one bot token, which is
the exact failure supervision exists to prevent. Its exit handler then
kept reaping the live instance's state and rescheduling restarts.
The same window exists before the timer is even scheduled: the
exit-cleanup chain runs first, and a replacement made mid-chain would be
followed by a restart scheduled for the retired entry.
start() now retires a dead existing entry explicitly - cancel its timer,
mark it stopped, drop its exit listener. Both the timer callback and the
cleanup chain already stand down on "stopped", so one mark covers both
phases.
* fix(core): serialise supervisor start/stop and wait for stopped processes to die
Found by exercising a hub restart against a live webhook connector: the
new hub's boot reconnect restarts the adopted survivor - which suspends
inside stop() on the CLI cleanup - while the user's `cline connect`
arrives as connector.start. With no per-instance serialisation the two
starts interleaved across that suspension and both spawned. The map
tracked one process while the other lived on untracked, holding the
connector's webhook port; the tracked chain crash-looped on EADDRINUSE
through all five attempts and ended state=failed, while the ghost kept
running with no way to reach it through list() or stop().
Two changes:
- start/stop (and the backoff-restart spawn) now run under a per-
instance-key promise queue, so one instance has exactly one lifecycle
operation in flight. The exit-cleanup chain also stands down when its
entry is no longer the one in the map.
- stop() waits for the process to actually die after SIGTERM (bounded,
then SIGKILL) instead of returning while it still holds its listen
port - the race that turned the double-spawn into a crash loop, and
that could burn a backoff cycle on any webhook connector restart.
process.kill is now injectable (killProcess), which also stops the test
suite from signalling arbitrary real pids like 600 on the host.
Verified live: the same kill-hub-then-reconnect sequence now converges
to one tracked running process, with the concurrent user start
correctly answered "already running under the hub".
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Tauri's universal-apple-darwin target lipos the Rust binary but expects
sidecars to already be fat binaries, so build-sidecar-bin.ts now compiles
both Bun slices and merges them when the target triple is universal.
The publish workflow builds one universal bundle instead of a two-leg
matrix, verifies every Mach-O in the bundle carries both slices, and the
updater manifest points both darwin-aarch64 and darwin-x86_64 at the same
universal artifact so existing per-arch installs migrate automatically.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Add user-selectable color themes to the CLI TUI
Adds a theme system to the interactive TUI (cline -i):
- New tuiTheme global setting persisted in global-settings.json
- Built-in themes: Auto (terminal-adaptive, default), Cline Dark,
Cline Light, Tokyo Night, Gruvbox Dark, Nord, Dracula, Catppuccin
Mocha, One Dark, Solarized Dark, Solarized Light
- /theme command, command palette entry, and a Theme row in
/settings General tab, all opening a live-preview theme picker
- Named themes paint their background, default foreground, accents,
syntax highlighting, and derived diff colors across the TUI
- CLINE_THEME env var overrides the persisted theme at startup
Closes#12872
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Widen theme picker dialog and label column
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Format theme picker
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Give each theme a descriptive picker blurb
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Theme all main-surface components instead of static palette colors
The ask-question / tool-approval element, toasts, queued prompts,
autocomplete dropdown, chat error cards, searchable lists, and the
onboarding screens hardcoded the brand palette (act blue, selection
highlight, black-on-selection text) and fixed dark grays, so they
ignored the active theme.
- ResolvedTheme gains selection/textOnSelection; the selected-row text
flips between black and white by WCAG contrast against the accent
- Inline ask-question / tool-approval, Toast, QueuedPrompts,
AutocompleteDropdown, SearchableList, and chat error cards now use
theme accents and the themed selection pair
- Onboarding screens derive subtle borders/details from the theme
background instead of #333333/#555555, and use themed accents
- Dialog surfaces (settings, pickers, history) intentionally keep their
static dark surface styling
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>