Commit Graph
215 Commits
Author SHA1 Message Date
BeeandSaoud Rizwan 4d28d82efa feat(cli): handle outdated hub sessions with drain and replace flow (#13727)
* feat(cli): handle outdated hub sessions with drain and replace flow

Add logic to detect when the CLI is newer than the running Hub and provide
users with options to either keep the older Hub running (to avoid
interrupting active sessions from other clients) or force-replace it.

Implement `describeOutdatedHubSessions` helper to show quantified session
activity in the dialog, and add `HubOutdatedContent` UI component with
detailed messaging for the `build_mismatch` case. The `unsupported_protocol`
case remains a modal requiring update, while the softer mismatch now uses
a toast with enter-to-replace or escape-to-keep choices.

Includes tests for draining and replacing an older busy hub when forced.

* fix(hub): gate desktop hub_upgrade behind trusted connection and make drain-first a hard guarantee

Address review: an originless local WebSocket client could invoke the
forceful hub_upgrade command, and a failed drain request still allowed a
forced retirement, so work started during the wait window could be killed.

- hub_upgrade now requires the same canApproveTools per-connection gate as
  the tool-approval commands.
- upgradeManagedHub skips the idle-wait window when the drain was not
  established (an undrained hub keeps admitting work, so waiting only
  widens the blast radius) and refuses to replace a busy hub that did not
  accept the drain, force or not. An idle hub is still replaced so
  pre-drain-endpoint hubs (404) remain upgradable.

* fix(hub): treat failed activity readings as unknown, not idle, during hub upgrade

A transient session.list failure inside the drain wait window previously
read as an idle hub, which could end the grace window early and authorize
retirement while turns were still finishing.

- Failed readings never end the wait window early, never overwrite the
  last real observation, and never authorize a non-forced retirement.
- Without force, a hub whose activity was never confirmed is handed back
  un-drained (still_busy) instead of retired; an undrained hub is now
  replaced only when positively observed idle.
- With force and an accepted drain, an unanswerable hub is still replaced:
  the user already consented to interrupting its sessions.

* fix(hub): never retire an undrained hub on an idle snapshot

An older hub that rejects the drain has no admission barrier, so a single
idle reading cannot authorize retirement: a session admitted right after
the snapshot would die in a retire the consent prompt never covered.

upgradeManagedHub now retires a hub only under an accepted drain. The
undrained-idle case is delegated to the locked ensure path, which
re-checks activity immediately before its own retire ladder and attaches
(deferring the swap) when new work arrived in the meantime; the upgrade
then reports still_busy instead of replaced, and the desktop/TUI surfaces
tell the user to retry.

* fix(hub): require an accepted drain unconditionally before any upgrade retirement

Review follow-up: the undrained-idle delegation still reached
retireDiscoveredHub, whose own drain attempt is best-effort, so a session
admitted after the idle re-check could die in the shutdown.

upgradeManagedHub now fails fast when the hub does not accept the drain -
no wait window, no idle exception, no delegation. The drain is the
admission barrier that keeps every subsequent reading true through the
retire; a hub too old or wedged to accept it is left to the automatic
ensure path, which replaces it once idle at the next client startup, and
the error says so.

* fix(hub): establish the drain barrier before the automatic idle check

Review follow-up: the automatic incompatible-hub path read session
activity first and drained only inside the retire ladder, so a session
admitted between the idle snapshot and the shutdown could be terminated.

retireIncompatibleHub now requests the drain before the busy check: with
the drain accepted, the idle reading stays true through the retire. A
deferred (busy) hub, and one whose retirement fails or is skipped by the
circuit breaker, gets the drain lifted so it never sits alive-but-refusing
work. Hubs that do not accept the drain (pre-/drain builds answer 404)
keep the historical best-effort snapshot rather than being stranded
forever.

* polish(hub): tighten the outdated-hub dialog copy

Two short sentences instead of four long ones, spell out what Quit Cline
does (closes the app, leaves the Hub running), and rename the action to
Update Now in both the desktop dialog and the TUI variant.

* fix(cli): show the keep-Hub reminder toast when the outdated-hub dialog is dismissed (#13754)

dialog.choice() resolves undefined on Esc rather than rejecting, so the
reminder toast in .catch() never ran. Move it to the falsy branch of
.then(), matching the unsupported_protocol handler.

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-09-01 20:40:40 -07:00
Saoud RizwanandSaoud Rizwan b427fae9f9 feat(desktop): group scheduled runs under their schedule in the sidebar (#13752)
* feat(core): stamp schedule id, name, and run number onto scheduled sessions

Sessions started by the cron runner only carried a generic
sessionHistoryOrigin.trigger = "hub-schedule", so clients could tell a
session was scheduled but not which schedule it belonged to or which run
it was. The runner now passes schedule provenance to the runtime handlers,
which merge it into the session metadata alongside the origin trigger:

  scheduleId          the hub schedule's external id
  scheduleName        the schedule title
  scheduleExecutionId the cron run id
  scheduleRunNumber   1-based position among every run created for the spec

The run number comes from a new SqliteCronStore.getRunOrdinal, which counts
runs of every status in creation order so a later cancellation never shifts
numbers already stamped onto earlier sessions. A reclaimed run keeps its
number, so two sessions with the same number make a duplicate visible.

HubScheduleRuntimeHandlers.startSession gains an optional second argument
carrying the metadata; existing implementations that ignore it keep working.

* feat(desktop): group scheduled runs under their schedule in the sidebar

A schedule that fires daily filled the sidebar's Scheduled section with a
row per run, each titled with the same prompt text, which read as if the
task had been duplicated. Runs of one schedule now fold into a single
collapsible row named after the schedule, with the run count on the right;
expanding it lists the runs as "Run N" sub-items (newest first) with their
usual status dot, time, hover card, context menu, and delete button. The
group holding the active session expands on its own so a run opened from
the Schedules page is visible. Grouping also applies inside project groups
when sorting by project. The Scheduled header now counts schedules rather
than runs.

Threads learn the schedule identity from the metadata the runner now
stamps (scheduleId, scheduleName, scheduleRunNumber). Runs recorded before
that fall back to the schedule executions list the hook already polls,
which now yields the schedule id and name instead of a bare session id set,
and finally to grouping by shared title. Runs without a number are labelled
with their start time instead of "Run N".

* fix(desktop): reopen a collapsed schedule group when one of its runs is opened

A stored collapse used to win over the active-session default for the
sidebar's lifetime, so a run opened from the Schedules settings page
could stay hidden inside its collapsed group. Opening a session now
clears the stored choice for the group that holds it; the group can
still be collapsed afterwards.

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>

---------

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
2026-09-01 19:51:57 -07:00
Saoud Rizwan b9977a139f feat(desktop): import sessions from Claude Code, Codex, and opencode (#13744)
* feat(core): session import service for Claude Code, Codex, and opencode history

Adds a SessionImportService to @cline/core that discovers sessions in the
on-disk stores of Claude Code (~/.claude/projects JSONL), Codex
(~/.codex/sessions rollouts + session_index titles), and opencode
(opencode.db sqlite), translates each conversation into Cline's native
MessageWithMetadata format, and persists it through CoreSessionService as
a completed, listable, resumable session.

Key mechanics:
- Claude Code: parentUuid tree walk from the newest leaf picks the active
  branch (edits/retries branch the log); same-message.id assistant lines
  merge back into one turn; sidechains, meta lines, and slash-command
  wrappers are excluded; ai-title/summary lines provide titles.
- Codex: real prompts come from user_message event_msg lines (user-role
  response_items are injected AGENTS/environment context, with a fallback
  for old rollouts); function_call/output pairs map to tool_use/tool_result;
  resumed rollouts that re-embed the original session id dedupe to the
  richest file; token_count events stamp per-turn metrics.
- opencode: reads a temp snapshot of the WAL-mode db; inline tool parts
  split into tool_use + tool_result to preserve provider-valid structure;
  child (subagent) sessions and synthetic parts are skipped.
- Shared sanitizer guarantees replayability: orphaned tool_use gets a
  placeholder result, orphaned tool_results and empty text blocks drop,
  provider-session-scoped signatures/encrypted reasoning strip.
- Imported sessions pass every history-visibility gate (terminal status,
  non-empty provider/model, chat-workspace fallback cwd, no fabricated
  checkpoint metadata) and carry metadata.importedFrom for idempotent
  re-discovery (alreadyImportedSessionId).

* feat(desktop): sidecar commands for importing sessions from other tools

Adds two sidecar WebSocket commands backed by @cline/core's
SessionImportService:

- list_importable_sessions: returns { installedTools, sessions } where
  sessions are ImportableSessionSummary rows (tool, sourceId, title, cwd,
  timestamps, messageCount, preview, alreadyImportedSessionId) discovered
  in the local Claude Code / Codex / opencode stores.
- import_sessions: takes { selections: [{ tool, sourceId }] }, validates
  each selection against the known tool list, imports sequentially
  (per-session transactional), and broadcasts session_import_progress
  events ({ index, total, result }) so the UI can render live progress.
  Returns { results } with per-item ok/sessionId/title/error.

* feat(desktop): import sessions UI for Claude Code, Codex, and opencode

Adds an Import Sessions dialog to the desktop app driven by the sidecar's
list_importable_sessions / import_sessions commands:

- Scan phase discovers local history from all three tools and groups it
  per tool with select-all checkboxes, per-row title, relative time,
  message count, and workspace folder; rows already imported are disabled
  and badged (idempotent re-open).
- Text filter across title, folder, and first-prompt preview.
- Import phase streams session_import_progress events into a progress bar
  and per-item result list; the dialog cannot be dismissed mid-import via
  overlay click. Done phase summarizes successes and lists failures with
  their error messages.
- Entry points: an Import button in the Sessions view header and an
  "Import sessions" row in Settings → General.
- use-session-history subscribes to session_import_progress so history
  refreshes no matter which surface started the import.
- Wire types live in webview/lib/session-import.ts (mirrors the core
  module's types so the client bundle never imports node-only code).

* fix(desktop): import dialog crash rendering session timestamps

formatRelativeTime takes a string (parseTimestamp calls .trim() on any
truthy value), but the import dialog passed the numeric updatedAtMs,
crashing the page with 'e.trim is not a function' as soon as scanned rows
rendered. Convert to an ISO string at the call site.

Slipped through because the webview has no typechecking anywhere:
tsconfig.dev.json excludes webview/ and next.config sets
typescript.ignoreBuildErrors, and the webview's own tsconfig currently
carries 64 pre-existing errors.

* feat(desktop): offer session import during onboarding

Adds an 'import' onboarding step between connect/github and done. The
step scans for importable Claude Code / Codex / opencode history on
entry and silently advances when nothing (new) is found or the scan
fails, so only people with actual history from other tools ever see it.
When sessions are found it summarizes the count and source tools, opens
the same ImportSessionsDialog used by the Sessions page for picking, and
flips to a confirmation state once at least one session imports. Skip is
always available, including while the scan is still running.

* fix(desktop): import dialog text overflow, collapsible sections, select all

- Titles no longer clip or push the row wide: they word-wrap up to two
  lines (line-clamp-2 + break-words, with min-w-0 down the flex chain so
  long unbroken Codex prompt titles can actually shrink); the meta line
  keeps time/count fixed and truncates only the workspace name; progress
  rows get the same min-w-0 treatment.
- Each tool section header is now a collapse toggle (chevron +
  aria-expanded) so one tool with hundreds of sessions doesn't force
  scrolling past it; collapsed headers still show count and selected
  count, and filtering forces sections open so search matches can't hide
  in a collapsed group. Collapse state resets per dialog open.
- New global Select all row above the list with indeterminate state and
  an x-of-y selected counter; it operates on the currently visible
  (filtered) selectable sessions, matching the per-section checkboxes.

* fix(desktop): import dialog header and search clipped by intrinsic column width

The dialog grid used the default auto column track, so a single
unbreakable string in a session title (Codex titles often contain URLs)
set the column's min-content width wider than the fixed 620px dialog --
break-words affects layout but not intrinsic sizing -- and
overflow-hidden then clipped everything in the column, including the
description and the search field. Pin the column to minmax(0,1fr) so the
container width always wins and long words wrap at the box edge instead.

Also add sm:max-w-none (the primitive's sm:max-w-lg survives
tailwind-merge across variants and was silently capping the dialog at
512px) and shrink-0 on the search and select-all rows so a tall list can
never compress them vertically.

* fix(desktop): onboarding import step rescanned after import and looped to done screen

The import step's scan effect depended on onContinue, an inline arrow the
parent recreates every render — and importing itself re-renders the app
shell via the history refresh. Each re-render re-ran the scan, and when
the user had imported everything (select all), the re-scan found zero
remaining sessions and hit the nothing-to-import auto-advance, yanking
them past their own import confirmation onto the done screen. The scan
now runs exactly once per step entry (onContinue held in a ref for the
async auto-skip paths).

Also, after a successful import the button is now 'Start building' and
completes onboarding directly instead of routing through the separate
done screen — two consecutive confirmation screens read as a loop. The
skip and nothing-found paths still go through the done screen so those
users get the 'You're all set' confirmation.

* fix(core): consolidate imported tool_results into the message after their tool_use

The import sanitizer answered missing tool_use ids with a separate
placeholder user message while leaving real results for the same turn in
later user messages. Anthropic requires every tool_result for a turn in
the user message immediately following it, so a partially-answered turn
would still 400 on resume. Rebuild any incomplete or split span as one
consolidated results message in tool_use order (placeholders for missing
ids, duplicates dropped) followed by a message carrying whatever else the
span held, mirroring the legacy migration sanitizer.

* fix(desktop): imported sessions resume on the user's configured provider; batch adapter caches

Opening a history session adopts the row's provider/model
(use-chat-session: session.provider || prev.provider), so imported rows
stamped with the source tool's provider — openai-native for Codex,
whatever opencode reported — resumed on providers the user may never have
configured and failed on first send. The dialog now passes the app's
current model selection (lastProvider/lastModelByProvider, i.e. what a
new chat would run on) and the service stamps it on the row; both halves
must be present so a Cline provider is never paired with a foreign model
id. The source provider/model are preserved in metadata.importedFrom and
per-message modelInfo stays accurate. Codex's provider id is corrected to
Cline's openai-native, and opencode's openai/google map to
openai-native/gemini.

Adapters also gain per-batch caches released via dispose(): Codex's
convert() re-walked the sessions tree and re-read every rollout head per
imported session (O(sessions x files)); it now builds the session-id ->
richest-file index once per batch. opencode copied the whole WAL db per
imported session; it now snapshots once per batch.

* fix(core): roll back failed imports and dedupe at import time

Addresses both Greptile P1s on #13744:

- A write failing after createRootSessionWithArtifacts (messages, status,
  manifest, title) left a half-written pid-0 session in history whose
  importedFrom marker also blocked retrying the source. persistConverted
  now deletes the session on any later failure and rethrows.
- Dedup markers were read through listSessions, which caps its scan at
  2000 rows, so a prior import older than the newest 2000 sessions was
  invisible and the source could be imported again. Add
  listSessionMetadata (ids + metadata for every row, no manifest reads or
  reconciliation) and use it for markers. Also check idempotency at
  import time, not only at discovery: a request for an already-imported
  source resolves to the existing session (alreadyImported: true) instead
  of writing a copy, covering stale pickers and repeated requests.

* fix(core): create imported sessions terminal and mark them imported last

Two failure modes shared one root cause -- the import wrote its session
in stages and claimed success too early:

- The row was created running/pid-0 and flipped to completed afterwards.
  The stale-session reconciler runs in the hub daemon against the same
  SQLite DB and, in that window, marks such rows failed and stamps
  terminal_marker metadata. createRootSessionWithArtifacts now accepts
  status/endedAt/exitCode so imports are created completed with the
  source session's end time; the separate status flip and manifest
  rewrite are gone.
- The importedFrom marker was written at creation, so a session whose
  later writes failed (and whose rollback delete also failed) still
  blocked retrying its source. The marker is now the final write, so it
  means 'this import finished' and a half-written session can never
  claim the source.

listSessionMetadata is unbounded by default so dedup sees every row.

* fix(core): resolve TS2352 casts in session-import tests (#13746)

tsc rejects casting ContentBlock[] straight to Record<string, unknown>[]
(RedactedThinkingContent is not comparable), which failed the Quality
Checks typecheck. Route the five assertion-site casts through a small
blocks() helper that widens via unknown.

* fix(core): flatten Codex content-block tool outputs during import

Newer Codex rollouts write custom_tool_call_output.output as an array of
Responses-API content blocks ({type:"input_text", text}) instead of a
plain string. The importer JSON.stringified that array into the
tool_result content, and the chat UI's tool-summary parser then rendered
each non-text block as its type label, so imported exec calls showed up
as "[input_text][input_text]" with no output.

Concatenate the text of string/text-bearing blocks (they are stream
chunks, so no separator) and keep the JSON fallback for anything else.

* fix(desktop): edit-and-resend on runs without a checkpoint

Editing a message forks the session before that run, and the sidecar
always routed that through manager.restore with workspace: true. Imported
sessions carry no checkpoint history, so editing any of their prompts
failed with "No checkpoint found at or before run N" — even after the
user had continued the session in Cline, since only the new runs get
checkpoints.

When no checkpoint exists at or before the edited run there is no
workspace state to roll back, so fork the trimmed transcript onto the
current workspace (the same path a full-history fork takes) instead of
erroring. Runs that do have a checkpoint still restore the workspace.

* fix(core): roll back failed session creation and coalesce overlapping imports

Two gaps Greptile flagged on the import path:

createRootSessionWithArtifacts upserts the row before writing the messages
file and manifest, and the call sat above persistConverted's rollback try.
A file write failing there left a completed row with no transcript in
history. Creation now runs inside the rollback, and deleteSession already
tolerates a missing row or missing files.

Each import_sessions request builds its own service and snapshots the
existing-import markers once, so two overlapping requests for one source
(a second window, a double-fired command) both passed the dedupe check and
persisted two sessions. A module-level in-flight map keyed by tool:sourceId
makes the later caller wait on the first write and report its session as
already imported.

* fix(desktop): resolve the import resume target like a new chat does

An imported Claude Code session resumed on the Anthropic provider instead
of the user's Cline selection. The dialog read model-selection storage
directly and required both a remembered provider and a remembered model;
the composer only records a model from the explicit picker handlers, so
anyone running on the default model has no entry, the lookup came back
empty, and the service fell back to the source tool's provider.

Resolve the target with getInitialChatConfig() -- the same chain a new
chat uses (remembered selection, then the built-in default), which is
never empty -- and have the import_sessions handler default to the cline
provider and CLINE_DEFAULT_MODEL_ID when a caller sends nothing, matching
other server-started sessions. The source provider can no longer become
the resume target.
2026-09-01 19:39:20 -07:00
Saoud RizwanandSaoud Rizwan 6d5a9793fc Desktop marketplace: show detail panel only on item click, left-align detail content (#13747)
* Desktop marketplace: show detail panel only on click, left-align detail content

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>

* Desktop marketplace: drop license cell, single Learn more link (homepage, else repo)

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>

* Desktop marketplace: keep selected entry open while list is filtered

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>

---------

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
2026-09-01 17:03:54 -07:00
Bee 9b1374059e fix(desktop): enable macOS voice input (#13741) 2026-09-02 00:57:32 +02:00
8eb5f3d57f 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>
2026-08-31 19:23:26 -07:00
Saoud Rizwan a7a57509e2 chore(desktop): release v0.0.21 2026-08-31 14:23:28 -07:00
John Choi bcfa7c7e4d 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
2026-08-31 12:09:01 -07:00
c096030ace 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>
2026-08-31 11:45:33 -07:00
John Choi 60c74bc727 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
2026-08-28 16:21:32 -07:00
1fbcfab05d 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>
2026-08-28 19:06:47 +02:00
Bee ce71fe5eb9 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
2026-08-28 02:45:05 -07:00
Saoud Rizwan 936c018689 chore(desktop): release v0.0.20 2026-08-27 18:15:36 -07:00
BeeandHarrison 957a4bf5d9 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>
2026-08-27 17:55:29 -07:00
Tomás BarreiroandJohn Choi b78f6d16d0 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>
2026-08-27 17:29:08 -07:00
BeeandSaoud Rizwan 29530caa58 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>
2026-08-27 15:55:22 -07:00
Saoud RizwanandSaoud Rizwan f753a01d85 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>
2026-08-27 13:45:44 -07:00
Saoud RizwanandSaoud Rizwan c017c7016e 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>
2026-08-27 12:04:53 -07:00
Saoud RizwanandSaoud Rizwan 1d5d3b0055 Add tooltips explaining Live and After recording badges on voice input models (#13610)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
2026-08-26 23:39:15 -07:00
Saoud RizwanandSaoud Rizwan 80dd573156 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>
2026-08-26 23:38:05 -07:00
Saoud RizwanandSaoud Rizwan ce2f7a00bb 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>
2026-08-26 23:37:20 -07:00
Saoud RizwanandSaoud Rizwan ad1408636e 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>
2026-08-26 23:23:08 -07:00
Saoud RizwanandSaoud Rizwan 8981079a43 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>
2026-08-26 23:07:50 -07:00
John Choi ee0982cb98 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
2026-08-26 15:58:58 -07:00
John Choi 70654acc3e 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
2026-08-26 11:37:52 -07:00
Saoud Rizwan 40c3a4dbd8 chore(desktop): release v0.0.19 2026-08-26 02:14:21 -07:00
Saoud Rizwan c0d6301884 chore(desktop): release v0.0.18 2026-08-26 00:56:19 -07:00
Saoud RizwanandSaoud Rizwan d71f097656 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>
2026-08-26 00:14:55 -07:00
Saoud Rizwan 6539f4deea 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.
2026-08-26 00:01:00 -07:00
Saoud RizwanandSaoud Rizwan 036fc75b1f 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>
2026-08-25 23:40:07 -07:00
Saoud Rizwan 6fc40127a6 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.
2026-08-25 23:28:21 -07:00
Saoud Rizwan 110138b540 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.
2026-08-25 15:19:29 -07:00
3497391c5a 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>
2026-08-25 14:30:59 -07:00
Mikołaj Kondratek 095385b985 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.
2026-08-25 08:58:23 -07:00
Saoud Rizwan 491b30b806 chore(desktop): release v0.0.17 2026-08-25 01:49:58 -07:00
Saoud Rizwan 8b046d04f9 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)
2026-08-25 01:43:58 -07:00
Saoud RizwanandSaoud Rizwan 8a6c6f8afe 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>
2026-08-24 23:54:09 -07:00
Saoud RizwanandSaoud Rizwan 4f5f238407 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>
2026-08-24 22:51:00 -07:00
Saoud RizwanandSaoud Rizwan 8f69880ac4 Hide Channels and Agents sections from desktop app sidebar (#13527)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
2026-08-24 21:54:59 -07:00
Saoud RizwanandSaoud Rizwan a0c341e93c 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>
2026-08-24 21:53:47 -07:00
Saoud RizwanandSaoud Rizwan 83b2588c9c 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>
2026-08-24 19:35:51 -07:00
Saoud RizwanandSaoud Rizwan 6e09e81a79 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>
2026-08-24 18:14:01 -07:00
Saoud Rizwan 6cb653a362 chore(desktop): release v0.0.16 2026-08-22 16:34:23 -07:00
Tomás Barreiro fb58e340a2 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
2026-08-22 00:25:30 +02:00
Saoud Rizwan fb60f9e5fd chore(desktop): release v0.0.15 2026-08-20 22:42:08 -07:00
Haley Park 05da55857a feat(desktop): reskin first-run onboarding (#13441) 2026-08-20 20:48:34 -07:00
Haley Park 35583ee8bb feat(desktop): interactive welcome hero graphic (#13399)
* feat(desktop): add interactive welcome hero

* feat(desktop): support composable welcome hero variants
2026-08-20 20:47:52 -07:00
Saoud RizwanandSaoud Rizwan eef7958cad 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>
2026-08-20 18:17:19 -07:00
Haley Park e6f4d5fef7 feat(desktop): refresh app icons and branding (#13400) 2026-08-20 08:42:50 -07:00
Saoud RizwanandSaoud Rizwan 7903c76812 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>
2026-08-19 22:13:40 -07:00