Commit Graph
311 Commits
Author SHA1 Message Date
Bee f67e933376 fix(desktop): bake the OTLP trace exporter into desktop builds and record who launched the hub (#15135) 2026-10-16 11:11:53 -07:00
BeeandSaoud Rizwan 83ea9a41d1 refactor: Start the desktop and SSH remote hubs through the bundled Cline CLI (#14792)
* Let hosts choose the hub daemon launcher and defer spawn-only setup

Adds setHubDaemonLauncher so an embedding process can start the hub
daemon through a bundled Cline CLI instead of its own executable, and a
beforeSpawn hook on ensureDetachedHubServer that only runs when a new
daemon is actually started.

* Add programmatic options to cline hub ensure

hub ensure/start gain --json (URL + auth token), --allow-port-fallback,
--no-connectors and --login-shell-path, and the hub command accepts
--discovery-path for a dedicated discovery record. These let the desktop
app and SSH remotes start or reuse a hub through the CLI.

Also extracts the standalone binary compile step into
script/compile-binary.ts so other packages can build the CLI binary.

* Run SSH remote hubs through the Cline CLI

SSH remotes now upload the Cline CLI binary and start or reuse the remote
hub with 'cline hub ensure --json --discovery-path ...', stopping it with
'cline hub stop'. The dedicated remote helper entrypoint is removed;
cleanup records from older releases still stop through the old flag.

* Run the desktop backend on the bundled Cline CLI

The desktop app no longer compiles its own executable. It bundles the
self-contained Cline CLI (externalBin cline-cli) plus the backend as a JS
bundle that runs on the CLI's embedded runtime. At startup the backend has
the CLI run 'hub ensure', which reuses a compatible healthy Hub or starts
one, and any Hub it starts later also goes through the CLI binary. Linux
CLI copies are bundled for SSH remotes.

* Launch the desktop backend through the bundled Cline CLI

The Tauri shell resolves the bundled cline-cli and the backend bundle
(per-platform resource layouts) and starts 'cline-cli run' on it. The
Windows installer stops users of cline-cli.exe, and also stops and removes
the backend executable of earlier releases on upgrade.

* Recognize the CLI-hosted desktop backend in cline doctor --fix

* Update desktop publish workflow and docs for the bundled Cline CLI

Telemetry selfchecks and smoke tests run the backend bundle on the
bundled CLI, and the Windows signature gate verifies cline-cli.exe.

* fix(desktop): address runtime packaging review findings

* fix(desktop): preserve failed hub cleanup and handle exited daemons

* fix(hub): distinguish missing discovery records from read failures

* fix(hub): preserve discovery recovery while enforcing strict shutdown reads

* fix(cli): do not count hub commands as CLI activations (#14870)

* fix(cli): do not count hub commands as CLI activations

The desktop app runs `cline-cli hub ensure` on every launch and the SSH
remote flow runs `cline hub ensure` on every connect. Both went through
runCli(), which captured user.extension_activated unconditionally, so each
desktop launch was double-counted as a CLI activation and every SSH remote
host (with its own distinct id) showed up as a new CLI user.

Skip the activation capture for the hub subcommand; it is plumbing other
clients invoke, not a user activation. Hub-owned task telemetry is
unaffected since the daemon path never enters runCli().

* test(cli): clear the hub command mock between main.test.ts cases

* test(cli): stop stubbing the hub command module in main.test.ts

The module-wide vi.mock("./commands/hub") replaced createHubCommand with
a no-op, so `cline hub dashboard` (routed through createHubCommand) never
reached the dashboard mock and three dashboard tests failed. Drop the
module mock and assert the activation exclusion through
`hub dashboard --no-open`, which is already mocked.

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-10-13 21:58:41 -07:00
1932d5dc37 feat(vscode): Cline Cloud sessions - run tasks in a hosted sandbox from the extension (#13799)
* feat(sdk): support header-authenticated remote Hub connections

Ports the NodeHubClient resolveConnectionHeaders option from #13519 so a
client can authenticate a remote proxy's WebSocket upgrade with HTTP headers
(refreshed on every reconnect), and threads it through HubRuntimeHost and
RemoteRuntimeHost so ClineCore remote runtimes can reach Cline Cloud sandboxes.

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

* feat(vscode): cloud session runtime, control-plane client and history integration

Adds the extension-host side of Cline Cloud sessions:
- CloudSessionsService: REST client for GitHub App status/repos/branches and
  the outer session records (create/list/status/delete/rename/history)
- CloudSessionHost: an SdkSessionHost over RemoteRuntimeHost that dials the
  sandbox Hub through the api.cline.bot proxy with the account token, maps
  outer/inner ids and re-emits events so the existing chat pipeline is reused
- SdkCloudSessionCoordinator: starts/reopens cloud tasks, keeps sandbox
  connections alive to track running/finished status, notifies on completion
  and projects cloud sessions into task history
- CloudService proto + handlers, NewTaskRequest execution target, history
  cloud fields/filter, ext-cloud-sessions feature flag

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

* feat(vscode): cloud sessions UI — home task-target panel, running strip, history and account

- Home screen: Local | Cloud chooser above the composer with GitHub repo and
  branch pickers (prefilled from the workspace's origin remote and branch) and
  an onboarding card that walks through connecting GitHub, polling until done
- Running-in-the-cloud strip and cloud status pills in Recent/History, plus a
  Cloud Only history filter
- Task header cloud badge (repo @ branch, opens the dashboard); cloud tasks
  render inline diffs, hide checkpoint/restore and only attach images
- Account view: Cline Cloud card with GitHub connection status

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

* fix(vscode): cloud task fixes found in manual testing

- claim the task-view generation after clearTask (which bumps it), so cloud
  task start/open is not treated as superseded and left on Thinking
- track completion from the agent done event and keep completed/failed over
  the trailing idle status so History shows Done instead of Cloud
- stop the branch picker from re-querying on every render
- readable popover text in the repo/branch pickers; hide cost chips on cloud rows

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

* docs(vscode): cloud sessions follow-ups and test stub for org switch reset

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

* fix(vscode): make cloud sessions Act-only

Match the desktop app and the cloud dashboard: cloud sessions are created in
Act mode regardless of the selected mode, no per-turn mode is sent, the host
refuses Plan/Act toggles while a cloud session is active, and the webview pins
the toggle to Act (disabled, with a tooltip) while a cloud task is shown or
about to start.

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

* test(vscode): add local cloud session boundary

* fix(vscode): preserve cloud outcomes and stale-work ownership

* fix(vscode): drain cloud cleanup before account changes

* fix(vscode): build cloud sandbox config through the Cline path

* docs(vscode): clarify cloud proxy rollout boundary

* fix(vscode): close cloud picker when leaving chat

* fix(vscode): wait for cloud session before opening dashboard

* fix(vscode): keep account history in document flow

* fix(vscode): report available cloud task usage

* fix(vscode): cancel cloud tasks during provisioning

* fix(vscode): make cloud model billing explicit

* fix(vscode): hide local approvals for cloud tasks

* fix(vscode): resolve cloud prompt before session adoption

* fix(vscode): seed local cloud development profile

* fix(vscode): sanitize local cloud fixture errors

* fix(vscode): handle cloud state before hydration

* fix(core): honor proxies for remote Hub WebSockets

* fix(vscode): preserve cloud task IDs

* fix(vscode): explain cloud auto-approval

* test: repair cloud and proxy CI fixtures

* fix(vscode): resolve cloud status after restart

* fix(vscode): resolve flags for restored accounts

* fix(vscode): keep cloud tasks in the cloud and in Act mode

* test(vscode): let the local cloud fixture delay model replies

CLINE_LOCAL_CLOUD_MODEL_DELAY_MS holds the scripted reply so cancellation and navigation can be exercised while a cloud task is still thinking.

* fix(vscode): cancel a cloud task that is still provisioning

Cancelling during "Starting a cloud sandbox..." did not stop the start:

- Cancel arriving while initTask awaited remote config found no pending
  start, so the sandbox went on to provision, connect and run the prompt
  behind a "Resume Task" view, and the user paid for a sandbox they had
  cancelled. initTask now claims the cloud start before its first await.
- Cancel arriving during the readiness poll took effect only after the
  sandbox came up. createSession accepts an abort signal that stops the
  poll, so the unused record is deleted as soon as the control plane
  answers.
- clearTask (New Task) now cancels a pending cloud start too; a
  provisioning task has no SDK session for the task controller to end.

The local cloud fixture reports `ready` like the hosted control plane and
accepts CLINE_LOCAL_CLOUD_PROVISION_DELAY_MS to hold new sandboxes in
`provisioning`, so this window is reproducible. The cloud Vitest config
restores the Zod namespace export the same way vitest.config.ts does.

* fix(vscode): keep cloud status displays truthful and hosts open while in use

A status check that resolved a cloud task as idle or finished closed the
host while openCloudTask could still be reading messages or attaching
the session on that same host. Entries now count the openCloudTask calls
using them, and neither status resolution nor the idle sweep disposes a
host that is pinned or belongs to the displayed task.

Cancelling a cloud start while remote config was still loading left the
turn phase at streaming after the view had returned home. initTask no
longer sets the phase for cloud starts; the coordinator sets it once the
start is still current and its task view is installed.

useResolvedCloudStatuses kept a resolved status after history reported
a newer one, so a task resolved as running stayed "Working" after it
completed. The hook now returns statuses only for tasks history still
reports as unknown, and the running strip lists a task only while its
effective status is active or unconfirmed.

An unknown status showed a spinner labelled "Checking" for as long as
connections kept failing. It is now shown as Unconfirmed with a static
icon; only provisioning and running animate.

* test(core): run the proxy tunnel fixture with a real Node executable

When Bun runs vitest it reports its own Node-compatible shim as
process.execPath and places it first on PATH. That runtime ignores
NODE_EXTRA_CA_CERTS, so the spawned fixture failed TLS verification on
the Windows CI runner. The test now looks past the shim for an installed
Node and is skipped when none is available.

* fix(vscode): remember cloud task status across restarts

After a restart every cloud row in History showed "Unconfirmed" and
joined the running strip until a socket to each sandbox re-learned its
status, and the first status a socket reported bumped the row's
timestamp to now, reordering History (CLINE-3307, CLINE-3424,
CLINE-3418).

The coordinator now writes the last settled status it confirmed over a
live connection to global state (`cloudSessionStatuses`, keyed by
session id with the observation time) and reads it back when it first
sees a record. The remembered status is trusted only while the control
plane's `updatedAt` is no later than the observation plus a one-minute
grace, because the control plane touches `updatedAt` when a client
connects; a later touch means another client may have run the agent, so
the row falls back to unknown and is resolved live as before. Active
statuses are never remembered. Ids the account no longer lists are
pruned on every list refresh.

A connection that only learns the current status no longer counts as
activity; the row's timestamp moves only when the status changes while
connected or the user sends a follow-up.

* fix(vscode): settle account switches without blocking History

* fix(vscode): refresh stale cloud outcomes without restarting

* test(vscode): wait for cloud run cleanup before removing sandbox

* fix(vscode): reserve switch errors for account transitions

* refactor(vscode): remove unused cloud accessors and refresh notes

* test(vscode): seed expired sessions and a 402 model reply in the cloud fixture

The loopback fixture only ever served live sandboxes, so the read-only
expired path and the out-of-credits path could not be exercised without a
day's wait or real spend.

CLINE_LOCAL_CLOUD_SEED_EXPIRED=1 seeds two Personal sessions whose
sandboxes are gone: one with an archived transcript, one that answers
/history with the control plane's 404 "no history captured". Their
WebSocket upgrade returns 410 like the real proxy and /status reports
"expired". CLINE_LOCAL_CLOUD_INSUFFICIENT_CREDITS=1 makes every scripted
model reply the hosted API's 402 insufficient_credits body.

* fix(vscode): explain expired cloud sessions instead of showing an error

Opening a cloud task whose sandbox the control plane retired showed a red
error row and, when no transcript had been archived, a synthetic Done row
above it. QA read this as data loss (CLINE-3322).

The row is now an informational notice that says the sandbox was retired
after 24 idle hours and names the repository to start a new cloud task on.
It tells three cases apart: the archived conversation is shown above it;
no conversation was archived (the control plane answered 404); or the
archive could not be loaded right now, with the error. An empty transcript
is no longer rendered, so no Done row appears. The History pill reads
Archived with a tooltip explaining the retirement.

* fix(vscode): show the out-of-credits card for cloud task failures

A cloud turn that failed on the Cline API's 402 insufficient_credits
reply ended with Retry / Start New Task and no explanation (CLINE-3376).
A Hub-hosted session does not forward the agent's "error" event; the
client learns of the failure only from run.failed, which the runtime host
turns into done(reason: "error") with the failure message as its text.
The translator recorded the error outcome for that event but rendered
nothing.

done(reason: "error") now renders the same api_req_started and
api_req_failed rows as an "error" event, once per turn, so ErrorRow shows
the Buy Credits card (or the plain error) for cloud tasks as it does for
local ones. Both paths share one helper.

* fix(vscode): explain cloud sessions deleted or retired since History loaded

Opening a cloud task deleted from the dashboard showed "Could not connect
to this cloud session: Unexpected server response: 404" and left the row
in History (CLINE-3377). The Hub client reports only the HTTP status of
the refused upgrade.

A failed connection now asks the control plane for the session's status.
A deleted session shows a notice and leaves History; a session retired
since the list was fetched opens as expired, with its archived
conversation if one exists. Other failures keep the connection error.

The loopback fixture now answers an unknown session's upgrade with 404,
as the hosted proxy does, instead of 401.

* fix(vscode): only suggest cloud branches that exist on GitHub

The cloud branch picker was prefilled with the workspace's checked-out
branch even when it had never been pushed. The sandbox clones from
GitHub, so the session then failed to start (CLIENTS-109).

The workspace default is now the remote branch the checkout corresponds
to: its upstream on origin when one is configured, otherwise the same
name when origin has it. A local-only branch yields no suggestion and the
picker falls back to the repository's default branch. Branches listed
from the GitHub API were already remote.

* fix(vscode): accumulate streamed text for cloud tasks

A cloud task's reply flickered chunk by chunk and only appeared whole
when the message finished (CLIENTS-111). The translator showed each
content_start's `accumulated` text, falling back to the chunk alone. A
local session sends the running total, but a Hub-hosted session forwards
assistant.delta as a bare chunk, so each update replaced the last.

The translator now keeps the running total itself when `accumulated` is
absent, and clears it with the other per-block streaming state.

* fix(vscode): show the cloud model in the composer while Cloud is selected

With Cloud selected, or a cloud task open, the composer footer still
showed the local provider and model, e.g. anthropic:claude-opus, although
the sandbox runs a Cline model (CLIENTS-116).

The extension now reports cloudModelId in state: the model the displayed
cloud task runs on, else the one a new cloud task would start with. The
start path and the label share one function, so they cannot disagree.
The composer shows it as cline:<model> whenever it pins the mode to Act
for Cloud.

* fix(vscode): release the retained host of a deleted cloud session

When a session opened from History turns out to have been deleted, its
entry leaves the registry. A connection retained from an earlier status
check stayed open with nothing to watch; dispose it with the entry.

* fix(vscode): keep expired-session and cloud-model displays truthful

An archived cloud transcript was rendered with the final turn marked
completed, so the last reply of a task that was still running when its
sandbox retired showed as a green Done box. Retirement is not
completion; the final reply now stays a plain text row.

An archive that exists but holds no messages rendered nothing yet the
notice pointed at "the conversation above", and the History tooltip
promised a readable conversation for every Archived row. An empty
archive now reads as none, and the tooltip says to open the session to
see whether one was saved.

With a non-Cline local provider the composer showed the cached or
fallback recommendation while the start path refreshed the list first
and could pick a different model. The start now uses the model shown
when the user submitted, snapshotted before any await, and the
composer warms the recommendation cache once so the label moves from
the fallback to the live recommendation before the next submit.

* fix(vscode): confirm an account switch overtaken by its own auth update

switchOrganization confirmed a switch by re-reading the organizations
and treating an answer overtaken by a newer read as no answer. The
extension's auth refresh after the PUT starts such a newer read, so a
switch that succeeded returned false and the ClinePass switch control
asked the user to retry it.

Every read now returns the server's answer; only the newest read still
writes context state.

* fix(core): connect header-authenticated Hub sockets under Bun

The desktop sidecar runs the cloud session controller under Bun. Since
a2cefba05 the header-authenticated Hub client imported the npm `ws`
package through a `node-ws` alias so a proxy agent could be attached.
Under Bun 1.3.13, the toolchain this branch was pinned to, Bun's
node:http client never emits `upgrade`, so the npm `ws` treats the 101
as an ordinary response and connect() fails with "Unexpected server
response: 101". Cloud sessions in the desktop app could not connect.

Import `ws` directly again. Bun substitutes its own client, which reads
the proxy URL and Proxy-Authorization from the same `agent` option that
Node's `ws` uses, so one socket shape tunnels on both runtimes and the
`node-ws` dependency and its @ts-expect-error go away.

The tunnel test now runs the fixture under Bun as well as Node. Bun's
TLS client does not read NODE_EXTRA_CA_CERTS, so the Bun leg tunnels a
plain ws:// destination; it still checks the CONNECT, the upgrade and
where the credentials land. With the `node-ws` import the Bun leg fails
on 1.3.13 and the Node leg passes, which is the gap that let this ship.

* fix(vscode): retry the cloud model recommendation after a failed fetch

The composer shows the model a new cloud task will run on. When the
local provider is not Cline that is the top recommendation, warmed once
by the coordinator. A fetch that failed returned the built-in fallback
without caching it, but the coordinator kept its warm-up handle, so it
never asked again and the fallback stayed in the composer and in new
sandboxes until the extension restarted.

Release the handle when a fetch settles without filling the cache, so
the next state post retries.

* fix(vscode): stop the recommendation warm-up from polling a failing endpoint

The warm-up posted state after every fetch, including one that returned
the built-in fallback. That post reads the model again, which starts the
next fetch, so a recommendation endpoint that keeps failing was polled
without bound while Cloud was selected with a non-Cline provider.

Post state only when the fetch filled the cache. A failed fetch still
releases the handle, so the next read the user causes retries.

* fix(vscode): disable the composer while a cloud sandbox is provisioning

The composer allows sending during the streaming phase so follow-ups can
queue on a running task, and a cloud start sets that phase while the
sandbox is still provisioning. There is no session to queue on yet, so
askResponse hit the expired-session guard and answered "This cloud
session is no longer running" while the sandbox kept starting, dropping
the message.

The displayed cloud task now reports status provisioning while its start
is pending, and the composer keeps submit disabled on that status so the
draft stays in place until the sandbox is running.

* fix(vscode): re-enable the composer after a failed cloud start

The failure path posts state while the start is still pending, so the
composer kept seeing "provisioning" and stayed disabled. Post state once
more after the pending start is cleared. The askResponse guard, still
reachable through the extension API while the composer is disabled, now
says the sandbox is starting instead of that the session is gone.

* style(vscode): wrap two JSX lines the CI formatter rejects

Biome 2.4.5 on main wraps these attributes; the branch's Quality Checks
failed on them after the rebase onto that formatter version.

* test(vscode): pin the composer's provisioning status through a failed start

The displayed cloud task reports provisioning while its start is pending,
and the last state post after a failed start no longer carries it, so the
composer re-enables without waiting for an unrelated refresh.

* fix(vscode): keep a draft typed while a cloud task is provisioning

handleSendMessage cleared the composer a second time after its RPC
resolved. Every send path already clears the draft before the RPC, so
the second clear only ever removed text the user typed while the RPC was
pending. A local newTask resolves too quickly to notice; a cloud newTask
resolves only after the sandbox is provisioned, so the draft the disabled
composer was holding for the user vanished when the sandbox came up.

* fix(vscode): follow main's rename of the sign-in retry field

#14628 replaced pendingClineAuthRetryPrompt with pendingClineAuthRetry.
The cloud coordinator's clearTask callback and the cloud branch of
initTask still cleared the old field, which no longer exists.

* fix(vscode): tear down cloud sessions when ClinePass switches the account

Selecting the ClinePass provider switches the active account to Personal
by calling switchAccount directly. The Account view's organization switch
runs the same call inside resetCloudSessions, which detaches the displayed
cloud task and its authenticated hosts and drains pending cleanup before
the account changes. Without that, an organization cloud task stayed on
screen after the account became Personal, and a still-pending DELETE could
use the new account's credentials.

Route the ClinePass switch through the same reset, and only when an
organization is active: every API configuration update passes through
this path, and the reset would otherwise clear a displayed cloud task for
a model change that switches nothing.

* fix(vscode): page History's Cloud Only filter after filtering

The Cloud Only filter ran on a page already cut from the merged
local-and-cloud list, and hasMore was computed from that page. With more
than a page of newer local tasks the first page came back empty while
cloud tasks existed further down, and hasMore said there was nothing more.

When Cloud Only is on, list the whole history, filter, then apply offset,
limit and hasMore to the filtered records.

* fix(vscode): stop reporting a model change the cloud sandbox never made

CloudSessionHost.updateSessionModel called an optional method that
RemoteRuntimeHost does not have, then recorded the new model anyway.
Choosing a different local model while a cloud task was displayed made
the composer name that model while the sandbox kept running on the one
it was started with.

The Hub protocol has no command to change a running session's model, so
drop the method. The lifecycle already treats a host without it as
unable to switch, and the composer keeps naming the model the sandbox
was started on.

* fix(vscode): hide a cloud task's cost until the sandbox reports usage

The task header decided whether to show the cost badge from the local
provider's cost display setting. For a cloud task with no usage yet that
showed $0.0000, which reads as a confirmed zero for a charge that is not
known. Cloud tasks now show the badge only when the sandbox has reported
usage; local tasks keep the provider-based decision.

* fix(vscode): do not offer to open a cloud task's files in the local editor

Cloud edits render through DiffEditRow, whose file name and open icon
call openFileRelativePath. The path is relative to the sandbox's
/workspace, so clicking opened an unrelated local file or failed. The row
now takes canOpenFile; ChatRow passes false for cloud tasks, which drops
the click action and the open icon and leaves the path as plain text.

* fix(vscode): keep the cloud repository and branch choice truthful

Two ways the stored cloud target could name something the current
account cannot start a sandbox on:

- The repository selection persists across account changes. When the new
  account has no accessible repositories the panel showed onboarding, but
  the old URL stayed stored, so the composer still treated the target as
  ready to submit. Clear the stored repository once the account's list
  is known and does not contain it.

- The workspace branch was persisted from the local remote-tracking ref
  without checking GitHub. A branch deleted on GitHub but not pruned
  locally was suggested and submitted, and provisioning failed. Start on
  the repository's default branch and ask GitHub for the workspace branch
  by name; the unfiltered branch list is paginated, so its first page
  cannot decide. Suggest the branch only if GitHub returns it and the
  default branch is still selected, so a branch the user has since chosen
  stays.

* fix(vscode): reconcile retained cloud connections with the account's list

Two ways a cloud session's record and its connection could disagree:

- A list refresh kept any entry that still had a host, so a task deleted
  from the dashboard stayed in History with an open authenticated
  connection, and a 401 cleared the entries without closing their hosts.
  An entry whose record has left the list, or every entry on a 401, is now
  dropped and its host closed. The displayed task is the exception in both
  cases: its host stays so that opening it can explain a deletion, and so
  that signing in again after a 401 leaves a task that can continue.

- When a sandbox retires after History was listed, its WebSocket rejects
  with 410 while /status may answer 200 with status "expired". The probe
  treated every successful /status as "not expired", so the task opened
  to a connection error instead of the retirement notice. A 200 that says
  expired now counts as retirement, like a 410.

* fix(vscode): subscribe monitored cloud tasks to their Hub event stream

The Hub streams a session's events only to clients subscribed to it, and
the runtime host subscribes when it first sends a command for that
session. A connection opened only to resolve a History row's status sent
none: it listed sessions, read the status, and then waited on an event
stream that was never opened. A turn finished by another client left the
row on Running and the completion notification never fired.

Listing the task's pending prompts after discovery sends that first
command and opens the stream.

* test(vscode): keep the local cloud fixture alive when a client drops its socket

An extension host that reloads or exits closes its bridged WebSocket
without a close frame. The bridge attached no "error" listener to either
side, so ws raised an unhandled error and the fixture process exited,
taking every sandbox with it. Terminate the failed socket instead.

* Own the account boundary in switchAccount

Four paths changed the active account (the Account view, ClinePass
selection and two remote-config paths) and only one of them tore down
the previous account's cloud task and connections first. Each caller
had to remember to wrap the switch, and two did not.

switchAccount now runs every change inside a boundary the controller
installs once (the cloud coordinator's reset), and serialises switches:
one requested while another is in flight waits for it and then decides
against the account as it is then. A caller that read a stale account
cannot skip a needed switch or repeat one that just happened, so the
ClinePass gate and the Account view's pending-switch map are gone.

* Resolve the cloud task target in the extension

The task-target panel computed the whole stored target from five
effects (account repositories, workspace remote, GitHub branch check,
repository click, Local/Cloud click), each writing the result back.
Any two could interleave and the later write won: a branch check that
landed after the user clicked Local flipped the target back to Cloud.

The extension now stores only what the user chose, one field per
choice (Local/Cloud, a repository, a branch), and resolves the rest on
every state post: the stored repository if this account can reach it,
else the workspace's, else the first; the user's branch, else the
workspace branch once GitHub confirms it exists, else the default.
Nothing derived is written, so no lookup can override a click, and a
repository chosen under another account is never submitted. Failed
lookups retry after 30 seconds; the GitHub connection is cached per
account and shared with the Account view.

The webview panel renders the resolved view and sends each click as
one choice.

* test(vscode): keep the cloud fixture alive when the Hub socket fails early

The fixture's session proxy attached its error handler to the upstream
Hub socket only after "open". An error during the handshake was
unhandled and exited the fixture process, which the extension then saw
as "Timed out connecting to hub after 8000ms". Attach the handler at
construction for both sides of the bridge.

* Refresh aged cloud-target inputs and always restore the server account

The task-target resolver cached repositories and the workspace checkout
for as long as the account and directory stayed the same. Repository
access changes in the browser and the checkout changes in the terminal,
and neither notifies the extension, so the picker could keep offering a
revoked repository or suggesting the previous branch. A loaded input now
ages out (repositories after 60 s, the checkout after 10 s): a read past
that returns the cached answer and reloads behind it.

switchAccount skipped the request when the cached account already
matched. The server's active account is shared by every client on the
login, so another client may have moved it. The request is now always
sent; only the cloud teardown is gated on the cached account changing.

* Retry a failed cloud start instead of declaring the session gone

A cloud task whose sandbox never came up shows "Failed to start the cloud
session" with Retry / Start New Task, but Retry appended "This cloud
session is no longer running" and the footer then stayed disabled: the
webview holds its recovery claim until the turn phase advances, and the
refusal never advanced it.

The coordinator now hands the failed input to the controller, which keeps
it as the same pending start retry a "Sign in to Cline" error uses. Retry
runs the cloud task again on the same repository and branch; a prompt
typed into the composer starts one with that prompt. Refusing a response
to a session that is gone sets the turn phase, so the footer releases
its claim either way.

The task-header tooltip no longer doubles its full stop when the task
has no repository. The local cloud fixture starts a sandbox's Hub when
the session becomes ready rather than on the first socket upgrade, so a
cold start cannot outlast the client's connect timeout.

* test(vscode): let the cloud fixture refuse session sockets

CLINE_LOCAL_CLOUD_REFUSE_SOCKETS=1 answers every session socket upgrade
with 503, so a cloud start fails after its sandbox is ready and the
footer's Retry path can be exercised by hand.

* Replace the carried-forward transcript when the new epoch's snapshot lands

Retrying a failed cloud start appended the new attempt below the old
one instead of replacing it. The new task's first partials reach the
webview before the debounced state snapshot of the new epoch; the
reducer deliberately carries the old rows forward on a lone newer-epoch
partial, but then treated the snapshot as same-epoch and merged, so the
old rows were never removed. The replica now remembers that it carried
rows forward, and the first snapshot at that epoch replaces them while
keeping the newer-epoch partials already held.

A prompt typed after a failed start also left its optimistic bubble
below the new task row: the bubble is user_feedback, but a restart is
confirmed by a task row. Any user-authored row with the same text and
attachments now confirms the bubble.

* test(vscode): keep the cloud scope fixture complete

Supply the failed-start callback required by the cloud retry path and
assert that the coordinator hands it the failed task and original input.
Check the fixture keys against the options interface before casting its
partial service doubles, so a new required option fails typechecking.

* fix(vscode): send Cancel to the task on screen, not a cloud start left behind (#14794)

A cloud start stays pending after the user opens another task from
History. Cancel always cancelled that pending start first, so cancelling a
running task (cloud or local) cleared its view instead, and a cloud turn
kept running in its sandbox. Only cancel the pending start when its own
view, or no task, is on screen.

* fix(vscode): delete cloud sandboxes left provisioning by a reload or quit (#14791)

* fix(vscode): delete cloud sandboxes left provisioning by a reload or quit

A terminating extension host cannot reach the network (VS Code's fetch
resolves proxies through the window it is detaching from), so the cleanup
DELETE a reload or quit triggers during provisioning never leaves the
process and the sandbox is orphaned. Record each provisioning sandbox in a
per-extension-host file under the Cline data directory until its task
starts or its cleanup is confirmed, and let the next extension host delete
records whose owner has exited.

* fix(vscode): recover abandoned cloud starts only when it is safe

Address review of the abandoned-start journal:

- Keep a record until the control plane confirms the sandbox is gone. A
  401, 429 or 5xx no longer erases it; only a successful DELETE or a 404
  under the account and endpoint that started it settles it.
- Keep a record until the sandbox has started the first turn, not just
  until the send was queued. Recovery connects to a sandbox before
  deleting it and leaves one that already holds a conversation, in case the
  host exited after the turn was accepted but before recording that.
- Name each journal by pid and extension-host lifetime and refresh it while
  it has records, so an earlier lifetime of this pid, or a pid reused by
  another process, reads as abandoned instead of being skipped or
  overwritten.
- Run recovery as one scope operation of the account that started the
  sandboxes: an account change waits for a DELETE in flight and stops the
  rest, nothing runs after disposal, and reset and dispose cancel the
  recheck timer.

* fix(vscode): never recover a cloud start that may still be live

- A sandbox's saved transcript stays empty until the first turn's
  iteration ends, so recovery could delete a task whose first turn was
  running. Treat any Hub session status other than idle as a turn the
  sandbox has taken.
- Drop the journal heartbeat. A missed refresh does not prove the owner
  exited (it may be paused in a debugger or asleep), so a journal whose
  pid is alive is never taken; a pid reused by another process only
  delays recovery until that process exits.
- Bound the sandbox inspection with a timeout and stop awaiting its
  detach, so an unresponsive sandbox cannot hold the account scope.

* refactor(vscode): simplify abandoned cloud start recovery

Drop the record right before the first prompt is sent instead of waiting
for the sandbox to report turn acceptance. A surviving record then means
the prompt never left the extension host, so its sandbox holds no user
work and recovery can just DELETE it. This removes the turn-acceptance
tracking in CloudSessionHost and the sandbox inspection (connect, status
check, transcript read, timeout) that recovery needed to tell an orphan
from a live task.

Also fail the start when the record cannot be written, so its normal
cleanup DELETE runs while the host can still reach the network, and treat
only ESRCH as proof that a journal's owner has exited.

* fix(vscode): fail a cloud start whose pending record cannot be dropped

Recovery deletes any recorded sandbox without inspecting it, so the record
must be gone before the first prompt is sent. If dropping it fails, fail the
start instead of sending; its normal cleanup then deletes the sandbox. After
a confirmed deletion a leftover record is harmless, so that path only logs.

* fix(vscode): preserve provisioning tasks opened from History (#14793)

Keep a pending cloud start alive when its own History row is opened,
reject older selections, and relist its sandbox when startup settles.
Carry the task-view fence across clearTask instead of claiming again.

Retain abandoned-start journaling from the target branch and verify its
record survives own-row reopening until immediately before the first send.

* fix(vscode): keep a failed cloud start's images when Retry is clicked

The webview's Retry response omits images, which protobuf decodes as an
empty list, so `images ?? retry.images` dropped the attachments and the
retried sandbox ran on the text prompt alone. Fall back to the failed
start's images unless the response actually carries some.

* fix(vscode): stop sending the account token into cloud sandboxes (#14970)

* fix(vscode): keep cloud sessions out of Delete All History (#14967)

* fix(vscode): keep cloud sessions out of Delete All History

* fix(vscode): report the actual Delete All History count

* fix(vscode): cancel a provisioning cloud start when the account changes (#14968)

* fix(vscode): forget a remembered cloud status once the agent runs again (#14969)

* fix(vscode): forget a remembered cloud status once the agent runs again

* test(vscode): cover forgetting a remembered status on connect

* feat(vscode): resume suspended cloud sandboxes and use the SDK's cloud client (#14973)

* test(vscode): let the local cloud fixture suspend and resume sandboxes

* fix(vscode): resume suspended cloud sandboxes instead of treating them as unknown

* fix(vscode): reopen cloud tasks whose last turn failed

* refactor(vscode): talk to the cloud control plane through the SDK's CloudSessionApi

* feat(vscode): show cloud provisioning progress and dashboard renames

* refactor(core): export the cloud session system prompt

* feat(vscode): give cloud sandboxes the SDK's shared prompt and work-branch rules

* perf(vscode): keep the local model catalog out of cloud session.create

* docs(vscode): update cloud session follow-ups for the SDK alignment

* fix(vscode): reconnect from scratch after resuming a suspended sandbox

* fix(vscode): link an organization to its own GitHub connect page

* test(vscode): only auto-suspend resumable fixture sandboxes

* fix(vscode): show the resume notice when a suspended cloud task is opened from History

showTaskWithId only dismisses the History view after the open completes,
which for a suspended sandbox is after the whole resume. The notice the
coordinator posts stayed covered and History sat unchanged for the
duration. Bring the chat view forward right after the notice is posted.

* fix(vscode): keep the UI on the server's account after a slow switch (#14971)

* fix(vscode): keep the UI on the server's account after a slow switch

* fix(vscode): retract a switch error only from the newest account read

* feat(vscode): opt-in Cloud toggle in Settings behind ext-cloud-sessions (#15018)

* feat(vscode): add opt-in Cloud toggle in Settings behind the ext-cloud-sessions flag

* fix(vscode): refresh History when the Cloud toggle changes

* fix(vscode): close four cloud-session gaps found in review and QA

- A cancelled cloud start left a ghost STARTING row in History. A list during
  provisioning adds an entry for the new record before the start holds one, and
  cleanupUnusedSession only dropped the caller's entry. It now drops the entry
  by session id and invalidates History after the DELETE settles.
- A failed DELETE left entry.host pointing at a disposed host, so a reopened
  task stopped reporting status. The host is cleared before disposal.
- Switching repositories left the previous repository's branches selectable
  while the new list loaded, so a stale branch could be saved against the new
  repository. The list is cleared and the picker disabled until it loads.
- Attached files were silently dropped from a cloud start. The composer now
  refuses the send and says why.
- compile-tests could not resolve @cline/core/cloud under classic node
  resolution; tsconfig.test.json maps the subpath to the built declarations.

* docs(vscode): track cloud session follow-ups as GitHub issues instead of a checked-in file

* merge: preserve latest VS Code cloud updates with reviewed fixes

* Saoudrizwan/cloud greptile fixes 26ac (#15073)

* test(vscode): stub @cline/core/cloud for the CommonJS integration host

* fix(vscode): keep the open cloud task's model visible when Cloud is turned off

* fix(vscode): restore a cloud runtime the Hub lost after a turn reports it missing

---------

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>
Co-authored-by: Dominic Cooney <dominic.cooney@cline.bot>
Co-authored-by: Dominic Cooney <dominiccooney@users.noreply.github.com>
Co-authored-by: Dominic Cooney <55120+dominiccooney@users.noreply.github.com>
Co-authored-by: Dominic Coon <dominic@cline.bot>
Co-authored-by: John Choi <john.choi@cline.bot>
2026-10-13 20:31:05 -07:00
Saoud RizwanandSaoud Rizwan faf05ef067 fix(desktop): stop UPX-packing the Linux SSH remote helpers (#14928)
* fix(desktop): stop UPX-packing the Linux SSH remote helpers

Since the Bun 1.4.2 upgrade (#14631) the compiled helper lazily reads its
embedded module payload back from its own executable at runtime. On a
UPX-packed binary those reads return NUL padding, so the helper starts but
every --remote-hub-ensure fails with "SyntaxError: Invalid character:
'\0'" and SSH remotes cannot start (desktop 0.0.38 through 0.0.44).

Ship the helpers uncompressed again and drop the upx installs from the
publish workflow. Bundles grow by roughly 175 MB until the CLI-managed Hub
replaces the helper binary.

Fixes #14815
Fixes #14781

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

* ci(desktop): smoke-test the bundled SSH remote helper before publishing

Run each Linux job's native remote helper through --remote-hub-ensure and
--remote-hub-stop against an isolated CLINE_DIR and discovery record. The
UPX regression started the helper fine but failed on exactly this path,
and nothing in the publish workflow exercised it.

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

---------

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
2026-10-07 15:54:57 -07:00
Saoud RizwanandSaoud Rizwan af7e4ce5e9 ci(desktop): publish Linux arm64 deb and rpm packages (#14672)
* ci(desktop): publish Linux arm64 deb and rpm packages

Turn the build-linux job into an x64/arm64 matrix. The arm64 leg runs
natively on ubuntu-22.04-arm so the sidecar smoke test exercises the real
binary; the collect step names packages per arch (amd64/arm64 deb,
x86_64/aarch64 rpm), which generate-update-manifest.ts already maps to the
linux-aarch64-deb and linux-aarch64-rpm updater keys.

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

* test(desktop): cover arm64 Linux packages in updater manifest test

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

---------

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
2026-09-29 15:53:22 -07:00
Dominic Cooney 159951f82d ci(vscode): make the e2e job spend its time on the tests (#14607)
* ci(vscode): make the e2e job spend its time on the tests

Windows e2e runs hit the 20-minute limit after their tests had passed:
the job was saving a multi-gigabyte bun cache that only that pull request
could read. The job also repeated the type check and lint that Quality
Checks already runs, and downloaded Playwright browsers the tests never
start.

- Restore the bun cache on every run and save it only from main, right
  after the install, so a later test failure keeps it.
- Build the extension with build:production and load it as the
  extension under development. The tests already ran that copy; the VSIX
  they also installed was never used, and packaging it ran
  vscode:prepublish's checks.
- Install only Playwright's ffmpeg, which it records test videos with.
  The tests launch VS Code through Electron, not a Playwright browser.
- Let runs on main finish instead of cancelling them on the next merge.
- Raise the timeout to 30 minutes for the run on main that installs
  without a cache and saves a new one.

* ci(vscode): make the e2e workflow comments match its behavior

A run on main that is still waiting to start is replaced by the next merge; only a run that has started always finishes. The comments now say so, and name the directory VS Code loads the extension from.

* ci(vscode): run Quality Checks when the Playwright config changes

The e2e build no longer runs the type check and lint, and
playwright.config.ts was the one lintable e2e input outside Quality
Checks' path filter. A change to only that file could pass CI unlinted.

* ci(vscode): stop caching Bun's install cache in e2e

Restoring the cache and installing took about as long as an uncached
`bun install` in ext-vscode-test's jobs on the same commits: 3:48-4:31
against 2:39-5:06 on Windows, and 25s against 23s on Ubuntu. With no
cache to save, a run on main can be cancelled by the next merge again
and the job fits main's 20-minute timeout, so concurrency and the
timeout go back to main's settings.

The e2e path filter also drops apps/vscode/.vscodeignore: test:e2e no
longer packages a VSIX, so nothing in the job reads that file.
2026-09-29 21:18:37 +02:00
Saoud Rizwan 3ba8dd8fa7 chore: upgrade Bun to 1.4.2 (#14631)
* chore: upgrade Bun toolchain and embedded runtime to 1.4.2

Bun 1.4.1+ writes a valid ad-hoc Mach-O signature for bun build --compile
output, so the CLI is no longer SIGKILLed on macOS 27, and 1.4 ships
x64 runtimes as baseline-only, so compiled binaries run on pre-AVX2 CPUs.

Pins engines.bun, packageManager, .tool-versions, every setup-bun step,
and @types/bun. The lockfile only moves @types/bun/bun-types so the AI SDK
hoisting is left untouched.

* fix(core): swallow reader.cancel() rejection in web_fetch size guard

Bun 1.4 rejects cancel() on an errored stream, which surfaced as an
unhandled rejection right before the size-limit error was thrown.

* build(cli): boot the hub from the compiled binary as a smoke test

--version only proves the executable launches. Bun 1.4.1/1.4.2 have a
compile-time chunk ordering regression with splitting: true that only
shows up once real modules load, so start the hub daemon from the freshly
compiled host binary in an isolated data dir and require a healthy status
before the build is considered good.

* test(core): stop asserting the forced-exit path in hub shutdown e2e

Under Bun 1.3 http.Server.close() never settled while a WebSocket was
open, so the daemon always exited through the 2s deadline and the e2e
asserted the forced-exit log line. Bun 1.4 completes the graceful close
(~50ms instead of ~2s in a side-by-side probe), so that line no longer
appears. The exit code, timing, discovery cleanup, and endpoint checks
still cover the behavior; the forced path stays covered by
shutdown-coordinator.test.ts.

* build(cli): isolate the smoke test hub from CLINE_HUB_DISCOVERY_PATH

The env var overrides the data-dir-derived discovery record, so an
inherited value would have pointed hub start/stop at a real hub.
2026-09-28 21:33:32 -07:00
Saoud Rizwan d16bd8c7d5 desktop: add Linux support (#14464)
* desktop: use GTK folder picker on Linux and open it off the main thread

rfd's default xdg-portal backend needs a D-Bus session and a portal
backend; without them pick_folder() returns None and the app treats it as
a cancel, so Open folder... silently does nothing. Switch to the gtk3
backend (the app is a GTK window already) and, like tauri-plugin-dialog,
create the dialog on the main thread and await it elsewhere, because the
GTK dialog is driven by the GLib main loop Tauri owns and a blocking
pick_folder() on the main thread deadlocks. macOS and Windows keep the
blocking dialog on the main thread.

* desktop: stage Linux updates and install them on restart

On Linux deb/rpm installs, Update::install runs pkexec dpkg -i / rpm -U,
which asks for the user's password. The background cycle was calling
download_and_install on every non-Windows platform, so an available
update would pop a root password prompt out of nowhere every two hours.
Take the Windows path instead: download and stage in the background,
install when the user chooses Restart now, then relaunch into the new
version.

* desktop: add Linux deb/rpm entries to the updater manifest

Map <Product>_<version>_amd64.deb and <Product>_<version>_x86_64.rpm
(plus their arm64 counterparts) to the installer-specific
linux-<arch>-deb / linux-<arch>-rpm keys. No bare linux-<arch> key is
emitted because the updater falls back to it for any package format
without an entry of its own.

* desktop: Linux packages ship deb + rpm only, with package metadata

The AppImage target is dropped from local Linux packaging: linuxdeploy
cannot process the Bun-compiled sidecar (ldd fails on it and patchelf
corrupts it), which aborts the whole bundle step. Add category,
descriptions, and homepage so the .desktop entry gets Categories and
Comment and the deb/rpm stop reporting Description: (none).

* ci(desktop-publish): build and publish Linux x64 deb and rpm

New build-linux job on ubuntu-22.04 mirroring the Windows job: Tauri
Linux build deps plus upx, updater-signed deb and rpm via
--bundles deb,rpm, the same feed-endpoint, telemetry, and packaged
sidecar guardrails, and a desktop-linux-x64 artifact the release and
nightly jobs consume.

* docs(desktop): document Linux deb/rpm releases and install steps
2026-09-23 21:56:59 -07:00
Saoud Rizwan c10b8cb4be ci(ext): add ext-vscode-publish workflow for standalone stable releases (#14277)
* ci(ext): add ext-vscode-publish workflow for standalone stable releases

Copy of ext-vscode-ab-package.yml with the A/B machinery removed: no
legacy-extension checkout or npm test suite, no loader build, no
stitch/set-version stamping, no union manifest. What remains is the
job layout that has been shipping since 4.1.0 — ungated preflight
(version regex, main-only publish, Marketplace monotonicity) and test
gate, an environment-less build job that packages the .vsix as a run
artifact, and a publish job on the `publish` environment that uploads
that artifact with `vsce publish --packagePath` before tagging.

The prebuilt-artifact upload is the point. ext-vscode-publish-stable.yml
lets a bare `vsce publish` re-run vscode:prepublish and repack the
source tree, and that upload died with `Request timeout: /_apis/gallery`
on all five v4.1.18 attempts (Sep 15-17) while the same code line went
out as 4.1.19 through the packagePath path in three seconds.

Differences from the A/B workflow beyond the removals: the version
input is asserted equal to apps/vscode/package.json instead of being
stamped in, publish=true is refused off main, README.marketplace.md is
swapped in for packaging (as the standalone workflows did), and the
build asserts the packaged manifest identity plus the absence of a
process.env.TELEMETRY_SERVICE_API_KEY literal in dist/extension.js.

* docs(skills): point publish-extension at ext-vscode-publish

Stable releases go through the new workflow; the stable-repack workflow
is marked do-not-use with the 4.1.18 timeout evidence, the dispatch and
post-publish sections describe the new job order (tag after publish,
continue-on-error bookkeeping), and the retirement list now covers
deleting ext-vscode-publish-stable.yml and publish-marketplace.mjs.

* ci(ext): retry Marketplace publish and correct the failure analysis

The workflow header and runbook previously attributed the v4.1.18 stable
failures to the repack/upload path. Reading the error against vsce's
source shows the hung request was OPTIONS /_apis/gallery, the client's
route-discovery call with no body, so the VSIX was never uploaded and
nothing about the artifact can be the cause. The stable job was also
running the workspace-vendored vsce 3.9.2 (bun run puts node_modules/.bin
first on PATH), not the global 4.0.0 it installed. Both are now stated
as facts, with the mechanism explicitly marked as unknown.

Add a 3-attempt retry around vsce publish. It cannot double-publish:
vsce queries the listing before uploading and exits 0 with 'already
published' if an earlier attempt landed but its response was lost.

* ci(ext): make publish retry duplicate-safe and check every inlined env var

Greptile findings on #14277, both verified against source:

- vsce only treats an already-published version as success when
  --skip-duplicate is passed; without it _publish throws 'already
  exists' (both on the pre-upload listing check and on a 409 from the
  upload). The retry loop therefore needed the flag, otherwise an
  attempt whose upload landed but whose response was lost would fail
  all three attempts and skip the tag/release/Slack bookkeeping.

- apps/vscode/esbuild.mjs inlines eleven build-time variables, each only
  when non-empty, but the packaged-bytes check looked for just one. Now
  every variable the build step supplies is asserted absent from
  dist/extension.js. Self-tested against the shipped 4.1.19 bundle: all
  eleven absent; OTEL_METRIC_EXPORT_INTERVAL (not supplied) present as
  the negative control.

* ci(ext): pass github.ref to the shell via env, not inline interpolation

Ref names may contain shell metacharacters; keep the run block free of
inline expression interpolation for anything that is not the
regex-validated version input.
2026-09-23 18:43:22 -07:00
Mikołaj Kondratek 4a56a43f39 test(desktop): run the x86 NSIS installer hooks against x64 processes in CI (#14176)
* test(desktop): run the x86 NSIS installer hooks against x64 processes in CI

The sidecar-lock installer hook shipped broken for four releases because
it was only ever exercised under 64-bit pwsh, never inside the 32-bit
installer NSIS actually produces. Add a bun test that compiles the real
hooks file into an x86 NSIS installer with the same NSIS 3.11 build the
Tauri bundler pins, then runs it against 64-bit fixture processes:

- a fresh install and a repeated install with nothing running succeed
- running cline-app.exe and code-sidecar.exe holders in the target
  directory are stopped, the locked exe is replaced, and a same-named
  process from a side-by-side install (Beta) survives untouched
- the uninstaller releases and deletes the files
- a silent install fails closed (exit 2, nothing written) when Restart
  Manager reports an error

The test needs Windows x64 and makensis (MAKENSIS_PATH). A new
windows-installer job in desktop-test.yml downloads the pinned NSIS zip,
verifies its SHA1, and runs the test whenever the hooks or the test
change. Passive-mode failures show a Retry/Cancel dialog by design and
are not automated.

* ci(desktop): disable setup-bun cache in the windows-installer job

The job installs nothing, so there is no cache to save, and setup-bun's
cache-save post step hangs on windows-latest until the 10-minute timeout
cancels the run after the test has already passed.

* test(desktop): fail the installer suite when the NSIS or Tauri CLI pin drifts

desktop-test.yml downloads nsis-3.11 by hand to match what the locked
@tauri-apps/cli bundles installers with. Nothing tied the two together, so
a Tauri CLI bump (new NSIS pin in tauri-bundler) or an edit to the download
would leave the suite exercising a different NSIS than the shipped
installer. Pin both ends in the test: bun.lock must still resolve the CLI
version whose pin was checked, and makensis must report that NSIS version.

* ci(desktop): run the installer drift guard when bun.lock changes

The suite asserts that bun.lock still resolves the pinned @tauri-apps/cli,
but both workspaces declare it as ^2.0.0, so a lockfile refresh can move
the resolved version without touching any path the workflow watched. Add
bun.lock to both trigger lists so the guard runs on its own input.
2026-09-21 11:24:12 +02:00
Saoud Rizwan 9a2512bb98 ci(desktop): tag nightlies and announce the changes to test in Slack (#14310)
* ci(desktop): tag nightlies and announce the changes to test in Slack

Each successful nightly now ends with a job that tags the built commit
desktop-nightly-<utc stamp> and posts to the release channel the
commits since the nearest desktop tag (previous nightly or last
release), scoped to the desktop app, the SDK packages the sidecar
bundles, and the publish workflow, with PR and compare links. The tag
is the only record a nightly leaves behind and is what the next one
compares against.

* ci(desktop): make the nightly announce job rerun-safe and length-capped

Cap the Slack commit list by total characters as well as count, since
15 long subjects could still exceed a section block's 3000-character
limit and fail the job. Skip tag creation when this build's tag already
exists at HEAD, and exclude it from the baseline lookup, so a rerun
after a failed Slack post retries the notification instead of dying on
'tag already exists'.
2026-09-19 14:55:41 -07:00
Saoud Rizwan 93ceab9782 fix(ci): prime the Trusted Signing token before the Windows desktop build (#14309)
The Windows desktop job signs with jsign + Azure Trusted Signing, and
tauri-sign-windows.ps1 fetches its token with az account get-access-token
on every file. The first such call comes after the Rust build, and az
must present the GitHub OIDC assertion from azure/login to get a token
for a new resource. That assertion is only accepted for ~10 minutes
(5-minute lifetime plus 5 minutes of clock skew). Every Windows job
signed its first file within 8.5 minutes of login until UPX-packing the
remote helpers (#14303) added two minutes to the build, after which both
attempts of the Sep 19 nightly failed at 10:28 and 10:08 with only
Tauri's 'failed to run pwsh' in the log.

Fetch the codesigning token once right after login so the per-file calls
are served from az's cache, and let that step fail with az's real error
if the token can't be acquired.
2026-09-19 14:07:54 -07:00
Saoud RizwanandSaoud Rizwan 18b8a63109 build(desktop): UPX-compress the bundled Linux SSH remote helpers (#14303)
Each helper is a bun --compile executable: ~100 MB of Bun runtime around
~14 MB of our code, and both Linux helpers ship inside every desktop
bundle. Since #14117 that added ~220 MB to the macOS .app (175 MB -> 435
MB installed, 74 MB -> 154 MB DMG) and ~52 MB to the Windows installer.

UPX packs each ELF in place to ~24-27 MB and it self-extracts in memory
on launch, so the installer, the SSH upload, and the remote run are
unchanged. Verified the packed x64 helper boots a Hub via
--remote-hub-ensure and shuts it down via --remote-hub-stop; cost is
~1.6 s extra startup per invocation and ~55 MB extra RSS on the daemon.
strip is not usable here: it drops Bun's appended module payload.

The publish workflow installs upx on the macOS and Windows runners and
the build fails under CI if it is missing; local dev builds warn and
skip so bun run dev keeps working without it.

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
2026-09-19 13:07:07 -07:00
Bee 8f0978111f fix(desktop): build Linux x64 helpers for pre-AVX2 CPUs (#14262) 2026-09-18 13:47:38 -07:00
Saoud Rizwan 8872d81661 ci(desktop): boot the packaged sidecar against an isolated Hub before publishing (#14249) 2026-09-17 22:07:27 -07:00
Bee 706ceac669 feat(ci): add nightly desktop publish workflow (#14244)
* feat(ci): add nightly desktop publish workflow

Support manually dispatched nightly desktop builds from main, with auto-generated version tags and conflict-safe concurrency.

* fix(desktop): keep nightly builds as Actions artifacts
2026-09-17 17:39:14 -07:00
Saoud Rizwan 999a2b8e7b fix(desktop): claim the Hub daemon sentinel in the sidecar entrypoint (#14243)
* fix(desktop): claim the Hub daemon sentinel in the sidecar entrypoint

Since #14117 the sidecar left the daemon sentinel claim to the shared
remote helper in @cline/core, whose daemon import resolves to the dist
build of core, while the sidecar bundle itself resolves core from source
through tsconfig paths. The daemon therefore published a different build
id than the sidecar expected, the sidecar retired its own Hub on every
launch, timed out with "No compatible hub runtime is available", and
Tauri reported "desktop backend exited before publishing its endpoint"
(desktop 0.0.31). Claim the sentinel in the sidecar again so the daemon
loads from the same copy of core.

* test(desktop): start the compiled sidecar against an isolated Hub in CI

Source-level tests cannot catch a build identity split between the
desktop client and its embedded Hub daemon, so compile the real
entrypoint, bootstrap an isolated Hub with --remote-hub-ensure on an
OS-assigned port and a dedicated discovery path, pin the sidecar to that
port, and require the ready line plus a healthy /health response. Fails
with "No compatible hub runtime is available" without the entrypoint fix.
2026-09-17 17:06:36 -07:00
Saoud Rizwan d6ede1a89c fix(desktop): seed Bun's cache so the Windows publish job can cross-compile the Linux SSH helpers
build-sidecar-bin.ts cross-compiles the Linux remote helpers on every
platform, and the Windows installer ships them as Tauri resources. Bun
cross-compiles by downloading the target runtime's release zip and
extracting the executable, and on a Windows host that extraction fails
deterministically ("Failed to extract executable for
'bun-linux-x64-v1.3.13'. The download may be incomplete.") even though
the download and archive are intact. This broke the 0.0.31 publish run
twice on fresh runners; the macOS job builds the same helpers fine.

Bun only downloads when $BUN_INSTALL_CACHE_DIR lacks
bun-<target>-v<version>, so the Windows job now seeds both Linux
runtimes from the @oven/bun-<target> npm packages (byte-identical to
the zip payload) before building, and the broken extractor never runs.
2026-09-17 14:14:19 -07:00
John Choi 9e12953bed fix(ci): build LLM dependency before UI release chat tests (#14202) 2026-09-17 09:35:34 -07:00
063ce7c06d feat(telemetry): enable release traces and preserve Langfuse user/session tracking (#13982)
* feat(telemetry): enable OTLP traces exporter in publish builds

Bakes OTEL_TRACES_EXPORTER=otlp into stable/nightly extension and CLI
publish builds. With the client trace pipeline (#13974) this turns on
cline-provider AI SDK trace emission at 100% task sampling, metadata-only
(no prompt/completion content).

Kill switches without a release: remote config openTelemetryTracesExporter
(extension) and the prod otel-collector's probabilistic sampler.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(telemetry): record prompt/completion content on traced requests

Sets CLINE_TRACE_RECORD_CONTENT=true alongside the traces exporter, so
cline-provider traces carry full message content (recordInputs/
recordOutputs) instead of metadata only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci: assert content-capture env is inlined into the packaged extension

Stable gates pre-publish (right after vsce package); nightly packages
inside publish-nightly.mjs so its check is post-publish detection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ci): gate nightly publication on content flag inlining

* fix(telemetry): cover combined release packaging and verify trace artifacts

* fix(ci): type trace artifact checker and assert missing bundle fixture

* fix(test): use the .exe outfile Bun compile produces on Windows

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(telemetry): propagate Langfuse identity attributes through OTLP

* refactor(telemetry): move Langfuse integration ownership to llms

* refactor(ci): remove brittle trace artifact checks

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: abeatrix <beatrix@cline.bot>
2026-09-16 17:50:22 -07:00
Haley Park e7d224dec6 feat(ui): share native Switch across desktop settings (#14073)
* feat(ui): add shared native Switch

* refactor(desktop): adopt shared Switch

* ci(ui): run desktop settings integration tests
2026-09-11 16:18:15 -07:00
John ChoiandSaoud Rizwan e15bb0a7e5 feat(desktop): add cloud session foundations (#13558)
* feat(sdk): support authenticated remote Hub connections

* fix(sdk): pin compatible SAP connectivity

* chore(sdk): defer SAP smoke fix to main

* fix(sdk): preserve hub connection failures

* feat(hub): recover pending session approvals

* fix(hub): scope approval recovery to attached clients

* feat(desktop): add cloud session foundations

* fix(desktop): require boolean cloud rollout flag

* test(desktop): run sidecar suite in pull requests

* ci: test desktop changes in stacked pull requests

* fix(desktop): scope cloud model catalog rollout

* fix(sdk): clear feature flags on identity change

* test(hub): reconnect approval session owner

* docs(sdk): clarify remote Hub connection headers

* docs(hub): describe approval recovery

* docs(llms): describe cloud catalog opt-in

* test(sdk): tighten hub header coverage

* test(hub): remove redundant approval setup

* chore(desktop): trim cloud foundation scaffolding

* fix(hub): preserve approval recovery for existing clients

* test(core): batch root history fixture inserts

* test(sdk): await daemon health after discovery publication

* feat(hub): recover pending session approvals

* fix(hub): scope approval recovery to attached clients

* test(hub): reconnect approval session owner

* docs(hub): describe approval recovery

* test(hub): remove redundant approval setup

* fix(hub): preserve approval recovery for existing clients

* refactor(hub): drop cloud-only pending approval API

* chore(desktop): trim redundant cloud session comments

* fix(desktop): preserve cached flags and share account auth

* fix(llms): retain cloud model names after catalog merge

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-09-11 13:49:57 -07:00
Saoud RizwanandSaoud Rizwan 194214af76 ci(desktop): stop Tauri from skipping the DMG layout AppleScript on Actions (#13998)
Tauri's DMG bundler passes --skip-jenkins to bundle_dmg.sh whenever CI=true,
which GitHub Actions always sets. That skips the Finder AppleScript that
applies the background, window size, and icon positions from tauri.conf.json,
so every published DMG since #13563 shipped with the stock Finder window even
though the artwork was generated and validated. Set TAURI_BUNDLER_DMG_IGNORE_CI
so the script runs on the macOS runner's GUI session.

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
2026-09-09 12:18:30 -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
89970ea794 Sign Windows CLI binaries with Azure Trusted Signing; surface app-control launch errors (#13021)
* feat(cli): sign Windows binaries with Azure Trusted Signing and surface app-control launch errors

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

* fix(cli): use _CLI-suffixed signing profile secret, normalize endpoint, fail loud on partial config

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

---------

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-08-26 16:13:23 -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
Saoud RizwanandSaoud Rizwan f91af30401 Add Desktop App and Cloud Platform to bug report issue template (#13532)
* Add Desktop App and Cloud Platform to bug report surfaces

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

* Rename Surface Diagnostics field to Diagnostics

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

---------

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
2026-08-24 19:49:47 -07:00
Saoud Rizwan e7ed29109b ci(vscode): make combined nightly manual-dispatch only
The PublishNightly environment gained required reviewers, so each cron
run parked on approval, held the workflow's concurrency group, and
silently cancelled every scheduled run queued behind it. 20 consecutive
scheduled nightlies died this way between 2026-07-31 and 2026-08-21;
the only nightlies that shipped in that window were manual dispatches.

Drop the cron rather than leave a trigger that cannot succeed unattended.
2026-08-21 17:01:56 -07:00
Mikołaj Kondratek e70b3ffc4b ci(vscode): upload E2E failure recordings from the right path (#13427)
The job sets working-directory: apps/vscode, but that default applies to run
steps only, not to `uses:` steps. Since #10961 moved the extension under apps/
and added that default, the artifact path has resolved against the repo root,
matched nothing, and every failing run logged "No files were found with the
provided path: test-results/playwright/" instead of uploading recordings.

Widen to test-results/ so Playwright's error-context snapshots ship alongside
the videos.
2026-08-20 20:10:12 +02:00
Saoud RizwanandSaoud Rizwan 878faf0a95 Rename desktop app from "Cline Code" to "Cline" (#13401)
* Rename desktop app from Cline Code to Cline

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

* Format touched Rust test assertions

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

---------

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
2026-08-19 15:29:00 -07:00
Saoud Rizwan 36397f47eb ci: tidy workflow cache config and job permissions (#13403)
Publish workflows now always do clean npm installs (no dependency
cache in their test gates), the e2e workflow's cache keys are
exact-match only, and the e2e job drops an id-token permission it
never used.
2026-08-19 14:31:41 -07:00
Saoud Rizwan 3f0c5cdc92 ci: stop over-long changelogs from silently dropping release Slack posts (#12955)
Slack section blocks reject text longer than 3000 characters. The Slack
action logs that rejection as ##[error] but does not fail the step, so an
over-long changelog drops the release announcement while the run stays
green — cline@3.0.50 (3272 chars) published to npm, tagged, and cut a
GitHub release with no Slack post and nothing red to notice.

Every publish workflow pasted the changelog section verbatim into one
section block, so all six were exposed; the SDK, desktop, and extension
sections were only 150-350 chars under the ceiling.

Add a slack_content output alongside content: unchanged when the section
fits, otherwise trimmed on a line boundary with a link to the full
release notes. Only the Slack payload uses it — GitHub release bodies and
the desktop updater manifest still get the whole section.
2026-08-19 13:34:40 -07:00
Saoud Rizwan 76ac1c7f55 ci(ui-publish): build @cline/shared before ui typecheck (#13354)
@cline/ui's generated-media imports @cline/shared/browser, which resolves to
shared's dist output. The build-shared step sat after typecheck/test/build,
so the first ui-publish dispatch since #13025 failed at Typecheck UI with
TS2307. Move the step to right after install.
2026-08-18 18:01:10 -07:00
Saoud Rizwan eeaed357ef fix(ci): lock the legacy publish workflow to the legacy-extension branch (#13350)
The branch dispatch input was a free-form string with no validation. Both
jobs checked it out and ran full npm lifecycle scripts from it: the publish
job next to VSCE_PAT/OVSX_PAT (and npm run publish:marketplace executes a
script from that same ref with the PATs in env), and the test job with NO
environment approval at all while inheriting the workflow-level
contents/packages/checks/pull-requests write grants. A dispatch pointing at
e.g. refs/pull/N/head would run outside-contributor code with the
marketplace keys behind one approval, or with a repo-write token behind
none.

Remove the input and hardcode the protected legacy-extension branch, drop
the workflow-level permissions to contents: read, and elevate only the
publish job to contents: write (tag push + GitHub release). The branch
input's default was legacy-extension, so normal publishes are unchanged.
publish-extension skill dispatch command updated to match.
2026-08-18 16:12:04 -07:00
Saoud Rizwan bca9b64206 fix(ci): always build the legacy bundle from the legacy-extension branch (#13349)
The combined-VSIX workflow took legacy-ref as a free-form dispatch input
with no publish-time validation (next-ref has one: publish requires main).
Any typed ref — a PR merge ref, an unprotected branch — would be built
into the published VSIX by the environment-less build job, and the publish
environment approver only ever sees an opaque prebuilt artifact, so the
approval protected the marketplace PAT but not the shipped bytes.

Remove the input entirely and hardcode the protected legacy-extension
branch, which makes that branch's protection rules load-bearing for
releases. The tested-sha pinning between test-legacy and build is
unchanged. publish-extension skill dispatch command updated to match.
2026-08-18 16:00:35 -07:00
Saoud Rizwan 5ad2dd5fc8 feat(desktop): beta release channel from desktop-experimental branch (#13321)
* feat(desktop): add beta release channel from desktop-experimental branch

Adds a 'channel' input (stable|beta) to desktop-publish.yml. Beta releases
are tagged desktop-vX.Y.Z-beta.N on the desktop-experimental branch, built
with the tauri.beta.conf.json overlay (Cline Code Beta / bot.cline.app.beta,
side-by-side install with stable), published as prerelease GitHub releases,
and served by a separate rolling desktop-beta update feed. Both channels
dispatch from main so the PublishDesktop signing gates are unchanged.

Guards: stable channel now rejects prerelease tags (previously a beta tag
could clobber desktop-latest and auto-update every stable install onto it),
feed selection is fail-closed and cross-checked in the release job, and the
build asserts the compiled binary embeds exactly its own channel's feed URL.
Changelog extraction is exact-version now that stable and beta sections
interleave across branch merges.

Process doc in apps/examples/desktop-app/EXPERIMENTAL.md; publish-desktop
skill now asks stable-or-beta.

* docs(desktop): warn against renaming the desktop-latest feed

* docs(desktop): document the code-trust model for publish approvals

The beta dispatch-from-main invariant protects the workflow definition, not
the checked-out tag's build scripts, which run with signing secrets in scope
for stable and beta alike. Make explicit that the PublishDesktop reviewer
approval is the trust gate for that code, and that desktop-experimental
therefore needs main-grade merge controls.
2026-08-17 17:24:01 -07:00
Saoud RizwanandSaoud Rizwan ddfb67515b fix(desktop): inline telemetry config into the packaged sidecar binary (#12925)
* fix(desktop): inline telemetry config into the packaged sidecar binary

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

* fix(desktop): reject non-http OTLP endpoints in the telemetry selfcheck

---------

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
2026-08-04 20:43:01 -07:00
Saoud Rizwan 62c57a0ccd ci(ui): publish @cline/ui without a manual approval gate (#12938)
The publish job ran under the shared `Publish` GitHub environment, whose
required reviewers turned every @cline/ui release into a two-person
ceremony. Nothing in the job reads secrets from that environment — it
authenticates to npm purely over OIDC trusted publishing — so the
environment bought us an approval prompt and nothing else. sdk-publish
and cli-publish already publish unattended the same way.

The npm trusted publisher for @cline/ui was registered with
`environment: Publish`, which pins the OIDC token's environment claim, so
it has been re-registered without it (same repo, workflow file, and
permissions). That change is already live; landing this without it would
have broken publishing.

Access is still gated by workflow_dispatch (write access required), the
`refs/heads/main` ref check, and the typed `publish` confirmation.
2026-08-04 16:23:46 -07:00
Saoud RizwanandSaoud Rizwan 058c8c90a2 feat(desktop): ship a single universal macOS DMG instead of per-arch downloads (#12923)
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>
2026-08-04 12:42:55 -07:00
Saoud RizwanandSaoud Rizwan 46fcde0a96 ci(desktop): drop the Rust build cache from the code-signing job (#12898)
* ci(desktop): drop the Rust build cache from the code-signing job

The `build` job is the only one that can read the Apple Developer ID
certificate and the Tauri updater signing key, and it restored a
swatinem/rust-cache archive before running them. A restored cache archive
is attacker-controlled the moment the Actions cache is poisoned, which is
the pivot used against this repo's nightly workflow in Feb 2026 and the
reason actions/cache was stripped from the credential-bearing publish
jobs at the time. This workflow was added months later and reintroduced
the pattern. The updater key is the worst thing here to leak: it signs
every auto-update the installed desktop app accepts.

The cache was also not buying anything. Across the eight runs of this
workflow, seven logged "No cache found" on both matrix legs; only the run
32 minutes after another one hit, saving 1-3 minutes. A release cadence
measured in days does not outlive the entry under the repo's 10 GB LRU
eviction, so the steady state was a cold build regardless. Cold builds
took 5-7 minutes against a 90-minute timeout.

No behaviour change otherwise: the step had no id and no outputs, so
nothing referenced it.

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

* ci(desktop): trim the cache-removal comment to the constraint

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

---------

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
2026-08-03 19:16:23 -07:00
Saoud Rizwan 1654517614 ci(desktop): gate desktop publish secrets behind PublishDesktop environment (#12854)
* ci(desktop): gate desktop publish secrets behind PublishDesktop environment

The Apple signing/notarization and Tauri updater secrets were repository
secrets, readable by any workflow in the repo and by anyone with push
access via a branch carrying a modified workflow. Move them behind the
PublishDesktop environment, which requires reviewer approval and
restricts deployments to main.

The build job now declares the environment, so those secrets are readable
only there and only after an approval. Add a preflight check because a
missing secret fails dangerously rather than loudly: Tauri silently skips
code signing when APPLE_CERTIFICATE is empty and skips notarization when
APPLE_API_KEY is empty, so a misconfigured environment would still
publish an unsigned, un-notarized bundle. Only a missing updater key was
already caught, by the .sig check in Collect artifacts.

validate stays ungated so a bad tag fails in seconds rather than after an
approval, matching the ungated-build/gated-publish split in
ext-vscode-ab-package. The shared Slack and telemetry secrets stay where
they are; scoping them to this environment would silently empty them in
the CLI, SDK, and extension publish workflows.

* ci(desktop): verify signing secrets are not repository-scoped

The preflight added in the previous commit checks that the signing
secrets are non-empty, which proves presence but not scope, and then
reported that they had resolved from PublishDesktop. An environment-gated
job resolves repository and organization secrets too — environment values
merely take precedence — so a credential left at repository level would
pass that check while the message claimed the migration had worked. This
workflow already demonstrates it: the gated build job reads the shared
Slack and telemetry secrets, none of which are on the environment.

Add the complementary check to validate, which declares no environment: a
signing secret that resolves there can only be repository- or
organization-scoped, so it fails the run and names the offenders. Neither
check establishes provenance alone; together they do. validate is
ungated, so a misplaced secret now fails before the approval rather than
after it.

Also drop the provenance claim from the build message and correct the
skill doc, which stated that a repository-level secret would be invisible
to the gated job.

Reported by greptile on #12854.
2026-08-02 01:55:24 -07:00
Saoud Rizwan b0ee2cc80a ci(vscode): publish combined stable VSIX to Open VSX and harden ab-package gates (#12805)
* ci(vscode): publish combined stable VSIX to Open VSX and harden ab-package gates

* ci(vscode): address greptile review — env-routed version input, bookkeeping survives Open VSX failure
2026-07-31 20:09:09 -07:00
Saoud Rizwan 69e3149f5f ci(vscode): add tag, GitHub Release, and Slack bookkeeping to combined publish workflow 2026-07-30 20:59:08 -07:00
Saoud Rizwan 311959e757 ci(vscode): add test gate to combined A/B publish + publish-extension skill (#12764)
* ci(vscode): gate the combined A/B package workflow on both bundles' test suites

* docs(skills): add publish-extension skill for VS Code extension releases

* ci(vscode): pin tested revision for next bundle and refuse publishing untested next-refs

* ci(vscode): pin legacy bundle to the revision its test gate ran against
2026-07-30 17:54:29 -07:00
Saoud Rizwan 078abcd055 fix(ci): build shared package before the ui-publish desktop chat test
The desktop chat integration test renders components from @cline/ui, but
it also pulls @cline/shared/browser through the desktop app's own
message-content module. That subpath resolves to dist output no step in
this job produced, so the suite failed to collect.

Build @cline/shared before the test, and install the full workspace: the
two-package filter did not provide enough of the tree for that build.
2026-07-30 12:02:42 -07:00
Saoud Rizwan 283c3ba937 ci: grant pull-requests write so promo-comment deletion works on PRs (#12606) 2026-07-27 16:51:00 -07:00
Saoud Rizwan 69c9a9ac28 ci: delete coding-agent promo comments on PRs (#12604) 2026-07-27 16:44:48 -07:00
Saoud Rizwan 1bd200e906 ci: strip cloud-agent promo wrappers from PR bodies (#12588)
* ci: strip cloud-agent promo wrappers from PR bodies

* ci: make PR body strip vendor-agnostic, pin github-script to SHA
2026-07-27 15:10:05 -07:00
Saoud Rizwan f5224abdf5 feat(desktop): auto-updates + automated signed releases from GitHub Actions (#12420)
* feat(desktop): auto-update via Tauri updater with restart prompt

The Rust shell now checks the desktop-latest GitHub release feed on launch
and every 2 hours, downloads and stages updates in the background, and
exposes get_update_status/restart_to_apply_update commands. The webview
polls the status and shows a persistent toast with a one-click restart once
an update is staged; ignored updates apply on next launch. Updater
artifacts are only produced with the CI config overlay
(tauri.release.conf.json) so local packaging keeps working without the
updater signing key. Also mounts the previously-unmounted Toaster so
existing toast() calls render.

* ci(desktop): add desktop-publish release workflow and publish-desktop skill

desktop-publish.yml mirrors cli-publish: dispatch with a desktop-vX.Y.Z
tag + confirm gate, validates the tag against package.json and
tauri.conf.json, builds signed+notarized DMGs for aarch64 (native) and
x86_64 (cross-compiled sidecar via bun --target), generates the updater
manifest, publishes the versioned GitHub release, refreshes the rolling
desktop-latest auto-update feed, and posts to Slack. Adds the release
skill, changelog, and README docs for the required GitHub secrets.

* fix(desktop): address review — outlast sidecar shutdown window, dedupe update toast across remounts

stop() now polls for 7s before escalating to kill, past the sidecar's own
5s SHUTDOWN_TIMEOUT_MS graceful-shutdown budget, so clicking Restart now
(or quitting) during session persistence can't SIGKILL the sidecar
mid-write. notifiedVersion moves to module scope so a page remount doesn't
re-toast an update the user already dismissed.

* docs(desktop): move publish-desktop skill to .cline/skills, slim README release section

Match the publish-cli convention: the skill lives in .cline/skills/ and is
symlinked from both .agents/skills/ and .claude/skills/ so all agents pick
it up. The README's release section shrinks to a pointer + the two
never-lose invariants (desktop-latest feed, updater private key); the repo
secrets table moves into the skill, which also fixes its dangling reference
to a 'Release automation' README section and escapes the pipe that broke
the GFM table cell.
2026-07-21 16:37:18 -07:00
Saoud Rizwan 7274d8badc feat(ui): add agent chat components, Storybook, and npm releases (#12374)
* feat(ui): add shared agent chat components and Storybook

* ci(ui): add standalone npm publishing

* docs(ui): keep release commands environment-neutral

* refactor(ui): simplify package validation

* refactor(ui): tighten package and release contracts

* docs(ui): remove duplicate install guidance

* ci(ui): make publishing workflow manual-only
2026-07-17 18:22:57 -07:00