Commit Graph
317 Commits
Author SHA1 Message Date
Dominic Cooney 2e9571acf0 refactor(cli): simplify dashboard webview asset resolution (#15209)
* refactor(cli): simplify dashboard webview asset resolution

* test(cli): remove obsolete wrapper layout dashboard test
2026-10-20 16:56:44 -07:00
9cb0410253 fix(core): run plugin commands in the active session runtime (#14505)
* 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>
2026-10-20 16:45:45 -07:00
Dominic Cooney 82486434b3 fix(cli): make build:platforms:single work on Windows (#15206)
* fix(cli): make build:platforms:single work on Windows

Replace shell rm -rf with rmSync in apps/cli/script/build.ts, retry
temporary smoke-test directory cleanup while the detached hub process
exits on Windows, and use mkdtempSync with non-Windows chmodSync in
apps/cli/script/compile-binary.ts.

* fix(cli): warn if smoke-test directory cleanup fails after retries

Use rmSync maxRetries/retryDelay for post-smoke-test cleanup and log a
warning with the directory path and error if removal still fails.
2026-10-20 08:47:31 +09:00
2377a20973 fix(core): load embedded plugin and dashboard resources (#15080)
* 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>
2026-10-19 15:37:08 -07:00
Mikołaj Kondratek d800bf8271 chore: pin the skills CLI release and let Dependabot bump it (#15144)
* 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
2026-10-19 21:45:38 +02:00
Saoud Rizwan c718573538 chore(cli): release v3.0.73 2026-10-16 19:37:19 -07:00
Saoud Rizwan d9deb814e8 chore(cli): release v3.0.72 2026-10-15 22:25:46 -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
Saoud Rizwan c96fb0992a chore(cli): release v3.0.71 2026-10-13 00:57:27 -07:00
Saoud Rizwan df56169ed5 fix(cli): let the compiled binary resolve plugin SDK imports natively (#15048)
* fix(cli): let the compiled binary resolve plugin SDK imports natively

Plugins import @cline/core from the wrapper's node_modules. Bun standalone
executables skip package.json loading at runtime by default, so every bare
import from those files failed to resolve natively and jiti fell back to
babel-transpiling the whole SDK graph (~440 files, ~8s cold). That blew the
8s plugin load timeout after an upgrade or reboot and left the plugin
failed for the life of the hub. With autoloadPackageJson the cold load
takes ~1.4s, matching the 3.0.70 subprocess sandbox.

* fix(cli): show the resumed transcript when startup posts a notice

Resuming a session hydrates the transcript after the session starts. A
plugin load failure posts a status notice during that start, and the
hydration guard treated any existing entry as user activity and skipped
the transcript, so the resumed session looked empty. Skip only once the
user has submitted, and keep startup notices after the transcript.

* Revert "fix(cli): show the resumed transcript when startup posts a notice"

This reverts commit 0fd43afc70.
2026-10-13 00:05:21 -07:00
BeeandSaoud Rizwan 12a4c6bfe2 feat(core): load plugins in the hub process and surface plugin errors (#14957)
* feat(core): load plugins in the hub process and surface plugin errors

Plugins now load in-process through one registry per process instead of a
node/bun subprocess. The compiled CLI and the hub it starts no longer need a
runtime on PATH, and plugins can use the Bun global under the Bun-built hub.
The subprocess sandbox stays available with CLINE_PLUGIN_MODE=sandbox.

The registry wraps import, setup, every hook, tool, and command call with
timeouts and error capture, and tracks loading/ready/degraded/failed/disabled
per plugin with the last error's phase, message, stack, and counts. A failing
plugin no longer silently drops its tools:

- session.create takes a `plugins` policy shaped like toolPolicies, and its
  reply and session.created event carry `pluginIssues`
- failures after start (setup and hooks run on the first turn) reach each
  affected session as a status notice
- the hub answers plugins.list/status/reload and broadcasts
  plugin.status_changed; `cline hub plugins [--reload]` wraps them
- the daemon marks a plugin failed instead of shutting down when an uncaught
  error's stack points into it
- the CLI prints a warning naming the plugin and error, and
  `cline config tools --json` lists failed plugins as plugin_failure entries

Setup and tool contexts gain cwd and emitEvent; __clinePluginHost keeps
working as a shim. Compiled binaries fall back to jiti/static when babel is
not on disk next to them.

* fix(core): give each session its own copy of every plugin module

Plugins written for the per-session sandbox keep state at module level. The
cline-monitor plugin, for example, builds one plugin object per import and
throws "Create a separate monitor plugin handle for each session" on a second
setup(). Sharing one imported copy across hub sessions broke it on the
second session.

The registry now hands each session its own copy: the first session claims
the copy imported at startup, and later ones re-evaluate the module while
reusing jiti's transform cache (about 180ms for cline-monitor instead of
2.8s with no cache).

A setup() failure now costs only the session it happened in: other sessions
keep their copies, the next session tries again, and a successful setup
returns the status to ready. Import failures, attributed uncaught errors, and
repeated hook/tool failures still turn the plugin off for every session.

* docs(sdk): document the plugin hooks that exist and how hook failures behave

The Hook Stages list (snake_case stages such as tool_call_before) and the
Hook Policies table (mode, retries, queueLimit, maxConcurrency) described no
code: plugin hooks are the seven runtime hooks, and there is no per-hook
policy object. Replace both with the real hooks, what each can return, and
how hookErrorMode and the 3-second hook timeout apply.

* fix(core): report failed plugins as failed in settings

Settings marked a plugin "failed" only when the plugin tool report had no
entry for it. The report now lists failed plugins with their state, so they
showed as "available" (desktop Extensions page and other core settings
hosts). Use the reported state, and fill loadError with the failing phase
and message so the reason is visible.

Also tell only the calling session about a per-session setup failure;
other sessions' copies still work. Keep the hub log line's own message
instead of letting the plugin error text overwrite it.

* fix(core): address plugin registry review findings

Reload and generations: each session copy records the generation (import)
it came from. A reload starts a new generation without invalidating older
copies, so running sessions keep working. Failures and blocking are counted
per generation, so a broken reload cannot turn off copies that still work,
and an old copy's failures cannot change the current status. A stray error
attributed by stack turns off every live generation, since a stack cannot
tell copies of one file apart.

Sandbox mode: the hub daemon no longer preloads plugins in sandbox mode,
and tool listing and MCP settings sync inspect plugins through the sandbox.
The session plugin policy now applies in sandbox mode: paths whose file
name the policy turns off never reach the sandbox, and plugins matched only
by their exported name are dropped after import.

Calls: commands, rule content, and message builders go through the same
guarded path as hooks and tools (timeout, error capture, failure count), and
calls into a copy that is turned off, failed setup, or whose session ended
are refused. A failing rule contributes no text and a failing message
builder leaves messages unchanged.

Cleanup: ctx.onDispose registers per-session cleanup. Timers a copy creates
while its own code runs, including at import, are tracked and cleared when
the session releases the copy; host callbacks given to plugins run outside
that scope so host timers are never cleared with them. Release runs on
session stop and dispose, and now also when building the session runtime
fails. The automation-events example uses ctx.onDispose.

Registrations: automation event types are validated while setup runs. If
the host still rejects a registration on commit, the copy's setup is
recorded as failed and the error is rethrown so the partial registrations
are discarded.

Also: forward importTimeoutMs, hookTimeoutMs, and contributionTimeoutMs to
the in-process registry per session; key the plugin tool inspection cache on
registry status; keep the module copy imported by preload for the first
session; report settings-disabled plugins a session would use as issues
with reason "settings".

* fix(core): keep this bindings and route import-time plugin events

- Timer tracking forwards the runtime's this (the timer handle in Node and
  Bun) to plugin callbacks.
- Plugin methods keep their receiver: rule content, message builders,
  command handlers, and setup() are called on their own objects, including
  setup() in the existing in-process loader.
- Each plugin copy owns one async scope from import onward. Claiming the
  copy for a session fills in that scope's session id and event callback,
  so events emitted by work the module started at import reach the session.
- Each generation tracks its own health, so an older copy's first failure
  after a reload is reported to its sessions even though the status shows
  the reloaded generation.

* fix(core): bind plugin changes to sessions started afterwards

Plugins are bound to a session when it starts; added, edited, or reloaded
plugins reach only sessions started afterwards. Change detection used the
entry file's mtime and size, which missed edits to files the entry imports
(common for package plugins) and same-size edits within one clock tick, so
the next new session could claim a stale preloaded copy.

Detect changes with a content fingerprint of the entry file and every file
it reaches through relative static imports, for the registry, the plugin
tool inspection cache, and the plugin command service. Add a bootstrap test
that a session started before a plugin is added or changed keeps its
original tools even when its setup runs afterwards, and a registry test for
a change outside the entry file.

* fix(core): include side-effect imports in plugin change fingerprints

The fingerprint only followed `from`, `import()`, and `require()`
specifiers, so a fix to a file the entry imports only for side effects
(`import "./setup.js"`) left the fingerprint unchanged and new sessions kept
getting the failed entry.

Scan statement-level side-effect imports for the fingerprint only. They are
kept out of the dependency preflight, where a loose match on text inside a
string (`'run import "x" first'`) would reject a valid plugin.

* fix(core): retry failed plugin imports on a backoff

Change detection follows static and side-effect relative imports, but a
broken import can also be fixed where no fingerprint sees it: a dynamically
imported file, a file behind a block comment, or a dependency installed in
node_modules. Such plugins stayed failed until an explicit reload.

Retry a plugin whose import failed when a session starts, at most every 30
seconds, even if no change was detected. A broken plugin costs at most one
extra import attempt per 30 seconds, bounded by the import timeout.

Replace the SHA-1 fingerprint with a non-cryptographic checksum; it only has
to notice edits to plugin source.

* feat(desktop): show plugin failures in the chat

Plugin failures reached the desktop webview only as core logs written to the
devtools console. Show them as status lines in the chat:

- session start returns `pluginWarnings` for plugins the session asked for
  but could not load, and the webview adds one status line per warning
- plugin notices that arrive later on the session stream (setup and hooks
  run on the first turn) carry `metadata.pluginIssue`; the webview adds
  those as status lines too, while other core logs stay out of the
  transcript

* fix(desktop): keep plugin warnings when saved history reloads

Finishing a turn replaces the live transcript with saved history, which
never contains plugin warnings (they come from the start reply and stream
notices), so the explanation of a missing plugin disappeared after the first
turn. Carry the session's live plugin-warning lines into canonical history
at their original position by time.

* refactor(core): simplify the in-process plugin registry (#14999)

* refactor(core): simplify the plugin registry

Drop the pieces added in review rounds that cost more than they buy:

- Global timer tracking. It monkeypatched setTimeout/setInterval/
  setImmediate and their clear* counterparts on globalThis for the whole
  host process (the hub, the CLI, and the shared VS Code extension host),
  and attributed timers by async scope, so a host callback invoked from
  inside a plugin call had its timers cancelled when the session released
  its plugin copy. Plugins stop what they start in ctx.onDispose, which
  stays.
- Plugin generations and per-generation health. Blocking and consecutive
  failures are counted per plugin again; a re-import (reload or source
  change) resets them. Running sessions keep their copies callable unless
  the plugin is turned off, so a broken reload fails closed for them.
- Automatic retry of failed imports on a 30 s backoff, and the side-effect
  import scan in the change fingerprint. plugins.reload covers changes the
  fingerprint cannot see.
- The duplicated registerAutomationEventType validation; the commit
  try/catch already records a host-rejected registration as a setup
  failure.

* refactor(core): do not apply the session plugin policy in sandbox mode

The sandbox is an opt-in escape hatch and the per-session policy is new;
filtering sandbox results by file-derived and exported names added two
passes nobody needs yet. The policy is documented as in-process only.

* revert(desktop): drop the live plugin-warning merge into saved history

Reverts 90df99574. The merge re-inserted unpersisted warning lines by
timestamp after every history reload and still lost them on reopen.

* fix(core): keep plugin status consistent across a reload and a late block

- A re-import now resets `blocked` and the failure count only after it
  succeeds, so calls that hit the failure threshold while the import was in
  flight cannot leave the plugin blocked under a `ready` status.
- Sessions are notified when a plugin is turned off even if its status
  already read `failed` from another session's setup failure.
- Docs: say which relative imports the change fingerprint follows, that a
  failed reload turns the plugin off for running sessions too, and that the
  sandbox ignores the session plugin policy.

* fix(core): attribute stray plugin errors by the plugin's real path too

Module resolution follows symlinks and, on Windows, expands 8.3 short names
(the GitHub runner's temp dir is C:\Users\RUNNER~1\...), so a stack frame can
spell the plugin path differently from the configured one and the uncaught
error was never attributed. Match the realpath as well, and compare with
normalized separators and case on win32. Fixes the Windows-only failure of
"attributes a stray error to the plugin whose file is in its stack", which
also fails on the base branch.

* fix(core): match both realpath spellings when attributing plugin errors

On Windows fs.realpathSync keeps 8.3 short names while realpathSync.native
expands them, and module resolvers differ in which they use. Accept either
so the stack frame matches regardless.

* fix(core): do not count a stale plugin copy's failures against the reloaded module

Each copy is stamped with the entry's import counter. After a reload (or a
source change), failures from copies of the previous import are reported to
their own session but no longer bump the error counts, block the plugin, or
change its status, so a session still running a broken copy cannot turn off
the fixed plugin for everyone else. The pre-review registry had this guard
via its generation number; the simplification dropped it.

* fix(core): don't let a stale session import turn off a reloaded plugin

A session's copy import that failed after plugins.reload (or a source change)
had already re-imported the plugin was recorded against the current module:
the catch around claimInstance had no import identity, so recordFailure
blocked the plugin and dropped the freshly loaded copy for every session.

Capture the entry's importId before claiming. If it changed by the time the
import fails, report the failure to that session only and leave the current
module's status alone, matching how stale copies' call failures are handled.

* fix(desktop): don't duplicate a queued prompt behind a plugin start warning

Session start appends plugin warnings after the optimistic user bubble. The
chat_queued_prompt_start handler searched backward for that bubble and
stopped at the first non-user row, so it hit the warning, missed the bubble,
and appended the prompt a second time until saved history replaced it.

Skip plugin warning rows in that search.

* feat(core): default a worker-thread transport for isolated plugins (#15034)

* feat(core): add a worker-thread transport for isolated plugins

CLINE_PLUGIN_MODE=worker runs each session's plugins in a worker thread
inside the host runtime. It reuses the subprocess sandbox protocol and
bootstrap, so it needs no node/bun executable on PATH and works from the
compiled CLI and hub. terminate() stops a plugin stuck in a synchronous
loop, and an uncaught error or process.exit() ends only that worker.

- WorkerSandbox mirrors SubprocessSandbox (timeouts, idle reclaim,
  per-worker pending calls) over the worker message port.
- The bootstrap talks to parentPort in a worker and posts ready once it
  listens; the host holds calls until then because Bun drops earlier
  messages.
- Workers override process.cwd() instead of chdir, which Bun would apply
  to the whole host process.
- Uncloneable values fall back to JSON, matching the subprocess IPC.
- The sandbox test suite runs against both transports, plus worker-only
  tests for loops, stray throws, process.exit, cwd, and no runtime.

* fix(core): ship a self-contained plugin bootstrap and snapshot queued worker calls

The CLI copies plugin-sandbox-bootstrap.js beside the compiled binary,
where nothing can resolve @cline/shared or jiti, so a worker (or
subprocess) exited before initializing any plugin. Bundle every package
into the bootstrap and fail the core build if it imports anything but
Node builtins.

Calls made before a worker reports ready kept the caller's args object,
so later changes leaked into the call and an uncloneable value added in
that window threw from the message listener. Queue a structured clone
instead, skip calls that already timed out, and reject a call whose
flush fails.

* test(core): give the worker loop-recovery test room for a cold worker on CI

The recovery call boots a fresh worker and re-imports the plugin within
the same tool timeout, which 200ms did not cover on CI runners.

* fix(core): keep worker plugin stdout off the host's stdout under Bun

Bun workers write console.log and process.stdout straight to the host's
stdout (Bun ignores the Worker stdout option), so a plugin's logging
corrupted --json output and JSON-RPC streams. Discard stdout-bound output
inside the worker, matching the subprocess sandbox's stdio "ignore".

* fix(core): complete discarded stdout write callbacks in plugin workers

The worker's process.stdout.write replacement dropped the completion
callback, so a plugin awaiting a write hung until the call timed out.
Call it on the next tick for both write(chunk, cb) and
write(chunk, encoding, cb).

* feat(core): run plugins in workers by default, through the plugin registry

Worker mode now goes through PluginRegistry instead of around it, so it
keeps everything in-process mode has: plugin status and plugins.list/
status/reload, plugin.status_changed, degraded/failed tracking with the
failure threshold, session notices, the per-session plugins policy,
stale-copy protection, and per-session timeouts. Only where a copy runs
changes: each plugin gets its own worker per session, and the copy the
registry wraps is a proxy to it.

One worker per plugin means a worker that dies on its own (uncaught
error, unhandled rejection, process.exit) is that plugin's failure with
no stack attribution: the plugin is turned off and the host and other
plugins keep running. The hub preloads status in throwaway workers.

- CLINE_PLUGIN_MODE defaults to worker; in_process and sandbox are
  opt-ins. Hosts that ship no bootstrap fall back to in_process.
- WorkerSandbox reports unexpected exits; sandbox tool proxies honor a
  tool's own timeoutMs.
- The bootstrap is resolved lazily and can be set with
  CLINE_PLUGIN_BOOTSTRAP_PATH. The desktop app ships it as a Tauri
  resource and points the sidecar at it; the VS Code build copies it
  next to the bundle as .mjs.

* fix(core): carry setup context, tool context, and tool definitions into plugin workers

Worker plugins lost what in-process plugins get, now that workers are the
default:

- Setup context: ctx.cwd, ctx.emitEvent, and ctx.onDispose. Cleanup runs
  in the worker before it stops (session release, shutdown, idle
  reclaim); calls arriving during an idle stop wait for a fresh worker.
- Tool context: the abort signal (the host forwards aborts by call id),
  emitUpdate (routed back to the calling tool), and emitEvent.
- Tool definitions: lifecycle, executionMode, resultPolicy, maxRetries,
  and any other plain field now reach the runtime.
- Setup gets its own timeout inside the worker. A setup that throws,
  times out, or takes the worker down fails only that session's copy,
  instead of being reported as an import failure that turns the plugin
  off for every session.

* fix(core): don't treat an exit during plugin cleanup as a crash

stopGracefully() ran the dispose method before marking the worker as
stopping, so a disposer that called process.exit() or threw from a timer
looked like an unexpected exit and turned the plugin off for every
session. Mark the worker as stopping before cleanup starts.

* fix(core): apply the session plugin policy by a worker plugin's own name

The host checked the policy against the file-derived name before the
worker had imported the plugin, so a policy keyed by the plugin's
exported name misfired: "*" off plus the plugin turned on by name
skipped it for good, and turning it off by name still ran its setup().

The worker now applies the policy after import, against both names, and
before setup. The host skips the worker up front only once it has seen
the plugin's own name for its current code.

* fix(core): keep setup prompts and cleanup for worker plugins

Worker plugins run setup() while the session bootstraps, before it is
registered, unlike in-process plugins whose setup runs on the first turn.

- Prompts a plugin sends to its session during setup (steer_message,
  queue_message, pending_prompt) found no session and were dropped. The
  host now holds them until the session is registered, then delivers them.
- A setup that registered ctx.onDispose and then threw or timed out lost
  its cleanup, since only plugins that loaded are disposed later. The
  worker now runs that cleanup right away, so a child process started
  before the failure does not outlive it.

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-10-12 22:29:54 -07:00
Saoud RizwanandSaoud Rizwan bf71bf7cd9 Default to the first Cline recommended model (Sonnet 5.5 fallback) across CLI, extension, and desktop (#14959)
* Default to the first Cline recommended model (Sonnet 5.5 fallback) across CLI, extension, and desktop

- @cline/shared: CLINE_DEFAULT_MODEL_ID is now the anthropic/claude-sonnet-5.5 fallback
- @cline/llms: the cline provider's defaultModelId prefers GENERATED_CLINE_RECOMMENDED_MODELS.recommended[0]
- CLI: resolve the provider's SDK default before falling back to the first catalog entry; drop the hardcoded sonnet-4.6 fallback
- VS Code: openRouterDefaultModelId -> anthropic/claude-sonnet-5.5; cline provider falls through to the SDK default

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

* Default the SDK openrouter provider to anthropic/claude-sonnet-5.5

The VS Code extension's OpenRouter model field is populated from this SDK
default via resolveModelInfo, so the extension-side openRouterDefaultModelId
constant alone did not change the fresh-install selection.

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

* Desktop: adopt the provider catalog default model once the catalog loads

A fresh install booted on CLINE_DEFAULT_MODEL_ID and kept it when the user
skipped onboarding, so the chat bar never picked up the Cline provider's
recommended-list default. Swap the bootstrap fallback for the catalog
default unless a model was already remembered for that provider.

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

* Scope the recommended-model default to the Cline providers

Only cline/cline-pass declare a curated default; other manifests may expose
a placeholder like "default" (ollama, lmstudio), so the CLI keeps falling
back to the first catalog entry for them. The desktop bootstrap swap now also
applies only to new Cline chats, never to hydrated sessions.

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

* Test both branches of the cline default model selection

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

* Simplify: drop the desktop bootstrap swap and connector catalog lookup

The desktop app keeps CLINE_DEFAULT_MODEL_ID as its pre-onboarding default
(onboarding already stores the provider's recommended default), and the
connector only swaps its hardcoded literal for the shared constant.

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

* CLI: only the cline provider uses the SDK default before the catalog

cline-pass's manifest default is just the first bundled model, not a curated
pick, so it keeps the live catalog's own ordering.

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

* Connectors: prefer the provider's declared default over the Cline fallback

Non-Cline connector sessions with no saved model were handed a Claude id.
Use the provider manifest default when it is a real model, skipping the
"default" placeholder that catalog-less local providers expose.

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

---------

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
2026-10-09 19:14:41 -07:00
Darsh Nandu e6ad24c43a fix(cli): apply --thinking in ACP mode (#14894) 2026-10-09 14:01:41 +02:00
akushonkamen 75111f347f fix(cli): accept agent_error hook events in the hook command (#14945)
The `hook` command's dispatcher had no case for `agent_error`, which the
shared hook schema accepts and the CLI runtime emits when a run fails. A
valid `agent_error` payload fell through to the default branch, so the
command exited 1 without a JSON response. It now returns `{}` like the other
agent events. Adds the first unit tests for the command.

Fixes #14808
2026-10-09 10:48:40 +02:00
Stanislav Gelman 95e54e4a02 fix(cli): treat a provider API key from the environment as configured (#14926)
The TUI's startup check now accepts a provider API key from the environment
(e.g. OPENROUTER_API_KEY) when the provider declares exactly one environment
variable, as the runtime does when no key is saved. The key is checked through
isProviderSettingsUsable, so the provider's other requirements still apply.
Providers with several environment variables (Bedrock, Vertex, ...) are
unchanged, and nothing is persisted.

Related to #14923
2026-10-08 16:24:47 +02:00
Saoud Rizwan 0322bc5d51 chore(cli): note removed schedule and history options in v3.0.70 changelog 2026-10-07 22:43:19 -07:00
Saoud Rizwan 9c4de34543 chore(cli): release v3.0.70 2026-10-07 22:43:07 -07:00
Dominic CooneyandDominic Cooney d630fa8f24 fix(cli): reject schedule and history options that do nothing (#14931)
* fix(cli): reject schedule and history options that do nothing

Several options were accepted and saved, but nothing ever read them, so
scripts that used them succeeded and silently did nothing:

- `cline schedule create|update --autonomous`, `--no-autonomous`,
  `--idle-timeout` and `--poll-interval` wrote `metadata.autonomous`.
  Its only reader was the scheduler package, removed in 7a042fe25.
- `--delivery-channel` wrote `metadata.delivery.channelId`, which no
  connector reads. Chat-created schedules and `/whereami` stop writing
  and showing it too.
- `cline history --page` has been hidden and ignored.

These options are removed, so they now fail with "unknown option".

A delivery is also rejected when it can never be delivered. Connectors
post a scheduled result only to a chat they can find from the thread,
so `--delivery-adapter` or `--delivery-bot` without a thread was saved
and then never posted. `create` and `update` now fail unless the
delivery, after merging with the schedule's existing one, has a thread
and an adapter. `create` checks before starting the hub.

`schedule import` no longer passes its file through the delivery flag
merge; it never registered those flags, so the merge only copied the
file's metadata.

Schedules saved with the old keys still load: nothing reads them, and
`update` keeps metadata it doesn't recognise.

* fix(cli): check schedule deliveries set with --metadata-json

mergeScheduleMetadata checked the delivery only when a delivery flag was
set, so create --metadata-json '{"delivery":{"adapter":"telegram"}}'
saved a delivery with no thread, which no connector ever posts. It now
takes the stored metadata, the --metadata-json object and the flags
separately, and checks the delivery whenever the JSON or the flags set
it. An update that changes neither keeps a stored delivery as it is,
so editing other metadata on an old schedule still works, and
"delivery": null removes the delivery.

* refactor(cli): check every schedule delivery input in one place

A schedule's delivery can come from the stored schedule, from
--metadata-json or an import file's metadata, and from the delivery
flags. Each path checked it differently, and import didn't check it.

connectors/schedule-delivery.ts states what the connectors read from
metadata.delivery and checks a delivery once, returning issues that name
an abstract field (adapter, threadId, ...). It also rejects an adapter
that is not a connector in the catalog, which no connector would post.

commands/schedule/delivery-input.ts merges the three inputs in order of
precedence and remembers which input set each key, so every issue is
reported as the flag or JSON path the user would change:
"--metadata-json delivery.threadId", "<file> metadata.delivery.adapter"
or "--delivery-thread <id>". All issues are reported together. One
table defines the delivery flags, their keys and their names in
messages.

A delivery that only the stored schedule sets is still kept unchecked,
and "delivery": null still removes it.

---------

Co-authored-by: Dominic Cooney <55120+dominiccooney@users.noreply.github.com>
2026-10-08 14:11:41 +09:00
0adccbdb70 docs, fix(cli): match the CLI reference and --help to what the CLI does (#14895)
* docs: match the CLI reference to CLI 3.0.68

The CLI reference and the pages that repeat it had drifted from the CLI.
They named a `dev` command that no longer exists, swapped the --config
and --data-dir defaults, described `connect` as an IDE adapter, said
--thinking defaults to medium, and showed a JSON output shape (say/ask
messages) that the CLI never prints. The documented `jq -r .text`
example prints only nulls.

Replace the pasted help with the 3.0.68 output and document what it
lists: --compaction, --worktree, --update, --kanban, `skill`,
`dashboard`, the `auth` options, `config <target>`, `connect --stop`
and `--restart`, `mcp add`/`remove`, the `history` subcommands and
options, and the `hub` subcommands including `drain` and `upgrade`.
Rewrite the JSON section around the agent_event and run_result lines,
with a jq filter that prints the final answer. Note that -k also saves
the key, drop the `-i` image example (-i opens the TUI), and use the
Cline provider's model id in the API auth example.

Co-authored-by: Harshil Mistry <125483347+harshil-mistry@users.noreply.github.com>
Co-authored-by: MarMar Labs <marmar9615@icloud.com>

* docs: correct CLI defaults and JSON notes from review

--retries defaults to 3, not the 6 that `cline --help` prints; note the
discrepancy under the pasted help. Without --thinking, Cline uses the
reasoning level saved from the TUI before the provider's default. Drop
--page, which the CLI accepts but ignores. Say that a --timeout stops
the run without a run_aborted line, and that scripts should skip line
types they don't use.

Use Claude Sonnet 5.5 in the auth examples, and say "in your terminal"
where the pages described commands that need one.

* fix(cli): make --help match what the CLI does

`cline --help` said --retries defaults to 6, but runs use 3. The help
now reads the same CLI_DEFAULT_MAX_CONSECUTIVE_MISTAKES constant that
main.ts passes to the session, and a test ties the two together.

--thinking now says that, without the flag, the CLI uses the level
saved in the TUI before the provider default. -k says the key is also
saved as the provider's key. `history --page` is hidden from help: the
CLI never reads it, but it stays accepted so existing scripts keep
working.

The CLI reference shows the corrected help and no longer notes the
wrong --retries default. The CLI README's -k and --thinking rows match.

* fix(cli): name /model in the --thinking help

Readers don't know what "the TUI" is. The level --thinking falls back to
is the one chosen in the model picker, which users open with /model, so
the help, the CLI reference, the overview and the README now say that.

* fix(cli): warn that cline history --page is ignored

cline history lists the newest sessions up to --limit and has never
read --page. The option stays accepted so existing scripts keep
working, but it now prints a deprecation notice to stderr. The
terminal tests that expected --page in help, or ran it as if it
selected a page, are removed.

* fix(cli): say hub upgrade does not wait for --zen tasks

cline hub upgrade waits only until no client is attached. A --zen run
detaches once the hub accepts it, and scheduled runs never attach, so
the upgrade can stop both mid-run. The help text and the hub command
lists in the docs now say so.

* docs: drop dashboard from the root help listing

cline --help no longer lists dashboard; it is cline hub dashboard.

* fix(cli): require a count for --retries; fix connect --restart example

--retries was declared with an optional value, so a bare --retries set
it to true and the CLI crashed calling .trim() on it. The option now
requires a count, and a bare --retries fails with commander's
"argument missing" error.

cline connect passes options after the channel to the channel's
connector, so "cline connect telegram --restart" never reached
connect. The examples now put --stop and --restart before the
channel, and the reference says why.

* docs: scope the JSON line format to agent runs

The JSON section read as the format of every --json, but only agent
runs print typed lines. cline history --json prints one array, and
each cline config target prints its own shape. The heading and both
pages now say the line format is for tasks, and name the others.

---------

Co-authored-by: Dominic Cooney <55120+dominiccooney@users.noreply.github.com>
Co-authored-by: Harshil Mistry <125483347+harshil-mistry@users.noreply.github.com>
Co-authored-by: MarMar Labs <marmar9615@icloud.com>
2026-10-07 20:38:35 +09:00
Dominic CooneyandDominic Cooney a7fa147e91 fix(cli): keep --header values when cline mcp add opens the wizard (#14901)
* fix(cli): keep --header values when cline mcp add opens the wizard

Without --yes, cline mcp add opens the add wizard with the name,
transport and URL filled in, but dropped every --header value. A user
who accepted the prefilled answers saved a remote server without its
Authorization header.

The wizard now selects "Static headers" and fills in the header field.
If the user leaves that field unchanged, the headers are saved exactly
as given on the command line, including values that contain commas,
which the comma-separated field cannot represent.

* fix(cli): parse wizard headers the way --yes does

The add wizard read every header from one comma-separated field and
split it with its own rules: it split on commas, dropped malformed
headers without a word, and accepted names and empty values that
cline mcp add --yes rejects. Its earlier fix kept unedited prefilled
headers by comparing text, so one edit sent them all back through the
comma split.

The wizard now asks for one header per prompt, prefilled from
--header, and checks each with parseMcpHeaders, the parser --yes uses.
A malformed header is rejected at the prompt with the --yes error
message, and placeholder warnings appear in both paths.

The test restores CLINE_MCP_SETTINGS_PATH with vi.unstubAllEnvs, so
an unset variable is removed instead of being set to "undefined".

* fix(cli): warn about placeholder headers before their wizard prompt

The add wizard warned about a placeholder header such as
"Bearer <token>" only after every header had been entered, too late to
fix it there. It now warns just before the prompt that shows a
prefilled placeholder, and again only if the user types a new one.

Clearing a prefilled header now drops that header alone. Before, an
empty answer ended the list, so clearing the first of two prefilled
headers also dropped the second.

---------

Co-authored-by: Dominic Cooney <55120+dominiccooney@users.noreply.github.com>
2026-10-07 20:38:24 +09:00
cline-cloud[bot]andCline 55a133b751 feat(cli): move dashboard under hub (#14900)
* refactor(cli): scope hub options to their subcommands

* feat(cli): move dashboard under hub and document its usage

---------

Co-authored-by: Cline <cline@users.noreply.github.com>
2026-10-07 17:45:01 +09:00
Dominic Cooney e49de78175 fix(cli): show help for bare hub command (#14897) 2026-10-07 16:16:45 +09:00
Dominic CooneyandDominic Cooney c995be480a fix(cli): make cline doctor log honour CLINE_LOG_PATH (#14893)
The CLI logger wrote to CLINE_LOG_PATH when it was set, but cline doctor
built the default path itself, so `cline doctor log` opened the wrong
file and `doctor --verbose`/`--json` read recently spawned processes
from it.

Move the path into resolveCliLogPath() in logging/log-path.ts and call
it from the logger and from both doctor readers. The module is separate
from the logger so cline doctor doesn't load pino.

The config page, the CLI reference, the proxy troubleshooting page and
the CLI README now say that `cline doctor log` opens the CLINE_LOG_PATH
file.

Co-authored-by: Dominic Cooney <55120+dominiccooney@users.noreply.github.com>
2026-10-07 15:35:14 +09:00
Saoud Rizwan ef9430ffb4 chore(cli): release v3.0.69 2026-10-06 22:46:37 -07:00
Saoud RizwanandSaoud Rizwan 42c20b9681 test(cli): drop MCP description tests that pin hardcoded default timeouts (#14886)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
2026-10-06 19:04:19 -07:00
Bee c00bf86595 fix(cli): expose yolo mode (#14724)
* fix(cli): expose yolo mode

Show `-y/--yolo` in CLI help with a clear sandbox warning so unsupervised
benchmark and CI runs can opt in deliberately.

* revert: drop unrelated desktop sidecar hub-reconnect change
2026-10-05 14:34:46 -07:00
Mikołaj KondratekandDante afabc824d8 fix(cli): preserve config --json when delegating to the runtime command (#14833)
The lazy `config` wrapper declares its own `--json` option, so Commander
consumes the flag before the remaining arguments are forwarded to the
real config command. `cline config --json` therefore reached the runtime
command without the flag and launched the interactive view, which fails
without a TTY, while `cline --json config` worked because the inner
command also reads the root option. `cline config --json mcp` silently
printed text for the same reason.

Copy the wrapper's parsed value onto the runtime command before parsing
the forwarded arguments, and add a non-TTY e2e check that both flag
placements produce the same JSON inventory.

Co-authored-by: Dante <duanjl.china@gmail.com>
2026-10-05 09:48:15 -07:00
Saoud Rizwan 241c1884a7 chore(cli): release v3.0.68 2026-10-01 21:21:14 -07:00
Saoud Rizwan 9087191126 chore(cli): release v3.0.67 2026-09-30 16:18:46 -07:00
BeeandCursor Agent 2a83dd1a0e fix(desktop): provider execution, browser metadata, and MCP paths - CLINE-3283 (#14206)
* llms/core: register catalog custom providers with the gateway on the agent run path

Custom providers added via Desktop's Add Provider dialog (persisted to
providers.json/models.json and registered in the @cline/llms model
catalog) were visible in every picker but failed at execution time with
'Unknown or disabled provider "<id>"'. The agent run path
(createAgentModelFromConfig -> createGateway) only seeded builtin
provider registrations, while the legacy ApiHandler path already bridged
catalog collections into the gateway via resolveProviderRegistrationSync.

Export that bridge from @cline/llms as
resolveGatewayProviderRegistration(Sync) and apply it in
createAgentModelFromConfig, mirroring the ApiHandler path.

Fixes cline/cline#14180

* desktop: resolve MCP settings through one canonical resolver

The sidecar carried its own homedir-based resolveMcpSettingsPath next to
the shared @cline/shared/storage resolver, and the Tauri shell had a
third copy. All three agree under default env, but the duplicates
ignored CLINE_DIR/CLINE_DATA_DIR (and the shared resolver's HOME
handling), so MCP reads/writes, the OAuth authorize command, and the
'Open MCP settings' window action could target different files.

Delete the sidecar duplicate so every sidecar command uses the shared
resolver, and mirror the shared resolver's precedence
(CLINE_MCP_SETTINGS_PATH > CLINE_DATA_DIR > CLINE_DIR/data >
~/.cline/data) in the Rust open_mcp_settings_file path.

Fixes cline/cline#14152

* refactor(desktop): resolve provider metadata in the host

* Authenticate provider model source requests

* Avoid iterable Headers requirement in VS Code compatibility build

* Refresh source catalogs when provider credentials or endpoints change

* fix(core): save provider settings when model discovery fails

* fix(core): propagate provider catalog persistence failures

* fix(core): restore provider settings when catalog writes fail

* fix(core): serialize provider mutations and persist settings once

* fix: preserve provider customizations and handle unknown auth metadata

* fix(core): track discovery ownership on initial catalog refresh

* fix(desktop): address provider catalog review feedback

- Treat an empty source catalog as best-effort during credential saves
- Prune models from source-backed entries saved before discovery ownership
- Format turn failures with the submitted provider's auth metadata
- Re-export ProviderLocalCli/resolveProviderLocalCli from @cline/llms for compatibility

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-09-30 10:23:37 -07:00
Saoud RizwanandSaoud Rizwan 3435f72fcf fix(cli): use widely supported glyph for auto-approve status indicator (#14686)
* fix(cli): use widely supported glyph for auto-approve status indicator

U+23F5 (BLACK MEDIUM RIGHT-POINTING TRIANGLE) is missing from nearly all
Linux monospace fonts, so the indicator rendered as tofu or nothing.
Swap it for U+25B6, which is already used elsewhere in the TUI and is
present in DejaVu Sans Mono, JetBrains Mono, Cascadia, Noto Sans Mono, etc.

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

* fix(cli): only use fallback auto-approve glyph on Linux

Keep U+23F5 on macOS/Windows where system fallback fonts cover it.

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

---------

Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
2026-09-30 00:03:12 -07:00
Saoud Rizwan 20d70ceed8 chore(cli): release v3.0.66 2026-09-29 19:20:17 -07: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
Bee 0cbfb91ac7 fix: omit plan/act prompt instructions in yolo mode (#14510) 2026-09-25 18:07:19 +09:00
BeeandSaoud Rizwan 4580b1bb72 fix(desktop): Windows sidecar/backend startup failures (#14401)
* fix(hub): start the desktop backend reliably on Windows proxy machines

Two Windows-specific defects kept the desktop sidecar from ever seeing the
Hub daemon it had just spawned (#14265, #14292):

- Bun's fetch honors HTTP(S)_PROXY with no localhost bypass, so on machines
  whose system proxy exports those variables (Clash, v2ray, corporate
  setups) every loopback discovery probe was sent to the proxy and failed.
  The healthy daemon looked unreachable, the sidecar died with "No
  compatible hub runtime is available" before publishing its endpoint, and
  every respawned daemon exited with "Hub instance lock is held by a live
  Hub". New ensureLoopbackProxyBypass() appends the loopback spellings to
  NO_PROXY/no_proxy whenever a proxy variable is set; it runs at every
  entrypoint (desktop sidecar, hub daemon, CLI, menubar) and lazily inside
  probeHubServer/requestHubDrain/requestHubShutdown so every embedder of the
  hub client is covered. Reproduced end-to-end on Linux: the compiled
  sidecar with a dead HTTP_PROXY fails exactly like the Windows reports
  before this change and publishes its endpoint after it.

- Compiled-Bun embedded entry detection only knew the POSIX /$bunfs/ mount,
  not the Windows B:\~BUN\ drive, so Windows daemons were spawned with a
  dead B:\~BUN\root\entry.js argument (visible verbatim in #14292) instead
  of the --cline-hub-daemon marker, hiding them from cline doctor's
  marker-based process scan. New isBunEmbeddedModulePath() handles both
  spellings and replaces the three divergent checks (hub daemon spawn, CLI
  internal launch, menubar entry detection).

The compiled-backend startup test now also boots the sidecar behind a dead
HTTP(S) proxy so the regression stays locked in CI.

* fix(desktop): clamp the main window to the monitor work area

The window opens at a fixed 1500x980 logical pixels. On small or DPI-scaled
displays — a 1920x1080 laptop at 150% scaling has only a 1280x720 logical
desktop — it came up larger than the screen, leaving the bottom and side of
the UI (the settings and account controls among them) off-screen and
unreachable (#14270). Shrink an oversized window to the monitor's work area
(which already excludes the taskbar) minus the OS chrome, and re-center it;
windows that fit are left untouched, and a nonsense work area reading never
collapses the window.

* fix(shared): merge both NO_PROXY casings instead of copying one over the other

ensureLoopbackProxyBypass picked whichever of NO_PROXY/no_proxy was set
first and wrote that value (plus the loopback hosts) to both casings. On
POSIX the two variables can be populated independently — a launcher setting
one and a shell profile the other — and different tools read different
casings, so exemptions present only in the other casing were silently
dropped and previously exempt traffic could start routing through the
proxy. Merge the union of both casings (first spelling wins on duplicate
entries, compared case-insensitively) so entries are only ever added, and
honor a wildcard opt-out in either casing.

Review finding from cline/cline#14401.

* test(core): clear proxy env before setting it in the loopback-exemption test

On Windows process.env is case-insensitive, so the previous order — assign
HTTP_PROXY, then delete http_proxy — deleted the variable that was just
set. ensureLoopbackProxyBypass then correctly saw no proxy and wrote
nothing, and the assertion received '' on the Windows CI leg (Ubuntu
passed because POSIX treats the casings as separate keys). Delete all four
variables first, then set HTTP_PROXY, which behaves identically on both
semantics. Test-setup fix only; verified against a simulated
case-insensitive env that the old order reproduces the CI failure and the
new order passes.

* ci: retrigger checks after unrelated Windows python-hook flake

The previous run's sole failure (hook-file-hooks.test.ts > executes python
hook files, 15s spawn timeout on the windows-latest runner) is untouched by
this branch and passed on the prior commit of this same branch; the
loopback-exemption test fixed in 01009dd91 now passes on Windows.

* chore: trim duplicated call-site comments in the loopback proxy bypass

Keep the explanation once, on ensureLoopbackProxyBypass, and reduce each
call site to a one-liner.

* fix(shared): exempt loopback in the non-wildcard casing when only one NO_PROXY casing is *

A wildcard in one casing left the other casing untouched, so a tool that
reads only the lowercase variable could still route 127.0.0.1 through the
proxy. Leave the wildcard casing alone and merge loopback into the other.

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-09-24 21:59:45 -07:00
Saoud Rizwan 9131e36429 chore(cli): release v3.0.65 2026-09-23 22:19:28 -07:00
Saoud Rizwan 2cc2f64444 fix: plugin slash command follow-ups (workspace-scoped menu, tolerate broken plugins) (#14471)
* fix(desktop): scope skill and workflow autocomplete to the composer workspace

The slash menu built its skills/workflows list from the sidecar's local
workspace root while the session expands commands from its own workspace,
so worktree or other-workspace sessions saw the wrong entries. The
composer now passes its workspaceRoot to list_user_instruction_configs,
matching what list_plugin_commands already does. Customize and Extensions
views keep the local root default.

(cherry picked from commit 0fa6e75f2413cbbb255f3872415d21129748281d)

* fix(core): continue without plugin commands when a plugin fails to load

A plugin with an invalid manifest made createPluginCommandService reject,
which the desktop surfaced as a send error for every slash prompt
(including skills and workflows) and the CLI worked around with its own
try/catch. The service now logs the load failure and caches an empty
command set under the same plugin-set key, so a broken plugin costs one
attempt instead of a sandbox spawn per prompt and is retried only when
the plugin set changes. Handler exceptions still propagate.

The CLI drops its local guard and both apps pass their logger through.

* fix(core): retry a failed plugin command load after a short delay

Sandbox startup timeouts and I/O hiccups are transient, so caching a
failed load until the plugin set changed could leave plugin commands
unavailable for the whole process lifetime. Failed loads are now retried
after 30 seconds while still avoiding a sandbox spawn on every prompt.
2026-09-23 21:54:09 -07:00
Saoud Rizwan b59bd44464 feat(desktop): run plugin slash commands in the sidecar (#14380)
* feat(desktop): run plugin slash commands in the sidecar

Plugin-registered commands (api.registerCommand) were only executed by the
CLI, so plugins like goal that hinge on their /command were inert in the
desktop app: /goal <task> was sent to the model as literal text and the
plugin's hooks never saw a pending goal.

The sidecar now resolves the leading /name token against installed plugin
contributions, runs the matching handler, forwards its reply to the webview
as a toast (chat_command_output) and dispatches only its submitPrompt to
the runtime. Reply-only commands (/goal status, /goal off) return
commandHandled without starting a turn. The slash menu also lists enabled
plugin commands from the hub's contribution inspection.

* test(desktop): format plugin slash command test case

* feat(desktop): keep plugin command host loaded per workspace

Spawning the plugin sandboxes on every slash command left the optimistic
bubble in a 'Thinking...' state for a few seconds before a reply-only command
retracted it. Keep the loaded host per workspace like the CLI's interactive
session does, keyed on the plugin module paths and mtimes so installs,
updates, and toggles reload it. This also drops the separate contribution
inventory pass since the load now happens at most once per plugin set.

* refactor(desktop): trim plugin slash command plumbing

Inline the command lookup in handleSend (plugin commands resolve before
skill expansion, matching the CLI), drop the name field from the runner's
result, and simplify the host cache and result normalization.

* fix(desktop): serialize plugin command host replacement

Chain host replacement onto the previous host promise so concurrent
commands that observe a stale plugin set shut down and reload the sandbox
exactly once instead of racing to overwrite the cache entry. Also trim
plugin command names in the slash menu to match the runner.

* refactor: share plugin slash command service between CLI and desktop

Move plugin command discovery, loading, name/result normalization, and
execution into @cline/core as createPluginCommandService. The CLI's
createWorkspaceChatCommandHost and the desktop sidecar both consume it, so
command behavior cannot drift between apps. The desktop slash menu now
lists commands from the same service (list_plugin_commands) instead of
deriving names from the hub's contribution inspection, so it shows the
plugin's own descriptions and only what can actually run.

* fix(desktop): scope plugin command autocomplete to the composer workspace

list_plugin_commands now takes the composer's workspaceRoot so the slash
menu discovers plugins from the same path handleSend executes in, instead
of always using the sidecar's local workspace root.
2026-09-23 20:37:25 -07:00
Bee 636de8b2af fix(sdk): persist session errors across desktop and CLI navigation ENG-2524 (#14033)
* fix: persist display-only session errors across clients

* fix: record terminal failures after retries and guard stale history

* fix(core): exclude display-only history from compaction

* cli: type manual compaction input as MessageWithMetadata

The display-only filter reads `message.metadata`, which exists on
MessageWithMetadata but not on Message, so `@cline/cli` typecheck failed and
aborted the parallel Quality Checks run before the other packages reported.

The caller already passes MessageWithMetadata, and canonicalMessages only ever
returns input.messages, so widening both signatures matches the real data and
also hands createSessionCompactionState the metadata its source hash expects.

* test(desktop): expect formatted error message prefix
2026-09-22 11:43:07 -07:00
Saoud Rizwan 844c30d7ea chore: release SDK v0.0.85 and CLI v3.0.64 (#14384)
* chore(sdk): release v0.0.85

* chore(cli): release v3.0.64
2026-09-22 00:45:28 -07:00
Saoud Rizwan f81c8d0094 docs(changelog): document compaction credential fix in v0.0.84 / v3.0.63 (#14376) 2026-09-21 20:17:38 -07:00
Saoud Rizwan 2da420f51f docs(changelog): document feature-flag poll fix in v0.0.84 / v3.0.63 (#14370) 2026-09-21 18:11:55 -07:00
John Choi 379a72d30d fix(core): await in-flight feature flag polls (#14354)
* fix(core): await in-flight feature flag polls

* fix(cli): refresh feature flags without blocking startup

* docs: remove feature flag startup architecture note
2026-09-21 17:13:56 -07:00
Saoud Rizwan db5d3d00cc docs(changelog): document run-start hook context in v0.0.84 / v3.0.63 (#14364) 2026-09-21 14:03:46 -07:00
Saoud Rizwan 1085135cf2 chore: release SDK v0.0.84 and CLI v3.0.63 (#14361)
* chore(sdk): release v0.0.84

* chore(cli): release v3.0.63
2026-09-21 13:43:55 -07:00
Mikołaj Kondratek 40d93ba132 fix(hooks): deliver run-start hook context in the SDK runtime; core-layer blocking is opt-in (#13524)
* fix(hooks): deliver run-start hook context and control on next

UserPromptSubmit/TaskStart/TaskResume hooks could not inject context on
the next engine, and at the SDK core layers their output was discarded
entirely (agent_start/agent_resume spawned detached with stdio ignored,
so even cancel was dead). Legacy delivered these as <hook_context>
blocks in the same turn as the user prompt.

Mirrors the tool-hook contextModification shape one level up:

- Contract: beforeRun now returns AgentRunStartResult, adding an
  appendContext channel next to stop/reason.
- Runtime: run-start context is collected in callBeforeRunHooks and
  injected as a <hook_context source="RunStart"> user message (hidden
  display role) after the run's input messages, so the model sees it on
  the run's first request. The existing post-tool flush is extracted
  into a shared helper, and stale contexts are cleared at run start. A
  stopping hook's context is never injected.
- SDK core layers (hook-file-hooks, subprocess): agent_start and
  agent_resume now run blocking with their control honored — cancel
  stops the run with the hook's message as the reason, context becomes
  appendContext with the existing 50KB truncation. prompt_submit stays
  a fire-and-forget message-added dispatch (the run input is not in the
  snapshot when beforeRun fires at these layers), but no longer fires
  for injected hook-context messages.
- Extension adapter: TaskStart and UserPromptSubmit contextModification
  now maps to appendContext (combined across both, cancel semantics
  unchanged).

* fix(hooks): make run-start context survive the orchestrator and harden the blocking path

Review fixes on top of the run-start context injection change:

- The session-runtime orchestrator's beforeRun merger returned undefined
  for every non-stopping result, discarding appendContext on every
  production path (VS Code, CLI, desktop) — only directly-constructed
  runtimes saw the feature. It now aggregates appendContext like its
  beforeModel/beforeTool siblings, with orchestrator-level tests.
- Two more message-added prompt dispatchers (the CLI's and the audit
  logger's) were still treating injected hook-context messages as user
  prompts; the adapter's latestUserPrompt likewise would have fed hooks
  their own previous output as the prompt on the next run. All filter on
  the system display role now.
- A blocking hook that spawned a background child sharing its stdout
  could hang the run forever: completion resolved on stdio close, which
  the grandchild holds open past the SIGKILL. The runner now settles
  shortly after process exit with whatever output arrived.
- Run-start hooks in the subprocess layer tolerate non-JSON stdout
  (their output was ignored for their whole prior life), instead of
  raising a dispatch error on every run; timeouts still surface.
- Run-start context is never inserted between a seeded trailing
  tool_use and its tool_result: the flush is deferred to the post-tool
  flush point, and the per-batch buffer reset that would have dropped
  the deferred context is removed (covered by a runtime test).
- A stopping layer now short-circuits the layer merge, so later layers'
  blocking hook commands don't run for an already-cancelled turn.
- Cleanup: the duplicated control mapper in hook-file-hooks collapses
  into stopOrContextResultFromControl (as subprocess.ts already did);
  the adapter's two inline stop-or-context tails use the shared mapper;
  the false 'input not in snapshot' rationale is corrected; the hooks
  README documents the blocking run-start contract and per-host
  timeouts.

* fix(hooks): make core-layer run-start blocking opt-in, defaulting to fire-and-forget

agent_start/agent_resume hooks at the SDK-core layers have always been
fire-and-forget with stdio ignored, so switching them to blocking by
default would silently stall every run start for hosts whose existing
hook scripts linger (a daemon-style script never exits, and the
settle-after-exit fallback only helps scripts that exit and leave
children behind).

Both layers now gate the blocking dispatch behind blockingRunStartHooks,
default off: the legacy detached dispatch runs and control output stays
ignored until a host flips the flag deliberately — at which point
long-running run-start hooks must background themselves (spawn a
detached child and exit, which the exit-settle explicitly supports).
The extension adapter path is unaffected and keeps run-start context
injection by default.

* feat(hooks): measure how long detached hooks actually run

The blocking run-start dispatch is opt-in because we cannot say how long
existing run-start hooks take: they are fire-and-forget, so nothing ever
awaited them and no telemetry records their runtime — the surfaces that
report hook durations today are exactly the ones that already run hooks
blocking.

The detached path now observes the child's exit and reports duration,
exit code, and whether it exited at all (a hook still running when the
parent goes away never reports, which is itself the signal). Nothing is
awaited, so run-start latency is unchanged. That gives the distribution
needed to decide whether the flag's default can flip.

* fix(hooks): record hooks that never exit, not just the ones that finish

The detached-runtime observation only fired when the hook process exited,
so every recorded sample had exited: true and a hook that lingers - the
exact case that decides whether run-start hooks can run blocking -
produced no data at all. The distribution would have described only the
harmless population.

The observer now reports exactly once per hook: on exit with its runtime,
or, if the hook is still running after a 30s observation window, a
censored sample carrying exited: false and that window as a lower bound.
The timer is unref'd, so nothing about this keeps the process alive or
delays the run.

* test(hooks): keep the censored-observation test from leaving an orphan

The test spawned a detached child that never exits, so the Windows
runner sat waiting on it until the 20s per-test timeout. The hook now
outlives the observation window and then exits on its own, which also
lets the test assert the stronger property: the later exit must not add
a second observation for the same hook. The grandchild in the
stdio-pipe test is shortened for the same reason.

* fix(hooks): report the observation window as a censored sample's duration

The censored branch reported measured elapsed time, which the timer can
undercut - setTimeout(100) firing at 99ms made the sample claim a hook
ran 99ms when what is actually known is that it ran at least 100ms and
was still going. Report the window instead: censored samples then share
one exact lower bound, which is also what makes them analyzable next to
the completed ones.

The test's child now outlives the window by a wide margin. A loaded CI
worker can fire the unref'd timer well after its nominal delay, and with
only a 600ms child the exit path won that race on Windows and reported
exited: true.

* fix(hooks): arm the detached observation window before writing hook stdin

The window was armed after awaiting the stdin write, and a hook that
never reads stdin can leave that write pending until it exits - which is
most fire-and-forget hooks, and is what Windows CI kept hitting. By the
time the window existed the hook was already gone, so every sample came
back exited: true and nothing was ever censored: exactly the bias the
censored observation was added to remove.

Arming at spawn fixes it. The new test reproduces the shape locally by
sending a payload past the pipe buffer to a hook that ignores stdin; it
fails against the previous ordering.

* fix(core): release hook stdio on fallback settle and skip runtime samples for failed spawns

The exit fallback resolved the hook result but left the stdout/stderr pipes
and their data listeners attached, so a background child the hook left
behind kept the host alive and kept accumulating output. Drop the pipes when
the fallback settles.

A detached hook that failed to spawn left the observation timer armed and
was later reported as a hook still running at the window. Cancel the timer
on spawn failure; the failure already surfaces through the rejected run.

* fix(hooks): inject run-start context ahead of a trailing tool call and share one block formatter

Deferring the run-start block past a seeded unresolved tool call only
delivered it if the model happened to call a tool next; otherwise the run
finished without it and the next run's buffer reset dropped it. Place the
block immediately before the trailing tool call instead, which keeps the
tool_use/tool_result pair adjacent and always delivers. The per-batch buffer
reset in tool execution is back now that nothing is held across flushes.

The run-start block is formatted by the same function as the tool blocks,
keyed by a discriminated origin, so trimming, tag neutralizing and attribute
escaping live in one place.

* chore(hooks): name the post-exit output grace period in the subprocess runner
2026-09-21 22:33:27 +02:00
Dominic Cooney 2755adfa46 fix(cli): keep interactive runtime imports out of command dispatch (#14024) 2026-09-19 12:07:50 +09:00
Dominic Cooney d48afb3542 fix(cli): preserve ask-question text input (#14274)
* fix(cli): preserve shifted ask-question input

* fix(cli): delete complete ask-question graphemes
2026-09-19 10:07:35 +09:00
Tomás BarreiroandSaoud Rizwan 7f75100da2 Add Client Type to Recommended Models requests (#14224)
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-09-17 12:40:44 -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