Compare commits

...

201 Commits

Author SHA1 Message Date
Dominic Cooney ff4f50b473 dev: add HTTP-controlled debug harness for the VSCode extension
Ports the debug harness from the SDK-migration branch to main as a standalone change. The harness (src/dev/debug-harness/server.ts) launches VSCode with the extension under CDP and exposes an HTTP API on :19229 for breakpoints, evaluate/stepping in the extension host and webview, Playwright UI automation, and OAuth-flow testing.

Includes the runtime affordances the harness depends on, all gated so they never activate in normal use: CLINE_CAPTURE_BROWSER browser-capture in openExternal() (utils/env.ts), a CLINE_CAPTURE_BROWSER-gated globalThis.__clineHandleUri hook for delivering simulated vscode:// OAuth callbacks (extension.ts), and window.__clineVsCodeApi exposure for harness postMessage access (platform.config.ts). Also includes the local MCP OAuth test server (src/dev/mcp-oauth-test-server) referenced by the harness README for end-to-end OAuth testing, plus .clinerules/debug-harness.md agent docs.

Verified: server boots on Windows and /health and /status respond on :19229; biome is clean on all touched files.
2026-06-11 20:52:31 +09:00
Saoud Rizwan e1bdeeff68 docs(changelog): note Vertex SDK companion bump in 3.89.2 (#11459) 2026-06-11 04:10:30 -07:00
Saoud Rizwan 49897830bb fix(vscode): align Anthropic Vertex SDK with runtime SDK (#11458) 2026-06-11 04:07:51 -07:00
Saoud Rizwan 1f316a2734 fix(vscode): remove unused ClineStorageMessage import in openai-format (#11457)
The SDK 0.50.1 upgrade widened convertToOpenAiMessages to take
Anthropic.Messages.MessageParam[], which left the ClineStorageMessage
import referenced only in comments. tsc does not flag unused imports in
this config, but biome lint does, and it blocked the 3.89.2 publish.
2026-06-11 03:52:55 -07:00
Saoud Rizwan 9c1f9133c7 v3.89.2 Release Notes (#11455) 2026-06-11 03:47:24 -07:00
Saoud Rizwan 2faef2b40d fix(vscode): upgrade @anthropic-ai/sdk to 0.50.1 for Node 24 compatibility (#11454)
VS Code 1.123.0 bumped its bundled runtime from Node 22 to Node 24
(Electron 39 to 42). The Anthropic provider broke on the updated editor
because the old SDK (<=0.41.x) shipped a legacy runtime built on
node-fetch and an internal _shims layer that does not work under Node 24.

0.50.1 is the first SDK release rewritten on top of the platform's native
fetch: it has zero runtime dependencies (no node-fetch, no _shims), which
removes the incompatibility. This is the actual fix; the earlier 0.40.1
bump did not change the runtime architecture.

The 0.50.1 type changes are minimal:
- Usage gained a required server_tool_use field, so fabricated Usage
  objects in the gemini/o1/openai/vscode-lm transforms set it to null.
- ContentBlockParam widened, so Anthropic.MessageParam is no longer
  structurally assignable to ClineStorageMessage. Handled by narrowing
  the two transform helpers that only ever receive Cline history
  (sanitizeAnthropicMessages, convertAnthropicMessageToGemini), typing
  getSavedApiConversationHistory as the Cline history it reads, and
  narrowing ContextManager's loosely-typed truncated output back to
  ClineStorageMessage at the two provider/hook boundaries.
2026-06-11 03:43:33 -07:00
Saoud Rizwan 64829bca8c chore(vscode): release v3.89.1 (#11451) 2026-06-11 02:49:39 -07:00
Saoud Rizwan 4c9ba6b091 fix(vscode): restore Anthropic provider on Node 24 by bumping SDK (#11449)
VS Code 1.123.0 bumped its bundled runtime from Node 22 to Node 24
(Electron 39 to 42). The extension passes VS Code's globalThis.fetch to
every provider SDK, but @anthropic-ai/sdk was pinned at 0.37.0, which
predates the SDK's native-fetch rewrite and relies on legacy _shims
runtime detection that breaks under Node 24. The modern OpenAI and Gemini
SDKs are unaffected, which is why only the Anthropic provider broke after
users updated VS Code.

Bump to ^0.40.1, the first release with the native-fetch rewrite that
restores Node 24 compatibility, while staying short of the latest line's
larger breaking surface.

The only code change the bump requires is narrowing the image source type:
ImageBlockParam.source widened from a base64-only type to
Base64ImageSource | URLImageSource. Add a getBase64ImageSource/
getImageDataUrl helper in shared/messages/content.ts and route the
provider transforms through it. Cline only ever produces base64 image
sources, so behavior is unchanged; the helper emits the same data URL the
inline code did.
2026-06-11 02:44:25 -07:00
Bee 6138bdfe40 feat: Enforce a production singleton Cline Hub (#11372)
* feat: Enforce a production singleton Cline Hub

This PR changes local Hub startup/discovery so production uses one stable daemon per user machine instead of silently creating additional hubs on random ports.

Replace resolveSharedHubOwnerContext with resolveProductionHubOwnerContext
across doctor and hub server lifecycle management to scope hub discovery
to the production owner.

Additionally:
- Preserve and propagate auth tokens when retiring incompatible hubs
- Throw a clear error when a compatible hub is already running but its
  discovery record is missing, guiding users to run 'cline doctor fix'
- Gate port fallback behind an explicit allowPortFallback override
- Update tests to mock the new production hub owner context

* patches

* fix

* hasExplicitPort

* Restored daemon cron startup, made discovery auth tokens required again, and fixed graceful hub stop/restart paths to use the selected production/shared owner context.

* clean up

* patches

* fix Polynomial regular expression

* test

* fix: require explicit hub port fallback in production

* fix(cli): stop pgrep from parsing the hub daemon marker as an option

pgrep treats the "--cline-hub-daemon" pattern as an unknown long option
and exits 2, so doctor never found stale daemons from compiled-binary
installs, which are exactly the processes 'cline doctor fix' is told to
clean up. Pass "--" before the pattern to end option parsing.

* fix(hub): retire legacy shared-owner hubs on production startup

Pre-singleton production builds tracked the local hub under the shared
owner discovery path and spawned daemons on random fallback ports. The
production owner context never reads that path, so upgrades would leave
those daemons running indefinitely with no way to reuse or stop them.
Retire the recorded legacy hub (its record carries the auth token and
pid needed for a graceful stop) and clear the legacy record before
resolving the production hub.

* refactor(hub): simplify stale discovery clearing, share capability list

shouldClearStaleHubDiscovery was only ever called with
discoveredVerified=false (the true assignment sits on a return path),
so the expected-hub probe and compatibility check had no effect and the
condition reduced to "a discovery record exists and was not reused".
Replace it with a plain conditional and drop the tests that exercised
unreachable states.

Also move the hub capability list into a typed HUB_CAPABILITIES
constant in @cline/shared next to HubCapabilityName so the server
cannot drift from the type.

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-06-10 16:59:13 -07:00
Dominic Cooney 7d119351b1 fix(cli): suppress flickering console windows on Windows (#11408)
* fix(cli): suppress flickering console windows on Windows by setting windowsHide on child processes

On Windows, child_process.spawn/execFile default to windowsHide: false,
so console-subsystem children (powershell, rg, git, node, npm) can
allocate a new visible console window - guaranteed when detached: true
is used. In the CLI this caused constant short-lived window flashes
from run_commands, the git status bar polling, ripgrep searches and
indexing, clipboard helpers, and hook/plugin node subprocesses.

Set windowsHide: true (CREATE_NO_WINDOW; a no-op on non-Windows) on all
remaining spawn/spawnSync/execFile call sites in the SDK core, CLI,
Cline Hub, and example plugins, matching the pattern already used by
the MCP client, checkpoint-hooks, and StandaloneTerminalProcess.

* Update apps/cli/src/commands/kanban.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-06-10 22:51:59 +09:00
Saoud Rizwan de987a5246 chore(cli): release v3.0.23 2026-06-09 17:43:24 -07:00
Saoud Rizwan 90050426df chore(sdk): release v0.0.46 2026-06-09 17:30:20 -07:00
Saoud Rizwan 205c5676ff fix(llms): fix disabled reasoning for Fable 5 error (#11397)
* fix(llms): avoid disabled reasoning for fable 5

* fix(llms): route fable reasoning by family

* Revert "fix(llms): route fable reasoning by family"

This reverts commit 6dd4e5dcf5.

* fix(llms): match claude fable reasoning workaround broadly
2026-06-09 17:24:34 -07:00
Bee 1c13edd395 fix(core): configured agent support as subagent tools (#11368)
* fix(core):  configured agent support as subagent tools

Introduce configured agent config parsing and tool creation for
subagents. Agent configs are defined via YAML frontmatter files
specifying name, description, tools, skills, model, and system prompt.

- Add `configured-agent-config` for loading and parsing agent
  definitions from search paths
- Add configured agent tool factory that wraps delegated agents as
  named subagent tools with policy and approval support

* patch

* patches

* fixes

* Infinite loop when YAML block is a non-object fix

* apply feedback

Forwarded host requestToolApproval into configured subagents.
Used the resolved workspace config root for configured-agent skills discovery.
Split configured-agent skill loading from root-session skills enablement.
Added host lifecycle/event plumbing for configured subagents via shared subagent callbacks.
Made UserInstructionConfigService.createSkillsExecutor optional and guarded its use.

* threaded

* test(core): cover configured subagent skill isolation (#11396)

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-06-09 17:23:00 -07:00
Ara a2a1936709 Fix Azure Foundry API version for CLI (#11359)
* Fix Azure Foundry API version for CLI

* Fix Azure API version setup
2026-06-09 16:28:37 -07:00
Max 35ce6a3f26 fix(cli): configure Vertex GCP settings (#11390)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-06-09 16:05:09 -07:00
Saoud Rizwan 2c4aeae4f3 fix(vscode): handle DeepSeek V4 reasoning format (#11392) 2026-06-09 15:10:38 -07:00
Tomás Barreiro 0c027d2731 Centralize OAuth management to the SDK (#11260)
* Centralize OAuth management to the SDK

* Update mock

* Cleanup TUI cline-account logic

* clean save credentails

* Remove unused code

* Reduce mocks

* use normalizeStoredAccessToken
2026-06-09 23:56:26 +02:00
Tomás Barreiro 6cc93c124e Format vscode using biome higher order rules (#11389) 2026-06-09 22:14:19 +02:00
Saoud Rizwan 7e5b8be28c chore(cli): release v3.0.22 2026-06-09 12:08:17 -07:00
Saoud Rizwan 764e901693 test(core): update legacy migration default to claude-fable-5
The Fable 5 PR (#11385) made claude-fable-5 the newest anthropic model,
which sorts first in the generated catalog. Legacy provider migration
defaults to the first catalog model, so the migrated default changed from
claude-opus-4-8 to claude-fable-5. Update the test expectation to match.
2026-06-09 11:55:08 -07:00
Saoud Rizwan 2cabb2ddf6 chore(sdk): release v0.0.45 2026-06-09 11:43:56 -07:00
Saoud Rizwan c32789f697 chore: bump version and update changelog (v3.89.0) (#11386) 2026-06-09 11:36:08 -07:00
Saoud Rizwan 349a8da750 feat(sdk): add Claude Fable 5 model support (#11385) 2026-06-09 11:31:50 -07:00
Saoud Rizwan f09dab7a0b feat(vscode): add Claude Fable 5 support to VS Code extension (#11384) 2026-06-09 11:19:29 -07:00
Robin Newhouse 3a3ea6ee96 Fix MiniMax M3 thinking controls across gateways [ENG-2163] (#11371)
* fix(llms): route MiniMax M3 thinking controls

* test(llms): tighten MiniMax M3 routing scope

* fix(llms): preserve fetch preconnect in MiniMax shim
2026-06-09 10:53:59 -07:00
dependabot[bot] 1c1ea0bd53 chore(deps): bump shell-quote from 1.8.3 to 1.8.4 in /apps/vscode (#11383)
Bumps [shell-quote](https://github.com/ljharb/shell-quote) from 1.8.3 to 1.8.4.
- [Changelog](https://github.com/ljharb/shell-quote/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/shell-quote/compare/v1.8.3...v1.8.4)

---
updated-dependencies:
- dependency-name: shell-quote
  dependency-version: 1.8.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-09 19:11:24 +02:00
Mikołaj Kondratek 70303d8541 Improve bug report form: rename surface dropdown, add IDE/CLI diagnostics (#11381)
* Improve bug report form: rename surface dropdown, add IDE/CLI diagnostics

Rename the 'Plugin Type' dropdown to 'Cline Surface' since CLI is not a plugin; the option values keep each choice unambiguous.

Add an 'IDE / CLI Diagnostics' field with per-surface copy-paste steps for About info (VSCode Help/About, JetBrains Help/About Copy button) and a CLI exception using 'cline --version'. System Information is left as-is; minor overlap is acceptable.

* Update repo-label-issues workflow for renamed Cline Surface field

The auto-labeler matches the rendered '### Plugin Type' heading. Since the form label was renamed to 'Cline Surface', update the three regexes so JetBrains/VS Code/CLI labels keep applying.
2026-06-09 18:38:53 +02:00
Saoud Rizwan 8ba15dfca6 chore(cli): release v3.0.21 2026-06-08 21:44:14 -07:00
Saoud Rizwan 6ab6a1eabc chore(sdk): release v0.0.44 2026-06-08 21:27:22 -07:00
Bee 2ad4146de1 doc(sdk): add host logger support in plugin examples (#11363)
* doc(sdk): add host logger support in plugin examples

Add examples to use the exposed `ctx.logger` to plugins via the `setup` second argument for
diagnostics. Wire logging into the agents-squad example to record setup,
subagent starts, follow-ups, and async failures, with a `logPluginError`
helper that falls back to severity-tagged logs. Update README with
logger usage guidance and examples.

* patches
2026-06-08 16:29:38 -07:00
Ara cfc2250717 fix(sdk): support Vertex ADC tool-use inference (#10773)
* fix(sdk): replay Vertex thought signatures

* fix(sdk): route Gemini 2.5 thinking config

* fix(sdk): tighten Vertex thinking replay routing

* chore(sdk): keep Vertex PR scoped to signatures

* chore(sdk): remove defensive thought signature fallback

* test(sdk): cover legacy Google thought signatures

* refactor(sdk): move Gemini model facts
2026-06-08 15:38:54 -07:00
Bee 730bac7f59 fix: empty SDK message content replay for Bedrock CLINE-2373 (#11320)
* fix: empty SDK message content replay for Bedrock CLINE-2373

This fixes SDK message formatting when persisted conversation history contains an empty user or assistant message, such as after an interrupted task is resumed.

Instead of dropping the message turn, the SDK now preserves it and inserts a text content block:

ERROR: EMPTY CONTENT

This prevents providers like Amazon Bedrock from rejecting replayed history with empty content arrays while avoiding message removal that could affect provider turn ordering.

* Exported EMPTY_CONTENT_TEXT from @cline/shared so core/shared use one constant
2026-06-08 14:47:53 -07:00
Bee 797ea1f607 feat: global auto-update setting for CLI startup updates (#11326)
* feat: global auto-update setting for CLI startup updates

* patches

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-06-08 14:22:41 -07:00
Saoud Rizwan 9d59de4a4c test(llms): align ChatGPT subscription model expectations (#11348) 2026-06-07 23:44:51 -07:00
Saoud Rizwan ae67ca7a13 fix(cli): show Cline credits refill link (#11345)
* fix(cli): show Cline credits refill link

* fix(cli): simplify Cline credits error matcher

* fix(cli): keep credits handling in TUI

* fix(cli): rename credits error matcher

* fix(cli): render credits dashboard as link

* fix(cli): remove credits redirect param

* fix(cli): document temporary credits matcher
2026-06-07 19:58:34 -07:00
Tomás Barreiro 7e2583f40c Fix broken tests (#11344) 2026-06-07 18:22:43 -07:00
Tomás Barreiro ecca88bb98 Clean-up the Codex model list (#11342) 2026-06-07 17:16:12 -07:00
Saoud Rizwan 4bb93ee5b9 chore: bump version and update changelog (v3.88.1) (#11334) 2026-06-06 18:26:28 -07:00
Saoud Rizwan 4f2d7398ed fix(vscode): include walkthrough files in extension package (#11333) 2026-06-06 17:56:55 -07:00
Saoud Rizwan bc184f346d fix(cli): scroll inline ask question responses (#11293)
* fix(cli): scroll inline ask question responses

* fix(cli): address ask question review feedback
2026-06-06 17:31:16 -07:00
Bee 96aea0d34b fix(cli): connector thread session routing & stale hub session (#11325)
* fix(cli): connector thread session routing & stale hub session

Fix connector thread session routing and stale hub session recovery

**PR Description**

This fixes connector messages from separate chat threads being routed into the wrong active runtime session.

**Issue**

In Slack, if a user sent a message in a different thread while another thread was still processing, the new message could be treated as a steer message for the active task. Users could also see errors like:

```text
Slack bridge error: session not found: 1780596180501_ms45m
```

when a connector thread had a persisted session id that no longer existed in the hub, such as after a hub restart.

**Cause**

Connector conversation bindings and active turn queues were using participant identity as the primary key in several paths. That allowed messages from the same user in different chat threads to resolve to the same connector session/active turn.

Separately, persisted connector `sessionId` values were trusted without checking whether the hub still had that runtime session. After a hub restart, the connector could try to send input to a stale session id.

**Fix**

- Store connector conversation bindings by thread id instead of participant key.
- Key connector active turn queues by thread id across Slack, Discord, Telegram, Google Chat, Linear, and WhatsApp adapters.
- Only treat a follow-up as a steer message when the active turn belongs to the same thread.
- Keep participant key/label as metadata instead of using it as the conversation binding key.
- Validate a persisted session id with the hub before reusing it.
- If the persisted session is missing, clear it from thread state and start a fresh runtime session.
- Update schedule delivery metadata to target thread ids while preserving participant metadata.
- Add regression coverage for cross-thread active sessions and stale persisted session ids.

**Verification**

```bash
bun -F @cline/cli typecheck
bunx vitest run apps/cli/src/connectors/connector-host.test.ts apps/cli/src/connectors/thread-bindings.test.ts apps/cli/src/connectors/adapters/slack.test.ts apps/cli/src/connectors/adapters/telegram.test.ts apps/cli/src/connectors/adapters/discord.test.ts apps/cli/src/connectors/adapters/gchat.test.ts apps/cli/src/connectors/adapters/linear.test.ts apps/cli/src/connectors/adapters/whatsapp.test.ts
```

* patches
2026-06-06 10:17:01 -07:00
Tomás Barreiro 676b446d47 Add debug section for Cline testers (#11318) 2026-06-05 12:09:01 -07:00
Ara a8835425bf chore: bump version and update changelog (v3.88.0) (#11316) 2026-06-05 09:58:50 -07:00
Tomás Barreiro e152741e1d Remove the recommended models feature flag (#11315)
* Remove the recommended models feature flag

* Fix recommended model flag CI

---------

Co-authored-by: Arafatkatze <arafat.da.khan@gmail.com>
2026-06-05 08:36:18 -07:00
Mikołaj Kondratek 6fec41ea70 fix(mcp): guard settings writes in delete/add so the watcher can't empty the list (#11240)
deleteServerRPC and addRemoteServer wrote cline_mcp_settings.json without
setting isUpdatingClineSettings, so the chokidar settings watcher was not
suppressed during the plugin's own write. The watcher is configured with
atomic: true and awaitWriteFinish (stabilityThreshold 100ms); on Windows it
races the non-atomic writeFile, reads a transient/empty file, and
readAndValidateMcpSettingsFile() returns { mcpServers: {} }. updateServerConnections({})
then tears down every in-memory connection, so deleting one MCP server emptied
the whole list in the UI after navigating away and back (CLINE-2097).

Wrap both methods in the same guard the sibling RPCs already use
(toggleServerDisabledRPC, toggleToolAutoApproveRPC, updateServerTimeoutRPC):
set isUpdatingClineSettings = true before the write and clear it on a 300ms
timer in finally, so the delayed watcher "change" event is skipped. addRemoteServer
had the same latent omission and is fixed in the same change.

Known tradeoff (pre-existing, unchanged by this fix): the guard is a single
shared boolean cleared by uncoordinated 300ms timers, so two settings writes
within 300ms can clear the flag early. Because awaitWriteFinish only emits once
the file is stable, the worst case there is a redundant reconnect, not the
empty-list data loss this fixes. A deterministic guard (per-op token or
content-compare-and-skip in the watcher) is out of scope for this targeted fix.

Adds McpHub.deleteServerRPC.test.ts covering the user-visible symptom (delete
one of two servers -> remaining server still returned/persisted, list not empty)
and the guard contract (flag set during write, cleared after 300ms, cleared on
the not-found error path).
2026-06-05 08:36:04 -07:00
Saoud Rizwan 5de1a45d1d chore(cli): release v3.0.20 2026-06-04 17:19:31 -07:00
Saoud Rizwan 717a2e643a fix(cli): name installed plugin wrappers from source (#11291) 2026-06-04 16:54:47 -07:00
Saoud Rizwan e50184d316 chore(cli): release v3.0.19 2026-06-04 14:48:01 -07:00
Saoud Rizwan 08c6f7ecbe fix(cli): use npm update for auto updates (#11285)
* fix(cli): use npm update for auto updates

* fix(cli): preserve npm update dist tag
2026-06-04 14:45:24 -07:00
Saoud Rizwan 15e6a685d6 chore(cli): release v3.0.18 2026-06-04 13:49:47 -07:00
Saoud Rizwan bc0eed950b test(core): skip chmod-based plugin uninstall failure test on Windows (#11284) 2026-06-04 13:34:56 -07:00
Saoud Rizwan 48316027a7 chore(sdk): release v0.0.43 2026-06-04 13:19:13 -07:00
Saoud Rizwan 4d15d16109 docs(cli): release the SDK before the CLI in the publish-cli skill (#11280)
* docs(cli): release the SDK before the CLI in the publish-cli skill

Add a Step 0 to the publish-cli skill that gates a CLI release on an SDK
release when the SDK changed since its last release, and relocate the
publish-cli skill to the repo root.

Why release the SDK alongside the CLI: the CLI bundles the SDK source via
workspace:*, so the CLI always ships the latest SDK code, but the hub
daemon stamps a buildId that defaults to the @cline/core version and a
running hub is only respawned when that buildId changes. Bumping the SDK
version forces a stale hub to be retired and respawned with the new code.
It also keeps SDK releases on a regular cadence in step with the CLI.

Step 0 covers detecting unreleased sdk/packages changes, bumping the
shared SDK version + llms CHANGELOG, committing to main, kicking off
sdk-publish.yml on the latest channel, and waiting for it before cutting
the CLI release. Also fixes the now-stale working-directory note (commands
run from the repo root, not sdk/) and updates the DEVELOPMENT.md path.

* chore: move opentui skill to repo root

Relocate the opentui TUI skill from apps/cli to the repo root, matching
the real-dir + symlink convention used by the other root skills (real dir
in .agents/skills, symlink from .claude/skills).

* docs(sdk): reformat the SDK changelog and move it to sdk/CHANGELOG.md

The changelog covers all SDK packages (they share one version and release
together), so move it from sdk/packages/llms/ to the SDK root, parallel to
apps/cli/CHANGELOG.md. Reformat to match the CLI changelog: a titled
header with flat, version-only sections, newest on top, no dates, and no
Next Release bucket. The unreleased entry that bucket held is captured
from commits when the next SDK release is drafted. Update the publish-cli
skill to draft SDK notes from commits and prepend a ## <version> section
at sdk/CHANGELOG.md.
2026-06-04 12:48:17 -07:00
Bee d2339f57f1 fix(slack): normalize channel mentions to original post thread (#11273)
* fix(slack): normalize channel mentions to original post thread

Route top-level Slack channel mentions to the originating post thread so
replies land in the correct conversation. Add `resolveSlackChannelMentionThread`
to rewrite non-DM mention threads using the message's `thread_ts`/`ts` and
channel, while preserving DM threads and already-correct threads.

Includes unit tests covering normalization, no-op, and DM cases.

* thread id
2026-06-04 11:49:56 -07:00
Ahmad Shahzad a209825116 Sync Fireworks AI model registry with current platform offerings (#11173)
* feat: update Fireworks model registry to improve Cline UX for Fireworks API users

The VS Code extension's Fireworks model list was significantly out of
date compared to the current active models available on the Fireworks
platform. This commit updates the registry to match the current model
lineup, ensuring users can select from the latest available models.

Changes:
- Default model: accounts/fireworks/models/kimi-k2p6 (was kimi-k2p5)
- SDK default: accounts/fireworks/models/kimi-k2p6 (was minimax-m2p5)

Removed 6 stale/phantom models no longer available:
  - qwen3-vl-30b-a3b-thinking
  - qwen3-vl-30b-a3b-instruct
  - deepseek-v3p2
  - glm-4p7
  - glm-5
  - minimax-m2p1

Added 9 missing models:
  - accounts/fireworks/models/kimi-k2p6
  - accounts/fireworks/routers/kimi-k2p6-turbo
  - accounts/fireworks/models/deepseek-v4-flash
  - accounts/fireworks/models/deepseek-v4-pro
  - accounts/fireworks/models/glm-5p1
  - accounts/fireworks/routers/glm-5p1-fast
  - accounts/fireworks/models/minimax-m2p7
  - accounts/fireworks/models/qwen3p6-plus
  - accounts/fireworks/models/gpt-oss-20b

Fixed metadata for 3 overlapping models:
  - kimi-k2p5: contextWindow 262144 → 256000, maxTokens 16384 → 256000
  - minimax-m2p5: maxTokens 16384 → 196608
  - gpt-oss-120b: maxTokens 16384 → 32768, cacheReadsPrice 0.01 → 0.015

All models now have cacheWritesPrice: 0 because Fireworks does not
charge a separate rate for prompt cache writes (cache writes are
billed at the standard input rate, matching the SDK catalog).

Three models remain in the UI but are scheduled for deprecation on
June 17 and will be removed then:
  - accounts/fireworks/models/kimi-k2p5
  - accounts/fireworks/models/minimax-m2p5
  - accounts/fireworks/models/qwen3p6-plus

Files:
- apps/vscode/src/shared/api.ts
- apps/vscode/webview-ui/src/components/settings/__tests__/APIOptions.spec.tsx
- sdk/packages/llms/src/providers/builtins.ts

* fix: update qwen3p6-plus context window and maxTokens
2026-06-04 19:19:26 +02:00
Mahesh Narayan acc1a25e51 Update agent storage guidance (#11025) 2026-06-04 09:53:18 -07:00
Saoud Rizwan 5f17a6963f fix(cli): clear abort indicator immediately (#11265) 2026-06-04 00:02:16 -07:00
Saoud Rizwan c7ddb96ddb chore(cli): release v3.0.17 2026-06-03 21:07:30 -07:00
Bee 107bce75b2 fix(cli): recover stale interactive sessions and suppress shutdown races (#11259)
* fix(cli): recover stale interactive sessions and suppress shutdown hook races

This fixes the CLI/TUI regression introduced between `3.0.14` and `3.0.15` where the interactive CLI could enter a broken state after stopping and restarting Cline Hub, then attempting to cancel a request with Escape.

The affected release window was:

- `49e8c1b32` / `v3.0.14`: known-good baseline
- `c33c3176e` / `v3.0.15`: release containing the regression
- `fad8271f4 feat: Cline Hub web app (#10969)`: relevant behavior change in the window

The Hub web app change introduced new Hub-backed runtime/session lifecycle behavior. After Ctrl+C or Hub shutdown, the CLI could still retain an `activeSessionId` that no longer existed in the Hub/runtime process. On the next interactive send, the CLI attempted to reuse that stale session and received `session not found`. Because cancellation also targeted the stale session, Escape stopped working and OpenTUI ended up receiving failures during input handling, which made the TUI look corrupted.

The same lifecycle issue also explains the Ctrl+C errors:

```text
error: hook dispatch failed: Hub connection closed (code=1006, reason=Connection ended)
error: WebSocket connection to 'ws://127.0.0.1:50168/hub' failed: Failed to connect
```

Those were caused by late hook dispatches racing against Hub shutdown. The CLI was still trying to send hook events over a Hub WebSocket that had already closed.

**What changed**

- Added missing-session recovery in the interactive runtime.
  - Detects `session not found` / stale session errors.
  - Reads any recoverable messages from the missing session.
  - Clears the stale active session state.
  - Starts a new interactive runtime session.
  - Retries the current turn once against the fresh session.

- Made hook dispatch shutdown-aware.
  - Runtime hooks now mark themselves as shutting down before session disposal.
  - Hook dispatches are skipped once shutdown begins.
  - Dispatch failures during shutdown are suppressed, since the Hub transport closing is expected at that point.

- Reordered CLI cleanup.
  - Hooks are shut down before stopping/disposing runtime sessions.
  - This prevents abort/stop lifecycle events from trying to dispatch over a closing Hub connection.

**Regression coverage**

Added tests for:

- Recovering from a disappeared active interactive session and retrying against a new session.
- Ensuring hook events are not dispatched after shutdown begins.

**Verification**

Passed:

```text
bunx vitest run apps/cli/src/utils/hooks.test.ts apps/cli/src/runtime/interactive/session-runtime.test.ts
bun -F @cline/cli typecheck
bun -F @cline/cli test:unit
bun -F @cline/cli test:e2e:cli:tui
git diff --check
```

* SessionNotFoundError

* fix(core): preserve stale session errors in hub runs

* clean up

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-06-03 21:00:19 -07:00
Saoud Rizwan 81792d20c6 fix(sdk): keep hub daemon alive on runtime abort (#11258) 2026-06-03 20:22:55 -07:00
Saoud Rizwan e8e2af705d feat(cli): improve Telegram connector with --allowed-user-id flag (#11256)
* feat(cli): add Telegram allowed user id flag

* fix(cli): tighten connector authorization hooks
2026-06-03 17:50:07 -07:00
Bee 8f00fcf3ed chore: bun run fix (#11252) 2026-06-03 13:45:06 -07:00
Saoud Rizwan a64d17734d chore(cli): release v3.0.16 2026-06-03 13:29:33 -07:00
Saoud Rizwan 8e621817c5 feat: add plugin uninstall ability (#11247)
* feat: add plugin uninstall flow

* fix: preserve disabled plugin settings on uninstall failure
2026-06-03 13:23:35 -07:00
Bee 423fde4828 feat(cli): add Slack socket mode support (#11245)
* feat(cli): add Slack socket mode support

Add socket mode as an alternative to webhook mode for Slack
connector, allowing connections without a public URL.

- Introduce `--connection` flag to select webhook or socket mode
- Add `--app-token` option for socket mode authentication
- Make signing secret and base URL conditional on webhook mode
- Add `parseSlackConnectionMode` with validation and tests
- Update CLI platform definition to support hybrid connection type
- Update README docs with socket mode usage examples

* use base-url and remove connection flag

* isSocketMode
2026-06-03 12:00:57 -07:00
Saoud Rizwan 76af785fa6 fix: allow skills plugin capability (#11244) 2026-06-03 10:42:12 -07:00
Bee dd7042fc7b fix(core): use union schema for read files tool input validation (#11225)
* fix(core): use union schema for read files tool input validation

Move the normalizeReadFileRequests helper logic directly into the
read_files tool executor, replacing the legacy helper with inline
validation against ReadFilesInputUnionSchema. This ensures invalid
union inputs are rejected before reaching the executor.

Update tests to reflect validation behavior and add coverage for
rejecting invalid union inputs.

* add new schema support
2026-06-03 10:20:54 -07:00
Tomás Barreiro 38e5b7c26b Fix VSCode CI (#11236)
* Fix VSCode CI

* fix nightly publish

* Fix install

* Fix biome

* fix tsc

* Fix windows set-up

* Ignore .vscode-test

* Add a LICENSE to the vscode extension

* Add type roots

* Remove workspaces

* remove vscode as a bun workspace
2026-06-03 18:58:27 +02:00
Dominic Cooney 8ae99cd69f feat(llms,core): route custom registered handlers through the agent runtime (#11235)
* feat(llms,core): route custom registered handlers through the agent runtime

Expose the handler-registry helpers (hasRegisteredHandler, getRegisteredHandler,
getRegisteredHandlerAsync, isRegisteredHandlerAsync) from @cline/llms, and have
core's createAgentModelFromConfig consult the registry: when a handler is
registered for a provider, build it via createHandler and adapt its ApiHandler
surface onto the AgentModel contract (the inverse of the gateway's
toApiStreamChunk).

This lets hosts register provider handlers that need host-only dependencies
(e.g. a vscode.lm-backed handler) and have them used by the main agent loop,
not just standalone createHandler callers.

* fix(core): resolve registered handlers lazily and avoid double finish

Address review feedback:
- createAgentModelFromConfig built the handler eagerly with the sync
  createHandler, which throws for providers registered via registerAsyncHandler.
  The adapter now accepts a handler factory and resolves it on the first stream
  via createHandlerAsync, supporting both sync- and async-registered handlers.
- Guard the adapter's catch-block finish with sawFinish so a handler that emits
  an explicit done chunk and then throws does not produce two finish events.

* fix(core): preserve thought signatures and finish-reason semantics in adapter

Further review feedback on the ApiHandler -> AgentModel adapter:
- Reasoning and tool-call thought signatures are now surfaced under
  metadata.thoughtSignature (the key downstream adapters read), instead of being
  stored as metadata.signature / dropped.
- A done chunk whose incompleteReason indicates max output tokens now maps to
  finish{reason:"max-tokens"} rather than "stop".
- A turn that ends with tool calls (no explicit done) now terminates as
  finish{reason:"tool-calls"}, matching the gateway/AI-SDK adapters.

* Apply remaining changes

* fix(core): report lazy handler-factory rejection as a finish(error) event

The lazy handler resolution (await source()) ran outside the adapter's
try/catch, so a rejecting factory (e.g. when the host API is unavailable at
stream time) escaped as a raw generator exception instead of a terminal
finish{reason:"error"} event. Move the resolution inside the try block so all
failure paths converge on the same terminal finish.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-04 01:40:59 +09:00
Max 845970ba7d improve cline provider migration (#11242)
- user's who are signed in with oauth in old extension were not properly
migrating their token. this commit handles that

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-06-03 09:38:18 -07:00
Max 444a9be6ec allow baseUrl field for anthropic vendor-type providers (#11227)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-06-02 20:01:49 -07:00
Saoud Rizwan ade7775337 feat(cli): install official plugins by slug (#11230)
* feat(cli): install official plugins by keyword

* fix(cli): harden official plugin clone

* fix(cli): remove official plugin repo env override
2026-06-02 18:24:37 -07:00
Tomás Barreiro e147682945 Set-up global greptile rules, kanban and other SDK files (#11233)
* Set-up global greptile rules, kanban and other SDK files

* Fix path

* Fix stale path

* Update vitest workspace config
2026-06-03 03:24:07 +02:00
Saoud Rizwan ae78fb422c docs(sdk): add custom model provider plugin example (#11234)
* docs(sdk): add custom model provider plugin example

Add an OpenRouter-backed example plugin demonstrating the providers
capability and registerProvider. It registers an OpenAI-compatible
provider plus its model catalog with the gateway so the agent can run
inference against an endpoint Cline does not bundle.

Registers under a distinct id (openrouter-plugin) to avoid colliding
with the built-in openrouter provider.

* docs(sdk): drop redundant provider section from plugin examples readme

* docs(sdk): drop provider demo line from plugin examples readme

* fix: support plugin model providers

* docs: remove provider plugin demo

* docs: address provider example review
2026-06-02 18:22:06 -07:00
Tomás Barreiro 220a21bdcf Move sdk/apps/ to apps/ (#11200)
* Move the apps to the root dir

* Update all references from sdk/apps/ to apps/

* Update dependencies

* Install bun types

* Fix types

* Fix types

* Fix linter

* Ingore apps from vscode

* Fix security warning

* Fix windows install

* Enable windows dev mode

* Revert "Enable windows dev mode"

This reverts commit a46c99282e.

* Revert "Ingore apps from vscode"

This reverts commit 47f7b265d2.

* Revert "Fix windows install"

This reverts commit 1dabba1556.

* update the repo root

* fix root dir

* fix path

* fix other path

* Fix unrelated changes

* fix: address apps move follow-up blockers (#11228)

* fix: update root app command paths

* fix: include moved apps in root checks

* fix: clean up moved app path references

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-06-03 01:49:57 +02:00
Saoud Rizwan 6aef7f5280 docs(cli): refine supply-chain scan alerts sample (#11224)
- Fold provider/model setup into a single `cline` run; drop the auth command
- Remove the /yolo on step from the Telegram setup
- Present scheduling as two clear options (Telegram chat vs terminal with
  delivery flags)
- Clarify how to find the schedule id before triggering a test run
2026-06-02 14:57:48 -07:00
Saoud Rizwan 4e56ed6922 docs(cli): add supply-chain scan alerts sample (#11222)
* docs(cli): add supply-chain scan alerts sample

Walkthrough for scheduling the Cline CLI to run Perplexity's Bumblebee
scanner and deliver compromise alerts to Telegram. Covers installing the
CLI, cloning/building Bumblebee and how it stays read-only, the Telegram
connector, and creating a scheduled scan that texts a clean/alert verdict.

* docs(cli): drop unsupported --delivery-thread from supply-chain sample
2026-06-02 14:04:33 -07:00
Saoud Rizwan 3a0f182408 fix(cli): show skills in slash autocomplete (#11220) 2026-06-02 13:18:30 -07:00
Saoud Rizwan 1f7adbd87e feat(cli): group plugin skills in settings (#11219) 2026-06-02 13:17:05 -07:00
Saoud Rizwan b0590554da feat: add skills bundled with plugins (#11161)
* feat: discover skills bundled with plugins

* fix: scope plugin bundled skills to active plugins

* fix: prevent ancestor skill discovery for plugins
2026-06-02 12:50:15 -07:00
Ara af2454f8d9 chore: bump version and update changelog (v3.87.0) (#11211) 2026-06-02 10:19:08 -07:00
Shantanu Gontia 1a4bf98e31 Update Sambanova Models (#11008)
* Update Sambanova Models

* moved to vscode/

* fix context windows

* Update Sambanova Models

* fix context windows

* Update api.ts

* Update sambanova prices
2026-06-02 18:52:00 +02:00
Ara 4139db4127 feat: add MiniMax M3 model (#11210) 2026-06-02 09:16:38 -07:00
Saoud Rizwan d55916e3ab fix(cli): show MCP OAuth errors in TUI (#11196)
* fix(cli): surface MCP OAuth errors in TUI

* chore(cli): reuse MCP status label helper
2026-06-01 20:08:23 -07:00
Bee 386ded5126 feat(cli): bundle and serve Cline Hub dashboard with cline dashboard (#11195)
* feat(cli): bundle and serve Cline Hub dashboard with cline dashboard

Add the @cline/cline-hub workspace dependency to the CLI and build the
Hub webview as part of CLI packaging. Copy the generated dashboard assets
into platform-specific CLI distributions so the dashboard is available in
built artifacts.

Refactor the Cline Hub server startup into an exported function so the CLI
can start and stop the dashboard server programmatically.

* fix(cli): resolve dashboard webview in wrapper installs

Detect the platform-specific CLI package from the published wrapper layout
and use its bundled cline-hub webview assets when no explicit dist path is
set. Add test coverage for resolving assets via CLINE_WRAPPER_PATH.

* patches

* patch

* fix server detachHub on stop

Imported detachHub.
Changed ClineHubDashboardServer.stop to () => Promise<void>.
Made stop() idempotent with a stopped guard.
Clears the health interval.
Calls server.stop(true).
Always calls await detachHub(ctx) in a finally, so hub client teardown still happens if the HTTP server stop throws.

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-06-01 17:35:24 -07:00
WaylandYang 44e15319e4 fix(plugin-sandbox): expose CLINE_PLUGIN_IMPORT_TIMEOUT_MS env override (#11065) (#11084)
* fix(plugin-sandbox): expose CLINE_PLUGIN_IMPORT_TIMEOUT_MS env override

The hardcoded 4000 ms importTimeoutMs default is too tight on Windows
cold-start; the plugin-sandbox tests already use 30_000 ms for the same
reason. This patch lets hosts raise the ceiling via env var without
touching code or adding a CLI flag, with explicit options.importTimeoutMs
still taking precedence.

Precedence: options.importTimeoutMs > env var > 4000.

Refs: #11065

* fix(plugin-sandbox): tighten env parsing + use vi.stubEnv (PR feedback)

- Number.parseInt accepts trailing garbage ("4000ms" -> 4000); switch
  to Number() + Number.isInteger() so malformed env values fall back
  to the default instead of silently consuming the numeric prefix.
- Replace manual process.env save/restore in the regression test with
  the idiomatic vi.stubEnv() / vi.unstubAllEnvs() pattern.

Per Greptile review on #11084.
2026-06-01 17:34:13 -07:00
Saoud Rizwan e424b28702 feat(cli): add plugins slash command (#11193)
* feat(cli): add plugins slash command

* fix(cli): address plugins command review feedback
2026-06-01 16:59:38 -07:00
Robin Newhouse db9971890e Add SDK telemetry for run_commands timeouts (#11149)
* feat(sdk): add run_commands timeout telemetry

* docs(sdk): document timeout telemetry event

* docs(sdk): move telemetry catalog to core docs

* fix(sdk): omit undefined timeout telemetry fields

* fix(sdk): mark timed out run_commands unsuccessful

* docs(sdk): defer telemetry catalog entry

* fix(sdk): limit run_commands timeout success override

* fix(sdk): tighten timeout telemetry plumbing
2026-06-01 16:22:04 -07:00
Tomás Barreiro d7cc9b6155 Move bun from the sdk/ to root (#11104)
* Move bun to root

* Fix scripts and pre-commit

* Update scripts

* Update workflows

* Fix cd

* fix pre-commit

* fix cli publish
2026-06-01 22:29:41 +02:00
Saoud Rizwan 05042d3ff7 docs(sdk): add env-blocker plugin example (#11192)
* docs(sdk): add env-blocker plugin example

Adds a beforeTool hook plugin that deterministically blocks the agent
from reading .env secret files via read_files, editor, or run_commands
(e.g. cat .env), while leaving .env.example/.sample/.template readable.
Demonstrates moving a security policy out of an AGENTS.md rule (a
suggestion the model can ignore) and into the execution path.

* docs(sdk): install env-blocker globally in usage examples

A secret-protection guard is most useful applied to every project, so
drop the --cwd . project-scoped install in favor of the global default.

* docs(sdk): trim env-blocker usage docs

* docs(sdk): limit env-blocker to read paths only

It is a read blocker, so only guard read_files and run_commands.
Drop the editor case (and with it the symmetric apply_patch concern),
keeping the example focused and simple.

* docs(sdk): rename env-blocker helpers for readability

collectPaths -> extractFilePaths, collectCommands -> extractShellCommands
so the beforeTool call sites read clearly at a glance.

* docs(sdk): rename commandTouchesEnv to commandReadsEnv

* docs(sdk): drop console.error from env-blocker hook
2026-06-01 12:12:16 -07:00
aikido-autofix[bot] dc2c662de6 [Aikido] Fix 53 security issues in @xmldom/xmldom, basic-ftp, axios and 14 more (#11145)
* fix(security): update dependencies

* fix: set unbounded axios fetch adapter limits for 1.16.0

---------

Co-authored-by: aikido-autofix[bot] <119856028+aikido-autofix[bot]@users.noreply.github.com>
Co-authored-by: TheRealSpencer <spencer@cline.bot>
2026-06-01 10:15:40 -07:00
Max 4b68826cb5 update changelog and bump version (#11184)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-06-01 09:26:14 -07:00
Mikołaj Kondratek 85be70bb25 fix(vscode): probe @vscode/ripgrep-universal layout for VS Code ≥1.122.0 (#11178)
VS Code 1.122.0 migrated its bundled ripgrep from `@vscode/ripgrep` to
`@vscode/ripgrep-universal`, which ships per-platform/arch subdirectories
(`node_modules/@vscode/ripgrep-universal/bin/<platform>-<arch>/{rg|rg.exe}`)
instead of the previous flat `node_modules/@vscode/ripgrep/bin/{rg|rg.exe}`.
The migration commit (microsoft/vscode@bf19e5ca / @c4471e24) landed on
`release/1.122` and ships in stable 1.122.0+. Microsoft hit the identical
bug in their own sandbox engines and patched it in microsoft/vscode#317978;
our `getBinaryLocation` was still on the retired layout.

Symptom: on VS Code 1.122.x, all four `checkPath` probes in extension.ts
miss, `getBinaryLocation("rg")` throws `Could not find ripgrep binary`,
and `searchFiles` returns `{results: [], errorReason: "unknown"}`. The
@-mention picker shows "No results found" immediately, regardless of
workspace or query.

Telemetry confirmed the regression bisects cleanly to the VS Code
version boundary, not any Cline release — `mention_failed` events with
`errorType=unknown` jumped from ~300/week on 1.121.0 to ~100k/week on
1.122.0/1, while JetBrains versions (which don't use this code path)
stayed flat. Every historical Cline version is affected when the user
is on 1.122+.

Fix: probe the new `@vscode/ripgrep-universal/bin/<platform>-<arch>/`
layout first (both regular and `.asar.unpacked` variants), then fall
through to the four legacy probes so users on ≤1.121.x keep working.
`<platform>-<arch>` is `${process.platform}-${process.arch}`, matching
the directory naming Microsoft documented in #317978
(darwin-arm64, darwin-x64, linux-x64, linux-arm64, linux-arm,
linux-ia32, win32-x64, win32-arm64, win32-ia32, etc.).

This also closes the residual #11105 reports that survived #11166:
Yufeng's PR moved the failure mode from `unknown` to
`ripgrep_spawn_failed` (bare `rg`/`rg.exe` on PATH fallback) but didn't
restore actual functionality for users on 1.122.x — they got
spawn-ENOENT instead of file-not-found. With this patch the bundled
binary resolves correctly and ripgrep runs as before.

Refs: https://github.com/cline/cline/issues/11105
Refs: https://github.com/cline/cline/issues/11142
Refs: https://github.com/microsoft/vscode/pull/317978
2026-06-02 00:11:02 +09:00
Saoud Rizwan c824d8380a bump version and update changelog (#11172) 2026-05-31 23:33:45 -07:00
Yufeng He fb80840086 fix: keep file search fallback alive (#11166)
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-05-31 23:28:28 -07:00
morning-verlu 42e4ea60db Fix marketplace getting started link (#11170)
Co-authored-by: morning-verlu <258725120+morning-verlu@users.noreply.github.com>
2026-05-31 23:12:08 -07:00
Saoud Rizwan 31a118fc0c test(core): expect Opus 4.8 default in legacy provider migration 2026-05-29 12:15:37 -07:00
Saoud Rizwan c33c3176ef chore(cli): release v3.0.15 2026-05-29 12:06:12 -07:00
Bee f5a3c591c8 chore(sdk): Model Catalog v1780081026557 (#11140)
Updated model catalog to v1780081026557 with `bun run build:models`

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-05-29 12:00:26 -07:00
Bee be930a69a8 feat(plugin): support rule contributions in sandbox (#11127)
* feat(plugin): support rule contributions in sandbox

Add plugin rule registration to the sandbox descriptor and handler state so
plugins can contribute static or dynamic rule content.

Update plugin installation to omit peer dependencies and use
legacy-peer-deps to avoid peer resolution failures during isolated installs.

* feat(cli): support participant mute targets in Discord

Resolve /mute and /unmute targets from Discord user mentions and raw
user IDs so a specific participant can be muted within a thread.

Update Discord system rules to guide agents toward thread-level and
participant-level mute commands, and add tests for target parsing.

* Update sdk/apps/cli/src/utils/chat-commands.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* fix(cli): normalize addressed bot command suffixes

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-05-29 11:59:58 -07:00
Ara 3651fe9a55 fix: stop Discord connector after repeated errors (#11121)
* fix(cli): clear connector sessions on hub shutdown

* fix: stop Discord connector after repeated errors

Added error tracking to Discord connector to prevent spam:
- Tracks errors by message within a 1-minute window
- After 3 identical errors, shuts down connector instead of posting
- Prevents repeated error messages flooding Discord channels
- Logs shutdown reason for debugging

* fix: address Greptile feedback

1. Error tracker now per-thread (includes thread.id in key)

   - Prevents cross-thread error aggregation

   - One thread having errors will not kill the whole connector

2. Use fixed time window instead of sliding window

   - Track firstSeen timestamp, not just lastSeen

   - Prevents indefinite spam from errors every 61s

   - Window properly resets after ERROR_WINDOW_MS from first error

3. Remove redundant delete in clearBindingSessionIds

   - binding.state.sessionId already deleted in earlier block

   - Cleanup was misleading/unnecessary
2026-05-29 11:52:17 -07:00
Saoud Rizwan 526d8e9c93 fix(cli): make oauth urls clickable in tui (#11139) 2026-05-29 11:50:12 -07:00
Bee fad8271f41 feat: Cline Hub web app (#10969)
* feat: Cline Hub web app

Add a Cline Hub app for monitoring connected clients, viewing and driving sessions, streaming assistant output, and restarting the local hub. Document local, LAN, and tunnel usage with room-secret gating, ignore generated Cline cache/config data, and update lockfile entries.

* feat: Cline Hub UI

* feat: provider config schema

* run command update

* Use Workspace versions

* fix: rename routines to schedules

* feat(schedule): add routine summary and update support

Include last execution data in routine schedule overviews and cache the
summary state in the UI to reduce unnecessary reloads.

Add support for updating routine schedules from the hub server, validate
required fields, and trigger schedules asynchronously after confirming they
exist.

* UI for Connectors

* Fix UI switch for telemetryOptOut

* Expands Recent Sessions UI - allow title update

* feat: Cline Hub routes

* Add "health" and "version" routes

* Refactor server.ts nto 17 focused modules

* Provider Model list search box

* Extensions -> Customizations

* fix: restore discord connector catalog

* fix

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-05-29 11:25:50 -07:00
Bee d6837edda8 feat(cli): support participant mute targets in Discord (#11126)
* feat(cli): support participant mute targets in Discord

Resolve /mute and /unmute targets from Discord user mentions and raw
user IDs so a specific participant can be muted within a thread.

Update Discord system rules to guide agents toward thread-level and
participant-level mute commands, and add tests for target parsing.

* patches

* update prompt
2026-05-29 11:16:10 -07:00
Ara d0f20ca135 fix(sdk): stabilize Windows test suite (#11128) 2026-05-29 10:57:09 -07:00
Robin Newhouse b2a113a2a5 test(sdk): fix Windows CI test failures (#11132) 2026-05-29 10:46:21 -07:00
Robin Newhouse 5efa8cfd3f fix: discover symlinked SDK skill directories (#11113)
* fix: discover symlinked SDK skill directories

* test: cover circular symlink skill discovery

* test: avoid native config watchers in snapshot tests
2026-05-28 17:57:21 -07:00
Ara 9f42aea85d feat(sdk): support global AGENTS rules (#11103)
* feat(sdk): support global AGENTS rules

* fix: address global AGENTS review feedback

* fix: classify global AGENTS by exact path
2026-05-28 16:56:55 -07:00
Bee b87f61f9e4 fix(cli): stabilize core tests on Windows (#11125)
* fix(cli): stabilize core tests on Windows

Avoid several Windows-specific failure modes in the core CLI test suite.

Vitest already runs test files inside worker pools. The workspace file indexer was lazily spawning a nested worker from a transformed TypeScript module via import.meta.url, which is fragile on Windows and can cause Vitest to report only a generic worker fork crash. Disable that worker path under VITEST and use the deterministic fallback indexer for tests.

Quote process.execPath in bash executor shell-string tests. Windows Node/Bun paths commonly contain spaces, so unquoted command strings can fail under PowerShell or cmd even though they work on Unix paths.

Make detached hub probing defensive by routing probeHubServer calls through a safe wrapper, so rejected or malformed probe results are treated as unreachable instead of destabilizing startup/prewarm flows.

Also clear CLINE_RUN_AS_HUB_DAEMON in daemon test setup so tests do not inherit daemon-mode state from the surrounding CLI environment except where explicitly set.

Validation: bunx vitest run --config vitest.config.ts src/hub/daemon/index.test.ts src/services/workspace/file-indexer.test.ts src/services/workspace/mention-enricher.test.ts src/extensions/tools/executors/bash.test.ts --reporter=dot

Validation: bun run typecheck

Validation: bun run test:unit

* patch
2026-05-28 16:41:49 -07:00
Bee 38f1c7eb14 fix(cli): steer active connector sessions across turn keys (#11115)
* fix(cli): bind discord sessions to individual message authors

Resolve Discord participants from normalized message author data and persist
participant-specific thread state in bindings. Restore or create sessions per
participant so different Discord users do not accidentally share chat state.

Also add coverage for bot author handling and owner user configuration.

* patches

* fix(cli): steer active connector sessions across turn keys

Detect active connector turns by session ID when the current turn key
does not match, so replies steer the existing runtime session instead of
starting a duplicate session.

Also treat queued runtime turns as a non-error completion and log the
queued state for connector transports.

* fix(cli): steer active connector sessions across turn keys

Detect active connector turns by session ID when the current turn key
does not match, so replies steer the existing runtime session instead of
starting a duplicate session.

Also treat queued runtime turns as a non-error completion and log the
queued state for connector transports.

* add /idel
2026-05-28 13:21:09 -07:00
Robin Newhouse 81121663a4 fix(sdk): pin SAP AI provider for smoke install (#11116) 2026-05-28 13:02:26 -07:00
Bee 854ac75fe0 feat(cli): bind discord sessions to individual message authors (#11114)
* fix(cli): bind discord sessions to individual message authors

Resolve Discord participants from normalized message author data and persist
participant-specific thread state in bindings. Restore or create sessions per
participant so different Discord users do not accidentally share chat state.

Also add coverage for bot author handling and owner user configuration.

* patches
2026-05-28 12:53:37 -07:00
Ara 107f0f8337 bump version and update changelog (#11112) 2026-05-28 10:58:07 -07:00
Dominic Cooney e330695cb5 chore(codeowners): replace @candieduniverse with @dominiccooney (#11111)
Eve Killaby (@candieduniverse) has left Cline; transfer her /.github/ codeowner slot to @dominiccooney so .github changes still have four code owners able to approve.
2026-05-28 10:38:42 -07:00
Saoud Rizwan c2879dba43 feat(models): add Claude Opus 4.8 provider support (#11110)
Add claude-opus-4-8 (200k) and claude-opus-4-8:1m model variants across the
Anthropic, Claude Code, Bedrock, and Vertex catalogs, mirroring the Opus 4.7
setup (same pricing, 1M tiers, global endpoint, adaptive thinking).

- Wire the OpenRouter/Vercel AI Gateway 1m suffix handling and Cline/OpenRouter
  model refresh derivation for anthropic/claude-opus-4.8
- Register 4.8 in adaptive thinking detection so it uses the reasoning-effort
  selector path
- Bump the Claude Code "opus" alias to 4.8
- Add context window switchers in the Cline and OpenRouter model pickers
- Add provider tests for the new model ids
2026-05-28 10:37:44 -07:00
Mikołaj Kondratek f2d692cfc2 ci: gate ext-jb-test-integration auto-trigger on PR author association (#11108)
* ci: gate ext-jb-test-integration auto-trigger on PR author association

Extend the existing MEMBER/OWNER/COLLABORATOR allow-list (already used
for the /test-jetbrains comment path) to pull_request_target [opened,
reopened] as well, so the same trust model applies regardless of how
the workflow is triggered. PRs from non-trusted authors no longer
auto-trigger; a maintainer can still opt them in via /test-jetbrains.

* ci: replace hardcoded app-id with CLINE_JETBRAINS_WORKFLOW_ID var

Matches the convention already in use in cline/intellij-plugin and lets us change the App ID without touching workflow code.

* Update .github/workflows/ext-jb-test-integration.yml

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* ci: rename CLINE_JETBRAINS_WORKFLOW_KEY to CLINE_JETBRAINS_APP_KEY

The secret holds a GitHub App private key. Matches the rename of the matching app-id var in the previous commit.

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-05-28 10:36:38 -07:00
Ara 0c90bd9bcf feat: add Moonshot Kimi K2.6 model (#11109) 2026-05-28 10:17:50 -07:00
Bee 33e521e551 fix: SAP AI Core uses AI SDK community provider (CLINE-2307) (#11075) 2026-05-27 20:12:01 -07:00
Ara 9e942fbb6b Fix Discord connector registration (#11077)
* fix(cli): register discord connector

* fix(cli): scope Discord reply fallback

* docs(cli): expand Discord connector setup

* fix(cli): move Discord empty reply fallback to adapter
2026-05-27 18:47:20 -07:00
Bee 6f609e8945 fix: writeDiagnostic for logging ACP output (#11091)
Replace writeErr with writeDiagnostic for logging ACP output so that they don't show up as error.
2026-05-27 14:14:49 -07:00
Ara 762e3c42ab fix(vscode): show Qwen 3.7 Max cache support (#11079)
* fix(vscode): route Qwen cache requests

* fix(vscode): keep qwen cache alias request-scoped

* fix(vscode): mark Vercel prompt-cache models

* fix(vscode): show Qwen 3.7 Max cache support
2026-05-27 13:39:28 -07:00
Tomás Barreiro 49e8c1b324 Update CLI to 3.0.14 (#11094) 2026-05-27 12:06:31 -07:00
Tomás Barreiro 71b8f43a7a Fix OTEL variable bundling (#11092) 2026-05-27 20:58:48 +02:00
Saoud Rizwan 3068fcfedf docs(sdk): note single-file plugin dep limit and pluginPaths dir form (#11076)
Single-file plugins can only import Node builtins and @cline/*. As soon
as a plugin needs an npm dep it has to ship as a package. Adds one
sentence each to the writing-plugins guide (with dependencies in the
example package.json) and plugin-install (noting pluginPaths accepts a
package directory for fast iteration).
2026-05-27 11:21:08 -07:00
Ara 7530900166 fix: repair vscode nightly publish workflows (#11072) 2026-05-26 12:14:25 -07:00
Dominic Cooney 2b45b7b7aa Remove the VSCode Nightly (SDK) publish workflow; we are just running the regular publish workflow from the SDK branch now. (#11074) 2026-05-26 11:34:25 -07:00
Tomás Barreiro 791d238996 Move vscode to apps (#10961)
* Move all vscode related files to /apps/vscode

* Fix launch and biome

* Ignore generated files

* Remove unused icons

* fix tsconfig

* Update workflows (#10962)

* Add default branch

* Move files to the right dir

* fix: add vscode publish README placeholder

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-05-25 21:35:59 +02:00
Ara 9ee618111d bump version and update changelog (#11036) 2026-05-25 10:47:32 -07:00
tjandy98 4bec5931f6 add gpt-5.5 (#11032) 2026-05-25 06:30:21 -07:00
Saoud Rizwan 3115679031 feat: add DeepSeek V4 models (#11027)
* feat: add DeepSeek V4 models

* fix: align DeepSeek V4 cache pricing
2026-05-25 06:29:49 -07:00
Saoud Rizwan 8a6441fddd chore(cli): release v3.0.13 2026-05-22 17:41:54 -07:00
Saoud Rizwan 7ef5b1e7af test(llms): add provider VCR smoke tests to prevent e.g. ChatGPT regressions (#11012)
* test(llms): add provider vcr smoke tests

* fix(llms): harden provider vcr recording cleanup
2026-05-22 17:37:20 -07:00
Saoud Rizwan 3e58e7b024 fix(cli): show loading dialog for history resume (#11013) 2026-05-22 17:20:59 -07:00
Willis d16667f0b8 fix: use correct base URL for Vertex AI global endpoint with Claude models (#10288)
* fix: use correct base URL for Vertex AI global endpoint with Claude models

The AnthropicVertex SDK constructs the API hostname as
`${region}-aiplatform.googleapis.com`, which produces
`global-aiplatform.googleapis.com` when region is "global".
This hostname does not exist and returns 404.

Per Google Cloud docs, the correct global endpoint hostname is
`aiplatform.googleapis.com` (no region prefix). This fix overrides
the baseURL when region is "global" to use the correct hostname.

Fixes #10287

* chore: add changeset for vertex global endpoint fix

* fix: rebase on main and re-apply global endpoint baseURL override
2026-05-22 17:11:15 -07:00
Saoud Rizwan a740c49524 fix(cli): defer empty session creation after reset (#11000)
* fix(cli): skip empty clear session restart

* fix(cli): defer empty session creation after reset

* fix(cli): clarify session start race guard

* fix(cli): avoid re-resuming after new session reset
2026-05-22 17:02:47 -07:00
Saoud Rizwan 3bc1ee8382 feat(shared): add VCR request body contracts (#10997)
* feat(shared): add VCR request body contracts

* fix(shared): tighten VCR request body contracts

* test(shared): cover legacy VCR cassette playback
2026-05-22 16:00:36 -07:00
Saoud Rizwan ecf354c753 chore(cli): release v3.0.12 2026-05-22 15:14:29 -07:00
TheRealSpencer a66c5ee973 chore(deps): pin protobuf to 7.5.8 via overrides (#10998) 2026-05-22 15:11:06 -07:00
Saoud Rizwan 46659eebeb fix(cli): show loading dialog during model settings transitions (#10999)
* fix(cli): show loading dialog during model settings transitions

* docs(cli): explain loading dialog render yield
2026-05-22 14:54:16 -07:00
Saoud Rizwan 899ee0bfea fix(cli): add inline ask question tool prompt (#10989)
* fix(cli): inline runtime tool prompts

* fix(cli): address inline prompt review
2026-05-22 13:57:01 -07:00
Bee db4598e310 Cline SDK 0.0.42 (#10994)
Version bump
2026-05-22 13:31:48 -07:00
Saoud Rizwan 627e539266 fix(cli): bypass release age gate for manual updates (#10987)
* fix(cli): bypass release age gate for manual updates

* fix(cli): use yarn env override for update age gate
2026-05-22 13:29:25 -07:00
Saoud Rizwan 87ff61915f chore(cli): release v3.0.11 2026-05-22 13:03:25 -07:00
Saoud Rizwan a305556452 fix(llms): revert implicit output cap regression (#10990)
* fix(llms): avoid implicit output token caps

* test(llms): cover ChatGPT OAuth output token regression
2026-05-22 12:59:43 -07:00
Robin Newhouse ca12e97288 fix(cli): make config footer toggle hint contextual (#10976) 2026-05-22 12:34:48 -07:00
Bee 0c89716849 feat(core): includes tool names in tool results across messages (#10975)
* fix(llms): Use Google auth for Vertex Gemini

- Pass `providerConfig.gcp.projectId` through to Vertex Gemini as `googleAuthOptions.projectId`
- Disable Vertex API-key express mode when GCP project config is present so `google-auth-library` handles auth
- Add coverage for `AgentConfig.providerConfig.gcp` forwarding and Vertex Gemini provider creation
- Fix vertex model list only contains Claude models issue

* feat(core): includes tool names in tool results across messages

Updated `tool_result` content blocks to consistently include the `name` of the tool being executed. This change propagates through provider helpers and applies the new schema across all associated unit and live tests, ensuring proper tracking and logging of tool interactions within messages.
2026-05-22 11:36:32 -07:00
Robin Newhouse 1c401dbe1a Fix SDK LLM live provider configs (#10977) 2026-05-22 11:04:33 -07:00
Bee ed78404e4b fix(llms): Use Google auth for Vertex Gemini (#10974)
- Pass `providerConfig.gcp.projectId` through to Vertex Gemini as `googleAuthOptions.projectId`
- Disable Vertex API-key express mode when GCP project config is present so `google-auth-library` handles auth
- Add coverage for `AgentConfig.providerConfig.gcp` forwarding and Vertex Gemini provider creation
- Fix vertex model list only contains Claude models issue
2026-05-22 11:02:35 -07:00
Saoud Rizwan 0157ed9efb chore(cli): release v3.0.10 2026-05-21 20:23:07 -07:00
Saoud Rizwan 5574c95ff2 docs(cli): note ignore-scripts local pack guard quirk in publish-cli skill 2026-05-21 20:22:44 -07:00
Saoud Rizwan 31ee8eb744 feat(cli): install plugins from file URLs (#10945)
* feat(cli): install plugins from file URLs

* fix(cli): harden remote plugin installs

* docs: document plugin file URL installs

* docs: simplify plugin file URL wording

* docs: trim CLI plugin example
2026-05-21 19:58:20 -07:00
Renee Huang 7952e230ae Add Ollama API key note in TUI settings (#10947)
* Add Ollama API key note in TUI settings

* refactor: centralize provider config field metadata

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-05-21 16:27:31 -07:00
Bee 6bcfa34ba9 feat(sdk): track idle and pending session states (#10959)
* feat(runtime): track idle and pending session states

Propagate idle and pending as non-terminal session statuses across the CLI, hub records, and active-session checks. Update runtime handling so interactive sessions remain active while idle or awaiting approval instead of being treated as ended.

Removed `status` from showing up in `cline history` for now.

* hydrate: false no longer reads message files for every session.
2026-05-21 16:09:45 -07:00
Ara cf18793434 feat(sdk): add Poolside provider (#10956) 2026-05-21 16:04:38 -07:00
Saoud Rizwan a7ba25d6e1 feat(uri): add /lg-task webhook integration for LG dashboard flow (#10194)
* feat(uri): add LG task deeplink webhook integration

* feat(uri): include prompt-file path in LG task prompt

* refactor(uri): move LG webhook setup into integration service
2026-05-21 16:03:21 -07:00
Ara 2af2028861 feat: add Gemini 3.5 Flash to Gemini providers (#10955) 2026-05-21 15:43:44 -07:00
Ara 932c8e68e1 Infer Telegram bot username from token (#10954)
* fix(cli): infer Telegram bot username from token

* fix(cli): address Telegram connector review feedback

* test(cli): confirm Telegram schedule delivery bot metadata

* fix(core): publish schedule completion events to connectors

* Revert "fix(core): publish schedule completion events to connectors"

This reverts commit 0d64f037b9.
2026-05-21 15:03:36 -07:00
Bee 3cd13352eb fix(cron): publish failed schedule execution events (#10937)
* fix(cron): publish failed schedule execution events

Add cron runner execution event publishing for completed and failed runs.
Update connector adapters to react to failed schedule executions so clients are notified when scheduled work does not complete successfully.

* add unit tests
2026-05-21 14:57:26 -07:00
Tomás Barreiro 2c842328a8 Inject OTEL variables into the cli at buildtime (#10958)
* Inject OTEL variables

* Add telemetry to the nightly

* Add variables to the build script
2026-05-21 23:43:30 +02:00
Saoud Rizwan d3b3ff1c33 fix(cli): satisfy config item hook lint (#10968) 2026-05-21 14:38:36 -07:00
Robin Newhouse 2508e76af8 Fix SDK model catalog token-limit semantics ENG-2100 (#10946)
* fix(llms): preserve catalog output limits

* chore(llms): regenerate model catalog
2026-05-21 14:19:35 -07:00
Ara a702bf9b38 fix(cli): soften rejected tool call display (#10871) 2026-05-21 14:00:55 -07:00
Robin Newhouse a8cea1dca3 Fix disabled skill availability in SDK CLI ENG-2058 (#10876)
* fix(sdk): hide skills tool when skills are disabled

* fix(cli): refresh skill slash commands after toggle
2026-05-21 11:23:14 -07:00
Bee 2a351ffdd5 fix: Bedrock legacy migration for awsProfile (#10943)
The new Bedrock path is not failing because credential_process is unsupported. Both old and new code use AWS SDK v3’s fromNodeProviderChain, which can load credential_process.

The practical difference is that the migrated config does not include the AWS profile name:

```
"aws": {
  "region": "us-east-1",
  "authentication": "profile"
}
```

There is no `"profile": "bedrock"` So the new llms provider never targets [profile bedrock]. It calls fromNodeProviderChain({ ignoreCache: true, clientConfig: { region } }), which means AWS SDK will use AWS_PROFILE if present, otherwise default.

## Cause

The migration code only migrates awsProfile when legacy awsUseProfile is true. But the old extension treats profile auth as active when awsAuthentication === "profile" too:

```
profile:
  legacyGlobalState.awsAuthentication === "profile" || legacyGlobalState.awsUseProfile
    ? trimNonEmpty(legacyGlobalState.awsProfile)
    : undefined
    ```

## Fixes

The migration now preserves awsProfile when awsAuthentication === "profile", even if the old awsUseProfile flag is missing. It also treats profile-based Bedrock settings as enough to migrate Bedrock without static AWS keys.
2026-05-20 16:46:38 -07:00
Bee 69f148bad9 refactor: cache global settings reads by file metadata (#10933)
* refactor: cache global settings reads by file metadata

Avoid repeated global settings file reads by caching parsed settings and
validating the cache with path, mtime, and size. Invalidate the cache after
writes so updates remain visible, and clarify the legacy skills config name.

* refactor for performance

mtime-keyed cache of the parsed GlobalSettings — repeated reads do statSync + 4 comparisons instead of readFile + JSON.parse + zod (~30-100× speedup on hot path).
statSync(filePath, { throwIfNoEntry: false }) — avoids exception construction on missing-file path.
Cache invalidated on write — doesn't rely on filesystem mtime resolution.
loadSettingsFromDisk helper — pulls the read/parse/validate flow into one place, eliminates the previous three duplicated settingsCache = {...} assignments.
toggleDisabledTool cleaned up — single set construction + single write call, no branched copy of writeGlobalSettings.

* add unit tests for caching logic

* object freeze
2026-05-20 12:11:46 -07:00
Bee 6ca92794e1 chore: model catalog updated 1779302019893 (#10934)
All files automatically changed and formatted by `cd sdk && bun run build:models`

Generated model catalog version updated to 1779251127504

This includes the new X AI build
2026-05-20 11:52:25 -07:00
Bee 27bd4c6c65 chore: generated model catalog update (#10921)
version 1779251127504
2026-05-20 09:03:39 -07:00
Saoud Rizwan 015b61924f chore(cli): release v3.0.9 2026-05-19 19:48:23 -07:00
Saoud Rizwan d238239a0f fix(core): handle hub abort cleanup failures (#10918) 2026-05-19 19:38:42 -07:00
Bee e81c35d7c0 fix: Speed up CLI plugin loading and config toggles ENG-2082 (#10884)
* fix: Speed up CLI plugin loading and config toggles

Load sandboxed plugins concurrently during initialization while preserving
existing duplicate override ordering. Update plugin tool discovery to use a
single sandbox per listing and cache descriptor results by plugin path stats,
provider, and model.

Make CLI plugin/tool config toggles persistence-only from the data loader and
update the TUI optimistically, avoiding full config reloads and repeated plugin
imports when users disable tools or plugins.

* patches

* fix: refresh plugin tools when config update lacks data

Reload config data with plugin tools included when a plugin action does not return updated data. This keeps the config view in sync and clears stale plugin tool errors after refresh.

* fix(cli): preserve config item state on missing toggle data

Only update the dialog item when toggle responses include a matching item. This avoids applying fallback enabled-state changes that can desync the UI when returned config data is missing or incomplete.

---------

Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
2026-05-19 18:18:47 -07:00
Ara 1ac5525fee fix: route Poolside Laguna models through next-gen prompts (#10910) 2026-05-19 16:40:19 -07:00
Saoud Rizwan a4d0937030 fix(cli): restore fuzzy file mention ranking (#10909)
* fix(cli): restore fuzzy file mention ranking

* fix(cli): address mention autocomplete review feedback
2026-05-19 16:09:43 -07:00
Bee 6d5c61f044 chore: gitignore SDK session and database files (#10907)
Ignore .cline session data, temp directories, SQLite artifacts, and generated metadata to prevent local SDK state and user data from being committed.
2026-05-19 13:54:53 -07:00
Mark Percival 8a6d031e8f fix(cli): keep interactive session live after cancel (#10903)
* fix(cli): keep interactive session live after cancel

* fix(sdk): use shared finish reason type
2026-05-19 13:44:59 -07:00
Renee Huang 3f808b369e sdk: add ClineCore CLI agent example (#10895)
* add a separate CLI agent using ClineCore, in comparison to the one built using Agent

* update SDK lockfile for ClineCore CLI example
2026-05-19 13:22:04 -07:00
Robin Newhouse e3b3e2306e fix(cli): accept dash-prefixed prompts after separator (#10905) 2026-05-19 12:47:07 -07:00
Tomás Barreiro b601b6e623 Update diff to 8.0.4 (#10904)
* Update diff to 8.0.4

* Remove types diff
2026-05-19 21:30:18 +02:00
Robin Newhouse ed008bd36e Route GLM thinking via provider metadata ENG-2019 (#10692)
* Route GLM thinking via provider metadata

* Address GLM provider routing review feedback
2026-05-19 12:12:51 -07:00
Saoud Rizwan 6e964c3ef0 chore(cli): release v3.0.8 2026-05-19 10:39:16 -07:00
Max 2e8579ebec bump version and update changelog (#10898)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-05-19 09:54:36 -07:00
Ara cb0515d5f4 fix(cli): use Telegram numeric participant ids (#10879) 2026-05-19 08:16:38 -07:00
Bee 8c00eb9a77 fix(cli): keep failed plugins visible in config UI ENG-2073 (#10878)
* fix(cli): keep failed plugins visible in config UI ENG-2073

Preserve plugins that fail during load or setup and attach their
diagnostic phase and error message. Surface those errors in config detail
rows so users can identify and fix broken plugin definitions.

* fix(cli): preserve multiple plugin initialization failures

Aggregate all setup/load failures per plugin path instead of overwriting earlier errors. Summarize multiple errors in the config UI so rows remain readable while retaining the full failure details.
2026-05-18 22:06:57 -07:00
Saoud Rizwan 1a4122eb53 fix(cli): avoid option right fork shortcut (#10883) 2026-05-18 19:06:22 -07:00
Ara e6fa037c4c Add real PR dashboard to code review bot example (#10812)
* add real PR dashboard to code review bot example

* docs: add code review bot dashboard screenshot
2026-05-18 17:49:24 -07:00
Ara 6e0ae8db58 Add Hub Monitor UI to menubar example (#10688)
* add menubar hub monitor dashboard

* docs: add menubar preview screenshot
2026-05-18 17:44:32 -07:00
Bee 0b1fe6a982 fix: remove demo runner from weather metrics example (#10875)
Remove the embedded runDemo entry point, ClineCore import, and runDemo export so the weather metrics example behaves as a plugin-only module.
2026-05-18 16:39:13 -07:00
Tomás Barreiro c18eef5d4c Remove unused stuff on sdk (#10869)
* Clean-up root files

* Clean vscode duplicated stuff

* Remove duplicated license

* Remove duplicated tailwind recommendation

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-05-19 01:36:34 +02:00
Saoud Rizwan 65443a01ae fix(sdk): stage readme during publish (#10870)
* fix(sdk): publish readme with sdk package

* fix(sdk): use absolute readme links

* fix(sdk): stage readme during publish
2026-05-18 16:22:08 -07:00
Robin Newhouse 7d56dd1e69 refactor(sdk): remove unused codex model list helper (#10874) 2026-05-18 16:21:45 -07:00
Saoud Rizwan 501f512066 chore: remove Kanban launch modal (#10864)
* chore: remove Kanban launch modal

* chore: remove unused Kanban state key

* test: tolerate missing post-login modal
2026-05-18 16:19:52 -07:00
John Simone 5810a5d1e7 Fix AWS Bedrock region and auth configuration in CLI (#10807)
* fix AWS region and profile detection logic

* add additional bedrock config fields in provider config screens

* address greptile feedback

* remove .cline directory

* Fix Bedrock catalog and AWS region handling

---------

Co-authored-by: Robin Newhouse <robin@cline.bot>
2026-05-18 15:56:08 -07:00
Ara 6a262b395a Polish multi-agent example war room demo (#10690)
* polish multi-agent war room demo

* docs: add multi-agent war room screenshot
2026-05-18 15:45:09 -07:00
Robin Newhouse 5117b7b0b8 docs: clarify SDK agent test workflow (#10860) 2026-05-18 15:35:31 -07:00
Tomás Barreiro 30ce8b1e72 Clean-up package-lock (#10872) 2026-05-18 23:43:30 +02:00
Tomás Barreiro 2fcac58e24 Extract GH variables as env variables during workflows (#10862)
* Extract GH variables as env variables during workflows

* Update other variables
2026-05-18 13:41:34 -07:00
Saoud Rizwan 112dbaa15b ci(cli): align release changelog formatting (#10863) 2026-05-18 13:17:51 -07:00
Saoud Rizwan 996a2f6a9c fix(agents): reset usage at start of each execute() call (#10713)
AgentRuntime.execute() reset iteration, pendingToolCalls, and lastError
between run/continue calls but not usage. This caused result.usage to
accumulate across calls, and local-runtime-host then added the
already-cumulative result onto the session baseline, double-counting
every prior turn's tokens on each new turn.
2026-05-18 12:50:10 -07:00
Renee Huang 47f6d00f61 docs: add back memorybank; correct CLI install commands (#10830)
* add back memorybank; correct CLI install commands

* add more details on plugin

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-05-18 12:02:04 -07:00
Mikołaj Kondratek 9ba2c932b4 fix(mcp): disable Restart Server button when server is toggled off (#10852)
* fix(mcp): disable Restart Server button when server is toggled off

The expanded 'Restart Server' button in the MCP server row stayed clickable even when the server's toggle was off, but the backing restartMcpServer call is a no-op for disabled servers, so the click did nothing.

Bring the expanded button into parity with the collapsed icon-button (line 227 in the same file), which already gates on server.disabled. No state, label, or styling change beyond the disabled predicate.

Fixes cline/intellij-plugin#559.

* Update webview-ui/src/components/mcp/configuration/tabs/installed/server-row/ServerRow.tsx

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-05-18 18:55:47 +02:00
Robin Newhouse 01aaa5dd04 fix(e2e): harden VS Code teardown (#10796)
ENG-2064
2026-05-18 09:31:31 -07:00
2797 changed files with 78573 additions and 15155 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
fix: use correct base URL for Vertex AI global endpoint with Claude models
+266
View File
@@ -0,0 +1,266 @@
---
name: publish-cli
description: Use when preparing, tagging, and publishing an apps/cli npm release. Guides changelog drafting, apps/cli/package.json version bumps, cli-vX.Y.Z tags, local npm publishing, and the publish-cli GitHub workflow.
---
# CLI Release
Use this skill when the user asks to release the CLI, publish `cline`, bump the CLI version, draft release notes, create a `cli-vX.Y.Z` tag, or trigger the CLI publish workflow.
The CLI is npm-only. Do not add alternate distribution or signing steps.
> Working directory: run every command below from the repository root. Paths and scripts (e.g. `apps/cli/package.json`, `sdk/packages/`, `bun release cli`, `bun run version`) are written relative to the repo root.
The skill should guide the user through one release preparation flow, then offer the publish path options. The two normal publish paths are GitHub Actions and local publishing from an authenticated machine.
## Release contract
- SDK prerequisite: the CLI depends on the SDK via `workspace:*` (`@cline/core`, `@cline/shared`, and friends). If the SDK changed since its last release, release the SDK first and wait for it to finish publishing before releasing the CLI. See "Step 0: Release the SDK first if it changed" below.
- Version source: `apps/cli/package.json`.
- Main release tag: `cli-vX.Y.Z`, where `X.Y.Z` matches `apps/cli/package.json`.
- Nightly release version: `X.Y.Z-nightly.TIMESTAMP`.
- Release prep includes approved release notes, a version bump, and an `apps/cli/CHANGELOG.md` update.
- Publish paths:
- GitHub workflow: `.github/workflows/cli-publish.yml`.
- Local publish helper: `bun release cli`.
- npm dist-tags and git tags are separate. `--tag latest` and `--tag nightly` are npm registry channels. `cli-vX.Y.Z` is a git tag for source history and GitHub releases.
- The GitHub main release workflow runs from `main`, requires an existing `cli-vX.Y.Z` tag, checks out that tag, and publishes from it.
- The GitHub nightly workflow publishes to npm with the `nightly` dist-tag and does not create a tag.
- The local release helper requires a clean checkout and `cli-vX.Y.Z` to point at `HEAD` locally and on `origin` before publishing.
- Local GitHub release creation requires `gh` to be authenticated with release permissions for the repo.
- Always ask before pushing commits or tags.
- Do not amend commits unless explicitly requested.
## Step 0: Release the SDK first if it changed
Do this before anything else in the Workflow below.
The CLI builds and ships against the SDK source in the monorepo (`workspace:*` for `@cline/core`, `@cline/shared`, and the rest), so a CLI release always contains the latest SDK code whether or not the SDK was released. The build and tests use that source too, not anything from npm. Releasing the SDK alongside the CLI is still worth doing for two reasons:
- Hub freshness. The hub daemon lives in `@cline/core` and stamps a `buildId` that defaults to the `@cline/core` package version (`resolveHubBuildId` in `sdk/packages/core/src/hub/discovery/index.ts`). A running hub is only retired and respawned when that `buildId` changes (`isCompatibleHubRecord` / `retireIncompatibleHub` in `sdk/packages/core/src/hub/daemon/index.ts`). So if the SDK code changed but the version did not, a user who upgrades the CLI keeps talking to their already-running hub, which is still executing the old SDK code. Bumping the SDK version makes the new CLI's `buildId` differ, so the stale hub is detected as incompatible and respawned with the fresh code.
- Release hygiene. We want regular SDK releases; cutting one whenever we cut a CLI release keeps the published SDK in step with what the CLI ships.
So when the SDK has changed, release it first (which bumps the `@cline/core` version), then cut the CLI release on top of that bump. Leave the CLI's SDK dependency as `workspace:*` — the fix is to release the SDK, not to pin the CLI.
1. Check for unreleased SDK changes.
```sh
git fetch origin --tags
git tag --list 'sdk/sdk/v*' 'sdk-v*' --sort=-v:refname | head -1
git log <last-sdk-tag>..origin/main --oneline --no-merges -- sdk/packages
```
`sdk/<pkg>/v*` tags are created by the `sdk-publish.yml` workflow; `sdk-v*` tags are created by the local `bun release sdk` helper. Use whichever is newest as the baseline.
If `git log` prints no commits, the SDK is already up to date. Skip the rest of Step 0 and continue with the Workflow below.
If it prints commits, sanity-check the diff (ignore entries that are only the previous version-bump commit's lockfile or generated files), then release the SDK.
2. Decide the SDK version bump.
All SDK packages share one version, read from `sdk/packages/llms/package.json`. Ask whether this is patch, minor, major, or an explicit version. Patch is the default. Do not guess if the user has not made it clear.
3. Draft the SDK release notes and update the changelog.
Draft user-facing notes from the SDK commits found in step 1, translating commit messages into user-facing language (same approach as the CLI release notes below). Prepend a new `## <version>` section with those notes to the top of `sdk/CHANGELOG.md`, using the header format `## <version>` with no date — the same flat, newest-on-top format as `apps/cli/CHANGELOG.md`. This is the SDK changelog (all SDK packages share one version) and it is maintained by hand; the `sdk-publish.yml` workflow does not read it.
4. Bump versions and regenerate.
```sh
bun run version <version>
```
This bumps every SDK `package.json` to the new version, regenerates the lockfile and the generated model catalog, formats, and builds. Review the result.
5. Commit and push the bump to `main`.
The `sdk-publish.yml` workflow publishes the version that is committed on `main` and tags that commit, so the bump must land on `main` before the workflow runs.
```sh
git add -A
git commit -m "chore(sdk): release v<version>"
```
Ask before pushing:
```sh
git push origin HEAD
```
6. Trigger the SDK publish workflow on the `latest` channel.
```sh
gh workflow run sdk-publish.yml -f channel=latest -f confirm_publish=publish
gh run list --workflow=sdk-publish.yml --limit=1 --json databaseId,url,status,createdAt --jq '.[0]'
```
The workflow runs the SDK tests, publishes `@cline/shared`, `@cline/llms`, `@cline/agents`, `@cline/core`, and `@cline/sdk` to npm with the `latest` dist-tag in dependency order, and pushes `sdk/<pkg>/v<version>` git tags.
7. Wait for the SDK workflow to succeed before starting the CLI release.
```sh
gh run watch <run-id> --exit-status
```
Do not start the CLI release until this run has finished successfully. The CLI does not install the SDK from npm, but cutting the CLI release on top of a clean, completed SDK release keeps the two in step: the CLI release commit then sits on top of the `@cline/core` version bump, so the shipped CLI carries the new version that forces a running hub to respawn with the new code, and you are not building a CLI release on top of an SDK release that failed midway.
After the SDK release succeeds, pull `main` so the CLI release is prepared on top of the SDK version bump:
```sh
git checkout main && git pull --ff-only
```
Then continue with the Workflow below.
For a local SDK publish from an authenticated machine instead of the workflow, `bun release sdk <version>` exists, but prefer the `sdk-publish.yml` workflow for normal releases so the CLI release can gate on a single GitHub Actions run.
## Workflow
Complete Step 0 first. Only proceed once the SDK is released (or you confirmed no SDK release was needed).
1. Gather context.
```sh
git status --short --branch
git fetch origin --tags
git tag --list 'cli-v*' --sort=-v:refname | head -10
node -p "require('./apps/cli/package.json').version"
```
Find the latest CLI tag. If there is no `cli-v*` tag, use the first relevant CLI release commit as the baseline and say that the baseline is inferred.
2. Collect release commits.
```sh
git log <last-cli-tag>..HEAD --oneline --no-merges -- apps/cli sdk/packages sdk/scripts .github/workflows/cli-publish.yml
```
The `sdk/packages` commits matter here even though the SDK was released separately in Step 0: the CLI bundles the SDK, so SDK changes ship in this CLI release too. Read those commits and fold anything user-relevant to the CLI into the release notes (provider/model updates, behavior changes, fixes the CLI inherits). Skip SDK changes that are purely internal or have no CLI-visible effect.
3. Draft user-facing release notes.
Include user-facing features, fixes, behavior changes, compatibility changes, and notable install or release changes. Exclude pure refactors, tests, style, chores, and internal file moves unless they matter to users.
Write a flat bullet list. Translate commit messages into user-facing language. If a commit is unclear, read the full commit before summarizing it.
Present the draft and wait for approval before editing files.
4. Decide the version bump.
Ask whether this should be patch, minor, major, or an explicit version. Do not guess if the user has not made it clear.
5. Update release files.
Update `apps/cli/package.json` to the approved version.
Prepend a section to `apps/cli/CHANGELOG.md` for the approved version using the approved release notes. Use the header format `## X.Y.Z` with no date. The publish workflow extracts the top section of the changelog by matching `^## [0-9]` and pastes it verbatim into the GitHub release body and the Slack release announcement, so the section content is the release notes that get shipped.
6. Verify before committing.
Run focused checks first:
```sh
bun -F @cline/cli typecheck
bun -F @cline/cli test:unit
```
For higher confidence, run:
```sh
bun run types
bun --cwd apps/cli run build:platforms:single
```
If the user wants full release confidence before tagging, run:
```sh
bun run test
bun --cwd apps/cli run build:platforms
```
Known local-only test failure: `src/commands/distribution-package.test.ts > rejects direct source package packing by default` will fail on machines that have `ignore-scripts=true` in `~/.npmrc` (set by the npm supply-chain hardening guide). Bun reads npm's `ignore-scripts` from `~/.npmrc`, so `bun pm pack --dry-run` skips the source-publish `prepack` guard and exits 0, which the test reads as a failure. CI does not set `ignore-scripts`, so the test passes there. Confirm by running `bun pm pack --dry-run` directly: with `~/.npmrc` in place it exits 0 with no guard output; with `~/.npmrc` moved aside it exits 1 and prints the guard message. This is not a release blocker by itself, but it does mean the local-publish path (`bun release cli`) will also bypass the source-publish guard on this machine; prefer the GitHub Actions publish path on machines with `ignore-scripts=true` set globally, or temporarily unset it (`npm config delete ignore-scripts` or `mv ~/.npmrc ~/.npmrc.bak`) for the duration of a local publish.
7. Commit release changes.
Only after the user approves the notes and version:
```sh
git add apps/cli/package.json apps/cli/CHANGELOG.md
git commit -m "chore(cli): release vX.Y.Z"
```
Ask before pushing the release commit:
```sh
git push origin HEAD
```
For the GitHub main release path, ask before creating and pushing the release tag:
```sh
git tag -a cli-vX.Y.Z -m "CLI vX.Y.Z"
git push origin refs/tags/cli-vX.Y.Z
```
8. Publish.
Ask the user which path to use:
- GitHub main release. Use this after the release commit is on `main` and the matching `cli-vX.Y.Z` tag has been pushed. The workflow publishes to npm from that tag, creates the GitHub release, and posts to Slack.
- Local release. Use this when the user wants to publish from this machine. The local machine must be authenticated to npm and GitHub.
- GitHub nightly release.
- Stop after the version commit.
For GitHub main release:
```sh
gh workflow run cli-publish.yml -f publish_target=main -f git_tag=cli-vX.Y.Z -f confirm_publish=publish
gh run list --workflow=cli-publish.yml --limit=1 --json url,status,conclusion,createdAt --jq '.[0]'
```
For GitHub nightly release:
```sh
gh workflow run cli-publish.yml -f publish_target=nightly
```
For forced GitHub nightly release:
```sh
gh workflow run cli-publish.yml -f publish_target=nightly -f force_nightly_publish=true
```
For local publish:
```sh
gh auth status
npm whoami
git tag -a cli-vX.Y.Z -m "CLI vX.Y.Z"
git push origin refs/tags/cli-vX.Y.Z
bun release cli
```
After a successful local publish, ask before running:
```sh
gh release create cli-vX.Y.Z --verify-tag --title "CLI vX.Y.Z" --notes "Paste the approved release notes here."
```
If publishing with another npm dist-tag:
```sh
bun release cli --tag next
```
9. Final response.
Report:
- version
- tag
- changelog file updated
- commit hash
- whether anything was pushed
- publish path selected
- workflow URL or local publish result
- tests and builds run
+128
View File
@@ -0,0 +1,128 @@
# Debug Harness
HTTP-controlled debugger for the VSCode extension at `src/dev/debug-harness/server.ts`.
## Quick start
```bash
# Build extension first if needed (protos + esbuild):
npm run protos && IS_DEV=true node esbuild.mjs
# Launch (skip-build if already built):
npx tsx src/dev/debug-harness/server.ts --skip-build --auto-launch
# In another terminal:
curl localhost:19229/api -d '{"method":"status"}'
```
## Data Isolation
The debugee runs with `CLINE_DIR=~/.cline2` by default, separate from your real `~/.cline`.
This prevents the debugee's logout from logging out the debugger, and vice versa.
Override with `--cline-dir /tmp/test-dir`. Check with `status()``clineDir`.
## Browser Capture & OAuth
The debugee runs with `CLINE_CAPTURE_BROWSER=1`, which intercepts `openExternal()` in
`src/utils/env.ts`. URLs are captured instead of opening a real browser:
- Logged to `$CLINE_DIR/data/debug-captured-urls.jsonl`
- POSTed in real-time to `/captured-url` on the harness server
- Queryable via `oauth.captured_urls`
### OAuth API
- **`oauth.captured_urls`** `{clear?}` — URLs the debugee tried to open
- **`oauth.read_stored_token`** — Check auth token presence in secrets.json
- **`oauth.simulate_callback`** `{path, code?, state?, provider?, token?}` — Build vscode:// callback URI
- **`oauth.read_captured_urls_file`** — Read on-disk JSONL of captured URLs
### OAuth testing flow
For **Cline OAuth** (SDK local callback): The SDK starts a local HTTP server, the auth URL
is captured. To complete: open the captured URL in a real browser (it redirects back to the
SDK's callback server), OR extract the callback port and `curl http://127.0.0.1:PORT/callback?code=...`.
For **MCP/Provider OAuth** (vscode:// URI): The redirect goes to a vscode:// URI.
`oauth.simulate_callback` only *builds* the URI — it does not deliver it, and the ESM
extension host can't `require()` the handler. To actually deliver the callback, call the
debug-only hook via `ext.evaluate` (with `awaitPromise: true`):
`globalThis.__clineHandleUri("vscode://saoudrizwan.claude-dev/...?code=...&state=...")`.
It runs the same `SharedUriHandler.handleUri` as VSCode's real URI handler and exists only
when `CLINE_CAPTURE_BROWSER` is set (the harness always sets it; never ships in prod).
For end-to-end MCP OAuth, get a real `code` from the local MCP OAuth test server
(`npm run dev:mcp-oauth-test-server`).
## Navigating Views — Use Commands, Not Clicks
Don't try to find/click small sidebar icons. Use VSCode commands via command palette.
Registered in `src/registry.ts`:
| Command | View |
|---------|------|
| `cline.accountButtonClicked` | Account / sign-in |
| `cline.historyButtonClicked` | Task history |
| `cline.settingsButtonClicked` | Settings |
| `cline.mcpButtonClicked` | MCP servers |
| `cline.plusButtonClicked` | New task (chat) |
| `cline.worktreesButtonClicked` | Worktrees |
```bash
curl localhost:19229/api -d '{"method":"ui.command_palette","params":{"command":"cline.accountButtonClicked"}}'
```
## Key commands
All via `POST localhost:19229/api` with `{"method":"...", "params":{...}}`:
- **`launch`** / **`shutdown`** — lifecycle
- **`ui.screenshot`** — screenshot to `/tmp/cline-debug/`; returns `{path}`**use `read_file` on the path to examine, do NOT `open` the file** (Preview.app covers the VSCode window)
- **`ui.open_sidebar`** — open the Cline sidebar
- **`ext.set_breakpoint`** `{file, line, condition?}` — breakpoint by source file (sourcemap-resolved)
- **`ext.evaluate`** `{expression, callFrameId?}` — eval in extension host
- **`ext.resume`** / **`ext.step_over`** / **`ext.step_into`** — stepping
- **`ext.call_stack`** — inspect when paused
- **`web.evaluate`** `{expression}` — eval in webview
- **`web.post_message`** `{message}` — send postMessage to extension host via exposed vsCodeApi
- **`wait_for_pause`** `{timeout?}` — block until breakpoint hit
- **`ui.locator`** `{role?, testId?, text?, frame?}` — Playwright locator (auto-retries on stale sidebar frame)
- **`ui.react_input`** `{text, selector?, clear?, submit?}` — set React textarea value via `execCommand('insertText')`; works reliably across multiple tasks
- **`ui.send_message`** `{text, images?, files?, responseType?}` — send chat message bypassing the textarea entirely (via gRPC postMessage)
- **`ui.command_palette`** `{command}` — run VSCode command
## Typical Session
```bash
# 1. Launch
curl localhost:19229/api -d '{"method":"launch","params":{"skipBuild":true}}'
# 2. Open sidebar + dismiss overlays (ALWAYS do this first)
curl localhost:19229/api -d '{"method":"ui.open_sidebar"}'
curl localhost:19229/api -d '{"method":"web.evaluate","params":{"expression":"document.querySelectorAll(\".sr-only\").forEach(el => el.parentElement?.click())"}}'
# 3. Navigate to view
curl localhost:19229/api -d '{"method":"ui.command_palette","params":{"command":"cline.accountButtonClicked"}}'
# 4. Check captured OAuth URLs if testing auth
curl localhost:19229/api -d '{"method":"oauth.captured_urls"}'
# 5. Verify
curl localhost:19229/api -d '{"method":"ui.screenshot"}'
```
## Caveats
- **⚠️ Dismiss promotional overlays FIRST**: On fresh launches, full-screen promo overlays block the sidebar. **Dismiss immediately after `ui.open_sidebar`**, before any other interaction or screenshot. May need to run twice:
```bash
curl localhost:19229/api -d '{"method": "ui.open_sidebar"}'
curl localhost:19229/api -d '{"method": "web.evaluate", "params": {"expression": "document.querySelectorAll(\".sr-only\").forEach(el => el.parentElement?.click())"}}'
```
- **Screenshots — don't open the file**: `ui.screenshot` and `ui.sidebar_screenshot` save PNGs to `/tmp/cline-debug/` and return the `{path}`. Use `read_file` on that path to examine screenshots. Running `open <path>` launches Preview.app on macOS which covers the VSCode window.
- **Scripts count = 0 after launch**: CDP connects after extension host starts, so scripts parsed during startup aren't tracked. Breakpoints still work via sourcemap resolution.
- **Port 9230**: Extension host inspector. If another VSCode instance uses this port, the harness will fail to connect. Kill other debug instances first.
- **macOS only** for now (Playwright Electron launch behavior).
- **Webview CDP**: `connect_webview` may fail depending on Electron version. `web.evaluate` still works via Playwright's `frame.evaluate()` fallback.
- **Sourcemap paths**: esbuild outputs relative paths like `../src/extension.ts` in the sourcemap. The resolver handles this, but if a file isn't found, use `ext.source_files` to see exact paths.
- **OAuth with fake codes**: Browser capture intercepts the URL but doesn't provide a valid auth code. For real OAuth testing, open the captured URL in a browser. For unit testing, mock the token exchange.
See `src/dev/debug-harness/README.md` for full API reference.
+9 -13
View File
@@ -140,12 +140,10 @@ Adding a new key to global state requires updates in multiple places. Missing an
Required steps:
1. Type definition in `src/shared/storage/state-keys.ts` - Add to `GlobalState` or `Settings` interface
2. Read from globalState in `src/core/storage/utils/state-helpers.ts`:
- Add `const myKey = context.globalState.get<GlobalStateAndSettings["myKey"]>("myKey")` in `readGlobalStateFromDisk()`
- Add to the return object: `myKey: myKey ?? defaultValue,`
3. StateManager handles read/write via `setGlobalState()`/`getGlobalStateKey()` after initialization
2. Add any default value or transform in `src/shared/storage/state-keys.ts` if the key needs one
3. Read and write the value through `StateManager` (`setGlobalState()` / `getGlobalStateKey()`) after initialization
Common mistake: Adding only the return value without the `context.globalState.get()` call. This compiles but the value is always `undefined` on load.
Persistent state is file-backed through `StateManager`; do not add new runtime reads or writes against VS Code `ExtensionContext` storage. That storage is only a legacy migration source.
Settings plumbing gotcha: if a key is user-toggleable from settings, wire both controller update paths:
- `src/core/controller/state/updateSettings.ts` for webview `updateSetting(...)`
@@ -159,22 +157,20 @@ Webview toggle gotcha: settings changes must also round-trip back in state paylo
If this round-trip wiring is missing, the backend value can update but the toggle in webview appears stuck or reverts.
## StateManager Cache vs Direct globalState Access
StateManager uses an in-memory cache populated during `StateManager.initialize(context)` in `common.ts`. For most state, use `controller.stateManager.setGlobalState()`/`getGlobalStateKey()`.
StateManager uses an in-memory cache populated during `StateManager.initialize()` from file-backed storage. For most state, use `controller.stateManager.setGlobalState()`/`getGlobalStateKey()`.
Exception: State needed immediately at extension startup (before cache is ready)
Exception: host migration code may read legacy VS Code storage before file-backed storage is initialized.
When Window A sets state and immediately opens Window B, the new window's StateManager cache is populated from `context.globalState` during initialization. If you need to read state in Window B right at startup (e.g., in `common.ts` during `initialize()`), read directly from `context.globalState.get()` instead of StateManager's cache.
Example pattern (see `lastShownAnnouncementId` and `worktreeAutoOpenPath`):
Example pattern:
```typescript
// Writing (normal pattern)
controller.stateManager.setGlobalState("myKey", value)
// Reading at startup in common.ts (bypass cache)
const value = context.globalState.get<string>("myKey")
// Reading after initialization
const value = controller.stateManager.getGlobalStateKey("myKey")
```
This is only needed for cross-window state read during the brief startup window before StateManager cache is fully usable. Normal state access after initialization should use StateManager.
Use `context.globalState` only in VS Code migration code that copies legacy ExtensionContext values into the shared file-backed stores.
## ChatRow Cancelled/Interrupted States
When a ChatRow displays a loading/in-progress state (spinner), you must handle what happens when the task is cancelled. This is non-obvious because cancellation doesn't update the message content—you have to infer it from context.
-2
View File
@@ -1,6 +1,4 @@
demo.gif filter=lfs diff=lfs merge=lfs -text
assets/docs/demo.gif filter=lfs diff=lfs merge=lfs -text
webview-ui/src/assets/cline_kanban_demo.mp4 filter=lfs diff=lfs merge=lfs -text
webview-ui/src/assets/cline_kanban_demo.webm filter=lfs diff=lfs merge=lfs -text
* text=auto eol=lf
+1 -1
View File
@@ -1,2 +1,2 @@
/.github/ @saoudrizwan @arafatkatze @maxpaulus43 @candieduniverse
/.github/ @saoudrizwan @arafatkatze @maxpaulus43 @dominiccooney
/README.md @saoudrizwan @juanpflores
+15 -3
View File
@@ -7,10 +7,10 @@ body:
value: |
**Important:** All bug reports must be reproducible using Claude Sonnet 4.5. Cline uses complex prompts so less capable models may not work as expected.
- type: dropdown
id: plugin-type
id: cline-surface
attributes:
label: Plugin Type
description: Which plugin are you reporting a bug for?
label: Cline Surface
description: Which Cline surface are you reporting a bug for?
options:
- VSCode Extension
- JetBrains Plugin
@@ -59,6 +59,18 @@ body:
placeholder: 'e.g., cline:anthropic/claude-sonnet-4.5, gemini:gemini-2.5-pro-exp-03-25'
validations:
required: false
- type: textarea
id: ide-diagnostics
attributes:
label: IDE / CLI Diagnostics
description: |
Paste the "About" diagnostics for your Cline surface. This captures the IDE build, runtime, and host details we need.
- VSCode Extension: open `Help → About` (Windows/Linux) or `Code → About Visual Studio Code` (macOS), then copy the info.
- JetBrains Plugin: open `Help → About` (Windows/Linux) or `<IDE name> → About` (macOS), then click `Copy` to grab build, runtime, OS, memory, and cores.
- CLI: there is no About dialog. Run `cline --version` and paste the output.
placeholder: Paste the copied About info or `cline --version` output here.
validations:
required: false
- type: textarea
id: system-info
attributes:
+1 -1
View File
@@ -44,7 +44,7 @@ For Responses API providers: add to `isNextGenModelProvider()` in `src/utils/mod
Modular: `components/` (shared) + `variants/` (model-specific) + `templates/` (`{{PLACEHOLDER}}`). Variants override components via `componentOverrides` in `config.ts` or custom `template.ts`. XS variant is heavily condensed inline. Always regenerate snapshots after changes.
## Global State Keys (silent failure risk)
Adding a key requires: type in `src/shared/storage/state-keys.ts`, read via `context.globalState.get()` in `src/core/storage/utils/state-helpers.ts` `readGlobalStateFromDisk()`, and add to return object. Missing the `.get()` call compiles fine but value is always `undefined`.
Adding a key requires updating the typed storage definitions in `src/shared/storage/state-keys.ts`; runtime reads and writes should go through `StateManager`, not VS Code `ExtensionContext` storage. Persistent state is file-backed so it works across VS Code, CLI, and JetBrains hosts.
## Slash Commands (3 places)
- `src/core/slash-commands/index.ts` — definitions.
+2 -2
View File
@@ -2,7 +2,7 @@ version: 2
updates:
# Main extension dependencies
- package-ecosystem: "npm"
directory: "/"
directory: "/apps/vscode"
schedule:
interval: "weekly"
# Group all updates into a single PR
@@ -20,7 +20,7 @@ updates:
# Webview UI dependencies
- package-ecosystem: "npm"
directory: "/webview-ui"
directory: "/apps/vscode/webview-ui"
schedule:
interval: "weekly"
groups:
+59 -18
View File
@@ -33,7 +33,7 @@ permissions:
defaults:
run:
working-directory: sdk
working-directory: .
jobs:
publish-main:
@@ -88,9 +88,9 @@ jobs:
- name: Validate release tag
id: version
env:
TAG: ${{ github.event.inputs.git_tag }}
run: |
TAG="${{ github.event.inputs.git_tag }}"
if [ -z "$TAG" ]; then
echo "git_tag is required when publish_target=main"
exit 1
@@ -105,12 +105,12 @@ jobs:
PACKAGE_VERSION=$(node -p "require('./apps/cli/package.json').version")
if [ "$PACKAGE_VERSION" != "$VERSION" ]; then
echo "sdk/apps/cli/package.json version ${PACKAGE_VERSION} does not match ${TAG}"
echo "apps/cli/package.json version ${PACKAGE_VERSION} does not match ${TAG}"
exit 1
fi
if ! printf "%s\n" "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then
echo "sdk/apps/cli/package.json has invalid version: ${VERSION}"
echo "apps/cli/package.json has invalid version: ${VERSION}"
exit 1
fi
@@ -132,17 +132,36 @@ jobs:
- name: Build SDK packages
run: bun run build:sdk
env:
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }}
OTEL_LOGS_EXPORTER: otlp
OTEL_METRICS_EXPORTER: otlp
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
- name: Run tests
run: bun run test
- name: Build platform binaries
run: bun script/build.ts --install-native-variants --skip-sdk-build
working-directory: sdk/apps/cli
working-directory: apps/cli
env:
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }}
OTEL_LOGS_EXPORTER: otlp
OTEL_METRICS_EXPORTER: otlp
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
- name: Verify build output
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
VERSION="${{ steps.version.outputs.version }}"
EXPECTED=(
"@cline/cli-darwin-arm64"
"@cline/cli-darwin-x64"
@@ -175,12 +194,13 @@ jobs:
env:
NPM_CONFIG_PROVENANCE: "true"
run: bun script/publish-npm.ts --tag latest
working-directory: sdk/apps/cli
working-directory: apps/cli
- name: Get Previous CLI Tag
id: prev_tag
env:
CURRENT_TAG: ${{ steps.version.outputs.tag }}
run: |
CURRENT_TAG="${{ steps.version.outputs.tag }}"
PREV_TAG=$(git describe --tags --abbrev=0 --match 'cli-v*' "$CURRENT_TAG^" 2>/dev/null || echo "")
echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT
@@ -200,13 +220,15 @@ jobs:
name: "CLI v${{ steps.version.outputs.version }}"
body: |
${{ steps.changelog.outputs.content }}
${{ steps.prev_tag.outputs.prev_tag != '' && format('Full Changelog: https://github.com/{0}/compare/{1}...{2}', github.repository, steps.prev_tag.outputs.prev_tag, steps.version.outputs.tag) || '' }}
${{ steps.prev_tag.outputs.prev_tag != '' && format('**Full Changelog**: https://github.com/{0}/compare/{1}...{2}', github.repository, steps.prev_tag.outputs.prev_tag, steps.version.outputs.tag) || '' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Summary
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
VERSION="${{ steps.version.outputs.version }}"
echo "Published cline@${VERSION} to npm with dist-tag 'latest'"
echo "Install with: npm install -g cline"
@@ -309,6 +331,15 @@ jobs:
- name: Build SDK packages
if: steps.check_commits.outputs.skip != 'true'
run: bun run build:sdk
env:
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }}
OTEL_LOGS_EXPORTER: otlp
OTEL_METRICS_EXPORTER: otlp
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
- name: Run tests
if: steps.check_commits.outputs.skip != 'true'
@@ -329,8 +360,9 @@ jobs:
- name: Update nightly package version
if: steps.check_commits.outputs.skip != 'true'
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
VERSION="${{ steps.version.outputs.version }}"
node -e '
const fs = require("node:fs");
const path = "apps/cli/package.json";
@@ -339,18 +371,26 @@ jobs:
fs.writeFileSync(path, `${JSON.stringify(pkg, null, "\t")}\n`);
'
cat apps/cli/package.json | grep '"version"'
env:
VERSION: ${{ steps.version.outputs.version }}
- name: Build platform binaries
if: steps.check_commits.outputs.skip != 'true'
run: bun script/build.ts --install-native-variants --skip-sdk-build
working-directory: sdk/apps/cli
working-directory: apps/cli
env:
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }}
OTEL_LOGS_EXPORTER: otlp
OTEL_METRICS_EXPORTER: otlp
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
- name: Verify build output
if: steps.check_commits.outputs.skip != 'true'
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
VERSION="${{ steps.version.outputs.version }}"
EXPECTED=(
"@cline/cli-darwin-arm64"
"@cline/cli-darwin-x64"
@@ -384,11 +424,12 @@ jobs:
env:
NPM_CONFIG_PROVENANCE: "true"
run: bun script/publish-npm.ts --tag nightly
working-directory: sdk/apps/cli
working-directory: apps/cli
- name: Summary
if: steps.check_commits.outputs.skip != 'true'
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
VERSION="${{ steps.version.outputs.version }}"
echo "Published cline@${VERSION} to npm with dist-tag 'nightly'"
echo "Install with: npm install -g cline@nightly"
@@ -15,9 +15,11 @@ jobs:
trigger-integration-test:
name: Run Tests
runs-on: ubuntu-latest
# Run on PR open/reopen, or when someone comments /test-jetbrains on a PR
# Auto-run only for trusted PR authors. Anyone else needs a maintainer
# to opt their PR in by commenting /test-jetbrains.
if: |
github.event_name == 'pull_request_target' ||
(github.event_name == 'pull_request_target' &&
contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.pull_request.author_association)) ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '/test-jetbrains') &&
@@ -27,8 +29,8 @@ jobs:
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: 1998650
private-key: ${{ secrets.CLINE_JETBRAINS_WORKFLOW_KEY }}
app-id: ${{ vars.CLINE_JETBRAINS_APP_ID }}
private-key: ${{ secrets.CLINE_JETBRAINS_APP_KEY }}
owner: cline
repositories: intellij-plugin
@@ -1,75 +0,0 @@
# TODO: Fold this workflow's SDK login changes into ext-vscode-publish-nightly.yml
# and delete this file. Pinned to dpc/sdk-migration-simpler-login while Max is iterating.
# Owner: Max Paulus
name: ext-vscode-publish-nightly-sdk
on:
schedule:
- cron: '0 12 * * *' # 4 AM PST (UTC-8) = 12 UTC
workflow_dispatch:
permissions:
contents: read
packages: write
checks: write
pull-requests: write
env:
# Keep the publish source pinned to one reviewed branch instead of accepting arbitrary refs.
SDK_NIGHTLY_REF: dpc/sdk-migration-simpler-login
jobs:
publish:
name: Publish Cline New SDK Extension Nightly
if: github.repository == 'cline/cline' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: PublishNightly
steps:
- name: Checkout trusted SDK nightly branch
uses: actions/checkout@v4
with:
ref: ${{ env.SDK_NIGHTLY_REF }}
lfs: true
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v4
with:
# Keep publish environment aligned with test workflow/tooling lockfile expectations.
# Newer LTS (Node 24 / npm 11) can make `npm list` fail with ELSPROBLEMS during vsce packaging.
node-version: 22
- name: Install root dependencies
run: npm ci --include=optional
- name: Install webview-ui dependencies
run: cd webview-ui && npm ci --include=optional
- name: Install Publishing Tools
run: npm install -g @vscode/vsce ovsx
- name: Verify LFS media assets are resolved
run: |
for FILE in webview-ui/src/assets/cline_kanban_demo.mp4 webview-ui/src/assets/cline_kanban_demo.webm; do
if grep -q "git-lfs.github.com/spec/v1" "$FILE"; then
echo "Error: $FILE is still a Git LFS pointer in CI checkout"
exit 1
fi
done
- name: Publish SDK nightly extension
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
OVSX_PAT: ${{ secrets.OVSX_PAT }}
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
CLINE_ENVIRONMENT: production
# OpenTelemetry production defaults (can be overridden at runtime)
OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }}
OTEL_LOGS_EXPORTER: otlp
OTEL_METRICS_EXPORTER: otlp
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
run: npm run publish:marketplace:nightly
@@ -20,6 +20,7 @@ jobs:
if: github.repository == 'cline/cline' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dpc/sdk-migration-simpler-login')
permissions:
contents: read
pull-requests: read
uses: ./.github/workflows/ext-vscode-test.yml
publish:
@@ -30,6 +31,9 @@ jobs:
if: github.repository == 'cline/cline' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dpc/sdk-migration-simpler-login')
runs-on: ubuntu-latest
environment: PublishNightly
defaults:
run:
working-directory: apps/vscode
steps:
- name: Checkout selected branch
@@ -40,6 +44,7 @@ jobs:
persist-credentials: false
- name: Show build source
working-directory: ${{ github.workspace }}
run: |
echo "Building ref: $GITHUB_REF"
echo "Building sha: $GITHUB_SHA"
@@ -52,24 +57,17 @@ jobs:
# Newer LTS (Node 24 / npm 11) can make `npm list` fail with ELSPROBLEMS during vsce packaging.
node-version: 22
- name: Install root dependencies
run: npm ci --include=optional
- name: Install extension dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode ci --include=optional
- name: Install webview-ui dependencies
run: cd webview-ui && npm ci --include=optional
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode/webview-ui ci --include=optional
- name: Install Publishing Tools
run: npm install -g @vscode/vsce ovsx
- name: Verify LFS media assets are resolved
run: |
for FILE in webview-ui/src/assets/cline_kanban_demo.mp4 webview-ui/src/assets/cline_kanban_demo.webm; do
if grep -q "git-lfs.github.com/spec/v1" "$FILE"; then
echo "Error: $FILE is still a Git LFS pointer in CI checkout"
exit 1
fi
done
- name: Publish Nightly Extension
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
@@ -87,6 +85,7 @@ jobs:
run: npm run publish:marketplace:nightly
- name: Tag published commit
working-directory: ${{ github.workspace }}
env:
GH_TOKEN: ${{ github.token }}
run: |
+12 -13
View File
@@ -36,6 +36,9 @@ jobs:
name: Publish Extension
runs-on: ubuntu-latest
environment: publish
defaults:
run:
working-directory: apps/vscode
steps:
- uses: actions/checkout@v4
@@ -47,6 +50,7 @@ jobs:
- name: Resolve Release Tag
id: resolve_tag
working-directory: ${{ github.workspace }}
env:
TAG: ${{ github.event.inputs.tag }}
AUTO_CREATE: ${{ github.event.inputs.auto_create_tag_from_main }}
@@ -110,11 +114,13 @@ jobs:
with:
node-version: 22
- name: Install root dependencies
run: npm install --include=optional
- name: Install extension dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode install --include=optional
- name: Install webview-ui dependencies
run: cd webview-ui && npm install --include=optional
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode/webview-ui install --include=optional
- name: Install Publishing Tools
run: npm install -g @vscode/vsce ovsx
@@ -135,15 +141,6 @@ jobs:
fi
echo "Tag and package version match: $TAG"
- name: Verify LFS media assets are resolved
run: |
for FILE in webview-ui/src/assets/cline_kanban_demo.mp4 webview-ui/src/assets/cline_kanban_demo.webm; do
if grep -q "git-lfs.github.com/spec/v1" "$FILE"; then
echo "Error: $FILE is still a Git LFS pointer in CI checkout"
exit 1
fi
done
- name: Package and Publish Extension
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
@@ -180,6 +177,7 @@ jobs:
- name: Get Previous Tag
id: prev_tag
working-directory: ${{ github.workspace }}
run: |
CURRENT_TAG="${{ steps.resolve_tag.outputs.tag }}"
PREV_TAG=$(git describe --tags --abbrev=0 "$CURRENT_TAG^" 2>/dev/null || echo "")
@@ -187,6 +185,7 @@ jobs:
- name: Get Changelog Entry
id: changelog
working-directory: ${{ github.workspace }}
run: |
# Get content between first ## [ and second ## [
CONTENT=$(awk '/^## \[/{if(found) exit; found=1; next} found{print}' CHANGELOG.md)
@@ -198,7 +197,7 @@ jobs:
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.resolve_tag.outputs.tag }}
files: "*.vsix"
files: "apps/vscode/*.vsix"
body: |
${{ steps.changelog.outputs.content }}
+33 -28
View File
@@ -36,24 +36,24 @@ jobs:
with:
filters: |
e2e:
- 'src/**'
- 'webview-ui/**'
- 'proto/**'
- 'tests/**'
- 'scripts/**'
- 'standalone/**'
- 'assets/**'
- 'walkthrough/**'
- 'package.json'
- 'package-lock.json'
- 'buf.yaml'
- 'tsconfig*.json'
- 'biome.jsonc'
- 'esbuild.mjs'
- '.mocharc.json'
- '.vscode-test.mjs'
- '.vscodeignore'
- 'playwright*.ts'
- 'apps/vscode/src/**'
- 'apps/vscode/webview-ui/**'
- 'apps/vscode/proto/**'
- 'apps/vscode/tests/**'
- 'apps/vscode/scripts/**'
- 'apps/vscode/standalone/**'
- 'apps/vscode/assets/**'
- 'apps/vscode/walkthrough/**'
- 'apps/vscode/package.json'
- 'apps/vscode/package-lock.json'
- 'apps/vscode/buf.yaml'
- 'apps/vscode/tsconfig*.json'
- 'apps/vscode/biome.jsonc'
- 'apps/vscode/esbuild.mjs'
- 'apps/vscode/.mocharc.json'
- 'apps/vscode/.vscode-test.mjs'
- 'apps/vscode/.vscodeignore'
- 'apps/vscode/playwright*.ts'
- '.github/workflows/ext-vscode-test-e2e.yml'
matrix_prep:
@@ -79,6 +79,9 @@ jobs:
permissions:
id-token: write
contents: read
defaults:
run:
working-directory: apps/vscode
steps:
- uses: actions/checkout@v4
- name: Setup Node.js environment
@@ -91,24 +94,24 @@ jobs:
uses: actions/cache@v4
id: root-cache
with:
path: node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
path: apps/vscode/node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('apps/vscode/package-lock.json') }}
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
- name: Cache webview-ui dependencies
uses: actions/cache@v4
id: webview-cache
with:
path: webview-ui/node_modules
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
path: apps/vscode/webview-ui/node_modules
key: ${{ runner.os }}-npm-webview-${{ hashFiles('apps/vscode/webview-ui/package-lock.json') }}
# Cache VS Code installation
- name: Cache VS Code
uses: actions/cache@v4
id: vscode-cache
with:
path: .vscode-test
key: vscode-${{ runner.os }}-stable-${{ hashFiles('.vscode-test.mjs', 'package.json') }}
path: apps/vscode/.vscode-test
key: vscode-${{ runner.os }}-stable-${{ hashFiles('apps/vscode/.vscode-test.mjs', 'apps/vscode/package.json') }}
restore-keys: |
vscode-${{ runner.os }}-stable-
@@ -121,17 +124,19 @@ jobs:
~/.cache/ms-playwright
~/Library/Caches/ms-playwright
~/AppData/Local/ms-playwright
key: playwright-browsers-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
key: playwright-browsers-${{ runner.os }}-${{ hashFiles('apps/vscode/package-lock.json') }}
restore-keys: |
playwright-browsers-${{ runner.os }}-
- name: Install root dependencies
run: npm ci
- name: Install extension dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode ci
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install webview-ui dependencies
run: cd webview-ui && npm ci
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode/webview-ui ci
- name: Install vsce
run: npm install -g @vscode/vsce
+70 -55
View File
@@ -36,38 +36,38 @@ jobs:
with:
filters: |
vscode:
- 'src/**'
- 'webview-ui/**'
- 'proto/**'
- 'tests/**'
- 'scripts/**'
- 'standalone/**'
- 'assets/**'
- 'walkthrough/**'
- 'package.json'
- 'package-lock.json'
- 'buf.yaml'
- 'tsconfig*.json'
- 'biome.jsonc'
- 'esbuild.mjs'
- '.mocharc.json'
- '.nycrc*.json'
- '.vscode-test.mjs'
- 'test-setup.js'
- 'apps/vscode/src/**'
- 'apps/vscode/webview-ui/**'
- 'apps/vscode/proto/**'
- 'apps/vscode/tests/**'
- 'apps/vscode/scripts/**'
- 'apps/vscode/standalone/**'
- 'apps/vscode/assets/**'
- 'apps/vscode/walkthrough/**'
- 'apps/vscode/package.json'
- 'apps/vscode/package-lock.json'
- 'apps/vscode/buf.yaml'
- 'apps/vscode/tsconfig*.json'
- 'apps/vscode/biome.jsonc'
- 'apps/vscode/esbuild.mjs'
- 'apps/vscode/.mocharc.json'
- 'apps/vscode/.nycrc*.json'
- 'apps/vscode/.vscode-test.mjs'
- 'apps/vscode/test-setup.js'
- '.github/workflows/ext-vscode-test.yml'
testing_platform:
- 'src/**'
- 'proto/**'
- 'standalone/**'
- 'testing-platform/**'
- 'tests/specs/**'
- 'package.json'
- 'package-lock.json'
- 'buf.yaml'
- 'tsconfig*.json'
- 'esbuild.mjs'
- '.vscodeignore'
- 'scripts/**'
- 'apps/vscode/src/**'
- 'apps/vscode/proto/**'
- 'apps/vscode/standalone/**'
- 'apps/vscode/testing-platform/**'
- 'apps/vscode/tests/specs/**'
- 'apps/vscode/package.json'
- 'apps/vscode/package-lock.json'
- 'apps/vscode/buf.yaml'
- 'apps/vscode/tsconfig*.json'
- 'apps/vscode/esbuild.mjs'
- 'apps/vscode/.vscodeignore'
- 'apps/vscode/scripts/**'
- '.github/workflows/ext-vscode-test.yml'
quality-checks:
@@ -75,6 +75,9 @@ jobs:
if: needs.detect-changes.outputs.vscode == 'true' || needs.detect-changes.outputs.testing_platform == 'true'
runs-on: ubuntu-latest
name: Quality Checks
defaults:
run:
working-directory: apps/vscode
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -85,16 +88,18 @@ jobs:
node-version: 22
cache: 'npm'
cache-dependency-path: |
package-lock.json
webview-ui/package-lock.json
apps/vscode/package-lock.json
apps/vscode/webview-ui/package-lock.json
- name: Install root dependencies
run: npm ci
- name: Install extension dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode ci
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install webview-ui dependencies
run: cd webview-ui && npm ci
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode/webview-ui ci
- name: Run Quality Checks (Parallel)
run: npm run ci:check-all
@@ -113,6 +118,7 @@ jobs:
defaults:
run:
shell: bash
working-directory: apps/vscode
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -123,19 +129,22 @@ jobs:
node-version: 22
cache: 'npm'
cache-dependency-path: |
package-lock.json
webview-ui/package-lock.json
apps/vscode/package-lock.json
apps/vscode/webview-ui/package-lock.json
- name: Install root dependencies
run: npm ci
- name: Install extension dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode ci
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install webview-ui dependencies
run: cd webview-ui && npm ci
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode/webview-ui ci
- name: Set up NPM on Windows
if: runner.os == 'Windows'
working-directory: ${{ github.workspace }}
run: |
npm config set script-shell "C:\\Program Files\\Git\\bin\\bash.exe"
@@ -201,13 +210,16 @@ jobs:
with:
name: pr-coverage-reports
path: |
coverage-unit/lcov.info
webview-ui/coverage/lcov.info
apps/vscode/coverage-unit/lcov.info
apps/vscode/webview-ui/coverage/lcov.info
test-platform-integration:
needs: [detect-changes, quality-checks]
if: needs.detect-changes.outputs.testing_platform == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/vscode
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -218,17 +230,19 @@ jobs:
node-version: 22
cache: 'npm'
cache-dependency-path: |
package-lock.json
webview-ui/package-lock.json
testing-platform/package-lock.json
apps/vscode/package-lock.json
apps/vscode/webview-ui/package-lock.json
apps/vscode/testing-platform/package-lock.json
- name: Install root dependencies
run: npm ci
- name: Install extension dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode ci
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install webview-ui dependencies
run: cd webview-ui && npm ci
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode/webview-ui ci
- name: Download ripgrep binaries
run: npm run download-ripgrep
@@ -237,7 +251,8 @@ jobs:
run: npm run compile-standalone
- name: Install testing platform dependencies
run: cd testing-platform && npm ci
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode/testing-platform ci
- name: Running testing platform integration spec tests
timeout-minutes: 7
@@ -247,7 +262,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: test-platform-integration-core-coverage
path: coverage/**/lcov.info
path: apps/vscode/coverage/**/lcov.info
# Keep the required "test" check as a tiny aggregate gate instead of the conditional
# VS Code matrix. GitHub treats conditionally skipped jobs as successful required
@@ -309,7 +324,7 @@ jobs:
uses: actions/download-artifact@v4
with:
name: pr-coverage-reports
path: .
path: apps/vscode
- name: Upload core unit tests coverage to Qlty
if: needs.detect-changes.outputs.vscode == 'true'
@@ -318,7 +333,7 @@ jobs:
token: ${{ secrets.QLTY_COVERAGE_TOKEN }}
# we can merge multiple files if necessary
files: |
coverage-unit/lcov.info
apps/vscode/coverage-unit/lcov.info
tag: unit:core
- name: Upload webview-ui unit tests coverage to Qlty
@@ -328,7 +343,7 @@ jobs:
token: ${{ secrets.QLTY_COVERAGE_TOKEN }}
# we can merge multiple files if necessary
files: |
webview-ui/coverage/lcov.info
apps/vscode/webview-ui/coverage/lcov.info
tag: unit:webview-ui
add-prefix: webview-ui/
@@ -339,12 +354,12 @@ jobs:
id: download-integration-coverage
with:
name: test-platform-integration-core-coverage
path: integration-core-coverage-reports
path: apps/vscode/integration-core-coverage-reports
- name: Upload core integration tests coverage to Qlty
if: needs.detect-changes.outputs.testing_platform == 'true' && steps.download-integration-coverage.outcome == 'success'
uses: qltysh/qlty-action/coverage@v2
with:
token: ${{ secrets.QLTY_COVERAGE_TOKEN }}
files: integration-core-coverage-reports/**/lcov.info
files: apps/vscode/integration-core-coverage-reports/**/lcov.info
tag: integration:core
+3 -3
View File
@@ -17,7 +17,7 @@ jobs:
const labels = context.payload.issue.labels.map(l => l.name);
// Check if JetBrains Plugin is selected
if (body.match(/###\s*Plugin Type\s*\n+JetBrains Plugin/i)) {
if (body.match(/###\s*Cline Surface\s*\n+JetBrains Plugin/i)) {
if (!labels.includes('JetBrains')) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
@@ -29,7 +29,7 @@ jobs:
}
// Check if VSCode Extension is selected
if (body.match(/###\s*Plugin Type\s*\n+VSCode Extension/i)) {
if (body.match(/###\s*Cline Surface\s*\n+VSCode Extension/i)) {
if (!labels.includes('VS Code')) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
@@ -41,7 +41,7 @@ jobs:
}
// Check if CLI is selected
if (body.match(/###\s*Plugin Type\s*\n+CLI/i)) {
if (body.match(/###\s*Cline Surface\s*\n+CLI/i)) {
if (!labels.includes('CLI')) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
+42 -29
View File
@@ -26,7 +26,7 @@ on:
defaults:
run:
working-directory: sdk
working-directory: .
jobs:
test:
@@ -59,19 +59,23 @@ jobs:
- name: Determine publish channel
id: channel
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_CHANNEL: ${{ inputs.channel }}
run: |
# Default to nightly for scheduled runs
if [ "${{ github.event_name }}" = "schedule" ]; then
if [ "$EVENT_NAME" = "schedule" ]; then
echo "channel=nightly" >> $GITHUB_OUTPUT
else
echo "channel=${{ inputs.channel }}" >> $GITHUB_OUTPUT
echo "channel=$INPUT_CHANNEL" >> $GITHUB_OUTPUT
fi
- name: Check for recent commits
id: check_commits
env:
CHANNEL: ${{ steps.channel.outputs.channel }}
FORCE_PUBLISH: ${{ inputs.force_publish }}
run: |
CHANNEL="${{ steps.channel.outputs.channel }}"
# Always publish for latest (production) releases
if [ "$CHANNEL" = "latest" ]; then
echo "Production release requested, proceeding with publish"
@@ -79,7 +83,7 @@ jobs:
exit 0
fi
if [ "${{ inputs.force_publish }}" = "true" ]; then
if [ "$FORCE_PUBLISH" = "true" ]; then
echo "force_publish enabled, proceeding with publish"
echo "skip=false" >> $GITHUB_OUTPUT
exit 0
@@ -141,9 +145,10 @@ jobs:
- name: Generate shared version
if: steps.check_commits.outputs.skip != 'true'
id: version
env:
CHANNEL: ${{ steps.channel.outputs.channel }}
run: |
CHANNEL="${{ steps.channel.outputs.channel }}"
BASE_VERSION=$(node -p "require('./packages/llms/package.json').version")
BASE_VERSION=$(node -p "require('./sdk/packages/llms/package.json').version")
if [ "$CHANNEL" = "nightly" ]; then
TIMESTAMP=$(date +%s)
@@ -159,11 +164,13 @@ jobs:
- name: Update all package versions and lockfile
if: steps.check_commits.outputs.skip != 'true'
run: bun scripts/version.ts "${{ steps.version.outputs.version }}"
env:
VERSION: ${{ steps.version.outputs.version }}
run: bun sdk/scripts/version.ts "$VERSION"
- name: Verify publishability
if: steps.check_commits.outputs.skip != 'true'
run: bun scripts/check-publish.ts
run: bun sdk/scripts/check-publish.ts
- name: Prepare package tarball directory
if: steps.check_commits.outputs.skip != 'true'
@@ -176,10 +183,11 @@ jobs:
if: steps.check_commits.outputs.skip != 'true'
env:
NPM_CONFIG_PROVENANCE: "true"
CHANNEL: ${{ steps.channel.outputs.channel }}
VERSION: ${{ steps.version.outputs.version }}
run: |
CHANNEL="${{ steps.channel.outputs.channel }}"
echo "Publishing @cline/shared@${{ steps.version.outputs.version }} with tag '${CHANNEL}'..."
cd packages/shared
echo "Publishing @cline/shared@${VERSION} with tag '${CHANNEL}'..."
cd sdk/packages/shared
TARBALL=$(bun pm pack --destination "$RUNNER_TEMP/sdk-npm-packs" --quiet)
npm publish "$RUNNER_TEMP/sdk-npm-packs/$(basename "$TARBALL")" --tag "$CHANNEL" --access public
@@ -187,10 +195,11 @@ jobs:
if: steps.check_commits.outputs.skip != 'true'
env:
NPM_CONFIG_PROVENANCE: "true"
CHANNEL: ${{ steps.channel.outputs.channel }}
VERSION: ${{ steps.version.outputs.version }}
run: |
CHANNEL="${{ steps.channel.outputs.channel }}"
echo "Publishing @cline/llms@${{ steps.version.outputs.version }} with tag '${CHANNEL}'..."
cd packages/llms
echo "Publishing @cline/llms@${VERSION} with tag '${CHANNEL}'..."
cd sdk/packages/llms
TARBALL=$(bun pm pack --destination "$RUNNER_TEMP/sdk-npm-packs" --quiet)
npm publish "$RUNNER_TEMP/sdk-npm-packs/$(basename "$TARBALL")" --tag "$CHANNEL" --access public
@@ -198,10 +207,11 @@ jobs:
if: steps.check_commits.outputs.skip != 'true'
env:
NPM_CONFIG_PROVENANCE: "true"
CHANNEL: ${{ steps.channel.outputs.channel }}
VERSION: ${{ steps.version.outputs.version }}
run: |
CHANNEL="${{ steps.channel.outputs.channel }}"
echo "Publishing @cline/agents@${{ steps.version.outputs.version }} with tag '${CHANNEL}'..."
cd packages/agents
echo "Publishing @cline/agents@${VERSION} with tag '${CHANNEL}'..."
cd sdk/packages/agents
TARBALL=$(bun pm pack --destination "$RUNNER_TEMP/sdk-npm-packs" --quiet)
npm publish "$RUNNER_TEMP/sdk-npm-packs/$(basename "$TARBALL")" --tag "$CHANNEL" --access public
@@ -209,10 +219,11 @@ jobs:
if: steps.check_commits.outputs.skip != 'true'
env:
NPM_CONFIG_PROVENANCE: "true"
CHANNEL: ${{ steps.channel.outputs.channel }}
VERSION: ${{ steps.version.outputs.version }}
run: |
CHANNEL="${{ steps.channel.outputs.channel }}"
echo "Publishing @cline/core@${{ steps.version.outputs.version }} with tag '${CHANNEL}'..."
cd packages/core
echo "Publishing @cline/core@${VERSION} with tag '${CHANNEL}'..."
cd sdk/packages/core
TARBALL=$(bun pm pack --destination "$RUNNER_TEMP/sdk-npm-packs" --quiet)
npm publish "$RUNNER_TEMP/sdk-npm-packs/$(basename "$TARBALL")" --tag "$CHANNEL" --access public
@@ -220,18 +231,19 @@ jobs:
if: steps.check_commits.outputs.skip != 'true'
env:
NPM_CONFIG_PROVENANCE: "true"
CHANNEL: ${{ steps.channel.outputs.channel }}
VERSION: ${{ steps.version.outputs.version }}
run: |
CHANNEL="${{ steps.channel.outputs.channel }}"
echo "Publishing @cline/sdk@${{ steps.version.outputs.version }} with tag '${CHANNEL}'..."
cd packages/sdk
echo "Publishing @cline/sdk@${VERSION} with tag '${CHANNEL}'..."
cd sdk/packages/sdk
TARBALL=$(bun pm pack --destination "$RUNNER_TEMP/sdk-npm-packs" --quiet)
npm publish "$RUNNER_TEMP/sdk-npm-packs/$(basename "$TARBALL")" --tag "$CHANNEL" --access public
- name: Create package tags for production publish
if: steps.check_commits.outputs.skip != 'true' && steps.channel.outputs.channel == 'latest'
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
VERSION="${{ steps.version.outputs.version }}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
@@ -250,9 +262,10 @@ jobs:
- name: Summary
if: steps.check_commits.outputs.skip != 'true'
env:
VERSION: ${{ steps.version.outputs.version }}
CHANNEL: ${{ steps.channel.outputs.channel }}
run: |
VERSION="${{ steps.version.outputs.version }}"
CHANNEL="${{ steps.channel.outputs.channel }}"
echo "Published SDK packages with tag '${CHANNEL}':"
echo " - @cline/shared@${VERSION}"
echo " - @cline/llms@${VERSION}"
+4 -4
View File
@@ -21,7 +21,7 @@ permissions:
defaults:
run:
working-directory: sdk
working-directory: .
jobs:
quality-checks:
@@ -96,12 +96,12 @@ jobs:
- name: Run SDK Tests (Windows)
if: ${{ !cancelled() && steps.build_sdk_step.outcome == 'success' && steps.build_cli_step.outcome == 'success' && matrix.os == 'windows-latest' }}
run: bun -F './packages/**' test
run: bun -F './sdk/packages/**' test
- name: Smoke test SQLite under Node
if: ${{ !cancelled() && steps.build_sdk_step.outcome == 'success' && matrix.os != 'windows-latest' }}
timeout-minutes: 10
run: bun scripts/ci-node-smoke.ts
run: bun sdk/scripts/ci-node-smoke.ts
- name: Run TUI e2e tests
if: ${{ !cancelled() && steps.build_sdk_step.outcome == 'success' && steps.build_cli_step.outcome == 'success' && matrix.os == 'ubuntu-latest' && matrix.node-version == '24.x' }}
@@ -109,4 +109,4 @@ jobs:
- name: Verify packages are publishable
if: ${{ !cancelled() && steps.build_sdk_step.outcome == 'success' && steps.build_cli_step.outcome == 'success' && matrix.os == 'ubuntu-latest' && matrix.node-version == '24.x' }}
run: bun scripts/check-publish.ts
run: bun sdk/scripts/check-publish.ts
+26 -5
View File
@@ -17,8 +17,8 @@ pnpm-lock.yaml
.actrc
CLAUDE.local.md
webview-ui/src/**/*.js
webview-ui/src/**/*.js.map
apps/vscode/webview-ui/src/**/*.js
apps/vscode/webview-ui/src/**/*.js.map
# Ignore coverage directories and files
coverage
@@ -35,9 +35,9 @@ coverage-unit
.worktrees
## Generated files ##
src/generated/
src/shared/proto/
webview-ui/src/services/grpc-client.ts
apps/vscode/src/generated/
apps/vscode/src/shared/proto/
apps/vscode/webview-ui/src/services/grpc-client.ts
*.tsbuildinfo
# E2E Tests
@@ -60,3 +60,24 @@ tests/**/cache
# Backup created by scripts/marketplace-readme.mjs while publishing.
# Should never be committed: only exists if a publish aborts mid-swap.
.README.github.bak
# Tauri generated code
apps/*/src-tauri/gen
apps/*/src-tauri/bin
apps/examples/*/src-tauri/gen
apps/examples/*/src-tauri/bin
# Tauri UI test snapshots
apps/*/src/tests/.tui-test
apps/*/src/tests/tui-traces
apps/vscode/webview-ui/src/**/*.js
apps/vscode/webview-ui/src/**/*.js.map
# SDK Session files / User data
.cline/data
.cline/tmp
*.db
*.db-shm
*.db-wal
.cline/**/managed.json
.cline/**/bundle.json
@@ -6,15 +6,18 @@
{
"id": "sdk-tool-handler-telemetry",
"rule": "Any new tool handler added to packages/agents/src or packages/core/src that performs a user-visible action (writes files, executes commands, modifies state, calls external APIs) must include a call to captureToolUsage() from packages/core/src/services/telemetry/core-events.ts, or emit a task.tool_used event via telemetry.capture(). Pure read-only helpers and getters are exempt. When in doubt, prefer instrumentation.",
"scope": ["packages/agents/src/**", "packages/core/src/**"],
"scope": [
"sdk/packages/agents/src/**",
"sdk/packages/core/src/**"
],
"severity": "high"
},
{
"id": "sdk-session-lifecycle-telemetry",
"rule": "New session start, end, or state-transition code paths in packages/core/src must call the appropriate typed helper from packages/core/src/services/telemetry/core-events.ts (captureTaskCreated, captureTaskCompleted, captureConversationTurnEvent, captureTokenUsage, etc.). Do not inline raw telemetry.capture() calls for session lifecycle events — always use the typed helper, which guarantees a consistent payload shape.",
"scope": [
"packages/core/src/cline-core/**",
"packages/core/src/runtime/**"
"sdk/packages/core/src/cline-core/**",
"sdk/packages/core/src/runtime/**"
],
"severity": "high"
},
@@ -22,8 +25,8 @@
"id": "sdk-no-raw-event-strings",
"rule": "All telemetry event name strings must be sourced from CORE_TELEMETRY_EVENTS in packages/core/src/services/telemetry/core-events.ts. If a PR introduces a string literal in a telemetry.capture(), telemetry.captureRequired(), or recordCounter()/recordHistogram()/recordGauge() call that does not reference CORE_TELEMETRY_EVENTS, flag it. New events must be added to CORE_TELEMETRY_EVENTS first, with a typed capture helper created alongside them.",
"scope": [
"packages/core/src/**",
"packages/agents/src/**",
"sdk/packages/core/src/**",
"sdk/packages/agents/src/**",
"apps/cli/src/**",
"apps/vscode/src/**"
],
@@ -32,13 +35,17 @@
{
"id": "sdk-auth-telemetry-completeness",
"rule": "Any new OAuth or authentication provider added under packages/core/src/auth must emit all four lifecycle events using the typed helpers from core-events.ts: captureAuthStarted (at flow entry), captureAuthSucceeded + identifyAccount (on token success), captureAuthFailed (on error), and captureAuthLoggedOut (on token invalidation or explicit logout). Flag PRs that introduce a new auth flow file without all four. Cross-reference packages/core/src/auth/cline.ts and packages/core/src/auth/codex.ts as canonical examples.",
"scope": ["packages/core/src/auth/**"],
"scope": [
"sdk/packages/core/src/auth/**"
],
"severity": "high"
},
{
"id": "sdk-telemetry-doc-update",
"rule": "Any PR that adds new event constants to CORE_TELEMETRY_EVENTS in packages/core/src/services/telemetry/core-events.ts, adds new capture* helper functions, or changes the payload shape of an existing event must update the Event Catalog section in DOC.md. Flag PRs that modify core-events.ts without a corresponding change to DOC.md.",
"scope": ["packages/core/src/services/telemetry/core-events.ts"],
"scope": [
"sdk/packages/core/src/services/telemetry/core-events.ts"
],
"severity": "medium"
}
]
@@ -1,19 +1,19 @@
{
"files": [
{
"path": "packages/core/src/services/telemetry/core-events.ts",
"path": "sdk/packages/core/src/services/telemetry/core-events.ts",
"description": "Single source of truth for all telemetry event names (CORE_TELEMETRY_EVENTS) and their typed capture helper functions. Every PR touching telemetry must be evaluated against this catalog. New events must be defined here first."
},
{
"path": "packages/shared/src/services/telemetry.ts",
"path": "sdk/packages/shared/src/services/telemetry.ts",
"description": "ITelemetryService interface definition. Defines the contract all telemetry implementations must satisfy (capture, captureRequired, recordCounter, recordHistogram, recordGauge, flush, dispose)."
},
{
"path": "packages/core/src/services/telemetry/TelemetryService.ts",
"path": "sdk/packages/core/src/services/telemetry/TelemetryService.ts",
"description": "Reference implementation of ITelemetryService used by all hosts. Multi-adapter fan-out service that forwards events to OpenTelemetry."
},
{
"path": "packages/core/src/services/telemetry/OpenTelemetryProvider.ts",
"path": "sdk/packages/core/src/services/telemetry/OpenTelemetryProvider.ts",
"description": "OpenTelemetry-backed provider that wires logs/metrics/traces exporters. Contains createConfiguredTelemetryService and createConfiguredTelemetryHandle, the canonical factories every host should use."
},
{
@@ -21,11 +21,11 @@
"description": "Public API and event documentation. The Event Catalog and 'Activation funnel' sections must be kept in sync with core-events.ts. Host integration rules (CLI dir ordering, hub daemon metadata forwarding) are documented here."
},
{
"path": "ARCHITECTURE.md",
"path": "sdk/ARCHITECTURE.md",
"description": "Architecture reference. Telemetry design decisions, completion semantics (submit_and_exit anchoring), and hub-daemon telemetry forwarding are documented here. Use as ground truth for design intent."
},
{
"path": "AGENTS.md",
"path": "sdk/AGENTS.md",
"description": "Package boundary rules. Telemetry runtime services live in @cline/core; @cline/agents must not own stateful telemetry. Use to evaluate whether a telemetry change is being made in the correct package."
}
]
@@ -10,9 +10,9 @@ The SDK uses OpenTelemetry (OTEL) as its sole telemetry transport. Events flow t
```
core-events.ts (event catalog + typed helpers)
ITelemetryService (packages/shared) ← interface contract
ITelemetryService (sdk/packages/shared) ← interface contract
TelemetryService (packages/core) ← multi-adapter fan-out
TelemetryService (sdk/packages/core) ← multi-adapter fan-out
OpenTelemetryAdapter → OpenTelemetryProvider ← OTLP transport
@@ -24,7 +24,7 @@ parallel-but-independent stacks; this `.greptile/` config covers only the SDK.
## The Single Source of Truth
`packages/core/src/services/telemetry/core-events.ts` is the single source of truth for all
`sdk/packages/core/src/services/telemetry/core-events.ts` is the single source of truth for all
event names. It exports:
- `CORE_TELEMETRY_EVENTS` — a frozen const object grouped by family
@@ -60,8 +60,8 @@ Emission ownership:
emitter in `prepareLocalRuntimeBootstrap`. Hosts must NOT re-emit these.
- `workspace.path_resolved`: emitted from default tool executors **only when**
`WorkspaceManager` exposes more than one root.
- `task.*`: emitted by core session lifecycle code in `packages/core/src/cline-core/` and
`packages/core/src/runtime/`. Hosts must not duplicate this emission.
- `task.*`: emitted by core session lifecycle code in `sdk/packages/core/src/cline-core/` and
`sdk/packages/core/src/runtime/`. Hosts must not duplicate this emission.
## `task.completed` Semantics
@@ -105,7 +105,7 @@ forwarding, hub-backed sessions silently drop their lifecycle telemetry.
## Auth Lifecycle Completeness
Every authentication provider in `packages/core/src/auth/` must emit all four auth lifecycle
Every authentication provider in `sdk/packages/core/src/auth/` must emit all four auth lifecycle
events using the typed helpers:
| Phase | Helper | Where it fires |
@@ -115,7 +115,7 @@ events using the typed helpers:
| Token error | `captureAuthFailed(provider, errorMessage)` | In the catch block |
| Token invalidation | `captureAuthLoggedOut(provider, reason)` | On invalid_grant or explicit logout |
Cross-reference `packages/core/src/auth/cline.ts` and `packages/core/src/auth/codex.ts` as
Cross-reference `sdk/packages/core/src/auth/cline.ts` and `sdk/packages/core/src/auth/codex.ts` as
canonical examples of all four phases.
## Single Telemetry Service Per Host
+9
View File
@@ -1 +1,10 @@
if ! command -v gitleaks >/dev/null 2>&1; then
echo "gitleaks is required for the pre-commit secret scan."
echo "Install it with: brew install gitleaks"
echo "Other install options: https://github.com/gitleaks/gitleaks#installing"
exit 1
fi
gitleaks git --pre-commit --redact --staged --verbose || exit 1
lint-staged
+1 -1
View File
@@ -1 +1 @@
lts/*
22
+2 -1
View File
@@ -1,2 +1,3 @@
node 22
bun 1.3.13
node 22
+2 -1
View File
@@ -5,6 +5,7 @@
"connor4312.esbuild-problem-matchers",
"ms-vscode.extension-test-runner",
"bradlc.vscode-tailwindcss",
"biomejs.biome"
"biomejs.biome",
"oven.bun-vscode"
]
}
+144 -31
View File
@@ -10,23 +10,23 @@
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionDevelopmentPath=${workspaceFolder}/apps/vscode",
"--disable-workspace-trust",
"--disable-extension",
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
"--disable-extension",
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
"${workspaceFolder}",
"${workspaceFolder}/apps/vscode",
"--disable-extensions"
],
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
"${workspaceFolder}/apps/vscode/dist/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}",
"envFile": "${workspaceFolder}/.env",
"envFile": "${workspaceFolder}/apps/vscode/.env",
"env": {
"IS_DEV": "true",
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}",
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}/apps/vscode",
"CLINE_ENVIRONMENT": "production"
}
},
@@ -35,22 +35,22 @@
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionDevelopmentPath=${workspaceFolder}/apps/vscode",
"--disable-workspace-trust",
"--disable-extension",
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
"--disable-extension",
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
"${workspaceFolder}"
"${workspaceFolder}/apps/vscode"
],
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
"${workspaceFolder}/apps/vscode/dist/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}",
"envFile": "${workspaceFolder}/.env",
"envFile": "${workspaceFolder}/apps/vscode/.env",
"env": {
"IS_DEV": "true",
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}",
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}/apps/vscode",
"CLINE_ENVIRONMENT": "staging"
}
},
@@ -59,22 +59,22 @@
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionDevelopmentPath=${workspaceFolder}/apps/vscode",
"--disable-workspace-trust",
"--disable-extension",
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
"--disable-extension",
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
"${workspaceFolder}"
"${workspaceFolder}/apps/vscode"
],
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
"${workspaceFolder}/apps/vscode/dist/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}",
"envFile": "${workspaceFolder}/.env",
"envFile": "${workspaceFolder}/apps/vscode/.env",
"env": {
"IS_DEV": "true",
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}",
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}/apps/vscode",
"CLINE_ENVIRONMENT": "local"
}
},
@@ -84,27 +84,27 @@
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--user-data-dir=${workspaceFolder}/dist/tmp/user",
"--user-data-dir=${workspaceFolder}/apps/vscode/dist/tmp/user",
"--profile-temp",
"--sync=off",
"--disable-extension",
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
"--disable-extension",
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
"--extensionDevelopmentPath=${workspaceFolder}",
"${workspaceFolder}"
"--extensionDevelopmentPath=${workspaceFolder}/apps/vscode",
"${workspaceFolder}/apps/vscode"
],
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
"${workspaceFolder}/apps/vscode/dist/**/*.js"
],
"preLaunchTask": "clean-tmp-user",
"internalConsoleOptions": "openOnSessionStart",
"postDebugTask": "stop",
"envFile": "${workspaceFolder}/.env",
"envFile": "${workspaceFolder}/apps/vscode/.env",
"env": {
"IS_DEV": "true",
"TEMP_PROFILE": "true",
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}",
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}/apps/vscode",
"CLINE_ENVIRONMENT": "production"
}
},
@@ -117,13 +117,13 @@
],
"sourceMaps": true,
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"${workspaceFolder}/apps/vscode/**",
"!**/node_modules/**"
],
"cwd": "${workspaceFolder}",
"cwd": "${workspaceFolder}/apps/vscode",
"outFiles": [
"${workspaceFolder}/dist/**/*.js",
"${workspaceFolder}/dist-standalone/**/*.js"
"${workspaceFolder}/apps/vscode/dist/**/*.js",
"${workspaceFolder}/apps/vscode/dist-standalone/**/*.js"
],
"preLaunchTask": "compile-standalone",
"runtimeExecutable": "npx",
@@ -131,11 +131,11 @@
"tsx"
],
"program": "scripts/test-standalone-core-api-server.ts",
"envFile": "${workspaceFolder}/.env",
"envFile": "${workspaceFolder}/apps/vscode/.env",
"env": {
"PROTOBUS_PORT": "26040",
"HOSTBRIDGE_PORT": "26041",
"WORKSPACE_DIR": "${workspaceFolder}",
"WORKSPACE_DIR": "${workspaceFolder}/apps/vscode",
"E2E_TEST": "true",
"CLINE_ENVIRONMENT": "local"
},
@@ -151,10 +151,10 @@
],
"sourceMaps": true,
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"${workspaceFolder}/apps/vscode/**",
"!**/node_modules/**"
],
"cwd": "${workspaceFolder}",
"cwd": "${workspaceFolder}/apps/vscode",
"runtimeExecutable": "npx",
"runtimeArgs": [
"mocha"
@@ -169,7 +169,7 @@
"--exit",
"${file}"
],
"envFile": "${workspaceFolder}/.env",
"envFile": "${workspaceFolder}/apps/vscode/.env",
"env": {
"TS_NODE_PROJECT": "./tsconfig.unit-test.json",
"NODE_ENV": "test",
@@ -188,7 +188,7 @@
"run",
"storybook"
],
"cwd": "${workspaceFolder}/webview-ui",
"cwd": "${workspaceFolder}/apps/vscode/webview-ui",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"serverReadyAction": {
@@ -199,6 +199,119 @@
"env": {
"IS_DEV": "true"
}
},
{
"name": "Launch Bun CLI (Prompt)",
"type": "bun",
"request": "launch",
"cwd": "${workspaceFolder}/sdk/apps/cli",
"runtime": "bun",
"runtimeArgs": [
"--conditions=development"
],
"program": "${workspaceFolder}/sdk/apps/cli/src/index.ts",
"args": [
"${input:cliPrompt}"
],
"env": {
"CLINE_BUILD_ENV": "development"
},
"presentation": {
"hidden": true
}
},
{
"name": "Launch RPC Server",
"type": "bun",
"request": "launch",
"cwd": "${workspaceFolder}/sdk/apps/cli",
"runtime": "bun",
"runtimeArgs": [
"--conditions=development"
],
"program": "${workspaceFolder}/sdk/apps/cli/src/index.ts",
"args": [
"rpc",
"start"
],
"env": {
"CLINE_BUILD_ENV": "development",
"CLINE_DEBUG_PORT_BASE": "9230"
},
"presentation": {
"hidden": true
}
},
{
"name": "Attach RPC Runtime (9230)",
"type": "bun",
"request": "attach",
"url": "ws://127.0.0.1:9230",
"localRoot": "${workspaceFolder}/sdk",
"remoteRoot": "${workspaceFolder}/sdk",
"presentation": {
"hidden": true
}
},
{
"name": "Attach Hook Worker (9231)",
"type": "bun",
"request": "attach",
"url": "ws://127.0.0.1:9231",
"localRoot": "${workspaceFolder}/sdk",
"remoteRoot": "${workspaceFolder}/sdk",
"presentation": {
"hidden": true
}
},
{
"name": "Attach Plugin Sandbox (9232)",
"type": "bun",
"request": "attach",
"url": "ws://127.0.0.1:9232",
"localRoot": "${workspaceFolder}/sdk",
"remoteRoot": "${workspaceFolder}/sdk",
"presentation": {
"hidden": true
}
},
{
"name": "Attach Connector Child (9233)",
"type": "bun",
"request": "attach",
"url": "ws://127.0.0.1:9233",
"localRoot": "${workspaceFolder}/sdk",
"remoteRoot": "${workspaceFolder}/sdk",
"presentation": {
"hidden": true
}
}
],
"compounds": [
{
"name": "Launch RPC Server Debugger",
"configurations": [
"Launch RPC Server",
"Attach RPC Runtime (9230)"
]
},
{
"name": "Launch CLI Debugger",
"configurations": [
"Launch Bun CLI (Prompt)",
"Attach RPC Runtime (9230)",
"Attach Hook Worker (9231)",
"Attach Plugin Sandbox (9232)",
"Attach Connector Child (9233)"
]
}
],
"inputs": [
{
"id": "cliPrompt",
"type": "promptString",
"description": "Prompt to send to the CLI",
"default": "hey"
}
]
}
+2 -1
View File
@@ -1,5 +1,6 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.insertFinalNewline": true,
"files.exclude": {
"out": false, // set this to true to hide the "out" folder with the compiled JS files
"dist": false // set this to true to hide the "dist" folder with the compiled JS files
@@ -16,7 +17,7 @@
// Protobuf settings
"protoc": {
"options": [
"--proto_path=proto"
"--proto_path=apps/vscode/proto"
]
},
// Enable Lint and format using Biome
+50 -22
View File
@@ -5,24 +5,28 @@
"tasks": [
{
"label": "compile-standalone",
"type": "npm",
"script": "compile-standalone",
"type": "shell",
"command": "npm run compile-standalone",
"group": "build",
"problemMatcher": [],
"presentation": {
"reveal": "always"
},
"options": {
"cwd": "${workspaceFolder}/apps/vscode"
}
},
{
"label": "npm: protos",
"type": "npm",
"script": "protos",
"type": "shell",
"command": "npm run protos",
"problemMatcher": [],
"isBackground": false,
"presentation": {
"reveal": "always"
},
"options": {
"cwd": "${workspaceFolder}/apps/vscode",
"env": {
"IS_DEV": "true"
}
@@ -60,8 +64,8 @@
"group": "build"
},
{
"type": "npm",
"script": "build:webview",
"type": "shell",
"command": "npm run build:webview",
"group": "build",
"problemMatcher": [],
"isBackground": true,
@@ -74,14 +78,15 @@
"reveal": "always"
},
"options": {
"cwd": "${workspaceFolder}/apps/vscode",
"env": {
"IS_DEV": "true"
}
}
},
{
"type": "npm",
"script": "build:webview:test",
"type": "shell",
"command": "npm run build:webview:test",
"group": "build",
"problemMatcher": [],
"isBackground": true,
@@ -94,6 +99,7 @@
"reveal": "always"
},
"options": {
"cwd": "${workspaceFolder}/apps/vscode",
"env": {
"IS_DEV": "true",
"IS_TEST": "true"
@@ -101,8 +107,8 @@
}
},
{
"type": "npm",
"script": "dev:webview",
"type": "shell",
"command": "npm run dev:webview",
"group": "build",
"problemMatcher": [
{
@@ -131,14 +137,15 @@
"reveal": "always"
},
"options": {
"cwd": "${workspaceFolder}/apps/vscode",
"env": {
"IS_DEV": "true"
}
}
},
{
"type": "npm",
"script": "watch:esbuild",
"type": "shell",
"command": "npm run watch:esbuild",
"group": "build",
"problemMatcher": {
"pattern": [
@@ -169,14 +176,15 @@
"reveal": "always"
},
"options": {
"cwd": "${workspaceFolder}/apps/vscode",
"env": {
"IS_DEV": "true"
}
}
},
{
"type": "npm",
"script": "watch:esbuild:test",
"type": "shell",
"command": "npm run watch:esbuild:test",
"group": "build",
"problemMatcher": {
"pattern": [
@@ -207,6 +215,7 @@
"reveal": "always"
},
"options": {
"cwd": "${workspaceFolder}/apps/vscode",
"env": {
"IS_DEV": "true",
"IS_TEST": "true"
@@ -214,8 +223,8 @@
}
},
{
"type": "npm",
"script": "watch:tsc",
"type": "shell",
"command": "npm run watch:tsc",
"group": "build",
"problemMatcher": "$tsc-watch",
"isBackground": true,
@@ -226,11 +235,15 @@
"presentation": {
"group": "watch",
"reveal": "always"
},
"options": {
"cwd": "${workspaceFolder}/apps/vscode"
}
},
{
"type": "npm",
"script": "watch-tests",
"type": "shell",
"command": "npm run watch-tests",
"label": "npm: watch-tests",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"dependsOn": [
@@ -240,7 +253,10 @@
"reveal": "always",
"group": "watchers"
},
"group": "build"
"group": "build",
"options": {
"cwd": "${workspaceFolder}/apps/vscode"
}
},
{
"label": "tasks: watch-tests",
@@ -262,11 +278,11 @@
"dependsOn": [
"watch"
],
"command": "rm -rf ${workspaceFolder}/dist/tmp/user && mkdir -p ${workspaceFolder}/dist/tmp/user"
"command": "rm -rf ${workspaceFolder}/apps/vscode/dist/tmp/user && mkdir -p ${workspaceFolder}/apps/vscode/dist/tmp/user"
},
{
"type": "npm",
"script": "storybook",
"type": "shell",
"command": "npm run storybook",
"group": "build",
"problemMatcher": [],
"isBackground": false,
@@ -279,10 +295,22 @@
"reveal": "always"
},
"options": {
"cwd": "${workspaceFolder}/apps/vscode",
"env": {
"IS_DEV": "true"
}
}
},
{
"label": "build-sdk",
"type": "shell",
"command": "bun run build:sdk",
"problemMatcher": [
"$tsc"
],
"options": {
"cwd": "${workspaceFolder}"
}
}
],
"inputs": [
+123
View File
@@ -1,5 +1,128 @@
# Changelog
## [3.89.2]
### Fixed
- Complete the fix for the Anthropic provider on VS Code 1.123 and later by upgrading the bundled Anthropic SDK to a release compatible with the Node 24 runtime.
- Update the Vertex AI provider to a compatible Anthropic Vertex SDK release so it works with the upgraded Anthropic SDK.
## [3.89.1]
### Fixed
- Restore the Anthropic provider on VS Code 1.123 and later, where the updated Node 24 runtime broke the bundled Anthropic SDK.
- Handle the DeepSeek V4 reasoning format.
## [3.89.0]
### Added
- Add Claude Fable 5 model support.
### Fixed
- Fix MiniMax M3 thinking controls across gateways.
### Changed
- Clean up the Codex model list.
## [3.88.1]
### Added
- Add a debug section in settings for Cline testers.
### Fixed
- Include the walkthrough markdown files in the VS Code extension package so the first-run walkthrough steps load correctly.
## [3.88.0]
### Added
- Add the latest Fireworks AI serverless models and update the default Fireworks model to Kimi K2.6.
### Fixed
- Fix MCP server delete/add flows so settings writes do not cause the MCP server list to be emptied by the file watcher.
- Remove stale Fireworks AI models and correct Fireworks model metadata and cache pricing.
### Changed
- Always use the upstream Cline recommended models endpoint instead of gating it behind a feature flag.
## [3.87.0]
### Added
- Add MiniMax M3 model support.
### Fixed
- Update VS Code extension dependencies to resolve security issues in `@xmldom/xmldom`, `basic-ftp`, `axios`, `undici`, and other direct/transitive packages.
## [3.86.2]
### Fixed
- Fix `@` file mentions and workspace file search on VS Code 1.122+ by resolving the new bundled `@vscode/ripgrep-universal` per-platform binary layout before falling back to legacy ripgrep paths.
## [3.86.1]
### Fixed
- Fix `@` file mentions failing to find files in some environments (notably VS Code Remote SSH, and after certain VS Code updates) by keeping the file-search fallback alive when the workspace index or bundled ripgrep binary is unavailable.
## [3.86.0]
### Added
- Add Claude Opus 4.8 provider support, including 1M-context variants where available.
- Add Moonshot Kimi K2.6 model support.
### Fixed
- Show prompt-cache support for Qwen 3.7 Max in the Cline provider.
- Fix the VS Code nightly publish workflow startup permissions.
### Changed
- Move the VS Code extension project into `apps/vscode`.
## [3.85.0]
### Added
- Add GPT-5.5 support to SAP AI Core.
- Add DeepSeek V4 Flash and Pro models.
- Add Gemini 3.5 Flash to Gemini and Vertex providers.
- Add `/lg-task` URI webhook integration for LG dashboard flows.
### Fixed
- Fix Vertex AI global endpoint handling for Claude models.
- Route Poolside Laguna models through next-gen prompts and native tool calling.
### Changed
- Update `diff` and `protobufjs` dependencies.
## [3.84.0]
### Added
- Add SAP AI Core support for additional hosted models
### Fixed
- Disable the MCP "Restart Server" button when a server is toggled off.
### Changed
- Remove the Cline Kanban launch modal and bundled demo media from the VS Code extension startup flow.
## [3.83.0]
### Fixed
+3 -2
View File
@@ -45,7 +45,7 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
3. Install [bun](https://bun.com)
4. Install the necessary dependencies for the extension and webview-gui:
```bash
npm run install:all
cd apps/vscode && npm run install:all && cd ../..
cd sdk && bun run build && cd ..
```
5. Generate Protocol Buffer files (required before first build):
@@ -61,7 +61,7 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
2. Push your branch and create a PR on GitHub. Our CI will:
- Run tests and checks
3. Testing
- Run `npm run test` to run tests locally.
- Run `cd apps/vscode && npm run test` to run tests locally.
- Before submitting PR, run `npm run format:fix` to format your code
### Extension
@@ -73,6 +73,7 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
- If you dismissed the prompts, you can install them manually from the Extensions panel
2. **Local Development**
- cd into the vscode extension, `cd apps/vscode`
- Run `npm run install:all` to install dependencies
- Run `npm run protos` to generate Protocol Buffer files (required before first build)
- Run `npm run test` to run tests locally
+8 -4
View File
@@ -51,7 +51,7 @@ for CI/CD and scripting.
npm i -g cline
```
<a href="./sdk/apps/cli/README.md">Learn more</a>
<a href="./apps/cli/README.md">Learn more</a>
<br><br>
</td>
@@ -129,7 +129,7 @@ npm install @cline/sdk
| Product | Description | Location | CHANGELOG |
|---------|------------|--------------|--------------|
| **SDK** | Node.js programmatic agent API and extension exports. | [`sdk/`](https://github.com/cline/cline/tree/main/sdk) | [CHANGELOG.md](https://github.com/cline/cline/blob/main/sdk/CHANGELOG.md) |
| **CLI** | Terminal UI, headless mode, shell commands, and CLI-specific flows. | [`sdk/apps/cli/`](https://github.com/cline/cline/tree/main/sdk/apps/cli) | [CHANGELOG.md](https://github.com/cline/cline/blob/main/sdk/apps/cli/CHANGELOG.md) |
| **CLI** | Terminal UI, headless mode, shell commands, and CLI-specific flows. | [`apps/cli/`](https://github.com/cline/cline/tree/main/apps/cli) | [CHANGELOG.md](https://github.com/cline/cline/blob/main/apps/cli/CHANGELOG.md) |
| **VS Code Extension** | The Marketplace extension and extension host integration. | [`/`](https://github.com/cline/cline/tree/main) (WIP migrating) | [CHANGELOG.md](https://github.com/cline/cline/blob/main/CHANGELOG.md) |
| **JetBrains Plugin** | JetBrains-hosted client that talks to the shared agent core. | Currently we are not open-sourcing JetBrains plugins | - |
| **Kanban** | Web-based multi-agent task board. | [`cline/kanban`](https://github.com/cline/kanban) | [CHANGELOG.md](https://github.com/cline/kanban/blob/main/CHANGELOG.md) |
@@ -212,8 +212,12 @@ cline schedule create "PR summary" \
Chat with your agent from any messaging platform: Telegram, Slack, Discord, Google Chat, WhatsApp, and Linear. Each conversation thread maps to an agent session with full context. Set up access control to restrict who can interact with your agent.
```bash
cline connect telegram -m my_bot -k $BOT_TOKEN
cline connect slack --token $SLACK_TOKEN --signing-secret $SECRET --base-url $URL
# Connect to Telegram
cline connect telegram -k $BOT_TOKEN
# Connect to Slack through webhook
cline connect slack --bot-token $SLACK_TOKEN --signing-secret $SECRET --base-url $URL
# Connect to Slack using socket mode
cline connect slack --bot-token $SLACK_TOKEN --app-token $SLACK_APP_TOKEN
```
## Headless CLI for CI/CD
+14
View File
@@ -0,0 +1,14 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.5/schema.json",
"root": false,
"extends": [
"../sdk/biome.json"
],
"linter": {
"rules": {
"a11y": {
"noStaticElementInteractions": "warn"
}
}
}
}
@@ -1,5 +1,134 @@
# Cline CLI Changelog
## 3.0.23
- Fixed Vertex AI GCP settings configuration
- Fixed the Azure Foundry API version
- Added support for configured agents as subagent tools
- Centralized OAuth management into the SDK
- Fixed an error caused by disabled reasoning on Fable 5
## 3.0.22
- Added support for the Claude Fable 5 model
- Fixed MiniMax M3 thinking controls so they route correctly across gateways
## 3.0.21
- Added a global auto-update setting that controls automatic updates on CLI startup
- Added a Cline credits refill link
- Fixed scrolling for inline ask-question responses
- Fixed connector thread session routing and stale hub session handling
- Added support for Vertex AI Application Default Credentials (ADC) with tool use
- Fixed empty message content replay for Bedrock
- Cleaned up the OpenAI Codex model list
## 3.0.20
- Installed plugin wrappers are now named from their source (npm package name, git repo, remote filename, official slug, or local directory) instead of an opaque hash, making installed plugins easier to identify.
## 3.0.19
- Fixed CLI auto-update to use `npm update` so updates apply reliably, while preserving the installed release channel (e.g. nightly).
## 3.0.18
- Fix Slack channel mentions so replies post in the original message's thread.
- Fix the abort indicator to clear immediately when a task is cancelled.
- Sync the Fireworks AI model registry and refresh the bundled model catalog with current platform offerings.
- Bump the bundled SDK to v0.0.43, which forces a running Cline Hub to restart so it picks up the latest SDK code.
## 3.0.17
- Fix a regression introduced in 3.0.15 where the interactive CLI could get stuck after stopping and restarting Cline Hub and then pressing Escape to cancel a request. The CLI now detects stale or missing sessions, recovers any pending messages, and starts a fresh session instead of failing with "session not found".
- Fix Ctrl+C and Hub shutdown races that surfaced as "hook dispatch failed" and WebSocket connection errors from late hook events racing against Hub shutdown.
- Fix the Hub daemon being shut down prematurely when a runtime request was aborted, so the daemon now stays alive.
- Improve the Telegram connector with a new `--allowed-user-id` flag to restrict which Telegram users are authorized to interact with the agent.
## 3.0.16
- Install official Cline plugins by slug off the new github.com/cline/plugins collection.
- Uninstall plugins using `cline plugin uninstall <plugin>` or in the TUI.
- Plugins can now bundle skills, and plugin skills are grouped together in settings.
- Add Slack socket mode support.
- Allow a custom base URL for Anthropic vendor-type providers.
- Fix OAuth token migration for users signed in through the old extension.
- Use a union schema for read-files tool input validation.
- Add a `CLINE_PLUGIN_IMPORT_TIMEOUT_MS` env override to control the plugin import timeout.
## 3.0.15
- Add Cline Hub, a web app for monitoring connected clients, viewing and driving sessions, streaming assistant output, and restarting the local hub, with local, LAN, and tunnel usage gated by a room secret.
- Support global AGENTS rules so agent rules can be applied across all sessions, not just per-project.
- Let plugins contribute static or dynamic rule content when installed in the sandbox.
- Bind Discord sessions to individual message authors so different Discord users no longer share chat state in a thread.
- Support participant mute targets in Discord: resolve `/mute` and `/unmute` from user mentions or raw user IDs to mute a specific participant in a thread.
- Make OAuth URLs clickable in the TUI.
- Refresh the bundled model catalog, adding Claude Opus 4.8, Moonshot Kimi K2.6, and Qwen3.7 Max (with cache support).
- Discover SDK skill directories that are symlinked, including handling circular symlinks.
- Steer active connector sessions across turn keys by matching on session ID, so replies continue the existing session instead of starting a duplicate.
- Stop the Discord connector after repeated identical errors (per thread, within a time window) to prevent error messages from flooding a channel.
- Fix Discord connector registration and reply fallback handling.
- Fix SAP AI Core to use the AI SDK community provider.
- Log ACP output as diagnostics instead of errors so normal output no longer appears as errors.
## 3.0.14
- Fix OTEL telemetry variable bundling so telemetry is correctly enabled in compiled CLI builds: guard against environments where `process.env` is undefined and remove optional chaining so bundlers can inline the values at build time.
## 3.0.13
- Show a loading dialog while resuming a session from history so the TUI no longer appears frozen during the load.
- Speed up the `/clear` command by deferring new session creation until you send the next prompt, so clearing no longer blocks on spinning up an empty session.
## 3.0.12
- Show a loading dialog while the config screen switches provider or model so the transition no longer looks frozen.
- Render the ask question tool prompt inline with the conversation so the question and suggested answers stay attached to the assistant turn that asked them, instead of appearing in a separate modal.
- Allow manual `cline update` runs to install the latest published version immediately, bypassing the release age gate that delays automatic updates.
- Refresh the bundled SDK to 0.0.42, updating the model catalog.
## 3.0.11
- Fix a regression in the ChatGPT OAuth provider where requests failed with `max_output_tokens not supported`, by restoring the full output token budget instead of applying an implicit cap.
- Hide the `Space toggle` hint in the config footer when the highlighted row is not toggleable (rules, agents, hooks).
- Authenticate Vertex Gemini through Google auth when `gcp.projectId` is configured, and surface the full Vertex model list instead of only Claude models.
- Include tool names in tool result content blocks so message logs and session history consistently track which tool produced each result.
## 3.0.10
- Install plugins from `file://` URLs in addition to npm and git sources.
- Show Ollama API key note in TUI settings so users know when to provide an API key.
- Keep interactive sessions alive when idle or awaiting approval instead of treating them as ended, and stop reading message files for every session when `hydrate: false`.
- Add Poolside as a provider.
- Add Gemini 3.5 Flash to the Gemini provider model list.
- Auto-detect Telegram bot username from the bot token so the Telegram connector no longer requires it to be configured separately.
- Notify connectors when a scheduled execution fails, not just when it succeeds.
- Bake OTEL telemetry variables into the CLI at build time so telemetry works in nightly and production builds.
- Preserve model output token limits from the SDK model catalog so context window math matches the upstream provider.
- Soften the visual treatment of rejected tool calls in the TUI.
- Hide the skills tool from the system prompt when skills are disabled, and refresh slash commands after toggling a skill.
- Restore AWS Bedrock profile-based auth during legacy config migration so profiles set via `awsAuthentication: "profile"` are preserved without `awsUseProfile`.
- Cache global settings reads keyed by file mtime so repeated reads skip the JSON parse and zod validation on the hot path.
## 3.0.9
- Speed up CLI startup with plugins by loading sandboxed plugins concurrently and caching plugin tool descriptors per plugin, provider, and model.
- Speed up plugin and tool config toggles by updating the TUI optimistically and persisting changes without reloading the full config or reimporting plugins.
- Restore fuzzy ranking for the @-mention file picker so the most relevant files appear first.
- Keep the interactive CLI session alive after cancelling a task instead of tearing the session down.
- Accept dash-prefixed prompts when passed after `--`, so prompts starting with `-` are no longer parsed as flags.
- Recover from hub abort cleanup failures so a cancel that hits an error no longer crashes the runtime host.
- Route GLM thinking through provider metadata so thinking-enabled GLM models behave correctly through the gateway.
## 3.0.8
- Use Telegram numeric participant ids so renamed users stay linked to the same participant in the Telegram connector.
- Keep failed plugins visible in the config UI with their load/setup phase and error details so broken plugin definitions are easier to diagnose.
- Move the Create Session Fork shortcut from Opt+F to Opt+R so terminal word-right navigation works again.
- Fix AWS Bedrock region and profile detection in the CLI onboarding, and surface bearer-token and additional Bedrock config fields in the provider config screens.
- Fix inflated token usage counts caused by AgentRuntime.execute() not resetting usage between calls, which the local runtime host was then double-counting on top of the session baseline.
## 3.0.7
- Skip the ChatGPT OAuth model refresh on session startup so the CLI launches without the extra network round-trip.
@@ -416,7 +416,7 @@ Then attach VS Code or Chrome DevTools to `ws://127.0.0.1:6499`.
## Publishing
The CLI is published as the `cline` wrapper package on npm with platform-specific binaries under `@cline/cli-*`. The release flow lives in the `publish-cli` skill (`sdk/apps/cli/.cline/skills/publish-cli/SKILL.md`).
The CLI is published as the `cline` wrapper package on npm with platform-specific binaries under `@cline/cli-*`. The release flow lives in the `publish-cli` skill (`.cline/skills/publish-cli/SKILL.md` at the repo root).
From the `apps/cli` workspace:
@@ -169,11 +169,14 @@ Bridge a chat surface into RPC-backed Cline sessions. Each conversation thread m
```sh
# Telegram (polling mode)
cline connect telegram -m my_bot -k 123456:ABCDEF...
cline connect telegram -k 123456:ABCDEF...
# Slack (webhook mode)
cline connect slack --bot-token $SLACK_BOT_TOKEN --signing-secret $SLACK_SIGNING_SECRET --base-url https://your-domain.com
# Slack (socket mode)
cline connect slack --bot-token $SLACK_BOT_TOKEN --app-token $SLACK_APP_TOKEN
# Google Chat (webhook mode)
cline connect gchat --base-url https://your-domain.com
+60 -3
View File
@@ -1,12 +1,65 @@
import { copyFileSync, mkdirSync } from "node:fs";
import {
copyFileSync,
cpSync,
existsSync,
mkdirSync,
readdirSync,
statSync,
} from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { $ } from "bun";
function defineProcessEnv(name: string): string {
return JSON.stringify(process.env[name] ?? "");
}
const sourcemap = Bun.env.CLINE_SOURCEMAPS === "1" ? "linked" : "none";
const rootDir = dirname(fileURLToPath(import.meta.url));
const repoRoot = join(rootDir, "../../");
const hubWebviewSourcePath = join(repoRoot, "apps/cline-hub/src/webview");
const hubWebviewDistPath = join(repoRoot, "apps/cline-hub/dist/webview");
const hubWebviewIndexPath = join(hubWebviewDistPath, "index.html");
const cliHubWebviewDistPath = join(rootDir, "dist/cline-hub/webview");
function newestFileMtimeMs(dir: string): number {
let newest = 0;
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (
entry.name === "node_modules" ||
entry.name === "dist" ||
entry.name === ".turbo"
) {
continue;
}
const path = join(dir, entry.name);
if (entry.isDirectory()) {
newest = Math.max(newest, newestFileMtimeMs(path));
} else if (entry.isFile()) {
newest = Math.max(newest, statSync(path).mtimeMs);
}
}
return newest;
}
function shouldBuildHubWebview(): boolean {
if (!existsSync(hubWebviewIndexPath)) {
return true;
}
try {
return (
newestFileMtimeMs(hubWebviewSourcePath) >
statSync(hubWebviewIndexPath).mtimeMs
);
} catch {
return true;
}
}
if (shouldBuildHubWebview()) {
console.log("Building Cline Hub webview...");
await $`bun -F @cline/cline-hub build:webview`.cwd(repoRoot);
}
const result = await Bun.build({
entrypoints: ["./src/index.ts"],
@@ -63,10 +116,9 @@ if (result.logs.length > 0) {
}
}
const rootDir = dirname(fileURLToPath(import.meta.url));
const coreBootstrapPath = join(
rootDir,
"../../packages/core/dist/extensions/plugin-sandbox-bootstrap.js",
"../../sdk/packages/core/dist/extensions/plugin-sandbox-bootstrap.js",
);
const cliBootstrapPath = join(
rootDir,
@@ -74,3 +126,8 @@ const cliBootstrapPath = join(
);
mkdirSync(dirname(cliBootstrapPath), { recursive: true });
copyFileSync(coreBootstrapPath, cliBootstrapPath);
if (existsSync(hubWebviewDistPath)) {
mkdirSync(dirname(cliHubWebviewDistPath), { recursive: true });
cpSync(hubWebviewDistPath, cliHubWebviewDistPath, { recursive: true });
}
@@ -1,7 +1,7 @@
{
"name": "@cline/cli",
"displayName": "cline",
"version": "3.0.7",
"version": "3.0.23",
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
"type": "module",
"publishConfig": {
@@ -10,7 +10,7 @@
"repository": {
"type": "git",
"url": "git+https://github.com/cline/cline.git",
"directory": "sdk/apps/cli"
"directory": "apps/cli"
},
"keywords": [
"cline",
@@ -68,33 +68,38 @@
},
"dependencies": {
"@agentclientprotocol/sdk": "^0.16.1",
"@clack/prompts": "^1.2.0",
"@chat-adapter/discord": "^4.23.0",
"@chat-adapter/gchat": "^4.23.0",
"@chat-adapter/linear": "^4.23.0",
"@chat-adapter/slack": "^4.23.0",
"@chat-adapter/telegram": "^4.23.0",
"@chat-adapter/whatsapp": "^4.23.0",
"@clack/prompts": "^1.2.0",
"@cline/cline-hub": "workspace:*",
"@gramio/format": "^0.7.0",
"chat": "^4.23.0",
"commander": "^14.0.3",
"@opentui-ui/dialog": "^0.1.2",
"@opentui/core": "0.1.102",
"@opentui/react": "0.1.102",
"@opentui-ui/dialog": "^0.1.2",
"chat": "^4.23.0",
"commander": "^14.0.3",
"fzf": "^0.5.2",
"marked": "^15.0.12",
"open": "^10.2.0",
"opentui-spinner": "^0.0.6",
"pino": "^10.3.1",
"react": "19.2.4",
"react-reconciler": "0.32.0",
"react-devtools-core": "^7.0.1",
"react-reconciler": "0.32.0",
"yaml": "^2.8.2",
"nanoid": "^5.1.7",
"zod": "^4.1.11"
},
"devDependencies": {
"@cline/core": "workspace:*",
"@cline/shared": "workspace:*",
"@microsoft/tui-test": "^0.0.2",
"@types/react": "19.2.14"
"@types/react": "19.2.14",
"vitest": "^4.0.18",
"@types/bun": "^1.3.10"
}
}
@@ -1,6 +1,14 @@
#!/usr/bin/env bun
import { existsSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
import {
cpSync,
existsSync,
mkdirSync,
readdirSync,
readFileSync,
realpathSync,
statSync,
} from "node:fs";
import { join, relative, resolve } from "node:path";
import { $ } from "bun";
import {
@@ -13,6 +21,30 @@ const cliDir = resolve(import.meta.dir, "..");
const rootDir = resolve(cliDir, "../..");
process.chdir(cliDir);
// Telemetry / OTEL environment variables that should be baked into the
// compiled binary at build time. Mirrors the list of secrets injected by the
// `cli-publish` GitHub Actions workflow. These are inlined via Bun's `define`
// so the CLI ships with the production telemetry configuration without
// requiring the end user to set any env vars.
const BUILD_TIME_INLINED_ENV_VARS = [
"TELEMETRY_SERVICE_API_KEY",
"ERROR_SERVICE_API_KEY",
"OTEL_TELEMETRY_ENABLED",
"OTEL_LOGS_EXPORTER",
"OTEL_METRICS_EXPORTER",
"OTEL_EXPORTER_OTLP_PROTOCOL",
"OTEL_EXPORTER_OTLP_ENDPOINT",
"OTEL_EXPORTER_OTLP_HEADERS",
] as const;
function buildInlinedEnvDefines(): Record<string, string> {
const defines: Record<string, string> = {};
for (const name of BUILD_TIME_INLINED_ENV_VARS) {
defines[`process.env.${name}`] = JSON.stringify(process.env[name] ?? "");
}
return defines;
}
const pkg = JSON.parse(readFileSync(join(cliDir, "package.json"), "utf-8"));
const version: string = pkg.version;
const repository: unknown = pkg.repository;
@@ -71,6 +103,48 @@ if (!buildOptions.skipSdkBuild) {
await $`bun -F @cline/cli build`.cwd(rootDir);
}
const hubWebviewSource = join(cliDir, "../cline-hub/src/webview");
const hubWebviewDist = join(cliDir, "../cline-hub/dist/webview");
const hubWebviewIndex = join(hubWebviewDist, "index.html");
function newestFileMtimeMs(dir: string): number {
let newest = 0;
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (
entry.name === "node_modules" ||
entry.name === "dist" ||
entry.name === ".turbo"
) {
continue;
}
const path = join(dir, entry.name);
if (entry.isDirectory()) {
newest = Math.max(newest, newestFileMtimeMs(path));
} else if (entry.isFile()) {
newest = Math.max(newest, statSync(path).mtimeMs);
}
}
return newest;
}
function shouldBuildHubWebview(): boolean {
if (!existsSync(hubWebviewIndex)) {
return true;
}
try {
return (
newestFileMtimeMs(hubWebviewSource) > statSync(hubWebviewIndex).mtimeMs
);
} catch {
return true;
}
}
if (shouldBuildHubWebview()) {
console.log("Building Cline Hub webview...");
await $`bun -F @cline/cline-hub build:webview`.cwd(rootDir);
}
const binaries: Record<string, string> = {};
function findOpenTuiParserWorker(): string {
@@ -128,6 +202,9 @@ async function buildCompiledBinary(input: {
external: ["@anthropic-ai/vertex-sdk"],
define: {
OTUI_TREE_SITTER_WORKER_PATH: bunfsRoot + parserWorkerPath,
// Inline telemetry/OTEL env vars at build time so the compiled
// binary ships with production telemetry configuration baked in.
...buildInlinedEnvDefines(),
},
throw: false,
});
@@ -182,7 +259,7 @@ for (const item of targets) {
// Copy plugin sandbox bootstrap if it exists
const bootstrapSrc = join(
rootDir,
"packages/core/dist/extensions/plugin-sandbox-bootstrap.js",
"sdk/packages/core/dist/extensions/plugin-sandbox-bootstrap.js",
);
if (existsSync(bootstrapSrc)) {
const bootstrapDir = join(cliDir, `dist/${dirName}/extensions`);
@@ -191,6 +268,14 @@ for (const item of targets) {
await Bun.write(join(bootstrapDir, "plugin-sandbox-bootstrap.js"), content);
}
if (existsSync(hubWebviewDist)) {
const hubWebviewDest = join(cliDir, `dist/${dirName}/cline-hub/webview`);
mkdirSync(join(cliDir, `dist/${dirName}/cline-hub`), {
recursive: true,
});
cpSync(hubWebviewDist, hubWebviewDest, { recursive: true });
}
// Generate platform package.json
await Bun.write(
join(cliDir, `dist/${dirName}/package.json`),
@@ -90,7 +90,7 @@ function buildHostSdkDependencies(): Record<string, string> {
for (const pkg of hostSdkPackages) {
dependencies[pkg.name] = readPackageVersion(
pkg.name,
join(cliDir, "../../packages", pkg.directory, "package.json"),
join(cliDir, "../../sdk/packages", pkg.directory, "package.json"),
);
}
return dependencies;
+102
View File
@@ -0,0 +1,102 @@
import type { ProviderSettingsManager } from "@cline/core";
import { loginAndSaveProviderOAuthCredentials } from "@cline/core";
import { getPersistedProviderApiKey } from "../commands/auth";
import { writeDiagnostic } from "../utils/output";
/**
* Supported ACP OAuth provider IDs.
*/
export const ACP_AUTH_METHODS = [
{ id: "cline", name: "Sign in with Cline" },
{ id: "openai-codex", name: "Sign in with ChatGPT Subscription" },
] as const;
export type AcpAuthMethodId = (typeof ACP_AUTH_METHODS)[number]["id"];
export function isAcpAuthMethodId(id: string): id is AcpAuthMethodId {
return ACP_AUTH_METHODS.some((m) => m.id === id);
}
/**
* Perform an OAuth login for the given provider in ACP mode.
*
* Since stdin/stdout are used for the JSON-RPC transport, all user-facing
* output is written to stderr and URLs are opened via the `open` package.
* If the OAuth flow requires interactive prompts (rare), defaults are used
* when available; otherwise an error is thrown.
*/
async function performOAuthLogin(input: {
providerId: AcpAuthMethodId;
providerSettingsManager: ProviderSettingsManager;
}): Promise<string> {
const [{ createOAuthClientCallbacks }, { default: open }] = await Promise.all(
[import("@cline/core"), import("open")],
);
const callbacks = createOAuthClientCallbacks({
onPrompt: ({ defaultValue }) => {
if (defaultValue) {
return Promise.resolve(defaultValue);
}
return Promise.reject(
new Error(
"OAuth flow requires interactive input which is unavailable in ACP mode",
),
);
},
onOutput: (message) => writeDiagnostic(`[acp/auth] ${message}`),
openUrl: (url) => open(url, { wait: false }).then(() => undefined),
onOpenUrlError: ({ url }) => {
writeDiagnostic(
`[acp/auth] Could not open browser automatically. Open this URL manually:\n${url}`,
);
},
});
const settings = await loginAndSaveProviderOAuthCredentials(
input.providerSettingsManager,
input.providerId,
{ callbacks },
);
const apiKey = getPersistedProviderApiKey(input.providerId, settings);
if (!apiKey) {
throw new Error(
`OAuth login did not persist credentials for ${input.providerId}`,
);
}
return apiKey;
}
export interface AcpAuthResult {
providerId: AcpAuthMethodId;
apiKey: string;
}
/**
* Authenticate via OAuth for the given ACP auth method.
*
* Uses `ProviderSettingsManager` to check for existing credentials first,
* falling back to a fresh OAuth login if needed.
*/
export async function authenticateAcpProvider(
methodId: AcpAuthMethodId,
providerSettingsManager: ProviderSettingsManager,
): Promise<AcpAuthResult> {
const existing = providerSettingsManager.getProviderSettings(methodId);
// Check for already-stored credentials.
const existingKey = getPersistedProviderApiKey(methodId, existing);
if (existingKey) {
writeDiagnostic(`[acp/auth] Using existing credentials for ${methodId}`);
return { providerId: methodId, apiKey: existingKey };
}
// Perform a fresh OAuth login.
writeDiagnostic(`[acp/auth] Starting OAuth login for ${methodId}`);
const apiKey = await performOAuthLogin({
providerId: methodId,
providerSettingsManager,
});
writeDiagnostic(`[acp/auth] Successfully authenticated with ${methodId}`);
return { providerId: methodId, apiKey };
}
+36
View File
@@ -0,0 +1,36 @@
import { afterEach, describe, expect, it, vi } from "vitest";
describe("runAcpMode", () => {
afterEach(() => {
vi.doUnmock("@agentclientprotocol/sdk");
vi.doUnmock("./acpAgent");
vi.restoreAllMocks();
});
it("writes the startup diagnostic without labeling it as an error", async () => {
const stderrWrite = vi
.spyOn(process.stderr, "write")
.mockImplementation(() => true);
vi.doMock("@agentclientprotocol/sdk", () => ({
ndJsonStream: vi.fn(() => ({})),
AgentSideConnection: class {
closed = Promise.resolve();
},
}));
vi.doMock("./acpAgent", () => ({
AcpAgent: class {},
}));
const { runAcpMode } = await import("./index");
await runAcpMode();
expect(stderrWrite).toHaveBeenCalledWith(
"[acp] starting ACP mode over stdio…\n",
);
expect(stderrWrite).not.toHaveBeenCalledWith(
expect.stringContaining("error:"),
);
});
});
@@ -1,5 +1,5 @@
import { Readable, Writable } from "node:stream";
import { writeErr } from "../utils/output";
import { writeDiagnostic } from "../utils/output";
export async function runAcpMode(): Promise<void> {
const { AgentSideConnection, ndJsonStream } = await import(
@@ -7,7 +7,7 @@ export async function runAcpMode(): Promise<void> {
);
const { AcpAgent } = await import("./acpAgent");
writeErr("[acp] starting ACP mode over stdio…");
writeDiagnostic("[acp] starting ACP mode over stdio…");
const stream = ndJsonStream(
Writable.toWeb(process.stdout) as WritableStream<Uint8Array>,
@@ -2,7 +2,37 @@ import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import type { ProviderSettingsManager } from "@cline/core";
import { describe, expect, it, vi } from "vitest";
import { getPersistedProviderApiKey, saveOAuthProviderSettings } from "./auth";
import {
getPersistedProviderApiKey,
normalizeAuthProviderId,
parseAuthCommandArgs,
saveOAuthProviderSettings,
} from "./auth";
describe("parseAuthCommandArgs", () => {
it("parses Azure API version quick setup option", () => {
expect(
parseAuthCommandArgs([
"--provider",
"openai-compatible",
"--apikey",
"key",
"--modelid",
"gpt-4.1",
"--baseurl",
"https://example.openai.azure.com/openai/deployments/gpt-4.1",
"--azure-api-version",
"2025-01-01-preview",
]),
).toMatchObject({
explicitProvider: "openai-compatible",
apikey: "key",
modelid: "gpt-4.1",
baseurl: "https://example.openai.azure.com/openai/deployments/gpt-4.1",
azureApiVersion: "2025-01-01-preview",
});
});
});
describe("saveOAuthProviderSettings", () => {
it("preserves existing manual apiKey while updating OAuth tokens", () => {
@@ -67,6 +97,12 @@ describe("getPersistedProviderApiKey", () => {
});
});
describe("normalizeAuthProviderId", () => {
it("keeps CLI-only codex shorthand in CLI parsing", () => {
expect(normalizeAuthProviderId("codex")).toBe("openai-codex");
});
});
describe("loadAuthTuiRuntime", () => {
it("loads OpenTUI React after provider catalog initialization", async () => {
const cliRoot = fileURLToPath(new URL("../..", import.meta.url));
@@ -3,11 +3,13 @@ import {
BUILT_IN_PROVIDER,
createOAuthClientCallbacks,
ensureCustomProvidersLoaded,
getProviderAuthHandler,
listLocalProviders,
loginAndSaveProviderOAuthCredentials,
type ProviderSettings,
type ProviderSettingsManager,
saveProviderOAuthCredentials,
} from "@cline/core";
import { getClineEnvironmentConfig } from "@cline/shared";
import { Command } from "commander";
import open from "open";
import React from "react";
@@ -37,40 +39,6 @@ const c = {
green: "\x1b[32m",
};
type CoreOAuthApi = {
loginClineOAuth: (input: {
apiBaseUrl: string;
useWorkOSDeviceAuth?: boolean;
callbacks: {
onAuth: (info: { url: string; instructions?: string }) => void;
onPrompt: (prompt: {
message: string;
defaultValue?: string;
}) => Promise<string>;
onManualCodeInput?: () => Promise<string>;
};
}) => Promise<OAuthCredentials>;
loginOcaOAuth: (input: {
mode?: "internal" | "external";
callbacks: {
onAuth: (info: { url: string; instructions?: string }) => void;
onPrompt: (prompt: {
message: string;
defaultValue?: string;
}) => Promise<string>;
onManualCodeInput?: () => Promise<string>;
};
}) => Promise<OAuthCredentials>;
loginOpenAICodex: (input: {
onAuth: (info: { url: string; instructions?: string }) => void;
onPrompt: (prompt: {
message: string;
defaultValue?: string;
}) => Promise<string>;
onManualCodeInput?: () => Promise<string>;
}) => Promise<OAuthCredentials>;
};
type AuthIo = {
writeln: (text?: string) => void;
writeErr: (text: string) => void;
@@ -81,6 +49,7 @@ type AuthQuickSetupInput = {
apikey: string;
modelid: string;
baseurl?: string;
azureApiVersion?: string;
};
type AuthCommandInput = {
@@ -90,6 +59,7 @@ type AuthCommandInput = {
apikey?: string;
modelid?: string;
baseurl?: string;
azureApiVersion?: string;
};
type ParsedAuthCommandArgs = {
@@ -97,30 +67,10 @@ type ParsedAuthCommandArgs = {
apikey?: string;
modelid?: string;
baseurl?: string;
azureApiVersion?: string;
parseError?: string;
};
let cachedCoreOAuthApi: Promise<CoreOAuthApi> | undefined;
async function getCoreOAuthApi(): Promise<CoreOAuthApi> {
if (!cachedCoreOAuthApi) {
cachedCoreOAuthApi = import("@cline/core").then((module) => {
const runtimeApi = module as Partial<CoreOAuthApi>;
if (
typeof runtimeApi.loginClineOAuth !== "function" ||
typeof runtimeApi.loginOcaOAuth !== "function" ||
typeof runtimeApi.loginOpenAICodex !== "function"
) {
throw new Error(
"Installed @cline/core does not expose OAuth login helpers required by the CLI",
);
}
return runtimeApi as CoreOAuthApi;
});
}
return cachedCoreOAuthApi;
}
/**
* Create the `auth` subcommand for Commander.
*
@@ -137,7 +87,8 @@ export function createAuthCommand(): Command {
.option("-p, --provider <id>", "provider id")
.option("-k, --apikey <key>", "API key")
.option("-m, --modelid <id>", "model id")
.option("-b, --baseurl <url>", "base URL");
.option("-b, --baseurl <url>", "base URL")
.option("--azure-api-version <version>", "Azure API version");
return cmd;
}
@@ -154,6 +105,7 @@ export function parseAuthCommandArgs(args: string[]): ParsedAuthCommandArgs {
apikey?: string;
modelid?: string;
baseurl?: string;
azureApiVersion?: string;
}>();
const positionalProvider = cmd.args[0];
return {
@@ -161,6 +113,7 @@ export function parseAuthCommandArgs(args: string[]): ParsedAuthCommandArgs {
apikey: opts.apikey,
modelid: opts.modelid,
baseurl: opts.baseurl,
azureApiVersion: opts.azureApiVersion,
};
}
@@ -200,6 +153,12 @@ async function ensureQuickSetupInputValid(
) {
return "base URL is only supported for OpenAI and OpenAI-compatible providers";
}
if (
input.azureApiVersion?.trim() &&
normalizedProvider !== BUILT_IN_PROVIDER.OPENAI_COMPATIBLE
) {
return "Azure API version is only supported for OpenAI-compatible providers";
}
return undefined;
}
@@ -209,6 +168,7 @@ function saveQuickAuthProviderSettings(input: {
apikey: string;
modelid: string;
baseurl?: string;
azureApiVersion?: string;
}): void {
const existing = input.providerSettingsManager.getProviderSettings(
input.providerId,
@@ -224,6 +184,12 @@ function saveQuickAuthProviderSettings(input: {
if (input.baseurl?.trim()) {
nextSettings.baseUrl = input.baseurl.trim();
}
if (input.azureApiVersion?.trim()) {
nextSettings.azure = {
...(nextSettings.azure ?? {}),
apiVersion: input.azureApiVersion.trim(),
};
}
input.providerSettingsManager.saveProviderSettings(nextSettings);
}
@@ -272,64 +238,18 @@ function createOAuthCallbacks(io: AuthIo): {
});
}
async function loginWithOAuthProvider(
providerId: string,
existing: ProviderSettings | undefined,
io: AuthIo,
): Promise<OAuthCredentials> {
const oauthApi = await getCoreOAuthApi();
const callbacks = createOAuthCallbacks(io);
if (providerId === "cline") {
return oauthApi.loginClineOAuth({
apiBaseUrl:
existing?.baseUrl?.trim() || getClineEnvironmentConfig().apiBaseUrl,
useWorkOSDeviceAuth: true,
callbacks,
});
}
if (providerId === "oca") {
const mode = existing?.oca?.mode;
return oauthApi.loginOcaOAuth({
mode,
callbacks,
});
}
if (providerId === "openai-codex") {
return oauthApi.loginOpenAICodex(callbacks);
}
throw new Error(
`Provider "${providerId}" does not support CLI OAuth flow (supported: cline, openai-codex, oca)`,
);
}
export function saveOAuthProviderSettings(
providerSettingsManager: ProviderSettingsManager,
providerId: string,
existing: ProviderSettings | undefined,
credentials: OAuthCredentials,
): ProviderSettings {
const auth = {
...(existing?.auth ?? {}),
accessToken: toProviderApiKey(providerId, credentials),
refreshToken: credentials.refresh,
accountId: credentials.accountId,
} as ProviderSettings["auth"] & { expiresAt?: number };
auth.expiresAt = credentials.expires;
const merged: ProviderSettings = {
...(existing ?? {
provider: providerId as ProviderSettings["provider"],
}),
provider: providerId as ProviderSettings["provider"],
auth,
};
providerSettingsManager.saveProviderSettings(merged, {
tokenSource: "oauth",
return saveProviderOAuthCredentials({
manager: providerSettingsManager,
providerId,
settings: existing,
credentials,
});
return merged;
}
export async function ensureOAuthProviderApiKey(input: {
@@ -348,19 +268,14 @@ export async function ensureOAuthProviderApiKey(input: {
selectedProviderSettings: input.existingSettings,
};
}
const credentials = await loginWithOAuthProvider(
input.providerId,
input.existingSettings,
input.io,
);
const selectedProviderSettings = saveOAuthProviderSettings(
const selectedProviderSettings = await loginAndSaveProviderOAuthCredentials(
input.providerSettingsManager,
input.providerId,
input.existingSettings,
credentials,
{ callbacks: createOAuthCallbacks(input.io) },
);
const handler = getProviderAuthHandler(input.providerId);
return {
apiKey: toProviderApiKey(input.providerId, credentials),
apiKey: handler?.getApiKey(selectedProviderSettings),
selectedProviderSettings,
};
}
@@ -370,12 +285,14 @@ async function runQuickAuthSetup(input: AuthCommandInput): Promise<number> {
const apikey = input.apikey?.trim() ?? "";
const modelid = input.modelid?.trim() ?? "";
const baseurl = input.baseurl?.trim();
const azureApiVersion = input.azureApiVersion?.trim();
const validationError = await ensureQuickSetupInputValid(
{
provider: providerId,
apikey,
modelid,
baseurl,
azureApiVersion,
},
input.providerSettingsManager,
);
@@ -389,6 +306,7 @@ async function runQuickAuthSetup(input: AuthCommandInput): Promise<number> {
apikey,
modelid,
baseurl,
azureApiVersion,
});
input.io.writeln(
`${c.green}Provider configured:${c.reset} ${c.cyan}${providerId}${c.reset} (${modelid})`,
@@ -473,12 +391,13 @@ export async function runAuthCommand(input: AuthCommandInput): Promise<number> {
const hasQuickSetupFlags =
typeof input.apikey === "string" ||
typeof input.modelid === "string" ||
typeof input.baseurl === "string";
typeof input.baseurl === "string" ||
typeof input.azureApiVersion === "string";
if (hasQuickSetupFlags) {
if (!input.explicitProvider?.trim()) {
input.io.writeErr(
"auth quick setup requires --provider <id> when using --apikey/--modelid/--baseurl",
"auth quick setup requires --provider <id> when using --apikey/--modelid/--baseurl/--azure-api-version",
);
return 1;
}
@@ -515,13 +434,10 @@ export async function runAuthProviderCommand(
return 1;
}
try {
const existing = providerSettingsManager.getProviderSettings(providerId);
const credentials = await loginWithOAuthProvider(providerId, existing, io);
saveOAuthProviderSettings(
await loginAndSaveProviderOAuthCredentials(
providerSettingsManager,
providerId,
existing,
credentials,
{ callbacks: createOAuthCallbacks(io) },
);
io.writeln(
`${c.green}You are now logged in to ${c.cyan}${providerId}${c.reset}`,
@@ -30,6 +30,18 @@ function resolveCliAgentConfigSearchPaths(cwd: string): string[] {
return [join(cwd, ".cline", "agents"), join(clineDir, "agents")];
}
function createConfigUserInstructionService(cwd: string) {
return createUserInstructionConfigService({
skills: {
workspacePath: cwd,
includePluginSkills: true,
cwd,
},
rules: { workspacePath: cwd },
workflows: { workspacePath: cwd },
});
}
async function runWorkflowsConfigCommand(
cwd: string,
outputMode: CliOutputMode,
@@ -39,11 +51,7 @@ async function runWorkflowsConfigCommand(
string,
{ id: string; name: string; instructions: string; path: string }
>();
const service = createUserInstructionConfigService({
skills: { workspacePath: cwd },
rules: { workspacePath: cwd },
workflows: { workspacePath: cwd },
});
const service = createConfigUserInstructionService(cwd);
try {
await service.start();
for (const record of service.listRecords<WorkflowConfig>("workflow")) {
@@ -90,11 +98,7 @@ async function runRulesConfigCommand(
string,
{ name: string; instructions: string; path: string }
>();
const service = createUserInstructionConfigService({
skills: { workspacePath: cwd },
rules: { workspacePath: cwd },
workflows: { workspacePath: cwd },
});
const service = createConfigUserInstructionService(cwd);
try {
await service.start();
for (const record of service.listRecords<RuleConfig>("rule")) {
@@ -142,11 +146,7 @@ async function runSkillsConfigCommand(
path: string;
}
>();
const service = createUserInstructionConfigService({
skills: { workspacePath: cwd },
rules: { workspacePath: cwd },
workflows: { workspacePath: cwd },
});
const service = createConfigUserInstructionService(cwd);
try {
await service.start();
for (const record of service.listRecords<SkillConfig>("skill")) {
@@ -420,11 +420,7 @@ async function runToolsConfigCommand(
async function loadInteractiveConfigDataForCommand(
cwd: string,
): Promise<Awaited<ReturnType<typeof loadInteractiveConfigData>>> {
const userInstructionService = createUserInstructionConfigService({
skills: { workspacePath: cwd },
rules: { workspacePath: cwd },
workflows: { workspacePath: cwd },
});
const userInstructionService = createConfigUserInstructionService(cwd);
try {
await userInstructionService.start();
return await loadInteractiveConfigData({
+184
View File
@@ -0,0 +1,184 @@
import { mkdirSync, mkdtempSync } from "node:fs";
import { arch, platform, tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { runDashboardCommand, waitForProcessShutdown } from "./dashboard";
const ENV_KEYS = [
"WORKSPACE_ROOT",
"HOST",
"CLINE_HUB_DASHBOARD_PORT",
"PUBLIC_URL",
"ROOM_SECRET",
"CLINE_HUB_WEBVIEW_DIST_DIR",
"CLINE_WRAPPER_PATH",
] as const;
const originalEnv = Object.fromEntries(
ENV_KEYS.map((key) => [key, process.env[key]]),
);
afterEach(() => {
for (const key of ENV_KEYS) {
const value = originalEnv[key];
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
});
describe("runDashboardCommand", () => {
it("starts the dashboard server, opens the invite URL, and waits for shutdown", async () => {
const output: string[] = [];
const errors: string[] = [];
const opened: string[] = [];
const stop = vi.fn();
let observedEnv:
| {
workspaceRoot: string | undefined;
host: string | undefined;
port: string | undefined;
publicUrl: string | undefined;
roomSecret: string | undefined;
webviewDistDir: string | undefined;
}
| undefined;
const webviewDistDir = mkdtempSync(join(tmpdir(), "cline-webview-dist-"));
mkdirSync(webviewDistDir, { recursive: true });
process.env.CLINE_HUB_WEBVIEW_DIST_DIR = webviewDistDir;
const exitCode = await runDashboardCommand({
cwd: "sdk",
host: "127.0.0.1",
port: "9090",
publicUrl: "http://127.0.0.1:9090",
roomSecret: "secret",
io: {
writeln: (text) => output.push(text ?? ""),
writeErr: (text) => errors.push(text),
},
startServer: async () => {
observedEnv = {
workspaceRoot: process.env.WORKSPACE_ROOT,
host: process.env.HOST,
port: process.env.CLINE_HUB_DASHBOARD_PORT,
publicUrl: process.env.PUBLIC_URL,
roomSecret: process.env.ROOM_SECRET,
webviewDistDir: process.env.CLINE_HUB_WEBVIEW_DIST_DIR,
};
return {
listenUrl: "http://127.0.0.1:9090/",
publicUrl: "http://127.0.0.1:9090",
inviteUrl: "http://127.0.0.1:9090/?roomSecret=secret",
hubUrl: "ws://127.0.0.1:25463/hub",
stop,
};
},
openUrl: async (url) => {
opened.push(url);
},
waitForShutdown: async (server) => {
await server.stop();
},
});
expect(exitCode).toBe(0);
expect(observedEnv).toEqual({
workspaceRoot: resolve("sdk"),
host: "127.0.0.1",
port: "9090",
publicUrl: "http://127.0.0.1:9090",
roomSecret: "secret",
webviewDistDir,
});
expect(opened).toEqual(["http://127.0.0.1:9090/?roomSecret=secret"]);
expect(stop).toHaveBeenCalledTimes(1);
expect(output.join("\n")).toContain("Cline dashboard listening at");
expect(output.join("\n")).toContain("ws://127.0.0.1:25463/hub");
expect(errors).toEqual([]);
expect(process.env.WORKSPACE_ROOT).toBe(originalEnv.WORKSPACE_ROOT);
expect(process.env.CLINE_HUB_WEBVIEW_DIST_DIR).toBe(webviewDistDir);
});
it("honors --no-open behavior", async () => {
const openUrl = vi.fn();
const exitCode = await runDashboardCommand({
openBrowser: false,
io: {
writeln: () => {},
writeErr: () => {},
},
startServer: async () => ({
listenUrl: "http://127.0.0.1:8787/",
publicUrl: "http://127.0.0.1:8787",
inviteUrl: "http://127.0.0.1:8787",
stop: vi.fn(),
}),
openUrl,
waitForShutdown: async () => {},
});
expect(exitCode).toBe(0);
expect(openUrl).not.toHaveBeenCalled();
});
it("finds webview assets from the published wrapper package layout", async () => {
const root = mkdtempSync(join(tmpdir(), "cline-wrapper-layout-"));
const wrapperPath = join(root, "node_modules", "cline", "bin", "cline");
const platformName = platform() === "win32" ? "windows" : platform();
const webviewDistDir = join(
root,
"node_modules",
"cline",
"node_modules",
"@cline",
`cli-${platformName}-${arch()}`,
"cline-hub",
"webview",
);
mkdirSync(join(wrapperPath, ".."), { recursive: true });
mkdirSync(webviewDistDir, { recursive: true });
process.env.CLINE_WRAPPER_PATH = wrapperPath;
delete process.env.CLINE_HUB_WEBVIEW_DIST_DIR;
let observedWebviewDistDir: string | undefined;
const exitCode = await runDashboardCommand({
openBrowser: false,
io: {
writeln: () => {},
writeErr: () => {},
},
startServer: async () => {
observedWebviewDistDir = process.env.CLINE_HUB_WEBVIEW_DIST_DIR;
return {
listenUrl: "http://127.0.0.1:8787/",
publicUrl: "http://127.0.0.1:8787",
inviteUrl: "http://127.0.0.1:8787",
stop: vi.fn(),
};
},
waitForShutdown: async () => {},
});
expect(exitCode).toBe(0);
expect(observedWebviewDistDir).toBe(webviewDistDir);
});
it("settles shutdown when server stop rejects", async () => {
const shutdown = waitForProcessShutdown({
listenUrl: "http://127.0.0.1:8787/",
publicUrl: "http://127.0.0.1:8787",
inviteUrl: "http://127.0.0.1:8787",
stop: vi.fn(async () => {
throw new Error("stop failed");
}),
});
process.emit("SIGINT", "SIGINT");
await expect(shutdown).rejects.toThrow("stop failed");
});
});
+195
View File
@@ -0,0 +1,195 @@
import { existsSync } from "node:fs";
import { arch, platform } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import open from "open";
import { c } from "../utils/output";
export interface DashboardServerHandle {
listenUrl: string;
publicUrl: string;
inviteUrl: string;
hubUrl?: string;
stop: () => void | Promise<void>;
}
interface DashboardCommandIo {
writeln: (text?: string) => void;
writeErr: (text: string) => void;
}
export interface RunDashboardCommandOptions {
cwd?: string;
host?: string;
port?: string;
publicUrl?: string;
roomSecret?: string;
openBrowser?: boolean;
io: DashboardCommandIo;
startServer?: () => Promise<DashboardServerHandle>;
openUrl?: (url: string) => Promise<void>;
waitForShutdown?: (server: DashboardServerHandle) => Promise<void>;
}
const DASHBOARD_PORT_ENV = "CLINE_HUB_DASHBOARD_PORT";
const WEBVIEW_DIST_ENV = "CLINE_HUB_WEBVIEW_DIST_DIR";
function setEnvValue(name: string, value: string | undefined): () => void {
const previous = process.env[name];
if (value === undefined) {
return () => {};
}
process.env[name] = value;
return () => {
if (previous === undefined) {
delete process.env[name];
} else {
process.env[name] = previous;
}
};
}
async function withDashboardEnvironment<T>(
options: RunDashboardCommandOptions,
fn: () => Promise<T>,
): Promise<T> {
const restore = [
setEnvValue(
"WORKSPACE_ROOT",
options.cwd ? resolve(options.cwd) : undefined,
),
setEnvValue("HOST", options.host),
setEnvValue(DASHBOARD_PORT_ENV, options.port),
setEnvValue("PUBLIC_URL", options.publicUrl),
setEnvValue("ROOM_SECRET", options.roomSecret),
setEnvValue(WEBVIEW_DIST_ENV, resolveDefaultWebviewDistDir()),
];
try {
return await fn();
} finally {
for (let i = restore.length - 1; i >= 0; i--) {
restore[i]?.();
}
}
}
function resolveDefaultWebviewDistDir(): string | undefined {
if (process.env[WEBVIEW_DIST_ENV]?.trim()) {
return undefined;
}
const moduleDir = dirname(fileURLToPath(import.meta.url));
const candidates = [
...resolveInstalledPlatformPackageWebviewCandidates(),
// Source checkout: apps/cli/src/commands/dashboard.ts
join(moduleDir, "../../../cline-hub/dist/webview"),
// Node bundle: apps/cli/dist/index.js
join(moduleDir, "cline-hub/webview"),
// Compiled platform package: apps/cli/dist/<platform>/bin/cline
join(dirname(process.execPath), "../cline-hub/webview"),
];
return candidates.find((candidate) => existsSync(candidate));
}
function resolveInstalledPlatformPackageWebviewCandidates(): string[] {
const packageName = resolvePlatformPackageName();
const starts = [
process.env.CLINE_WRAPPER_PATH
? dirname(process.env.CLINE_WRAPPER_PATH)
: undefined,
dirname(process.execPath),
].filter((value): value is string => !!value?.trim());
const candidates: string[] = [];
for (const start of starts) {
let current = start;
for (;;) {
candidates.push(
join(current, "node_modules", packageName, "cline-hub/webview"),
);
const parent = dirname(current);
if (parent === current) break;
current = parent;
}
}
return candidates;
}
function resolvePlatformPackageName(): string {
const platformName = platform() === "win32" ? "windows" : platform();
return `@cline/cli-${platformName}-${arch()}`;
}
async function startDefaultDashboardServer(): Promise<DashboardServerHandle> {
const { startClineHubDashboardServer } = await import("@cline/cline-hub");
return await startClineHubDashboardServer();
}
async function openDefaultUrl(url: string): Promise<void> {
await open(url, { wait: false });
}
export function waitForProcessShutdown(
server: DashboardServerHandle,
): Promise<void> {
return new Promise<void>((resolveShutdown, rejectShutdown) => {
let settled = false;
const cleanup = () => {
process.off("SIGINT", handleSignal);
process.off("SIGTERM", handleSignal);
};
const stop = async () => {
if (settled) return;
settled = true;
cleanup();
try {
await server.stop();
resolveShutdown();
} catch (error) {
rejectShutdown(error);
}
};
function handleSignal() {
void stop();
}
process.on("SIGINT", handleSignal);
process.on("SIGTERM", handleSignal);
});
}
export async function runDashboardCommand(
options: RunDashboardCommandOptions,
): Promise<number> {
try {
const server = await withDashboardEnvironment(options, () =>
(options.startServer ?? startDefaultDashboardServer)(),
);
const dashboardUrl =
server.inviteUrl || server.publicUrl || server.listenUrl;
options.io.writeln(
`${c.green}Cline dashboard listening at${c.reset} ${dashboardUrl}`,
);
if (server.hubUrl) {
options.io.writeln(`${c.dim}Hub endpoint: ${server.hubUrl}${c.reset}`);
}
if (options.openBrowser !== false) {
try {
await (options.openUrl ?? openDefaultUrl)(dashboardUrl);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
options.io.writeErr(`Failed to open browser: ${message}`);
}
}
await (options.waitForShutdown ?? waitForProcessShutdown)(server);
return 0;
} catch (error) {
options.io.writeErr(error instanceof Error ? error.message : String(error));
return 1;
}
}
@@ -14,6 +14,7 @@ import { getCliBuildInfo } from "../utils/common";
const {
mockSpawnSync,
mockResolveClineDataDir,
mockResolveProductionHubOwnerContext,
mockResolveSharedHubOwnerContext,
mockReadHubDiscovery,
mockProbeHubServer,
@@ -24,6 +25,15 @@ const {
} = vi.hoisted(() => ({
mockSpawnSync: vi.fn(),
mockResolveClineDataDir: vi.fn(() => "/tmp/cline-data"),
mockResolveProductionHubOwnerContext: vi.fn(() => ({
ownerId: "hub-production",
discoveryPath: path.join(
"/tmp/cline-data",
"locks",
"hub",
"production.json",
),
})),
mockResolveSharedHubOwnerContext: vi.fn(() => ({
ownerId: "hub-owner",
discoveryPath: path.join(
@@ -52,6 +62,7 @@ vi.mock("node:child_process", () => ({
vi.mock("@cline/core", () => ({
resolveClineDataDir: mockResolveClineDataDir,
resolveProductionHubOwnerContext: mockResolveProductionHubOwnerContext,
resolveSharedHubOwnerContext: mockResolveSharedHubOwnerContext,
clearHubDiscovery: mockClearHubDiscovery,
probeHubServer: mockProbeHubServer,
@@ -76,6 +87,15 @@ describe("runDoctorCommand", () => {
afterEach(() => {
vi.clearAllMocks();
mockResolveClineDataDir.mockReturnValue("/tmp/cline-data");
mockResolveProductionHubOwnerContext.mockReturnValue({
ownerId: "hub-production",
discoveryPath: path.join(
"/tmp/cline-data",
"locks",
"hub",
"production.json",
),
});
mockStopLocalHubServerGracefully.mockResolvedValue(false);
mockStopAllConnectors.mockResolvedValue({
stoppedProcesses: 0,
@@ -110,13 +130,14 @@ describe("runDoctorCommand", () => {
command === "pgrep" &&
Array.isArray(args) &&
args[0] === "-fal" &&
args[1] === "/apps/cli/src/index.ts"
args[1] === "--" &&
args[2] === "/apps/cli/src/index.ts"
) {
return {
status: 0,
stdout: [
"50174 /Users/example/.bun/bin/bun /Users/example/dev/sdk/apps/cli/src/index.ts hub start --cwd /workspace",
"50190 /Users/example/.bun/bin/bun /Users/example/dev/sdk/apps/cli/src/index.ts hey",
"50174 /Users/example/.bun/bin/bun /Users/example/dev/apps/cli/src/index.ts hub start --cwd /workspace",
"50190 /Users/example/.bun/bin/bun /Users/example/dev/apps/cli/src/index.ts hey",
].join("\n"),
};
}
@@ -261,12 +282,13 @@ describe("runDoctorCommand", () => {
command === "pgrep" &&
Array.isArray(args) &&
args[0] === "-fal" &&
args[1] === "/src-tauri/bin/code-sidecar"
args[1] === "--" &&
args[2] === "/src-tauri/bin/code-sidecar"
) {
return {
status: 0,
stdout:
"60123 /Users/example/dev/sdk/apps/examples/desktop-app/src-tauri/bin/code-sidecar\n",
"60123 /Users/example/dev/apps/examples/desktop-app/src-tauri/bin/code-sidecar\n",
};
}
return { status: 1, stdout: "" };
@@ -1,5 +1,5 @@
import { spawnSync } from "node:child_process";
import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
import { existsSync, readFileSync, rmSync } from "node:fs";
import { dirname, join } from "node:path";
import {
clearHubDiscovery,
@@ -7,13 +7,18 @@ import {
probeHubServer,
readHubDiscovery,
resolveClineDataDir,
resolveProductionHubOwnerContext,
resolveSharedHubOwnerContext,
stopLocalHubServerGracefully,
} from "@cline/core";
import { formatUptime } from "@cline/shared";
import { formatUptime, resolveClineBuildEnv } from "@cline/shared";
import { Command } from "commander";
import open from "open";
import { isProcessRunning } from "../connectors/common";
import {
type ActiveConnectorRecord,
listActiveConnectors,
} from "../connectors/status";
import { getCliBuildInfo } from "../utils/common";
import { c, writeln } from "../utils/output";
import { stopAllConnectors } from "./connect";
@@ -34,19 +39,6 @@ type StartupArtifact = {
stale: boolean;
};
type ActiveConnectorRecord = {
type: string;
pid: number;
hubUrl: string;
startedAt?: string;
applicationId?: string;
botUsername?: string;
userName?: string;
phoneNumberId?: string;
port?: number;
baseUrl?: string;
};
type SpawnedProcessRecord = {
timestamp?: string;
pid?: number;
@@ -63,6 +55,7 @@ type DoctorStatus = {
hubStartedAt?: string;
hubUptime?: string;
listeningPids: number[];
staleHubPids: number[];
hubStartupLocks: StartupArtifact[];
staleCliPids: number[];
staleSidecarPids: number[];
@@ -86,7 +79,11 @@ function listMatchingProcesses(pattern: string): ProcessRecord[] {
if (process.platform === "win32") {
return [];
}
const result = spawnSync("pgrep", ["-fal", pattern], { encoding: "utf8" });
// "--" stops pgrep's option parsing so patterns that start with dashes
// (e.g. the "--cline-hub-daemon" marker) are treated as patterns.
const result = spawnSync("pgrep", ["-fal", "--", pattern], {
encoding: "utf8",
});
if (result.status !== 0 && result.status !== 1) {
return [];
}
@@ -157,6 +154,25 @@ function listStaleCliPids(): number[] {
.map((record) => record.pid);
}
function listStaleHubPids(currentHubPids: number[]): number[] {
const current = new Set(currentHubPids.filter((pid) => pid > 0));
const patterns = [
"/sdk/packages/core/src/hub/daemon/entry.ts",
"/sdk/packages/core/dist/hub/daemon/entry.js",
"--cline-hub-daemon",
];
const records = new Map<number, ProcessRecord>();
for (const pattern of patterns) {
for (const record of listMatchingProcesses(pattern)) {
if (current.has(record.pid) || /\bpgrep\s+-fal\b/.test(record.command)) {
continue;
}
records.set(record.pid, record);
}
}
return [...records.values()].map((record) => record.pid);
}
function listStaleSidecarPids(): number[] {
const patterns = [
"/apps/examples/desktop-app/sidecar/index.ts",
@@ -244,7 +260,7 @@ function readStartupArtifact(path: string): StartupArtifact | undefined {
}
function listHubStartupLocks(_cwd: string): StartupArtifact[] {
const owner = resolveSharedHubOwnerContext();
const owner = resolveCliHubOwnerContext();
const ownerPath = join(`${owner.discoveryPath}.lock`, "owner.json");
if (!existsSync(ownerPath)) {
return [];
@@ -268,7 +284,7 @@ async function clearHubStartupArtifacts(
_cwd: string,
options?: { clearDiscovery?: boolean },
): Promise<{ startupLocks: number; discovery: number }> {
const owner = resolveSharedHubOwnerContext();
const owner = resolveCliHubOwnerContext();
const startupLocks = listHubStartupLocks(_cwd);
let clearedStartupLocks = 0;
for (const artifact of startupLocks) {
@@ -287,142 +303,6 @@ async function clearHubStartupArtifacts(
};
}
function listConnectorStatePaths(
type: ActiveConnectorRecord["type"],
): string[] {
const dir = join(resolveClineDataDir(), "connectors", type);
if (!existsSync(dir)) {
return [];
}
return readdirSync(dir)
.filter((name) => name.endsWith(".json") && !name.endsWith(".threads.json"))
.map((name) => join(dir, name));
}
function readJsonRecord(path: string): Record<string, unknown> | undefined {
if (!existsSync(path)) {
return undefined;
}
try {
const raw = readFileSync(path, "utf8");
const parsed = JSON.parse(raw) as unknown;
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
return parsed as Record<string, unknown>;
}
} catch {
// Ignore malformed connector state.
}
return undefined;
}
type ConnectorFieldKey = keyof Omit<
ActiveConnectorRecord,
"type" | "pid" | "hubUrl"
>;
const connectorFieldExtractors: Record<
ConnectorFieldKey,
(p: Record<string, unknown>) => string | number | undefined
> = {
startedAt: (p) => (typeof p.startedAt === "string" ? p.startedAt : undefined),
port: (p) => (typeof p.port === "number" ? p.port : undefined),
baseUrl: (p) => (typeof p.baseUrl === "string" ? p.baseUrl : undefined),
userName: (p) => (typeof p.userName === "string" ? p.userName : undefined),
botUsername: (p) =>
typeof p.botUsername === "string" ? p.botUsername : undefined,
applicationId: (p) =>
typeof p.applicationId === "string" ? p.applicationId : undefined,
phoneNumberId: (p) =>
typeof p.phoneNumberId === "string" ? p.phoneNumberId : undefined,
};
const connectorConfigs: Record<
string,
{ required: ConnectorFieldKey[]; optional: ConnectorFieldKey[] }
> = {
discord: {
required: ["userName", "applicationId"],
optional: ["startedAt", "port", "baseUrl"],
},
telegram: { required: ["botUsername"], optional: ["startedAt"] },
gchat: { required: ["userName"], optional: ["startedAt", "port", "baseUrl"] },
linear: {
required: ["userName"],
optional: ["startedAt", "port", "baseUrl"],
},
whatsapp: {
required: ["userName"],
optional: ["startedAt", "phoneNumberId", "port", "baseUrl"],
},
};
function readActiveConnectorRecord(
type: ActiveConnectorRecord["type"],
statePath: string,
): ActiveConnectorRecord | undefined {
const parsed = readJsonRecord(statePath);
if (!parsed) {
return undefined;
}
const pid = typeof parsed.pid === "number" ? parsed.pid : undefined;
const hubUrl =
typeof parsed.hubUrl === "string"
? parsed.hubUrl
: typeof parsed.rpcAddress === "string"
? parsed.rpcAddress
: undefined;
if (!pid || !hubUrl || !isProcessRunning(pid)) {
return undefined;
}
const config = connectorConfigs[type];
if (!config) {
return undefined;
}
const fields: Partial<
Omit<ActiveConnectorRecord, "type" | "pid" | "hubUrl">
> = {};
for (const key of config.required) {
const value = connectorFieldExtractors[key](parsed);
if (!value || (typeof value === "string" && !value.trim())) {
return undefined;
}
(fields as Record<string, unknown>)[key] = value;
}
for (const key of config.optional) {
const value = connectorFieldExtractors[key](parsed);
if (value !== undefined) {
(fields as Record<string, unknown>)[key] = value;
}
}
return { type, pid, hubUrl, ...fields } as ActiveConnectorRecord;
}
function listActiveConnectors(): ActiveConnectorRecord[] {
const connectorTypes: ActiveConnectorRecord["type"][] = [
"telegram",
"gchat",
"linear",
"whatsapp",
];
const records: ActiveConnectorRecord[] = [];
for (const type of connectorTypes) {
for (const statePath of listConnectorStatePaths(type)) {
const record = readActiveConnectorRecord(type, statePath);
if (record) {
records.push(record);
}
}
}
return records.sort((left, right) => {
if (left.type !== right.type) {
return left.type.localeCompare(right.type);
}
const leftName = left.botUsername ?? left.userName ?? "";
const rightName = right.botUsername ?? right.userName ?? "";
return leftName.localeCompare(rightName);
});
}
function formatHubUptimeFromStartedAt(
startedAt: string | undefined,
): string | undefined {
@@ -436,14 +316,25 @@ function formatHubUptimeFromStartedAt(
return formatUptime(Date.now() - timestamp);
}
function resolveCliHubOwnerContext() {
return resolveClineBuildEnv() === "production"
? resolveProductionHubOwnerContext()
: resolveSharedHubOwnerContext();
}
async function collectDoctorStatus(cwd: string): Promise<DoctorStatus> {
const owner = resolveSharedHubOwnerContext();
const owner = resolveCliHubOwnerContext();
const discovery = await readHubDiscovery(owner.discoveryPath);
const health = discovery?.url
? await probeHubServer(discovery.url)
? await probeHubServer(discovery.url, { authToken: discovery.authToken })
: undefined;
const current = health ?? discovery;
const hubUptime = formatHubUptimeFromStartedAt(health?.startedAt);
const listeningPids = listListeningPids(current?.port);
const currentHubPids = [
...(current?.pid ? [current.pid] : []),
...listeningPids,
];
return {
cwd,
hubUrl: current?.url,
@@ -451,7 +342,8 @@ async function collectDoctorStatus(cwd: string): Promise<DoctorStatus> {
hubPid: current?.pid,
hubStartedAt: health?.startedAt,
hubUptime,
listeningPids: listListeningPids(current?.port),
listeningPids,
staleHubPids: listStaleHubPids(currentHubPids),
hubStartupLocks: listHubStartupLocks(cwd),
staleCliPids: listStaleCliPids(),
staleSidecarPids: listStaleSidecarPids(),
@@ -533,6 +425,7 @@ export async function runDoctorCommand(
);
writeln(`hub uptime ${c.dim}${before.hubUptime ?? "n/a"}${c.reset}`);
writeln(formatPidList("hub listeners", before.listeningPids));
writeln(formatPidList("stale hub daemons", before.staleHubPids));
writeln(
formatPidList(
"hub startup locks",
@@ -557,6 +450,7 @@ export async function runDoctorCommand(
}
if (
before.listeningPids.length > 0 ||
before.staleHubPids.length > 0 ||
before.staleCliPids.length > 0 ||
before.staleSidecarPids.length > 0
) {
@@ -568,7 +462,9 @@ export async function runDoctorCommand(
}
const gracefullyStoppedHub = before.hubHealthy
? await stopLocalHubServerGracefully().catch(() => false)
? await stopLocalHubServerGracefully(resolveCliHubOwnerContext()).catch(
() => false,
)
: false;
const refreshedAfterGracefulStop = gracefullyStoppedHub
? await collectDoctorStatus(opts.cwd)
@@ -576,13 +472,20 @@ export async function runDoctorCommand(
const killedHub = gracefullyStoppedHub
? 0
: killPids(refreshedAfterGracefulStop.listeningPids);
const staleCliTargets = before.staleCliPids.filter(
const staleHubTargets = before.staleHubPids.filter(
(pid) => !refreshedAfterGracefulStop.listeningPids.includes(pid),
);
const killedStaleHubs = killPids(staleHubTargets);
const staleCliTargets = before.staleCliPids.filter(
(pid) =>
!refreshedAfterGracefulStop.listeningPids.includes(pid) &&
!staleHubTargets.includes(pid),
);
const killedCli = killPids(staleCliTargets);
const staleSidecarTargets = before.staleSidecarPids.filter(
(pid) =>
!refreshedAfterGracefulStop.listeningPids.includes(pid) &&
!staleHubTargets.includes(pid) &&
!staleCliTargets.includes(pid),
);
const killedSidecars = killPids(staleSidecarTargets);
@@ -604,6 +507,7 @@ export async function runDoctorCommand(
after,
killed: {
hubListeners: killedHub,
staleHubDaemons: killedStaleHubs,
cliProcesses: killedCli,
sidecarProcesses: killedSidecars,
connectorProcesses: stoppedConnectors.stoppedProcesses,
@@ -616,6 +520,7 @@ export async function runDoctorCommand(
return 0;
}
writeln(`killed hub listeners ${c.dim}${killedHub}${c.reset}`);
writeln(`killed stale hub daemons ${c.dim}${killedStaleHubs}${c.reset}`);
writeln(`killed cli processes ${c.dim}${killedCli}${c.reset}`);
writeln(`killed sidecar processes ${c.dim}${killedSidecars}${c.reset}`);
writeln(
@@ -632,6 +537,7 @@ export async function runDoctorCommand(
);
writeln(`hub healthy after fix: ${after.hubHealthy ? "yes" : "no"}`);
writeln(formatPidList("remaining hub listeners", after.listeningPids));
writeln(formatPidList("remaining stale hub daemons", after.staleHubPids));
writeln(
formatPidList(
"remaining hub startup locks",

Some files were not shown because too many files have changed in this diff Show More