* feat(desktop): configurable keybindings with hold-to-quit
Add a keybindings.json (settings/clients/desktop) that maps desktop app
shortcuts, a Keybindings settings view to record them, and Keybindings
menu items in the app and Help menus. Quit can require holding the keys
(default 1s on macOS) to avoid quitting by accident. Add Kbd/KbdGroup to
@cline/ui for rendering shortcuts.
* fix(desktop): harden keybinding capture and hold-to-quit
- Handle a custom quit shortcut with no hold time in the webview instead of
only restoring native Cmd+Q, which never fired the custom key.
- Quit only from a key-repeat event at or past the hold threshold so time
after the key was released never counts.
- Suspend all shortcuts while one is being recorded so re-recording the quit
keys cannot quit the app.
- Enforce Shift for named keys (Mod+Enter no longer matches Mod+Shift+Enter).
- Compare platform-resolved shortcuts for conflict warnings so aliases such
as Cmd+N and Mod+N are flagged.
- Ignore stale save broadcasts while a newer save is in flight.
- Normalize the released key (Space) when ending a hold.
- Never rewrite an existing keybindings.json when opening it, so a malformed
file can be fixed by hand.
- Fix the documented file path and two a11y lint errors.
* fix(desktop): restart quit hold on each fresh key press
* fix(desktop): validate shortcuts and restore native quit when unfocused
* fix(desktop): consume app shortcuts before input key handlers
* fix(desktop): stop the quit shortcut from reaching input handlers
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* refactor(core): move desktop cloud session orchestration into the hub
* fix(core): compare cloud account scope without hashing credentials
* fix(core): address hub cloud session review concerns
* fix(desktop): preserve successful account switches when cloud refresh fails
* fix(core): treat in-flight cloud commands as busy when replacing the hub
Hub replacement decided busy-ness from session.list, which never reported
cloud work, so a newer install could retire the hub mid-provisioning and
fail the pending create. session.list now reports activeCloudCommands, and
the automatic retirement, explicit upgrade, and recovery checks count it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): scope cloud discovery fallbacks to their organization
* fix(core): include organization lookup in cloud discovery deadline
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(llms): keep Bedrock Sonnet 4.5 through models.dev deprecation
models.dev now marks Claude Sonnet 4.5 on Bedrock (bare id and all geo
profiles) and Z.AI Coding Plan's glm-5.2 as deprecated, and catalog
normalization drops deprecated models. The next `bun run version` regen
would therefore:
- remove Sonnet 4.5 from the Bedrock catalog, so resolveBedrockModelId
can no longer confirm a geo profile and sends the bare id, which AWS
rejects for on-demand use
- leave zai-coding-plan's default model missing from its catalog
Retain the deprecated Sonnet 4.5 Bedrock entries in normalization (used
by both generation and the live catalog refresh) until AWS retires the
model, and move the Z.AI Coding Plan default to glm-5.3.
Also stop pinning ClinePass fallback tests to a specific model the Cline
backend no longer lists; they now check against the generated catalog.
* test(desktop): give the compiled plugin startup test a generous import timeout
Cold Windows runners intermittently take longer than the 4s default to start
the sandbox child, failing sdk-test. The test covers resource loading, not
startup time.
* revert(llms): drop the deprecated Bedrock Sonnet 4.5 retention
Let models.dev deprecations flow through the catalog as before. Point the
Bedrock geo-prefix test at Claude Haiku 4.5, which models.dev still lists, so
it passes after the release catalog regen.
Hub replacement decided busy-ness from session.list, which never reported
cloud work, so a newer install could retire the hub mid-provisioning and
fail the pending create. session.list now reports activeCloudCommands, and
the automatic retirement, explicit upgrade, and recovery checks count it.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(telemetry): report per-run stall signals
Add task.run_health (one per agent run, every exit path) and task.run_idle
(at most once per run, after 60s with no runtime event) so harness stalls
can be measured from telemetry instead of transcripts.
run_health carries the longest gap between runtime events and what preceded
it, finish reason, final message shape and a text signature of a final
text-only turn. session.input_sent gains agentBusy, msSinceTurnEnd and a
bumpKind label (probe/nudge/greeting) so the user's reaction to a run can be
paired with it; task.cancelled gains msSinceLastEvent. No message text is sent.
* refactor(telemetry): move run stall signals into a core observer
Stall diagnostics now come from a core RunHealthObserver that watches
runtime events, instead of living in the agent loop. Its fields are added
to agent.run-finished / agent.run-failed through a runEndTelemetryProperties
hook, replacing task.run_health and the task.cancelled additions.
Also fixes measurement gaps: tool-argument streaming now emits a
tool-call-delta progress event, the silence before a run ends is counted,
and a turn rejected before reaching history is labelled empty.
* fix(telemetry): tighten run stall signals
Clear finishReason per turn, skip textSignature for media-only replies,
keep stall fields off replayed queued prompts, and document the run
health observer.
* fix(telemetry): identify the run on its run-end stall fields
agent.run-finished / agent.run-failed carry no session, model or provider of
their own, so the stall fields could not be grouped by model or provider,
paired with the user's next message (session.input_sent), or filtered to
root agents. RunHealthObserver now adds sessionId, isSubagent, providerId and
modelId to the run-end properties; undefined values are omitted.
* fix(telemetry): keep stall labels on prompts sent during a queued run
A queued prompt replayed by the drain starts its run unlabelled, but the
session-wide drain flag also stripped the stall fields from new prompts
the user sent while that queued run was busy. Only the replay itself (the
input that starts the run) is unlabelled now.
Tool-argument chunks now report progress at most once a second: each
event copies the whole conversation, and a large file write streams
thousands of chunks.
* fix(telemetry): mark only the replayed queued prompt as a replay
The queue drain now marks the one input it replays, and runTurn consumes
that marker, instead of inferring a replay from the session-wide drain
flag. Fresh user input during a queued run keeps its stall fields
whether the run is busy or just finishing.
---------
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
* ci: require PublishNPM approval for every npm publish and drop CLI/SDK nightly crons
CLI, SDK, and @cline/ui publish jobs now run in the PublishNPM GitHub
environment, so nothing reaches npm until a reviewer approves the run.
The CLI nightly job and both nightly crons are removed: an unattended
scheduled run would sit waiting for approval every day.
* ci: keep the CLI nightly as a manual, PublishNPM-gated publish
* fix(core): reject an empty run_commands list instead of reporting success
`{"commands":[]}` validated, ran nothing, and came back as a successful
call with no results. Fail it with a clear tool error so the model sees
that no command was given, whether it emitted the empty list itself or
a repair produced it.
* fix(core): advertise the run_commands list as non-empty
The lenient input union still accepts an empty list so the normalizer
can reject it with one clear message; the advertised schema now also
tells the model up front, as minItems: 1.
* feat(desktop): add hub controls to About settings
* fix(desktop): require trusted connection for hub status
* fix(desktop): isolate local hub activity and track restart identity
* feat(core): add conversation summaries and cloud continuation support
* fix(core): isolate summary generation and preserve continuation context
* chore(core): trim summary docs and duplicate test setup
* refactor(core): trim conversation summary checks and tests
* test(core): preserve both model metadata paths in summary coverage
* fix(core): preserve tool results in conversation summaries
* fix(core): preserve cloud titles when dispatch is vetoed
* test(core): trim summary and cloud test scope
* fix(desktop): forward cloud send lifecycle hook
* fix(core): estimate re-compaction against API-safe projected messages
Once a compaction sidecar exists, createCompactionStateAwarePrepareTurn
passed the raw projection as apiMessages, so the auto-compaction trigger
counted full-size tool results that MessageBuilder.buildForApi truncates
to 8k chars before the request is sent. The estimate ran ~7x over the
provider's actual input count and compaction re-fired every few tool
calls.
Thread the orchestrator's provider-message transform into the prepareTurn
context as prepareApiMessages and run the projection through it before
the compaction pipeline estimates request size.
Fixes#15159
* fix(core): inject the API-message transform into compaction wiring
Address review: keep the dependency in the compaction wiring instead of
widening AgentPrepareTurnContext. createCompactionStateAwarePrepareTurn
now requires prepareApiMessages, with no raw-projection fallback, and
the local runtime host supplies the session runtime's
prepareProviderMessagesForApi (extension message builders +
MessageBuilder.buildForApi).
* chore(core): trim redundant comments and dead test scaffolding
* fix(core): own plugin commands in the runtime and hub
* docs(core): separate session command ownership from resource loading
* style(cli): sort imports after command activity integration
* fix(core): preserve plugin command delivery and catalog recovery
* test(desktop): require non-replaying plugin command dispatch
* fix(core): bound catalog refresh and remove generated test code
* fix(core): rearm plugin watches as discovery directories appear
* fix(core): reconcile sources after watcher registration
* fix(core): expose only plugin commands from resident sessions (#15185)
* fix(core): own plugin commands in the runtime and hub
* fix(core): expose only plugin commands from resident sessions
Session registries also hold the built-in skill and workflow commands. Their
handlers return the instruction text, which pluginCommands.run turned into a
reply, so Desktop and the CLI showed the skill body and never sent it to the
model. Mark commands registered through PluginRegistry and filter on that.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: abeatrix <beatrix@cline.bot>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): run workspace plugin commands for sessions the runtime lacks (#15186)
* fix(core): own plugin commands in the runtime and hub
* fix(core): run workspace plugin commands for sessions the runtime lacks
list() already falls back to the workspace catalog when the runtime does not
have the session, but run() threw SessionNotFoundError. Desktop calls run()
before its hub-loss reconnect and connectors call it before their stale
binding recovery, so a lost session failed the whole send.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* test(core): place missing-session plugin command test apart from sibling fixes
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: abeatrix <beatrix@cline.bot>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): treat hubs without plugin command RPCs as having no commands (#15188)
* fix(core): own plugin commands in the runtime and hub
* fix(core): treat hubs without plugin command RPCs as having no commands
A running Hub that predates plugins.commands.* replies unsupported_command, and
replacement defers while it has active work. The client threw on that reply, so
every non-built-in slash command, skills included, failed in Desktop until the
old Hub exited. Return an empty catalog and no command result instead.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): catch the unsupported_command error the hub client throws
NodeHubClient.command() throws HubCommandError for a non-ok reply, so checking
the reply never saw an older Hub's unsupported_command. Catch the thrown error
instead, and make the regression test reject the way the real client does.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: abeatrix <beatrix@cline.bot>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): target plugin commands at the session's workspace root (#15189)
* fix(core): own plugin commands in the runtime and hub
* fix(desktop): target plugin commands at the session's workspace root
Core checks a session command's workspace against the session's
workspaceRoot ?? cwd, but Desktop sent cwd first. A session whose cwd is a
subdirectory of its workspace root failed with 'Plugin command workspace does
not match the session'. Resolve the live session's workspace the same way.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: abeatrix <beatrix@cline.bot>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): refresh plugin command autocomplete when the active session changes (#15190)
* fix(core): own plugin commands in the runtime and hub
* fix(cli): refresh plugin command autocomplete when the active session changes
Session commands come from the active session's plugins, but autocomplete only
reloaded on mount or on a workspace catalog event. The first catalog loads
before a session exists, so session-only commands stayed hidden. Notify the
slash command subscription when a session starts or is cleared.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
---------
Co-authored-by: abeatrix <beatrix@cline.bot>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): keep subprocess-loaded plugin commands in resident sessions (#15207)
isPluginCommand only recognized commands wrapped by PluginRegistry, but with
CLINE_PLUGIN_MODE=sandbox sessions load plugins through loadSandboxedPlugins,
which registers commands directly. Resident sessions then listed no plugin
commands and /commands stopped reaching their handlers. Move the marker into
plugin-command-origin.ts and mark commands from both loaders.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): port packaged plugin runtime probe to runtime-owned plugin commands
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep skills and workflows working when the plugin catalog is unavailable (#15222)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* refactor(core): replace plugin catalog push with per-request freshness (#15224)
* refactor(core): replace plugin catalog push with per-request freshness
- Drop pluginCommands.subscribe and the plugins.commands.changed event. Hub
runtime hosts no longer open a global event stream, so the per-listener
event ID dedupe in NodeHubClient is removed with it.
- Reduce PluginCommandManager to a per-workspace catalog that fingerprints
plugin sources and settings on every request, keeps healthy commands
through a failed load, and retries that load after 30 seconds. File
watchers, debounced source checks, backoff timers, and per-path recovery
are removed.
- Release workspace catalogs that have been idle for 10 minutes so the
long-lived Hub does not keep plugin workers for every workspace it served.
- CLI suggestions reload on mount and active session change; desktop
composers re-list plugin and instruction commands when the slash menu
opens. Client retry loops are removed.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): isolate workspace catalog loads from the shared plugin registry
Catalog loads went through the process PluginRegistry, so a fatal import
failure in a catalog worker blocked the shared entry: live sessions' copies
failed their usability check, and the 30s retry was skipped as blocked until
a source edit. Each catalog load now uses its own registry.
The fingerprint no longer hashes the whole settings file. The resolved entry
list already excludes plugins disabled in settings, so unrelated preference
changes no longer restart workspace catalogs.
* fix(core): require a registered workspace for Hub plugin commands
An unregistered WebSocket connection reaches the plugin command handler
with no authority, which skipped the workspace check and accepted any
caller-chosen workspacePath. Reject it, matching task commands. Every
NodeHubClient registers on connect, so real clients are unaffected.
* fix(desktop): key cached plugin suggestions by session
A resident session's plugin commands can differ from the workspace
catalog. Switching sessions in the same workspace kept showing the
previous session's commands, indefinitely if the new read failed, and a
stale plugin name could hide a skill or workflow of the same name.
---------
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>
* fix(llms): send max_completion_tokens to SAP AI Core
Bump @jerome-benoit/sap-ai-provider from 4.8.0 to 5.0.3 and load it
through its /v4 entrypoint, the one built for AI SDK 7.
4.8.0 always sent the output limit as `max_tokens`. OpenAI reasoning
models reject it, and newer SAP AI Core landscapes reject it for every
model family ("Unsupported parameter: 'max_tokens' is not supported with
this model. Use 'max_completion_tokens' instead."). The provider sends
`max_completion_tokens` since 4.9.0, on both the Orchestration and the
Foundation Models APIs.
No saved setting changes meaning: Cline passes the same options to the
provider, and the upstream 4.x to 5.x notes only raise the runtime floor
to Node 22.12 and reject unsupported binary file objects.
The /v4 facade wraps the V3 model, so the tests that inspect the
resolved provider config now read it from the wrapped model. A new wire
test drives the real provider and SAP SDK clients against a local token
and inference server and asserts on the request body in both API modes.
* test(core): read the SAP model config through the /v4 wrapper
* fix(core): load embedded plugin and dashboard resources
* fix(core): isolate subprocess Bun mode and retain system CA trust
* fix(core): load compiled plugin resources directly from bunfs
* test(desktop): compare native paths and tolerate Hub cleanup on Windows
The standalone plugin test compared the child's process.execPath to
realpathSync(cli), which keeps Windows 8.3 short names (RUNNER~1) while the
child reports the long path. Use realpathSync.native. The dashboard test's
cleanup could hit EBUSY while the stopped Hub released its cwd; retry it like
the startup scenario cleanup does.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): package every declared SDK runtime export
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* chore: pin the skills CLI release and let Dependabot bump it
cline skill, the marketplace skill fallback (desktop, hub, VS Code) and
core's skill uninstall ran npx skills@latest, so a Vercel release could
change behaviour or raise the Node floor under shipped builds without any
change on our side (1.5.16 broke Node 18/20 on 2026-07-10, 1.5.19 raised
the floor to >=22.20 on 2026-07-16, both within days of us adopting it).
The release is now an exact devDependency of @cline/core, exposed as
SKILLS_CLI_PACKAGE, and Dependabot opens a PR for each new version so
bumps go through CI and review. The package's code is never executed by
us; only its package.json version is read at build time.
* test(core): guard the skills CLI pin against tags and ranges
* fix(llms): backfill Cline recommended model capabilities from Vercel catalog
* fix(llms): consult Vercel catalog only when OpenRouter has no match
* refactor(llms): pass the models.dev catalog to Cline recommended normalization
Models default to bash-style \" escaping, which PowerShell treats as the
end of a double-quoted string. Wrapped in cmd /c, the leftover text becomes
extra arguments, and in one reported case handed rmdir /s /q a bare \,
the root of the current drive.
For PowerShell shells only, tell the model that the backtick is the escape
character, to single-quote literal paths, to prefer cmdlets over cmd /c,
and to confirm recursive or forced deletes before running them.
Web search became default-on in #13957, so every Gemini API request now
carries the googleSearch grounding tool. Free-tier keys have no grounding
quota on Gemini 3+ models, so each turn fails with 429 'You exceeded your
current quota' (and is retried 6 times). On Gemini 2.x the AI SDK cannot
combine googleSearch with function tools and drops every Cline tool.
Remove the web_search model-tool capability from the gemini provider
manifest. Vertex, which has no free tier, keeps native search.
models.dev now lists https://opencode.ai/inference/go/openai/v1, which
only serves OpenAI routes: /messages returns 404 there, so every
Anthropic-protocol OpenCode Go model would fail after a catalog regen.
Routing appends per-model protocol endpoints to the base URL, so keep
the shared /inference/go/v1 root that serves chat, responses and
messages.
The reasoning-era model-id matcher only covered o1/o3/o4 and gpt-5, so
gpt-6 requests (gpt-6-sol, gpt-6.1-sol, gpt-6-1-sol) kept max_tokens and
OpenAI/Azure rejected them. Widen the gpt pattern to gpt-5 through gpt-9,
keeping the version digit single so Azure's gpt-35-turbo stays out.
The model catalog no longer lists minimax-m2.7 for OpenCode Go, so the
anthropic-protocol routing cases fall back to chat/completions. Use
minimax-m3, which the catalog still serves over messages.
* feat(desktop): enable cloud sessions for everyone
Remove the Cloud sessions preview toggle from Settings and the
code-cloud-agents rollout flag that revealed it. Cloud sessions are now
on by default; CLINE_CODE_CLOUD_AGENTS=0 still turns them off for
development and tests.
* fix(desktop): keep the code-cloud-agents rollout flag as the cloud gate
Only the Settings opt-in should go away. Cloud sessions are enabled
wherever the code-cloud-agents PostHog flag is on (or the
CLINE_CODE_CLOUD_AGENTS env override forces them).
Plugin code never runs on a host's main thread. The in_process execution
mode is removed along with the fallback to it when a host shipped no
worker bootstrap: that fallback turned a loud "plugins do not load" into
a silent "plugins run inside the hub", which is the failure class behind
CLINE_PLUGIN_MODE is now `worker` (default) or `sandbox`; `in_process` is
no longer honoured.
A host that ships no bootstrap for the requested mode fails every plugin
load with PluginBootstrapMissingError, which names
CLINE_PLUGIN_BOOTSTRAP_PATH, so the cause shows in `cline config tools`,
`cline hub plugins`, and session-start warnings instead of plugins
silently dropping. The SSH remote helper is such a host today: it is
uploaded as a single file with no bootstrap beside it, so plugin tools
are reported missing there instead of running inside the remote hub.
With no plugin code in the host, the registry loses the jiti import
path, per-session module copies, AsyncLocalStorage call scoping, the
__clinePluginHost shim, and stack attribution of uncaught errors. The
daemon's uncaughtException / unhandledRejection handlers are fatal
again. The in-process registry tests that cover retained behaviour
(hook timeouts and error modes, multi-session notices, capability
checks, discovery and settings issues, command/rule/builder timeouts,
host-rejected registrations, timeout overrides, reload) move to the
worker suite.
How each host obtains the bootstrap and the SDK packages plugins
import, and embedding them in compiled binaries, is left to follow-ups.
* fix(vscode): include attachments in ask_question answers
Answer a pending ask_question with the user's images and attached files
in the tool result itself, as the pre-SDK extension did, instead of
dropping them. The SDK's AskQuestionExecutor may now return text and
image blocks (the same shape read_file returns), and the answer bubble
shows the attachments.
* fix(vscode): let cancel settle an ask_question answer while attachments load
Cancelling the task while an answer's attached files were still being
extracted could not release the question, so the old run (and anything
waiting for it to stop) stalled until extraction finished. Keep the
claimed resolver reachable so clearPending settles it immediately.
* 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>