Compare commits

..

27 Commits

Author SHA1 Message Date
Dominic Cooney 18e2bdf194 fix(vscode): align plugin command loading with CLI 2026-07-14 09:08:39 +09:00
Dominic Cooney b83c9ec240 chore(vscode): update bun lock for plugin dependencies after rebase 2026-07-14 09:08:39 +09:00
Dominic Cooney 711a8c6ec0 fix(vscode): fix compile errors in plugin slash command coordinator
CI caught two real TypeScript errors that had gone unnoticed locally
(the dev environment's @cline/core build artifacts were stale, masking
them):

1. sdk-plugin-commands.ts: createContributionRegistry() was called
   without type arguments, defaulting TMessage to `unknown`. This
   doesn't match AgentExtension's setup() signature, which expects
   Message[] (per @cline/shared), causing a type mismatch. Mirror the
   CLI's equivalent call in plugin-chat-commands.ts, which explicitly
   parameterizes <Extension, AgentTool, Message[]>.

2. SdkController.ts: emitSessionEvents(messages, event) requires two
   arguments, but the plugin-command reply path only passed one. Add
   the missing status event, matching the pattern used elsewhere in
   this file (e.g. the provider-failure error path) and in
   sdk-followup-coordinator.ts.
2026-07-14 09:08:39 +09:00
Dominic Cooney d68badae38 fix(vscode): thread plugin slash commands into autocomplete menu and navigation
ChatTextArea/SlashCommandMenu destructure pluginSlashCommands from
ExtensionState and pass it to validateSlashCommand() for input
highlighting, but never pass it to getMatchingSlashCommands() for
arrow-key navigation, Enter/Tab selection, or the rendered
SlashCommandMenu itself. Plugin-registered commands (e.g. /goal) are
discovered correctly on the backend and shipped to the webview, but
never appear in the actual autocomplete dropdown.

Thread pluginSlashCommands through all three remaining call sites and
add it to the relevant useCallback/useLayoutEffect dependency arrays
so the menu updates once plugin discovery resolves asynchronously
after mount.
2026-07-14 09:08:39 +09:00
Dominic Cooney d67c403392 feat(vscode): surface plugin commands in slash command autocomplete (CLINE-2584) 2026-07-14 09:08:39 +09:00
Dominic Cooney 02f405f6e2 fix(vscode): bundle plugin sandbox bootstrap and surface plugin commands (CLINE-2584) 2026-07-14 09:08:39 +09:00
Saoud Rizwan b4ed8a226e chore(cli): release v3.0.40 2026-07-13 12:25:49 -07:00
Saoud Rizwan cbf40961db fix(hub): make markdown code component assignable to streamdown Components
The custom MarkdownCode node type used a narrow { metastring?: string }
shape that is not assignable from the hast Element passed by
react-markdown/streamdown, so a clean rebuild (fresh dependency resolve,
as done by the release version.ts) fails the `satisfies Components`
check. Widen node.properties to Record<string, unknown> and validate the
metastring value at read time.
2026-07-13 12:02:49 -07:00
Saoud Rizwan 2d05ba52da chore(sdk): release v0.0.60 2026-07-13 11:25:21 -07:00
Saoud Rizwan 5d3778b5cf feat(cli): manual API key escape hatch for Cline OAuth providers (#12254)
* feat(cli): manual API key escape hatch for Cline OAuth providers

Add a way to configure the cline / cline-pass providers with a dashboard
API key from the /settings provider flow, for users where OAuth login
isn't working:

- "Enter API key manually" option in the already-configured dialog
- K keybinding in the OAuth login dialog to switch to key entry
- Saving clears stored OAuth tokens (on both the shared cline storage
  entry and any direct cline-pass entry) since the auth handler prefers
  auth.accessToken over apiKey — a stale token would otherwise keep
  winning over the manual key
- isProviderConfigured now counts a persisted API key for OAuth
  providers so escape-hatch users aren't forced back into OAuth on
  every provider switch

* fix(cli): move API key fallback to OAuth dialog
2026-07-13 11:14:11 -07:00
Saoud Rizwan 8d0eb54a1f feat(telemetry): track auth refresh outcomes to measure the hard-logout fix (#12256)
* fix(sdk): stop misclassifying transient refresh failures as invalid_grant

getValidClineCredentials returned null for BOTH a rejected refresh token and
any transient error (network down, timeout, 5xx) that happened to land after
the access token expired. Callers treat null as 'session dead' — the
extension wipes providers.json over it, logging out every Cline process on
the machine, which is what CLI users then hit as 'Unauthorized: please
re-authenticate'. A laptop waking from sleep past the ~1h token expiry with
a background job (balance/banners/remote-config) refreshing before the
network is up was enough to trigger it — no refresh-token rotation involved.

Now: null means the refresh token was REJECTED (re-auth required); transient
failures throw so callers keep stored credentials and retry later. The
extension's refreshAccessToken catch and the CLI's error surface already
handle the throw correctly with no changes.

* fix(sdk): write providers.json atomically

providers.json was written with a bare writeFileSync while being read
concurrently by every other Cline process (CLI, extension, hub). A reader
catching a partial write parses garbage, which read() silently treats as
EMPTY settings — indistinguishable from being logged out — and any
subsequent save from that process persists the empty state, erasing every
configured provider.

Stage to a pid-unique temp file and rename into place; rename is atomic on
POSIX and replaces on Windows, so readers only ever see a complete file.

* feat(telemetry): track auth refresh outcomes to measure the hard-logout fix

Adds the observability needed to verify in production that the
transient-vs-invalid_grant fix is working, and to diagnose any logouts that
remain:

- user.auth_refresh_soft_failure — fires when a refresh fails for a reason
  that does NOT invalidate the session (network error, timeout, 5xx) and
  stored credentials were kept. Instances with tokenExpired=true were hard
  logouts before the fix, so this is the 'prevented logout' counter. Emitted
  from the SDK (CLI path) and from the extension's refresh/restore catches
  under the same event name so dashboards aggregate both clients.
- user.auth_logged_out now carries the HTTP status and errorCode that caused
  it, and the extension emits it (with a distinct reason) at every site that
  clears providers.json: refresh_rejected, restore_refresh_rejected, and
  handleDeauth's LogoutReason (user_initiated / cross_window_sync / …), which
  was previously accepted and ignored. Extension-triggered logouts were
  completely invisible before — including the legacy-extension cross-window
  cascade, which this now measures directly.

Success looks like: auth_logged_out volume drops after release while
auth_refresh_soft_failure appears in its place, and any remaining logouts
carry a reason/status we can act on.

* fix(telemetry): route auth refresh events through SDK
2026-07-13 11:13:05 -07:00
Saoud Rizwan a3989acc38 fix(sdk): don't log users out when token refresh fails due to network/server errors (#12255)
* fix(sdk): stop misclassifying transient refresh failures as invalid_grant

getValidClineCredentials returned null for BOTH a rejected refresh token and
any transient error (network down, timeout, 5xx) that happened to land after
the access token expired. Callers treat null as 'session dead' — the
extension wipes providers.json over it, logging out every Cline process on
the machine, which is what CLI users then hit as 'Unauthorized: please
re-authenticate'. A laptop waking from sleep past the ~1h token expiry with
a background job (balance/banners/remote-config) refreshing before the
network is up was enough to trigger it — no refresh-token rotation involved.

Now: null means the refresh token was REJECTED (re-auth required); transient
failures throw so callers keep stored credentials and retry later. The
extension's refreshAccessToken catch and the CLI's error surface already
handle the throw correctly with no changes.

* fix(sdk): write providers.json atomically

providers.json was written with a bare writeFileSync while being read
concurrently by every other Cline process (CLI, extension, hub). A reader
catching a partial write parses garbage, which read() silently treats as
EMPTY settings — indistinguishable from being logged out — and any
subsequent save from that process persists the empty state, erasing every
configured provider.

Stage to a pid-unique temp file and rename into place; rename is atomic on
POSIX and replaces on Windows, so readers only ever see a complete file.
2026-07-13 08:58:26 -07:00
Saoud Rizwan d199b1bff9 fix(desktop-app): allow loopback origins for Next dev resources (#12251)
Next 16 blocks dev-resource requests (/_next/webpack-hmr, dev fonts) from
origins that don't match the dev server's own hostname. Browsing the web
dev mode via 127.0.0.1 left the page hanging with 'Blocked cross-origin
request to Next.js dev resource' warnings. allowedDevOrigins is dev-only,
so production/Tauri builds are unaffected.
2026-07-12 22:18:45 -07:00
Saoud Rizwan d41eed1198 feat(desktop-app): env-configurable sidecar bind host, trusted origins, and webview WS endpoint (#12250)
* feat(desktop-app): env-configurable sidecar bind host, trusted origins, and webview WS endpoint

Allows running the desktop app's web dev mode (dev:web + dev:sidecar) inside
a Docker container with published ports:

- CLINE_SIDECAR_HOST: sidecar bind hostname (default remains 127.0.0.1)
- CLINE_SIDECAR_TRUSTED_ORIGINS: comma-separated extra browser origins for
  the sidecar's origin allowlist (validation itself stays on)
- NEXT_PUBLIC_SIDECAR_WS_ENDPOINT: overrides the webview's hardcoded
  ws://127.0.0.1:3126/transport fallback so a browser on the Docker host can
  dial the published port

All defaults are unchanged, so local/Tauri behavior is unaffected when the
env vars are absent. When bound to 0.0.0.0 the printed ready endpoint
advertises 127.0.0.1 since a wildcard bind is not dialable.

* chore(desktop-app): untrack next-env.d.ts

It was added to .gitignore previously but never removed from the index, so
it kept showing as modified: Next.js rewrites the routes.d.ts import path
depending on whether 'next dev' or 'next build' ran last. The file is
regenerated by Next on every dev/build run, and the app's typecheck
(tsconfig.dev.json) excludes webview/, so nothing needs it tracked.

* style(desktop-app): format SIDECAR_HOST declaration
2026-07-12 21:59:07 -07:00
Tomás Barreiro 6309971089 Add the ClinePass limit error to the CLI (#12191)
* Add the ClinePass limit error to the CLI

* Update apps/cli/src/runtime/run-agent.test.ts

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

* format code and improve instructions

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-07-11 02:41:55 +02:00
Saoud Rizwan 2d2c669421 fix(cli): reload provider config when switching models (#12232) 2026-07-10 16:57:50 -07:00
Dominic Cooney c5f146a418 Add debug logging for Cline credential lifecycle (ENG-2213) (#12000)
* fix(auth): add early SDK debug logging for Cline credential lifecycle (ENG-2213)

Adds targeted debug-level logging at key points in the Cline/Cline Pass
credential lifecycle to diagnose intermittent logout issues. Credentials
are never logged in cleartext; an 8-hex-digit SHA-256 hash is used instead.

The SDK has two logger layers:
1. ClineCore.logger — session-scoped, threaded from ClineCore.create({logger})
   into session config and the agent event bridge.
2. setSdkLogger() — early/module-level, for components that operate before
   or outside of ClineCore sessions: ProviderSettingsManager (constructed
   at startup), RuntimeOAuthTokenManager, and cline.ts auth functions
   (token refresh). These can't be reached by the session-scoped logger.

Both VS Code (common.ts) and CLI (main.ts) call setSdkLogger() once at
startup. When no logger is registered (or the host filters out debug),
every call is a no-op — logging is never collected in normal use.

Instrumentation points (SDK core, shared by both surfaces):
- ProviderSettingsManager.read(): logs provider IDs, last-used, and whether
  Cline auth is present (with hashed access/refresh token fingerprints)
- ProviderSettingsManager.saveProviderSettings(): logs the provider being
  saved, tokenSource, whether Cline auth was present before/after, and
  flags authDropped when a previously-present Cline auth block disappears
- RuntimeOAuthTokenManager.resolveProviderApiKeyInternal(): logs each
  decision point (no_settings, no_credentials, refresh_start, refresh_null,
  refreshed+saved, not_refreshed) with hashed token fingerprints
- cline.ts refreshClineToken(): logs the refresh request URL, response
  status/errorCode on failure, and new token hashes on success
- cline.ts getValidClineCredentials(): logs the outcome at each branch
  (no_current_credentials, still_valid, needs_refresh, invalid_grant,
  transient_failure_kept_current, transient_failure_expired)

VS Code extension (auth-service.ts):
- readClineCredentials/writeClineCredentials/clearClineCredentials: logs
  credential presence and hashes at each disk I/O point
- refreshAccessToken: logs refresh start, null result (cleared), changed
  (written), or unchanged outcomes
- fetchUserInfoFromApi: logs the GET /api/v1/users/me request and response
  status

What to collect when investigating:

VS Code extension:
- Open the "Cline" output channel (View -> Output -> select "Cline")
- Look for lines containing: [SdkAuthService], providers.read,
  providers.save, oauth.resolve, cline.refresh, cline.getCredentials
- Debug logging is emitted at the DEBUG level; it appears in the output
  channel when IS_DEV=true or in development builds

CLI:
- Set CLINE_LOG_LEVEL=debug environment variable before running cline
- Collect the log file at ~/.cline/data/logs/cline.cli.log (or the path
  set by CLINE_LOG_PATH)
- Look for the same event names as above

Files changed:
- sdk/packages/core/src/auth/auth-debug.ts (NEW): hashSecret,
  setSdkLogger, getSdkLogger, sdkDebug
- sdk/packages/core/src/auth/cline.ts: refresh/getCredentials logging
- sdk/packages/core/src/services/storage/provider-settings-manager.ts:
  read/save logging
- sdk/packages/core/src/runtime/orchestration/runtime-oauth-token-manager.ts:
  resolve logging
- sdk/packages/core/src/index.ts: export early logger utilities
- apps/vscode/src/sdk/auth-service.ts: credential lifecycle logging
- apps/vscode/src/common.ts: register SDK early logger
- apps/cli/src/main.ts: register SDK early logger

* fix(vscode): inline SDK debug metadata into log message string (ENG-2213)

* fix(auth): gate debug logging on CLINE_LOG_LEVEL at runtime (ENG-2213)

* fix(auth): use interpolated debug strings, remove log-level gating (ENG-2213)

* refactor: move early logger to sdk/packages/core/src/logging/early-logger.ts

* fix: address review feedback — early logger registration, log after write, remove getSdkLogger from public API

* fix(vscode): add ISO timestamps to all log lines

* fix core import

* fix import

* fix tests

---------

Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
Co-authored-by: BarreiroT <tomasmbarreiroi@gmail.com>
2026-07-10 23:40:46 +02:00
Saoud Rizwan 261ee4c313 fix(vscode): show requested line range on read-file chat rows (#12225)
The webview already knew how to render readLineStart/readLineEnd on
readFile tool rows, but the SDK message translator never populated
them, so successive ranged reads of the same file all rendered as
identical bare paths. Extract start_line/end_line from read_files
input (per-file and single-path forms) and render open-ended reads
(start_line only) as "start+".
2026-07-10 13:25:30 -07:00
Saoud Rizwan d45b051c04 fix(cli): detect bun global installs after symlink resolution in auto-update (#12224) 2026-07-10 12:15:26 -07:00
Bee 78c83cdf33 fix(cli): preserve session id when in same session (#12188) 2026-07-10 17:59:55 +08:00
Bee 3266121fa1 feat(desktop): add typography spec (#12215)
* feat(desktop): add typography spec

* remove unused background component
2026-07-09 19:34:28 -07:00
Bee 6467de65a2 fix(plugin): follow up fix for agent-squad (#12216)
Follow up on my last PR where the last commit revert the removal of the regex field from zod schema
2026-07-09 19:33:04 -07:00
Bee 65fe885638 fix(sdk): remove regex from zod schema for agent-squad plugin example (#12214)
* fix(sdk): remove regex from zod schema for agent-squad plugin example

The `HandoffPathInput` schema used negative lookaheads to reject absolute paths and `..` traversal segments. When converted to JSON Schema, this regex caused consumers without lookaround support to fail with `invalid JSON schema: regex lookaround is not supported`.

This change removes the lookaround-based regex from the published schema and moves those checks to runtime validation. It preserves validation for allowed characters, absolute paths, traversal segments, and maximum length while strengthening cross-platform directory containment checks using Node’s path utilities.

* add back logger examples
2026-07-10 10:19:27 +08:00
Bee c3033d6f13 fix(vscode): refreshGroqModels caused cacheReadsPrice undefined error (#12213) 2026-07-10 08:52:06 +08:00
Max cfb1327a1b fix vscode hmr not working (#12212) 2026-07-09 16:49:35 -07:00
Alex Taboada 264af96e1b fix(vscode): prevent infinite loading when initializing task with an image (#12203) 2026-07-09 18:16:52 +02:00
Robin Newhouse 10cb9bd97a Add compaction budget hardening (#12142)
* Add compaction budget projection contract

* Tighten budget projection contract types

* Tighten dropped block action paths

* Add pure compaction budget projection engine

* Fix budget projection truncation accounting

* Drop provider-native blocks during budget projection

* Recompute protected tail after thinking pruning

* Align budget projection test tool results

* Clean up budget projection fixture indentation

* fix(core): narrow compaction protected tail

* Fix budget projection action accounting

* Budget agentic compaction summary input

* Harden agentic summary budget fallback

* Align agentic compaction test tool result

* Align agentic file ops with projected input

* Budget basic compaction projections

* Clarify basic projection budget logging

* Align basic sanitization image expectation

* Align basic compaction budget expectation

* Emit compaction budget emergency telemetry

* Tighten compaction budget telemetry types

* Preserve compaction status notice reasons

* fix(core): account compaction tokens consistently

* fix(core): align skipped compaction token accounting
2026-07-09 02:15:55 -07:00
106 changed files with 5510 additions and 706 deletions
+1 -1
View File
@@ -123,7 +123,7 @@
"background": {
"activeOnStart": true,
"beginsPattern": "^Building webview for|^\\s*VITE",
"endsPattern": "^.*Local:\\s+http://localhost:[0-9]+/"
"endsPattern": "^.*Local:\\s+http://127\\.0\\.0\\.1:[0-9]+/"
}
}
],
+10
View File
@@ -1,5 +1,15 @@
# Cline CLI Changelog
## 3.0.40
- Added a manual API key escape hatch for Cline OAuth providers, so you can enter a key by hand from settings
- Fixed provider config not reloading when switching models
- Fixed auto-update failing to detect Bun global installs after symlink resolution
- Fixed unexpected logouts caused by transient network or server errors during token refresh
- The ClinePass usage-limit error is now surfaced clearly when you hit the limit
- Session id is now preserved when continuing within the same session
- Hardened context compaction budget handling
## 3.0.39
- You can now select Cline free models on the ClinePass provider in the model picker
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@cline/cli",
"displayName": "cline",
"version": "3.0.39",
"version": "3.0.40",
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
"type": "module",
"publishConfig": {
+16
View File
@@ -101,6 +101,22 @@ describe("getInstallationInfo", () => {
});
});
it("detects bun global installs from the resolved install path", () => {
// bun symlinks ~/.bun/bin/cline -> ~/.bun/install/global/node_modules/...,
// and realpathSync resolves through the symlink before detection runs.
const wrapperPath = createTempFile(
".bun/install/global/node_modules/cline/bin/cline",
);
process.env.CLINE_WRAPPER_PATH = wrapperPath;
process.argv = ["bun", "/$bunfs/root/cline", "update", "--verbose"];
expect(getInstallationInfo("1.2.3")).toEqual({
packageManager: PackageManager.BUN,
packageName: "cline",
updateCommand: "bun add -g cline@latest",
});
});
it("falls back to unknown when only Bun's virtual compiled path is available", () => {
delete process.env.CLINE_WRAPPER_PATH;
process.argv = ["bun", "/$bunfs/root/cline", "update", "--verbose"];
+6 -1
View File
@@ -118,7 +118,12 @@ export function getInstallationInfo(currentVersion: string): InstallationInfo {
updateCommand: `yarn global add ${DEFAULT_PACKAGE_NAME}@${tag}`,
};
}
if (scriptPath.includes("/.bun/bin")) {
// `bun add -g` symlinks bins into ~/.bun/bin, but realpathSync resolves
// them to ~/.bun/install/global/node_modules/..., so match both.
if (
scriptPath.includes("/.bun/bin") ||
scriptPath.includes("/.bun/install/global/")
) {
return {
packageManager: PackageManager.BUN,
packageName: DEFAULT_PACKAGE_NAME,
+2 -1
View File
@@ -158,8 +158,9 @@ vi.mock("./runtime/run-interactive", () => {
});
vi.mock("./utils/session", () => sessionMocks);
vi.mock("./session/session", () => sessionMocks);
vi.mock("@cline/core", () => {
vi.mock("@cline/core", async () => {
return {
...(await vi.importActual("@cline/core")),
resolveProviderConfig: llmMocks.resolveProviderConfig,
createTeamName: vi.fn(() => "team-test"),
createUserInstructionConfigService: vi.fn(() => ({
+11
View File
@@ -928,6 +928,17 @@ export async function runCli(): Promise<void> {
runAgent,
} = await loadCliRuntimeModules();
// Register the SDK early logger as early as possible — before any
// provider settings reads — so the full startup sequence is captured.
// These components operate before/outside ClineCore sessions, so the
// session-scoped logger can't reach them.
const { createCliLoggerAdapter } = await import("./logging/adapter");
const loggerAdapter = createCliLoggerAdapter({
runtime: "cli",
component: "main",
});
coreServer.setSdkLogger(loggerAdapter.core);
const userInstructionService = createUserInstructionConfigService({
skills: {
workspacePath: workspaceRoot,
@@ -157,6 +157,7 @@ function makeManager() {
ingestHookEvent: vi.fn(),
subscribe: vi.fn(),
updateSessionModel: vi.fn(),
updateSessionConnection: vi.fn(async () => {}),
pendingPrompts: {
update: vi.fn(),
},
@@ -814,6 +815,83 @@ describe("createInteractiveSessionRuntime", () => {
expect(runtime.getActiveSessionId()).toBe("session-2");
});
it("preserves the session id and applies changed provider config when restarting with the current messages", async () => {
const manager = makeManager();
const config = {
...createConfig(),
providerId: "cline",
modelId: "anthropic/claude-sonnet-4.6",
apiKey: "cline-key",
};
const messages: Message[] = [
{ role: "user", content: [{ type: "text", text: "hello" }] },
];
manager.readMessages.mockResolvedValue(messages);
const runtime = await makeRuntime(manager, { config });
await runtime.ensureReady();
expect(manager.start).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
config: expect.objectContaining({
providerId: "cline",
modelId: "anthropic/claude-sonnet-4.6",
apiKey: "cline-key",
}),
}),
);
config.providerId = "openai-compatible";
config.modelId = "custom-model";
config.apiKey = "new-key";
await runtime.restartWithCurrentMessages();
expect(manager.start).toHaveBeenCalledTimes(2);
expect(manager.start).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
config: expect.objectContaining({
sessionId: "session-1",
providerId: "openai-compatible",
modelId: "custom-model",
apiKey: "new-key",
}),
initialMessages: messages,
}),
);
});
it("updates the active session connection in place without restarting", async () => {
const manager = makeManager();
const runtime = await makeRuntime(manager);
await runtime.ensureReady();
await runtime.updateCurrentSessionConnection({
providerId: "openai",
modelId: "codex-test",
});
expect(manager.updateSessionConnection).toHaveBeenCalledWith("session-1", {
providerId: "openai",
modelId: "codex-test",
});
expect(manager.start).toHaveBeenCalledTimes(1);
expect(runtime.getActiveSessionId()).toBe("session-1");
});
it("does not reuse the session id when restarting empty", async () => {
const manager = makeManager();
const runtime = await makeRuntime(manager);
await runtime.ensureReady();
await runtime.restartEmpty();
expect(manager.start).toHaveBeenCalledTimes(2);
const secondStart = manager.start.mock.calls[1]?.[0] as {
config?: { sessionId?: string };
};
expect(secondStart?.config?.sessionId).toBeUndefined();
});
it("recovers empty read-driven restarts when the active interactive session disappeared", async () => {
const manager = makeManager();
manager.readMessages.mockRejectedValueOnce(
@@ -49,6 +49,9 @@ type RuntimeHooks = ReturnType<typeof createRuntimeHooks>;
type StartedSession = Awaited<ReturnType<CliCore["start"]>>;
type CurrentTurnInput = Omit<Parameters<CliCore["send"]>[0], "sessionId">;
type CurrentTurnResult = Awaited<ReturnType<CliCore["send"]>>;
export type SessionConnectionUpdate = Parameters<
CliCore["updateSessionConnection"]
>[1];
type AskQuestionRef = {
current: ((question: string, options: string[]) => Promise<string>) | null;
};
@@ -210,12 +213,18 @@ export function createInteractiveSessionRuntime(input: {
initial: Message[] = [],
sessionMetadata?: Record<string, unknown>,
initialCompactionState?: SessionCompactionState,
// Restarting an old session associate with this ID,
// For continuing the same conversation, e.g. after a config change.
sessionId?: string,
): Promise<void> => {
const generation = sessionStartGeneration;
const manager = await ensureSessionManager();
const started = await manager.start({
source: SessionSource.CLI,
config: buildSessionConfig(),
config: {
...buildSessionConfig(),
...(sessionId ? { sessionId } : {}),
},
toolPolicies: input.config.toolPolicies,
interactive: true,
initialMessages: initial,
@@ -415,7 +424,14 @@ export function createInteractiveSessionRuntime(input: {
messages: Message[],
sessionMetadata?: Record<string, unknown>,
initialCompactionState?: SessionCompactionState,
options?: { preserveSessionId?: boolean },
): Promise<void> => {
// Config-only restarts (model/mode/account changes) continue the same
// conversation, so they must keep the session id — otherwise each
// restart mints a new session history entry for the same conversation.
const reuseSessionId = options?.preserveSessionId
? activeSessionId || undefined
: undefined;
sessionStartGeneration += 1;
pendingResumeSessionId = undefined;
startupError = undefined;
@@ -431,6 +447,7 @@ export function createInteractiveSessionRuntime(input: {
messages,
sessionMetadata,
initialCompactionState,
reuseSessionId,
);
})().catch((error) => {
startupError = error;
@@ -473,9 +490,24 @@ export function createInteractiveSessionRuntime(input: {
systemPrompt: compactionState?.system_prompt,
})
: undefined,
{ preserveSessionId: true },
);
};
const updateCurrentSessionConnection = async (
update: SessionConnectionUpdate,
): Promise<void> => {
await ensureReady();
const manager = sessionManager;
const sessionId = activeSessionId;
if (!manager || !sessionId) {
// No live session to update; the next startup builds its config from
// the already-mutated CLI config, so nothing else is needed.
return;
}
await manager.updateSessionConnection(sessionId, update);
};
const restartEmpty = async (): Promise<void> => {
await restartWithMessages([]);
};
@@ -840,6 +872,7 @@ export function createInteractiveSessionRuntime(input: {
resetForNewSession,
restartWithMessages,
restartWithCurrentMessages,
updateCurrentSessionConnection,
resumeSession,
forkCurrentSession,
compactCurrentSession,
+153
View File
@@ -43,6 +43,15 @@ const CLI_SUBSCRIPTION_URL =
"https://app.cline.bot/promo?code=CLI-8OFF&personal=true";
const SDK_CLINE_PASS_SUBSCRIPTION_MESSAGE = `No access to ClinePass subscription models yet. Subscribe to ClinePass, the low cost open weights model coding plan: ${CLINE_PASS_SUBSCRIPTION_URL}`;
const CLI_CLINE_PASS_SUBSCRIPTION_MESSAGE = `No access to ClinePass subscription models yet. Subscribe to ClinePass, the low cost open weights model coding plan: ${CLI_SUBSCRIPTION_URL}`;
const CLINE_PASS_LIMIT_DETAIL_MESSAGE =
"You have reached your 5-hour Clinepass limit. The limit resets in 5h, please try again later.";
const CLI_CLINE_PASS_LIMIT_MESSAGE = [
"ClinePass limit reached",
CLINE_PASS_LIMIT_DETAIL_MESSAGE,
"Switch to Cline usage-based billing and retry with the Cline provider.",
"Interactive CLI: open the model selector with /model, choose Cline, then retry.",
"Headless CLI: rerun with --provider cline.",
].join("\n");
const CLINE_ORG_INDIVIDUAL_INFERENCE_SUBSCRIPTION_MESSAGE =
"Organization accounts cannot use ClinePass subscriptions. Go to /account -> change account to switch to your personal account for ClinePass";
@@ -65,6 +74,30 @@ vi.mock("@cline/core", () => ({
.includes(
"organization accounts cannot use individual model inference subscriptions",
),
isClinePassLimitError: (error: unknown) =>
error instanceof Error && error.name === "ClinePassLimitError",
extractClinePassLimitMessage: (text: string) => {
const normalized = text.toLowerCase();
const prefix = "you have reached your";
const suffix = "please try again later.";
const start = normalized.indexOf(prefix);
if (start === -1) return undefined;
const suffixStart = normalized.indexOf(suffix, start);
if (suffixStart === -1) return undefined;
const end = suffixStart + suffix.length;
if (!normalized.slice(start, end).includes("clinepass limit")) {
return undefined;
}
return text.slice(start, end);
},
isClinePassLimitMessage: (text: string) => {
const normalized = text.toLowerCase();
return (
normalized.includes("you have reached your") &&
normalized.includes("clinepass limit") &&
normalized.includes("please try again later.")
);
},
prewarmFileIndex: vi.fn(async () => undefined),
SessionSource: {
CLI: "cli",
@@ -769,6 +802,126 @@ describe("runAgent", () => {
expect(outputMocks.writeErr).not.toHaveBeenCalled();
});
it("formats ClinePass limit errors with usage-based billing guidance", async () => {
const startedAt = new Date("2026-03-22T00:00:00.000Z");
const endedAt = new Date("2026-03-22T00:00:01.000Z");
sessionManagerMocks.start.mockResolvedValue({
sessionId: "session-1",
manifestPath: "/tmp/manifest.json",
messagesPath: "/tmp/messages.json",
manifest: { session_id: "session-1" },
result: {
text: `Error: ${CLINE_PASS_LIMIT_DETAIL_MESSAGE}`,
usage: {
inputTokens: 0,
outputTokens: 0,
cacheReadTokens: 0,
cacheWriteTokens: 0,
totalCost: 0,
},
messages: [],
toolCalls: [],
iterations: 1,
finishReason: "error",
model: { id: "premium-model", provider: "cline-pass", info: {} },
startedAt,
endedAt,
durationMs: 1000,
},
});
sessionManagerMocks.getAccumulatedUsage.mockResolvedValue(undefined);
const { runAgent } = await import("./run-agent");
await expect(
runAgent("test prompt", {
cwd: process.cwd(),
enableAgentTeams: false,
enableSpawnAgent: false,
enableTools: [],
execution: { maxConsecutiveMistakes: 3 },
logger: undefined,
mode: "yolo",
modelId: "premium-model",
outputMode: "text",
providerId: "cline-pass",
systemPrompt: "system",
thinking: false,
toolPolicies: { "*": { autoApprove: true } },
verbose: false,
workspaceRoot: process.cwd(),
} as never),
).resolves.toBeUndefined();
expect(process.exitCode).toBe(1);
expect(outputMocks.writeErr).toHaveBeenCalledWith(
CLI_CLINE_PASS_LIMIT_MESSAGE,
);
});
it("does not duplicate ClinePass limit errors already displayed by agent events", async () => {
const startedAt = new Date("2026-03-22T00:00:00.000Z");
const endedAt = new Date("2026-03-22T00:00:01.000Z");
sessionManagerMocks.start.mockImplementation(async () => {
sessionEventsMocks.listener?.({
type: "error",
error: new Error(`Error: ${CLINE_PASS_LIMIT_DETAIL_MESSAGE}`),
recoverable: false,
});
return {
sessionId: "session-1",
manifestPath: "/tmp/manifest.json",
messagesPath: "/tmp/messages.json",
manifest: { session_id: "session-1" },
result: {
text: `Error: ${CLINE_PASS_LIMIT_DETAIL_MESSAGE}`,
usage: {
inputTokens: 0,
outputTokens: 0,
cacheReadTokens: 0,
cacheWriteTokens: 0,
totalCost: 0,
},
messages: [],
toolCalls: [],
iterations: 1,
finishReason: "error",
model: { id: "premium-model", provider: "cline-pass", info: {} },
startedAt,
endedAt,
durationMs: 1000,
},
};
});
sessionManagerMocks.getAccumulatedUsage.mockResolvedValue(undefined);
const { runAgent } = await import("./run-agent");
await expect(
runAgent("test prompt", {
cwd: process.cwd(),
enableAgentTeams: false,
enableSpawnAgent: false,
enableTools: [],
execution: { maxConsecutiveMistakes: 3 },
logger: undefined,
mode: "yolo",
modelId: "premium-model",
outputMode: "text",
providerId: "cline-pass",
systemPrompt: "system",
thinking: false,
toolPolicies: { "*": { autoApprove: true } },
verbose: false,
workspaceRoot: process.cwd(),
} as never),
).resolves.toBeUndefined();
expect(process.exitCode).toBe(1);
expect(outputMocks.writeErr).not.toHaveBeenCalled();
});
it("surfaces post-run bookkeeping failures after a completed result", async () => {
const startedAt = new Date("2026-03-22T00:00:00.000Z");
const endedAt = new Date("2026-03-22T00:00:01.000Z");
+72 -2
View File
@@ -1,5 +1,9 @@
import { describe, expect, it } from "vitest";
import { resolveReasoningForModelChange } from "./run-interactive";
import { describe, expect, it, vi } from "vitest";
import type { Config } from "../utils/types";
import {
applyInteractiveModelChange,
resolveReasoningForModelChange,
} from "./run-interactive";
describe("resolveReasoningForModelChange", () => {
it("persists disabled reasoning only when thinking is explicitly false", () => {
@@ -38,3 +42,69 @@ describe("resolveReasoningForModelChange", () => {
).toEqual({ enabled: true, effort: "medium" });
});
});
describe("applyInteractiveModelChange", () => {
it("restarts with the current transcript so a provider switch reloads its complete configuration", async () => {
const config = {
providerId: "openai-compatible",
modelId: "custom-model",
apiKey: "new-key",
thinking: undefined,
reasoningEffort: undefined,
} as Config;
const getProviderSettings = vi.fn(() => ({
provider: "openai-compatible",
apiKey: "new-key",
baseUrl: "https://example.com/v1",
headers: { "X-Custom-Header": "custom-value" },
client: "openai-compatible" as const,
protocol: "openai-chat" as const,
model: "old-model",
}));
const saveProviderSettings = vi.fn(() => ({
version: 1 as const,
providers: {},
}));
const ensureReady = vi.fn(async () => {});
const restartWithCurrentMessages = vi.fn(async () => {});
const updateCurrentSessionConnection = vi.fn(async () => {});
await applyInteractiveModelChange({
config,
providerSettingsManager: {
getProviderSettings,
saveProviderSettings,
},
sessionRuntime: {
ensureReady,
restartWithCurrentMessages,
updateCurrentSessionConnection,
},
});
expect(saveProviderSettings).toHaveBeenCalledWith({
provider: "openai-compatible",
apiKey: "new-key",
baseUrl: "https://example.com/v1",
headers: { "X-Custom-Header": "custom-value" },
client: "openai-compatible",
protocol: "openai-chat",
model: "custom-model",
});
expect(ensureReady).toHaveBeenCalledOnce();
expect(restartWithCurrentMessages).toHaveBeenCalledOnce();
expect(updateCurrentSessionConnection).toHaveBeenCalledWith({
providerId: "openai-compatible",
modelId: "custom-model",
});
expect(ensureReady.mock.invocationCallOrder[0]).toBeLessThan(
restartWithCurrentMessages.mock.invocationCallOrder[0] ?? 0,
);
expect(saveProviderSettings.mock.invocationCallOrder[0]).toBeLessThan(
restartWithCurrentMessages.mock.invocationCallOrder[0] ?? 0,
);
expect(restartWithCurrentMessages.mock.invocationCallOrder[0]).toBeLessThan(
updateCurrentSessionConnection.mock.invocationCallOrder[0] ?? 0,
);
});
});
+50 -18
View File
@@ -82,6 +82,51 @@ export function resolveReasoningForModelChange(
return existing.reasoning;
}
export async function applyInteractiveModelChange(input: {
config: Config;
providerSettingsManager: Pick<
ProviderSettingsManager,
"getProviderSettings" | "saveProviderSettings"
>;
sessionRuntime: Pick<
ReturnType<typeof createInteractiveSessionRuntime>,
| "ensureReady"
| "restartWithCurrentMessages"
| "updateCurrentSessionConnection"
>;
}): Promise<void> {
const { config, providerSettingsManager, sessionRuntime } = input;
await sessionRuntime.ensureReady();
await onProviderChange({
config,
providerId: config.providerId,
});
const existing = providerSettingsManager.getProviderSettings(
config.providerId,
) ?? {
provider: config.providerId,
};
const reasoning = resolveReasoningForModelChange(config, existing);
providerSettingsManager.saveProviderSettings({
...existing,
model: config.modelId,
...(reasoning === undefined ? {} : { reasoning }),
});
// Provider changes affect more than the model connection: startup resolves
// the endpoint, headers, provider-specific options, tools, and plugins. Rebuild
// the runtime with the existing transcript so all of that state changes
// together. restartWithCurrentMessages preserves the session ID.
await sessionRuntime.restartWithCurrentMessages();
// A same-ID restart reuses the existing manifest. Sync its connection label
// after the fully configured runtime is live so session history reflects the
// provider/model that will handle subsequent turns.
await sessionRuntime.updateCurrentSessionConnection({
providerId: config.providerId,
modelId: config.modelId,
});
}
export async function runInteractive(
config: Config,
userInstructionService?: UserInstructionConfigService,
@@ -687,25 +732,12 @@ export async function runInteractive(
onNewSession: async () => {
await sessionRuntime.resetForNewSession();
},
onModelChange: async () => {
await sessionRuntime.ensureReady();
await onProviderChange({
onModelChange: () =>
applyInteractiveModelChange({
config,
providerId: config.providerId,
});
const existing = providerSettingsManager.getProviderSettings(
config.providerId,
) ?? {
provider: config.providerId,
};
const reasoning = resolveReasoningForModelChange(config, existing);
providerSettingsManager.saveProviderSettings({
...existing,
model: config.modelId,
...(reasoning === undefined ? {} : { reasoning }),
});
await sessionRuntime.restartWithCurrentMessages();
},
providerSettingsManager,
sessionRuntime,
}),
onSessionRestart: async () => {
await sessionRuntime.ensureReady();
await sessionRuntime.restartEmpty();
@@ -5,9 +5,11 @@ import { useEffect, useState } from "react";
import "opentui-spinner/react";
import {
getClineOrgIndividualInferenceSubscriptionMessage,
getClinePassLimitDetailMessage,
getCliSubscriptionUrl,
getIndividualPlanFeatures,
isClineOrgIndividualInferenceSubscriptionErrorMessage,
isClinePassLimitErrorMessage,
isClinePassSubscriptionError,
} from "../../utils/cline-pass-errors";
import {
@@ -419,6 +421,54 @@ function ClineOrgIndividualInferenceSubscriptionErrorView(props: {
);
}
function ClinePassLimitErrorView(props: {
message: string;
defaultFg?: string;
terminalTheme: TerminalTheme;
}) {
const detail = getClinePassLimitDetailMessage(props.message) ?? props.message;
return (
<box flexDirection="row">
<text fg={palette.act} content="* " />
<box
flexDirection="column"
border
borderStyle="rounded"
borderColor={palette.act}
paddingX={1}
>
<text fg="red">ClinePass limit reached</text>
<text fg={props.defaultFg} selectable content={detail} />
<text
fg={props.defaultFg}
selectable
content="Switch to Cline usage-based billing and retry with the Cline provider."
/>
<box flexDirection="row">
<text fg="gray">Interactive CLI: </text>
<text
fg={props.defaultFg}
selectable
content="type /model, press tab to change provider, choose Cline, then retry."
/>
</box>
<box flexDirection="row">
<text fg="gray">Headless CLI: </text>
<text fg={props.defaultFg} selectable content="rerun with " />
<code
content="--provider cline"
filetype="bash"
syntaxStyle={getSyntaxStyle(props.terminalTheme)}
selectable
/>
<text fg={props.defaultFg} selectable content="." />
</box>
</box>
</box>
);
}
export function ChatEntryView(props: {
entry: ChatEntry;
accent?: string;
@@ -534,6 +584,15 @@ export function ChatEntryView(props: {
/>
);
}
if (isClinePassLimitErrorMessage(entry.text)) {
return (
<ClinePassLimitErrorView
message={entry.text}
defaultFg={defaultFg}
terminalTheme={terminalTheme}
/>
);
}
return (
<box flexDirection="row">
<text fg="red" content="* " />
@@ -1,8 +1,50 @@
import {
getProviderAuthStorageId,
type ProviderSettingsManager,
saveLocalProviderSettings,
} from "@cline/core";
import { CLI_PROMO_CODE } from "../../../utils/cline-pass-errors";
const CLINE_PASS_SUBSCRIPTION_PATH = "/dashboard/subscription";
const DEFAULT_APP_BASE_URL = "https://app.cline.bot";
/**
* Persist a manually entered API key for an OAuth-capable provider — the
* escape hatch for when OAuth login isn't working. Any stored OAuth tokens
* are cleared: the auth handler prefers auth.accessToken over apiKey, so a
* stale token would otherwise keep winning over the manual key.
*
* The key is written both to the provider's auth storage entry (cline-pass
* stores credentials under "cline") and to the provider's own entry: settings
* resolution lets a direct entry shadow the storage entry, and provider
* switching copies merged settings (including auth) into direct entries, so
* both must be updated for the manual key to reliably take effect.
*/
export function saveManualProviderApiKey(
manager: ProviderSettingsManager,
providerId: string,
apiKey: string,
): void {
// Empty strings delete these keys from the stored auth object.
const clearedAuth = { accessToken: "", refreshToken: "", apiKey: "" };
const storageProviderId = getProviderAuthStorageId(providerId) ?? providerId;
saveLocalProviderSettings(manager, {
providerId: storageProviderId,
apiKey,
auth: clearedAuth,
});
if (
providerId !== storageProviderId &&
manager.read().providers[providerId]
) {
saveLocalProviderSettings(manager, {
providerId,
apiKey,
auth: clearedAuth,
});
}
}
export function buildClinePassSubscriptionPageUrl(
appBaseUrl: string | undefined,
): string {
@@ -1,5 +1,16 @@
import { describe, expect, it } from "vitest";
import { buildClinePassSubscriptionPageUrl } from "./provider-picker-helpers";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { ProviderSettingsManager } from "@cline/core";
import { afterEach, describe, expect, it } from "vitest";
import {
getPersistedProviderApiKey,
isProviderConfigured,
} from "../../../utils/provider-auth";
import {
buildClinePassSubscriptionPageUrl,
saveManualProviderApiKey,
} from "./provider-picker-helpers";
describe("buildClinePassSubscriptionPageUrl", () => {
it("opens the personal subscription page on production by default", () => {
@@ -16,3 +27,99 @@ describe("buildClinePassSubscriptionPageUrl", () => {
);
});
});
describe("saveManualProviderApiKey", () => {
const tempDirs: string[] = [];
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
rmSync(dir, { force: true, recursive: true });
}
});
function createManager(): ProviderSettingsManager {
const dir = mkdtempSync(join(tmpdir(), "cline-cli-provider-picker-"));
tempDirs.push(dir);
return new ProviderSettingsManager({
filePath: join(dir, "providers.json"),
});
}
it("clears stored OAuth tokens so the manual key takes effect", () => {
const manager = createManager();
manager.saveProviderSettings({
provider: "cline",
auth: {
accessToken: "stale-access-token",
refreshToken: "stale-refresh-token",
accountId: "acct_123",
},
});
saveManualProviderApiKey(manager, "cline", "manual-api-key");
const settings = manager.getProviderSettings("cline");
expect(settings?.apiKey).toBe("manual-api-key");
expect(settings?.auth?.accessToken).toBeUndefined();
expect(settings?.auth?.refreshToken).toBeUndefined();
expect(settings?.auth?.accountId).toBe("acct_123");
expect(getPersistedProviderApiKey("cline", settings)).toBe(
"manual-api-key",
);
expect(isProviderConfigured("cline", settings)).toBe(true);
});
it("saves cline-pass keys to the shared cline auth storage entry", () => {
const manager = createManager();
manager.saveProviderSettings({
provider: "cline",
auth: {
accessToken: "stale-access-token",
refreshToken: "stale-refresh-token",
},
});
saveManualProviderApiKey(manager, "cline-pass", "manual-api-key");
// cline-pass inherits auth storage from the "cline" entry, so the key
// must land there and the stale tokens must be gone for both providers.
const clineSettings = manager.getProviderSettings("cline");
expect(clineSettings?.apiKey).toBe("manual-api-key");
expect(clineSettings?.auth?.accessToken).toBeUndefined();
const clinePassSettings = manager.getProviderSettings("cline-pass");
expect(getPersistedProviderApiKey("cline-pass", clinePassSettings)).toBe(
"manual-api-key",
);
expect(isProviderConfigured("cline-pass", clinePassSettings)).toBe(true);
});
it("clears stale credentials copied into a direct cline-pass entry", () => {
const manager = createManager();
manager.saveProviderSettings({
provider: "cline",
auth: {
accessToken: "stale-access-token",
refreshToken: "stale-refresh-token",
},
});
// Provider switching copies the merged settings (including auth) into
// a direct cline-pass entry, which shadows the shared "cline" entry.
manager.saveProviderSettings({
provider: "cline-pass",
apiKey: "stale-copied-key",
auth: {
accessToken: "stale-access-token",
refreshToken: "stale-refresh-token",
},
});
saveManualProviderApiKey(manager, "cline-pass", "manual-api-key");
const clinePassSettings = manager.getProviderSettings("cline-pass");
expect(clinePassSettings?.auth?.accessToken).toBeUndefined();
expect(getPersistedProviderApiKey("cline-pass", clinePassSettings)).toBe(
"manual-api-key",
);
});
});
@@ -37,7 +37,10 @@ import {
getSearchableListRowsWindow,
type SearchableItem,
} from "../searchable-list";
import { buildClinePassSubscriptionPageUrl } from "./provider-picker-helpers";
import {
buildClinePassSubscriptionPageUrl,
saveManualProviderApiKey,
} from "./provider-picker-helpers";
interface ProviderItem {
id: string;
@@ -724,13 +727,27 @@ export function CodexCliStatusContent(
);
}
/**
* Resolves `true` on successful login, `"use_api_key"` when the user opts
* into manual API key entry (only offered with `allowApiKeyFallback`).
*/
export type OAuthLoginResult = boolean | "use_api_key";
export function OAuthLoginContent(
props: ChoiceContext<boolean> & {
props: ChoiceContext<OAuthLoginResult> & {
providerId: string;
providerName: string;
allowApiKeyFallback?: boolean;
},
) {
const { resolve, dismiss, dialogId, providerId, providerName } = props;
const {
resolve,
dismiss,
dialogId,
providerId,
providerName,
allowApiKeyFallback,
} = props;
const [mode, setMode] = useState<"browser" | "device">(
providerId === "cline" ? "device" : "browser",
);
@@ -863,9 +880,18 @@ export function OAuthLoginContent(
if (key.name === "escape") {
cancelAuthAttempt();
dismiss();
return;
}
if (key.name === "k" && allowApiKeyFallback) {
cancelAuthAttempt();
resolve("use_api_key");
}
}, dialogId);
const escapeHint = allowApiKeyFallback
? "K to enter an API key instead, Esc to cancel"
: "Esc to cancel";
if (mode === "device") {
return (
<box flexDirection="column" paddingX={1} gap={1}>
@@ -893,7 +919,7 @@ export function OAuthLoginContent(
{deviceError && <text fg="red">{deviceError}</text>}
<text fg="gray">
<em>Esc to cancel</em>
<em>{escapeHint}</em>
</text>
</box>
);
@@ -916,7 +942,82 @@ export function OAuthLoginContent(
{error && <text fg="red">{error}</text>}
<text fg="gray">
<em>Esc to cancel</em>
<em>{escapeHint}</em>
</text>
</box>
);
}
/**
* Manual API key entry for OAuth-capable providers — the escape hatch for
* when OAuth login isn't working. Saving clears any stored OAuth tokens so
* the manual key takes effect (see saveManualProviderApiKey).
*/
export function OAuthApiKeyInputContent(
props: ChoiceContext<boolean> & {
providerId: string;
providerName: string;
providerSettingsManager: ProviderSettingsManager;
},
) {
const {
resolve,
dismiss,
dialogId,
providerId,
providerName,
providerSettingsManager,
} = props;
const [value, setValue] = useState("");
const submit = () => {
const apiKey = value.trim();
if (!apiKey) return;
saveManualProviderApiKey(providerSettingsManager, providerId, apiKey);
resolve(true);
};
useDialogKeyboard((key) => {
if (key.name === "escape") {
dismiss();
return;
}
if (key.name === "return") {
submit();
}
}, dialogId);
return (
<box flexDirection="column" paddingX={1} gap={1}>
<text fg={palette.act}>
<strong>{providerName}</strong>
</text>
<text fg="gray">
Use an API key from your Cline dashboard instead of OAuth login. This
replaces any saved login tokens.
</text>
<box flexDirection="column">
<text fg="gray">API key</text>
<box
border
borderStyle="rounded"
borderColor={palette.act}
paddingX={1}
>
<input
value={value}
onInput={setValue}
placeholder="Paste your API key"
flexGrow={1}
focused
/>
</box>
</box>
<text fg="gray">
<em>Enter to save, Esc to go back</em>
</text>
</box>
);
@@ -7,7 +7,10 @@ import {
type AccountDialogAction,
AccountDialogContent,
} from "../components/dialogs/account-dialog";
import { OAuthLoginContent } from "../components/dialogs/provider-picker";
import {
OAuthLoginContent,
type OAuthLoginResult,
} from "../components/dialogs/provider-picker";
import type { OpenModelSelectorOptions } from "./use-model-selector";
export function useAccountDialog(opts: {
@@ -60,14 +63,14 @@ export function useAccountDialog(opts: {
return;
}
if (action === "login") {
const saved = await dialog.choice<boolean>({
const saved = await dialog.choice<OAuthLoginResult>({
style: { maxHeight: termHeight - 2 },
closeOnEscape: false,
content: (ctx: ChoiceContext<boolean>) => (
content: (ctx: ChoiceContext<OAuthLoginResult>) => (
<OAuthLoginContent {...ctx} providerId="cline" providerName="Cline" />
),
});
if (saved) {
if (saved === true) {
await onAccountChange?.();
await openAccountDialog();
return;
+27 -2
View File
@@ -6,6 +6,7 @@ import {
refreshProviderModelsFromSource,
resolveProviderConfig,
} from "@cline/core";
import { isClineProvider } from "@cline/shared";
import type { ChoiceContext } from "@opentui-ui/dialog";
import type { DialogActions } from "@opentui-ui/dialog/react";
import { useCallback } from "react";
@@ -21,7 +22,9 @@ import {
ClinePassSubscriptionContent,
CodexCliStatusContent,
type ExistingProviderOption,
OAuthApiKeyInputContent,
OAuthLoginContent,
type OAuthLoginResult,
ProviderConfigInputContent,
ProviderPickerContent,
UseExistingOrReconfigureContent,
@@ -131,6 +134,23 @@ async function runProviderChange(
);
const existingSettings = manager.getProviderSettings(newProviderId);
// Manual API key entry is the escape hatch for when OAuth login isn't
// working; only the Cline providers accept a dashboard API key.
const supportsManualApiKey = isClineProvider(newProviderId);
const openManualApiKeyDialog = async (): Promise<boolean | undefined> =>
await dialog.choice<boolean>({
style: { maxHeight: termHeight - 2 },
closeOnEscape: false,
content: (ctx: ChoiceContext<boolean>) => (
<OAuthApiKeyInputContent
{...ctx}
providerId={newProviderId}
providerName={displayName}
providerSettingsManager={manager}
/>
),
});
let needsAuth = true;
if (isProviderConfigured(newProviderId, existingSettings)) {
let option: ExistingProviderOption | undefined;
@@ -165,17 +185,22 @@ async function runProviderChange(
if (needsAuth) {
let saved: boolean | undefined;
if (isOAuthProvider(newProviderId)) {
saved = await dialog.choice<boolean>({
const loginResult = await dialog.choice<OAuthLoginResult>({
style: { maxHeight: termHeight - 2 },
closeOnEscape: false,
content: (ctx: ChoiceContext<boolean>) => (
content: (ctx: ChoiceContext<OAuthLoginResult>) => (
<OAuthLoginContent
{...ctx}
providerId={newProviderId}
providerName={displayName}
allowApiKeyFallback={supportsManualApiKey}
/>
),
});
saved =
loginResult === "use_api_key"
? await openManualApiKeyDialog()
: loginResult;
} else if (isOpenAICodexCliProvider(newProviderId)) {
saved = await dialog.choice<boolean>({
style: { maxHeight: termHeight - 2 },
@@ -1,10 +1,13 @@
import { describe, expect, it } from "vitest";
import {
formatCliErrorMessage,
getCliClinePassLimitMessage,
getCliNotSubscribedMessage,
getClineOrgIndividualInferenceSubscriptionMessage,
getClinePassLimitDetailMessage,
getCliSubscriptionUrl,
isClineOrgIndividualInferenceSubscriptionErrorMessage,
isClinePassLimitErrorMessage,
isClinePassSubscriptionError,
} from "./cline-pass-errors";
@@ -46,4 +49,22 @@ describe("cline-pass-errors", () => {
).toBe(true);
expect(formatCliErrorMessage(new Error(raw))).toBe(formatted);
});
it("recognizes and formats ClinePass period limit errors with usage-billing guidance", () => {
const raw =
"Error: You have reached your 5-hour Clinepass limit. The limit resets in 5h, please try again later.";
const detail =
"You have reached your 5-hour Clinepass limit. The limit resets in 5h, please try again later.";
expect(isClinePassLimitErrorMessage(raw)).toBe(true);
expect(isClinePassLimitErrorMessage(new Error(raw))).toBe(true);
expect(getClinePassLimitDetailMessage(raw)).toBe(detail);
expect(formatCliErrorMessage(new Error(raw))).toBe(
getCliClinePassLimitMessage(raw),
);
expect(formatCliErrorMessage(new Error(raw))).toContain(
"Switch to Cline usage-based billing",
);
expect(formatCliErrorMessage(new Error(raw))).toContain("--provider cline");
});
});
+41
View File
@@ -1,10 +1,13 @@
import {
type ClineSubscriptionPlan,
extractClinePassLimitMessage,
getClineOrgIndividualInferenceSubscriptionMessage,
isClineNotSubscribedError,
isClineNotSubscribedMessage,
isClineOrgIndividualInferenceSubscriptionError,
isClineOrgIndividualInferenceSubscriptionMessage,
isClinePassLimitError,
isClinePassLimitMessage,
} from "@cline/core";
import { getClineEnvironmentConfig } from "@cline/shared";
@@ -24,6 +27,18 @@ export function getCliNotSubscribedMessage(): string {
return `No access to ClinePass subscription models yet. Subscribe to ClinePass, the low cost open weights model coding plan: ${getCliSubscriptionUrl()}`;
}
export function getCliClinePassLimitMessage(message: string): string {
const detail = getClinePassLimitDetailMessage(message) ?? message.trim();
const lines = [
"ClinePass limit reached",
detail,
"Switch to Cline usage-based billing and retry with the Cline provider.",
"Interactive CLI: open the model selector with /model, choose Cline, then retry.",
"Headless CLI: rerun with --provider cline.",
];
return lines.filter((line) => line.trim().length > 0).join("\n");
}
export function getIndividualPlanFeatures(
plans: ClineSubscriptionPlan[],
): string[] {
@@ -78,6 +93,27 @@ export function isClineOrgIndividualInferenceSubscriptionErrorMessage(
);
}
export function getClinePassLimitDetailMessage(
error: unknown,
): string | undefined {
return extractClinePassLimitMessage(
error instanceof Error ? error.message : String(error),
);
}
export function isClinePassLimitErrorMessage(error: unknown): boolean {
if (isClinePassLimitError(error)) {
return true;
}
if (error instanceof Error) {
return (
error.name === "ClinePassLimitError" ||
isClinePassLimitMessage(error.message)
);
}
return typeof error === "string" && isClinePassLimitMessage(error);
}
export function formatCliErrorMessage(error: unknown): string {
if (isClinePassSubscriptionError(error)) {
return getCliNotSubscribedMessage();
@@ -85,6 +121,11 @@ export function formatCliErrorMessage(error: unknown): string {
if (isClineOrgIndividualInferenceSubscriptionErrorMessage(error)) {
return getClineOrgIndividualInferenceSubscriptionMessage();
}
if (isClinePassLimitErrorMessage(error)) {
return getCliClinePassLimitMessage(
error instanceof Error ? error.message : String(error),
);
}
if (error instanceof Error) {
return error.message;
}
+63 -1
View File
@@ -1,19 +1,64 @@
import type { AgentEvent, TeamEvent } from "@cline/core";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { handleEvent, handleTeamEvent } from "./events";
import {
handleEvent,
handleTeamEvent,
resolveStatusNoticeLabel,
} from "./events";
import { setCurrentOutputMode } from "./output";
import type { Config } from "./types";
describe("resolveStatusNoticeLabel", () => {
it("maps compaction status reasons to stable labels", () => {
expect(
resolveStatusNoticeLabel({
type: "notice",
noticeType: "status",
displayRole: "status",
message: "auto-compacting",
reason: "auto_compaction",
} as AgentEvent),
).toBe("auto-compacting");
expect(
resolveStatusNoticeLabel({
type: "notice",
noticeType: "status",
displayRole: "status",
message: "manual",
reason: "manual_compaction",
} as AgentEvent),
).toBe("compacting");
expect(
resolveStatusNoticeLabel({
type: "notice",
noticeType: "status",
displayRole: "status",
message: "compaction-budget-adjusted",
reason: "compaction_budget_emergency",
} as AgentEvent),
).toBe("context budget adjusted");
});
});
describe("handleEvent text formatting", () => {
let output = "";
let errorOutput = "";
beforeEach(() => {
output = "";
errorOutput = "";
setCurrentOutputMode("text");
vi.spyOn(process.stdout, "write").mockImplementation((chunk: unknown) => {
output += String(chunk);
return true;
});
vi.spyOn(process.stderr, "write").mockImplementation((chunk: unknown) => {
errorOutput += String(chunk);
return true;
});
vi.spyOn(console, "error").mockImplementation((...args: unknown[]) => {
errorOutput += `${args.map(String).join(" ")}\n`;
});
});
it("adds a ⎿ before text that follows a tool block", () => {
@@ -160,6 +205,23 @@ describe("handleEvent text formatting", () => {
expect(output).toContain("── aborted (2 iterations) ──");
});
it("formats ClinePass limit agent errors before writing to stderr", () => {
handleEvent(
{
type: "error",
error: new Error(
"Error: You have reached your 5-hour Clinepass limit. The limit resets in 5h, please try again later.",
),
recoverable: false,
} as unknown as AgentEvent,
{} as Config,
);
expect(errorOutput).toContain("ClinePass limit reached");
expect(errorOutput).toContain("Switch to Cline usage-based billing");
expect(errorOutput).toContain("--provider cline");
});
it("suppresses heartbeat-only team progress messages", () => {
handleTeamEvent({
type: "run_progress",
+9 -3
View File
@@ -1,4 +1,5 @@
import type { AgentEvent, TeamEvent } from "@cline/core";
import { formatCliErrorMessage } from "./cline-pass-errors";
import { formatToolInput, formatToolOutput, truncate } from "./helpers";
import {
c,
@@ -27,8 +28,13 @@ export function resolveStatusNoticeLabel(
if (event.type !== "notice" || event.displayRole !== "status") {
return undefined;
}
if (event.reason === "auto_compaction") {
return "auto-compacting";
switch (event.reason) {
case "auto_compaction":
return "auto-compacting";
case "manual_compaction":
return "compacting";
case "compaction_budget_emergency":
return "context budget adjusted";
}
return event.message.trim() || undefined;
}
@@ -176,7 +182,7 @@ export function handleEvent(event: AgentEvent, config: Config): void {
case "error":
closeInlineStreamIfNeeded();
if (!event.recoverable || config.verbose) {
writeErr(event.error.message);
writeErr(formatCliErrorMessage(event.error));
}
break;
case "notice":
+8 -6
View File
@@ -42,11 +42,12 @@ export function getPersistedProviderApiKey(
* or endpoint config for the provider. Used by the picker to decide whether
* to offer "Use existing configuration?" before opening the configure dialog.
*
* Treats OAuth providers as configured when an access token is present; for
* everything else, any persisted API key, base URL, or model id counts. We
* don't enforce required fields here — the runtime no longer pre-flights
* credentials, so a missing key only matters when the API call actually
* runs and the provider's own auth error is surfaced.
* Treats OAuth providers as configured when an access token or a manually
* saved API key is present (the /settings escape hatch for when OAuth isn't
* working); for everything else, any persisted API key, base URL, or model id
* counts. We don't enforce required fields here — the runtime no longer
* pre-flights credentials, so a missing key only matters when the API call
* actually runs and the provider's own auth error is surfaced.
*/
export function isProviderConfigured(
providerId: string,
@@ -54,7 +55,8 @@ export function isProviderConfigured(
): boolean {
if (!settings) return false;
if (isOAuthProvider(providerId)) {
return Boolean(settings.auth?.accessToken?.trim());
// getPersistedProviderApiKey covers both auth.accessToken and apiKey.
return Boolean(getPersistedProviderApiKey(providerId, settings));
}
if (getPersistedProviderApiKey(providerId, settings)) return true;
if (settings.baseUrl?.trim()) return true;
@@ -20,10 +20,13 @@ import { cn } from "@/lib/utils";
type MarkdownCodeProps = ComponentProps<"code"> & {
"data-block"?: boolean | string;
// react-markdown/streamdown pass the hast `Element` here, whose
// `properties` is a broad `Record`. Keep this assignable from that type
// (rather than a narrow `{ metastring?: string }`) so the component stays
// compatible with `Components` regardless of how strict the resolved
// hast/streamdown types are; the metastring value is validated at read time.
node?: {
properties?: {
metastring?: string;
};
properties?: Record<string, unknown>;
};
};
@@ -67,7 +70,8 @@ const MarkdownCode = ({
);
}
const meta = node?.properties?.metastring;
const metaValue = node?.properties?.metastring;
const meta = typeof metaValue === "string" ? metaValue : undefined;
const startLineMatch = meta?.match(START_LINE_PATTERN);
const startLine = startLineMatch ? Number.parseInt(startLineMatch[1], 10) : 1;
const showLineNumbers = meta ? !NO_LINE_NUMBERS_PATTERN.test(meta) : true;
+2 -1
View File
@@ -24,7 +24,8 @@
"@cline/core": "workspace:*",
"@cline/llms": "workspace:*",
"@cline/shared": "workspace:*",
"@fontsource-variable/geist": "^5.2.8",
"@fontsource-variable/schibsted-grotesk": "^5.2.8",
"@fontsource/azeret-mono": "^5.2.9",
"@hookform/resolvers": "^3.9.1",
"@radix-ui/react-accordion": "1.2.12",
"@radix-ui/react-alert-dialog": "1.1.15",
@@ -1,6 +1,7 @@
import { existsSync, readFileSync } from "node:fs";
import { basename, join } from "node:path";
import {
buildConnectionUpdate,
buildWorkspaceMetadata,
type ClineCore,
type CoreSessionConfig,
@@ -179,58 +180,43 @@ function buildCoreSessionConfig(config: JsonRecord): JsonRecord {
export function buildSessionConnectionUpdate(
config: JsonRecord,
): SessionConnectionUpdate {
const thinking =
typeof config.thinking === "boolean" ? config.thinking : undefined;
const reasoningEffort = readReasoningEffort(config.reasoningEffort);
const thinkingBudgetTokens = readPositiveInteger(
config.thinkingBudgetTokens ?? config.thinking_budget_tokens,
);
const updates: SessionConnectionUpdate = {};
// Coerce the untrusted webview JSON (snake_case aliases, blank strings)
// into typed fields; the thinking/reasoning transition rules live in the
// shared @cline/core builder.
const providerId = String(config.provider ?? config.providerId ?? "").trim();
if (providerId) {
updates.providerId = providerId;
}
const modelId = String(config.model ?? config.modelId ?? "").trim();
if (modelId) {
updates.modelId = modelId;
}
const apiKey =
const rawApiKey =
typeof config.apiKey === "string"
? config.apiKey.trim()
: typeof config.api_key === "string"
? config.api_key.trim()
: undefined;
if (apiKey) {
updates.apiKey = apiKey;
}
if (typeof config.baseUrl === "string" && config.baseUrl.trim()) {
updates.baseUrl = config.baseUrl.trim();
}
if (config.headers && typeof config.headers === "object") {
updates.headers = config.headers as Record<string, string>;
}
if (config.providerConfig && typeof config.providerConfig === "object") {
updates.providerConfig =
config.providerConfig as SessionConnectionUpdate["providerConfig"];
}
if (thinking === false) {
updates.thinking = false;
updates.reasoningEffort = null;
updates.thinkingBudgetTokens = null;
return updates;
}
if (thinking === true) {
updates.thinking = true;
}
if (reasoningEffort) {
updates.thinking = true;
updates.reasoningEffort = reasoningEffort;
}
if (thinkingBudgetTokens !== undefined) {
updates.thinking = true;
updates.thinkingBudgetTokens = thinkingBudgetTokens;
}
return updates;
const baseUrl =
typeof config.baseUrl === "string" ? config.baseUrl.trim() : undefined;
const reasoningEffort = readReasoningEffort(config.reasoningEffort);
const thinkingBudgetTokens = readPositiveInteger(
config.thinkingBudgetTokens ?? config.thinking_budget_tokens,
);
return buildConnectionUpdate({
...(providerId ? { providerId } : {}),
...(modelId ? { modelId } : {}),
...(rawApiKey ? { apiKey: rawApiKey } : {}),
...(baseUrl ? { baseUrl } : {}),
...(config.headers && typeof config.headers === "object"
? { headers: config.headers as Record<string, string> }
: {}),
...(config.providerConfig && typeof config.providerConfig === "object"
? {
providerConfig:
config.providerConfig as SessionConnectionUpdate["providerConfig"],
}
: {}),
...(typeof config.thinking === "boolean"
? { thinking: config.thinking }
: {}),
...(reasoningEffort ? { reasoningEffort } : {}),
...(thinkingBudgetTokens !== undefined ? { thinkingBudgetTokens } : {}),
});
}
async function resolveSystemPrompt(config: JsonRecord): Promise<string> {
+5 -3
View File
@@ -5,7 +5,7 @@ import {
} from "./context";
import { resolveWorkspaceRoot } from "./paths";
import { startServer } from "./server";
import { BunRuntime, SIDECAR_MODE, SIDECAR_PORT } from "./types";
import { BunRuntime, SIDECAR_HOST, SIDECAR_MODE, SIDECAR_PORT } from "./types";
const SHUTDOWN_TIMEOUT_MS = 5_000;
@@ -59,8 +59,10 @@ async function main() {
const { port } = startServer(ctx, SIDECAR_PORT, shutdown);
const endpoint = `http://127.0.0.1:${port}`;
const wsEndpoint = `ws://127.0.0.1:${port}/transport`;
// A wildcard bind isn't a dialable address; advertise loopback instead.
const dialHost = SIDECAR_HOST === "0.0.0.0" ? "127.0.0.1" : SIDECAR_HOST;
const endpoint = `http://${dialHost}:${port}`;
const wsEndpoint = `ws://${dialHost}:${port}/transport`;
process.stdout.write(
`${JSON.stringify({
type: "ready",
+10 -1
View File
@@ -4,6 +4,7 @@ import { sendEvent } from "./context";
import { fetchMarketplaceCatalog } from "./marketplace";
import {
BunRuntime,
SIDECAR_HOST,
SIDECAR_MODE,
SIDECAR_PORT,
type SidecarContext,
@@ -15,12 +16,20 @@ type SidecarServer = {
upgrade(req: Request): boolean;
};
// Comma-separated extra origins (e.g. a dev server on a nonstandard port when
// the sidecar runs inside a container). Origin validation itself stays on.
const EXTRA_TRUSTED_ORIGINS = (process.env.CLINE_SIDECAR_TRUSTED_ORIGINS ?? "")
.split(",")
.map((origin) => origin.trim())
.filter(Boolean);
const TRUSTED_BROWSER_ORIGINS = new Set([
"tauri://localhost",
"http://tauri.localhost",
"https://tauri.localhost",
"http://localhost:3125",
"http://127.0.0.1:3125",
...EXTRA_TRUSTED_ORIGINS,
]);
const JSON_HEADERS = {
@@ -115,7 +124,7 @@ export function startServer(
for (const candidate of candidates) {
try {
server = BunRuntime.serve({
hostname: "127.0.0.1",
hostname: SIDECAR_HOST,
port: candidate,
fetch: createFetchHandler(ctx, onShutdown),
websocket: createWebSocketHandler(ctx),
@@ -115,4 +115,8 @@ export type BunRuntimeApi = {
export const BunRuntime = (globalThis as { Bun?: BunRuntimeApi }).Bun;
export const SIDECAR_PORT = Number(process.env.CLINE_SIDECAR_PORT) || 3126;
// Loopback-only by default. Set CLINE_SIDECAR_HOST=0.0.0.0 to accept
// connections from outside the local host (e.g. Docker port publishing).
export const SIDECAR_HOST =
process.env.CLINE_SIDECAR_HOST?.trim() || "127.0.0.1";
export const SIDECAR_MODE = "sidecar";
+139 -10
View File
@@ -1,21 +1,20 @@
@import "@fontsource-variable/geist";
@import "@fontsource-variable/schibsted-grotesk";
@import "@fontsource/azeret-mono/latin.css";
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
:root {
--font-geist-sans: "Geist Variable";
--font-geist-mono:
ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo,
monospace;
--font-desktop-sans: "Schibsted Grotesk Variable";
--font-desktop-mono: "Azeret Mono";
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.398 0.195 277.366);
--primary: oklch(0.75 0.12 165);
--primary-foreground: oklch(0.962 0.018 272.314);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
@@ -51,7 +50,7 @@
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.398 0.195 277.366);
--primary: oklch(0.75 0.12 165);
--primary-foreground: oklch(0.962 0.018 272.314);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
@@ -79,8 +78,68 @@
}
@theme inline {
--font-sans: var(--font-geist-sans), "Geist Fallback";
--font-mono: var(--font-geist-mono), "Geist Mono Fallback";
--font-sans: var(--font-desktop-sans), sans-serif;
--font-mono:
var(--font-desktop-mono), ui-monospace, "SFMono-Regular", Menlo, Consolas,
"Liberation Mono", monospace;
--font-weight-normal: 480;
--font-weight-medium: 560;
--font-weight-semibold: 640;
--font-weight-bold: 640;
--text-step-1: 12px;
--text-step-1--line-height: 16px;
--text-step-1--letter-spacing: 0.0025em;
--text-step-2: 14px;
--text-step-2--line-height: 20px;
--text-step-2--letter-spacing: 0em;
--text-step-3: 16px;
--text-step-3--line-height: 24px;
--text-step-3--letter-spacing: 0em;
--text-step-4: 18px;
--text-step-4--line-height: 26px;
--text-step-4--letter-spacing: -0.0025em;
--text-step-5: 20px;
--text-step-5--line-height: 28px;
--text-step-5--letter-spacing: -0.005em;
--text-step-6: 24px;
--text-step-6--line-height: 30px;
--text-step-6--letter-spacing: -0.00625em;
--text-step-7: 28px;
--text-step-7--line-height: 36px;
--text-step-7--letter-spacing: -0.0075em;
--text-step-8: 35px;
--text-step-8--line-height: 40px;
--text-step-8--letter-spacing: -0.01em;
--text-step-9: 60px;
--text-step-9--line-height: 60px;
--text-step-9--letter-spacing: -0.025em;
--text-xs: var(--text-step-1);
--text-xs--line-height: var(--text-step-1--line-height);
--text-xs--letter-spacing: var(--text-step-1--letter-spacing);
--text-sm: var(--text-step-2);
--text-sm--line-height: var(--text-step-2--line-height);
--text-sm--letter-spacing: var(--text-step-2--letter-spacing);
--text-base: var(--text-step-3);
--text-base--line-height: var(--text-step-3--line-height);
--text-base--letter-spacing: var(--text-step-3--letter-spacing);
--text-lg: var(--text-step-4);
--text-lg--line-height: var(--text-step-4--line-height);
--text-lg--letter-spacing: var(--text-step-4--letter-spacing);
--text-xl: var(--text-step-5);
--text-xl--line-height: var(--text-step-5--line-height);
--text-xl--letter-spacing: var(--text-step-5--letter-spacing);
--text-2xl: var(--text-step-6);
--text-2xl--line-height: var(--text-step-6--line-height);
--text-2xl--letter-spacing: var(--text-step-6--letter-spacing);
--text-3xl: var(--text-step-7);
--text-3xl--line-height: var(--text-step-7--line-height);
--text-3xl--letter-spacing: var(--text-step-7--letter-spacing);
--text-4xl: var(--text-step-8);
--text-4xl--line-height: var(--text-step-8--line-height);
--text-4xl--letter-spacing: var(--text-step-8--letter-spacing);
--text-6xl: var(--text-step-9);
--text-6xl--line-height: var(--text-step-9--line-height);
--text-6xl--letter-spacing: var(--text-step-9--letter-spacing);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
@@ -133,7 +192,7 @@
@apply border-border outline-ring/50;
}
body {
@apply m-0 bg-background text-foreground;
@apply m-0 bg-background text-base font-normal text-foreground;
}
}
@@ -206,3 +265,73 @@
::selection {
background: oklch(0.75 0.12 165 / 0.25);
}
/* Aurora background (components/ui/aurora-bg.tsx) */
@keyframes aurora-drift {
0%,
100% {
transform: translate(0, 0) rotate(0deg) scale(1);
}
25% {
transform: translate(14%, -10%) rotate(18deg) scale(1.25);
}
50% {
transform: translate(-4%, 6%) rotate(-6deg) scale(1.05);
}
75% {
transform: translate(-12%, -4%) rotate(-16deg) scale(0.9);
}
}
/* Curtain ribbons: sway side-to-side while skewing and stretching, like an
aurora curtain rippling. Ribbons are bottom-anchored (transform-origin
bottom), so skew/scale fan out from the horizon. */
@keyframes aurora-wave {
0%,
100% {
transform: translateX(0) skewX(0deg) scaleY(1);
opacity: 0.7;
}
20% {
transform: translateX(4%) skewX(8deg) scaleY(1.15);
opacity: 1;
}
45% {
transform: translateX(-3%) skewX(-10deg) scaleY(0.9);
opacity: 0.55;
}
70% {
transform: translateX(5%) skewX(12deg) scaleY(1.25);
opacity: 0.9;
}
}
/* Traveling wave: a 200%-wide striped sheet slides left by half its width,
looping seamlessly, while bobbing vertically — bands visibly roll across. */
@keyframes aurora-flow {
0% {
transform: translateX(0) translateY(0) skewX(-6deg);
}
25% {
transform: translateX(-12.5%) translateY(-4%) skewX(4deg);
}
50% {
transform: translateX(-25%) translateY(2%) skewX(-3deg);
}
75% {
transform: translateX(-37.5%) translateY(-5%) skewX(6deg);
}
100% {
transform: translateX(-50%) translateY(0) skewX(-6deg);
}
}
@keyframes aurora-twinkle {
0%,
100% {
opacity: 0.15;
}
50% {
opacity: 1;
}
}
@@ -826,6 +826,8 @@ function ChatThreadPane({
const displayedIsSwitching = hideDeletedSessionUi
? false
: isHydratingSession;
const isWelcomeState =
displayedMessages.length === 0 && !displayedIsSwitching;
const handleRenameTitle = useCallback(
async (nextTitle: string) => {
@@ -934,6 +936,7 @@ function ChatThreadPane({
}}
onRenameTitle={handleRenameTitle}
renamingTitle={renamingSession}
showSessionActions={!isWelcomeState}
status={status}
title={threadTitle}
/>
@@ -24,6 +24,7 @@ type AgentHeaderProps = {
canDeleteSession?: boolean;
deletingSession?: boolean;
onOpenDiff?: () => void;
showSessionActions?: boolean;
status?: ChatSessionStatus;
diff?: {
additions: number;
@@ -41,6 +42,7 @@ export function AgentHeader({
canDeleteSession,
deletingSession,
onOpenDiff,
showSessionActions = true,
status,
diff,
}: AgentHeaderProps) {
@@ -164,34 +166,35 @@ export function AgentHeader({
</DropdownMenu>
</div>
{/* Right: actions */}
<div className="flex items-center gap-2">
{/* DIFF */}
<Button
className={cn(
"flex items-center gap-1 rounded-md bg-secondary px-2 py-1 text-xs font-mono transition-colors",
hasChanges ? "hover:bg-secondary/80" : "cursor-default opacity-60",
)}
disabled={!hasChanges}
id="diff-stats"
onClick={() => onOpenDiff?.()}
size="sm"
type="button"
variant="secondary"
>
<span className="text-chart-2">+{additions}</span>
<span className="text-destructive">-{deletions}</span>
</Button>
{/* New Chat Button */}
<Button
className="flex items-center gap-1 rounded-md text-sm text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
onClick={() => onNewThread?.()}
size="icon-sm"
variant="ghost"
>
<Plus />
</Button>
</div>
{showSessionActions ? (
<div className="flex items-center gap-2">
<Button
className={cn(
"flex items-center gap-1 rounded-md bg-secondary px-2 py-1 text-xs font-mono transition-colors",
hasChanges
? "hover:bg-secondary/80"
: "cursor-default opacity-60",
)}
disabled={!hasChanges}
id="diff-stats"
onClick={() => onOpenDiff?.()}
size="sm"
type="button"
variant="secondary"
>
<span className="text-chart-2">+{additions}</span>
<span className="text-destructive">-{deletions}</span>
</Button>
<Button
className="flex items-center gap-1 rounded-md text-sm text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
onClick={() => onNewThread?.()}
size="icon-sm"
variant="ghost"
>
<Plus />
</Button>
</div>
) : null}
</header>
);
}
@@ -0,0 +1,100 @@
"use client";
import { useMemo } from "react";
interface Star {
left: string;
top: string;
size: number;
delay: string;
duration: string;
opacity: number;
}
// Big blurred gradient blobs that slowly drift/rotate to fake an aurora.
// Each entry is [positionClasses, gradient, animationDuration, animationDelay].
const BLOBS: Array<[string, string, string, string]> = [
[
"left-[-20%] bottom-[-40%] w-[70%] h-[80%]",
"radial-gradient(ellipse at center, oklch(0.55 0.2 278 / 0.55), transparent 70%)",
"16s",
"0s",
],
[
"left-[25%] bottom-[-50%] w-[60%] h-[90%]",
"radial-gradient(ellipse at center, oklch(0.65 0.19 200 / 0.4), transparent 70%)",
"22s",
"-6s",
],
[
"right-[-15%] bottom-[-40%] w-[65%] h-[85%]",
"radial-gradient(ellipse at center, oklch(0.6 0.18 310 / 0.5), transparent 70%)",
"19s",
"-12s",
],
[
"left-[10%] bottom-[-30%] w-[80%] h-[60%]",
"radial-gradient(ellipse at center, oklch(0.75 0.13 340 / 0.35), transparent 70%)",
"26s",
"-3s",
],
];
/**
* A decorative aurora background built entirely from CSS: blurred gradient
* blobs drifting on keyframe animations, plus twinkling star dots. No canvas,
* no WebGL, no per-frame JS. Absolutely positioned to fill its nearest
* positioned parent; pointer events pass through.
*
* Keyframes (`aurora-drift`, `aurora-twinkle`) live in app/globals.css.
*/
export function AuroraBackground({ starCount = 90 }: { starCount?: number }) {
// Random star field, generated once per mount.
const stars = useMemo<Star[]>(
() =>
Array.from({ length: starCount }, () => {
// Squared skew biases stars toward the bottom, where the glow lives.
const r = Math.random();
return {
left: `${Math.random() * 100}%`,
top: `${100 - (1 - r * r) * 45}%`,
size: Math.random() < 0.15 ? 3 : Math.random() < 0.5 ? 2 : 1,
delay: `${Math.random() * 4}s`,
duration: `${1.5 + Math.random() * 3.5}s`,
opacity: 0.3 + Math.random() * 0.6,
};
}),
[starCount],
);
return (
<div className="pointer-events-none absolute inset-0 overflow-hidden">
{BLOBS.map(([position, gradient, duration, delay], idx) => (
<div
key={`blob${idx}`}
className={`absolute blur-3xl animate-[aurora-drift_linear_infinite] ${position}`}
style={{
background: gradient,
animationDuration: duration,
animationDelay: delay,
}}
/>
))}
{stars.map((s, idx) => (
<span
key={`star${idx}`}
className="absolute rounded-none bg-[#b8f3ee] animate-[aurora-twinkle_ease-in-out_infinite]"
style={{
left: s.left,
top: s.top,
width: s.size,
height: s.size,
opacity: s.opacity,
animationDelay: s.delay,
animationDuration: s.duration,
}}
/>
))}
</div>
);
}
@@ -376,7 +376,7 @@ function ChatMessagesImpl({
className="h-full min-h-0 min-w-0 overflow-x-hidden overflow-y-auto"
ref={scrollAreaRef}
>
<div className="relative mx-auto w-full min-w-0 max-w-full overflow-x-hidden px-6 py-6">
<div className="relative mx-auto w-full h-full min-w-0 max-w-full overflow-x-hidden px-6 py-6">
{showIdleDetails ? (
<WelcomeScreen
provider={provider}
@@ -2,6 +2,7 @@
import { Check, FolderOpen } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { AuroraBackground } from "@/components/ui/aurora-bg";
import {
Command,
CommandEmpty,
@@ -105,6 +106,7 @@ export function WelcomeScreen({
return (
<div className="flex flex-1 flex-col items-center overflow-hidden bg-background">
<AuroraBackground />
<div className="relative z-10 flex w-full max-w-3xl flex-1 flex-col items-center px-6 py-12">
<div className="mb-8 flex flex-col items-center">
<h1 className="text-balance text-center text-3xl font-bold tracking-tight text-foreground">
@@ -59,7 +59,7 @@ type InstalledStatusState = "loading" | "ready";
const INSTALL_TIMEOUT_MS = 300_000;
const CODE_FONT_STYLE: CSSProperties = {
fontFamily:
'ui-monospace, "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace',
'"Azeret Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace',
};
const primitivePageDetails = {
@@ -35,8 +35,9 @@ let resolvedEndpointCache: string | null = null;
* or an integration test harness.
* 2. Tauri `get_desktop_backend_endpoint` command — used when running inside
* the full Tauri app shell.
* 3. Fallback to `ws://127.0.0.1:3126/transport` — the sidecar's default port
* when running in plain web/dev mode (`bun run dev:sidecar` + `bun run dev:web`).
* 3. `NEXT_PUBLIC_SIDECAR_WS_ENDPOINT` (inlined at build time), then fallback
* to `ws://127.0.0.1:3126/transport` — the sidecar's default port when
* running in plain web/dev mode (`bun run dev:sidecar` + `bun run dev:web`).
*/
export async function resolveDesktopBackendWsEndpoint(): Promise<string> {
if (resolvedEndpointCache) return resolvedEndpointCache;
@@ -64,8 +65,10 @@ export async function resolveDesktopBackendWsEndpoint(): Promise<string> {
throw new Error("Tauri returned an empty desktop backend endpoint");
}
// 3. Default sidecar port for local dev mode without the Tauri bridge.
resolvedEndpointCache = "ws://127.0.0.1:3126/transport";
// 3. Env override, then default sidecar port for local dev mode without
// the Tauri bridge.
const envEndpoint = process.env.NEXT_PUBLIC_SIDECAR_WS_ENDPOINT?.trim();
resolvedEndpointCache = envEndpoint || "ws://127.0.0.1:3126/transport";
return resolvedEndpointCache;
}
-6
View File
@@ -1,6 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -11,6 +11,10 @@ const nextConfig = {
turbopack: {
root: workspaceRoot,
},
// Dev-only: Next blocks HMR/font/dev-resource requests from origins that
// don't match the dev server's own hostname. Both loopback spellings are
// legitimate ways to reach a local or port-forwarded dev server.
allowedDevOrigins: ["localhost", "127.0.0.1"],
reactStrictMode: true,
typescript: {
ignoreBuildErrors: true,
@@ -1,9 +1,13 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "@fontsource-variable/schibsted-grotesk";
@import "@fontsource/azeret-mono/latin.css";
@custom-variant dark (&:is(.dark *));
:root {
--font-desktop-sans: "Schibsted Grotesk Variable";
--font-desktop-mono: "Azeret Mono";
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
@@ -75,8 +79,68 @@
}
@theme inline {
--font-sans: "Geist", "Geist Fallback";
--font-mono: "Geist Mono", "Geist Mono Fallback";
--font-sans: var(--font-desktop-sans), sans-serif;
--font-mono:
var(--font-desktop-mono), ui-monospace, "SFMono-Regular", Menlo, Consolas,
"Liberation Mono", monospace;
--font-weight-normal: 480;
--font-weight-medium: 560;
--font-weight-semibold: 640;
--font-weight-bold: 640;
--text-step-1: 12px;
--text-step-1--line-height: 16px;
--text-step-1--letter-spacing: 0.0025em;
--text-step-2: 14px;
--text-step-2--line-height: 20px;
--text-step-2--letter-spacing: 0em;
--text-step-3: 16px;
--text-step-3--line-height: 24px;
--text-step-3--letter-spacing: 0em;
--text-step-4: 18px;
--text-step-4--line-height: 26px;
--text-step-4--letter-spacing: -0.0025em;
--text-step-5: 20px;
--text-step-5--line-height: 28px;
--text-step-5--letter-spacing: -0.005em;
--text-step-6: 24px;
--text-step-6--line-height: 30px;
--text-step-6--letter-spacing: -0.00625em;
--text-step-7: 28px;
--text-step-7--line-height: 36px;
--text-step-7--letter-spacing: -0.0075em;
--text-step-8: 35px;
--text-step-8--line-height: 40px;
--text-step-8--letter-spacing: -0.01em;
--text-step-9: 60px;
--text-step-9--line-height: 60px;
--text-step-9--letter-spacing: -0.025em;
--text-xs: var(--text-step-1);
--text-xs--line-height: var(--text-step-1--line-height);
--text-xs--letter-spacing: var(--text-step-1--letter-spacing);
--text-sm: var(--text-step-2);
--text-sm--line-height: var(--text-step-2--line-height);
--text-sm--letter-spacing: var(--text-step-2--letter-spacing);
--text-base: var(--text-step-3);
--text-base--line-height: var(--text-step-3--line-height);
--text-base--letter-spacing: var(--text-step-3--letter-spacing);
--text-lg: var(--text-step-4);
--text-lg--line-height: var(--text-step-4--line-height);
--text-lg--letter-spacing: var(--text-step-4--letter-spacing);
--text-xl: var(--text-step-5);
--text-xl--line-height: var(--text-step-5--line-height);
--text-xl--letter-spacing: var(--text-step-5--letter-spacing);
--text-2xl: var(--text-step-6);
--text-2xl--line-height: var(--text-step-6--line-height);
--text-2xl--letter-spacing: var(--text-step-6--letter-spacing);
--text-3xl: var(--text-step-7);
--text-3xl--line-height: var(--text-step-7--line-height);
--text-3xl--letter-spacing: var(--text-step-7--letter-spacing);
--text-4xl: var(--text-step-8);
--text-4xl--line-height: var(--text-step-8--line-height);
--text-4xl--letter-spacing: var(--text-step-8--letter-spacing);
--text-6xl: var(--text-step-9);
--text-6xl--line-height: var(--text-step-9--line-height);
--text-6xl--letter-spacing: var(--text-step-9--letter-spacing);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
@@ -120,6 +184,6 @@
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
@apply bg-background text-base font-normal text-foreground;
}
}
+35
View File
@@ -1,5 +1,6 @@
import fs from "node:fs"
import path from "node:path"
import { createRequire } from "node:module"
import { fileURLToPath } from "node:url"
import * as esbuild from "esbuild"
@@ -178,6 +179,39 @@ const e2eBuildConfig = {
plugins: [aliasResolverPlugin, esbuildProblemMatcherPlugin],
}
/**
* Copies the plugin sandbox bootstrap from the built @cline/core package into
* the extension's dist directory. The bootstrap runs in an isolated child
* process spawned by SubprocessSandbox and must be a separate file — it cannot
* be inlined into the main bundle. resolveBootstrap() (bundled into
* extension.js) searches for it at dist/extensions/plugin-sandbox-bootstrap.js.
*
* The bootstrap has external runtime dependencies (jiti for TypeScript
* transpilation, @cline/shared) that it resolves via Node's standard module
* resolution from its on-disk location. Both must be direct dependencies of
* the extension so they are present in node_modules and resolvable from
* dist/extensions/. The CLI build performs the same copy in apps/cli/bun.mts.
*/
function copyPluginSandboxBootstrap() {
if (e2eBuild) return
const projectRequire = createRequire(import.meta.url)
let corePackageDir
try {
corePackageDir = path.dirname(projectRequire.resolve("@cline/core/package.json"))
} catch {
console.warn("[esbuild] @cline/core not found — skipping plugin sandbox bootstrap copy")
return
}
const bootstrapSrc = path.join(corePackageDir, "dist", "extensions", "plugin-sandbox-bootstrap.js")
if (!fs.existsSync(bootstrapSrc)) {
console.warn(`[esbuild] plugin-sandbox-bootstrap.js not found at ${bootstrapSrc} — build @cline/core first`)
return
}
const bootstrapDest = path.join(__dirname, destDir, "extensions", "plugin-sandbox-bootstrap.js")
fs.mkdirSync(path.dirname(bootstrapDest), { recursive: true })
fs.copyFileSync(bootstrapSrc, bootstrapDest)
}
async function main() {
const config = standalone ? standaloneConfig : e2eBuild ? e2eBuildConfig : extensionConfig
const extensionCtx = await esbuild.context(config)
@@ -187,6 +221,7 @@ async function main() {
await extensionCtx.rebuild()
await extensionCtx.dispose()
}
copyPluginSandboxBootstrap()
}
main().catch((e) => {
+2
View File
@@ -467,6 +467,7 @@
"@cline/agents": "workspace:*",
"@cline/core": "workspace:*",
"@cline/llms": "workspace:*",
"@cline/sdk": "workspace:*",
"@cline/shared": "workspace:*",
"@google/genai": "^1.30.0",
"@grpc/grpc-js": "^1.9.15",
@@ -519,6 +520,7 @@
"ignore": "^7.0.3",
"image-size": "^2.0.2",
"isbinaryfile": "^5.0.2",
"jiti": "^2.7.0",
"js-yaml": "^4.1.1",
"jschardet": "^3.1.4",
"json5": "^2.2.3",
+12
View File
@@ -1,6 +1,7 @@
import { WebviewProvider } from "./core/webview"
import "./utils/path" // necessary to have access to String.prototype.toPosix
import { setSdkLogger } from "@cline/core"
import { HostProvider } from "@/hosts/host-provider"
import { Logger } from "@/shared/services/Logger"
import type { StorageContext } from "@/shared/storage/storage-context"
@@ -35,6 +36,17 @@ export async function initialize(storageContext: StorageContext): Promise<Webvie
Logger.subscribe((msg: string) => HostProvider.get().logToChannel(msg)) // File system logging
Logger.subscribe((msg: string) => HostProvider.env.debugLog({ value: msg })) // Host debug logging
// Register the SDK early logger so diagnostic events from
// ProviderSettingsManager, RuntimeOAuthTokenManager, and Cline auth
// flow through Logger.debug → Cline output channel.
// These components operate before/outside of ClineCore sessions, so the
// session-scoped logger can't reach them.
setSdkLogger({
debug: (message) => Logger.debug(message),
log: (message) => Logger.log(message),
error: (message) => Logger.error(message),
})
// Initialize ClineEndpoint configuration (reads bundled and ~/.cline/endpoints.json if present)
// This must be done before any other code that calls ClineEnv.config()
// Throws ClineConfigurationError if config file exists but is invalid
@@ -0,0 +1,113 @@
import { beforeEach, describe, expect, it, vi } from "vitest"
import { refreshGroqModels } from "../refreshGroqModels"
const mocks = vi.hoisted(() => ({
axiosGet: vi.fn(),
captureProviderApiError: vi.fn(),
getModelsCache: vi.fn(),
getProviderCollectionSync: vi.fn(),
getSecretKey: vi.fn(),
setModelsCache: vi.fn(),
writeFile: vi.fn(),
}))
vi.mock("@cline/llms", () => ({
getProviderCollectionSync: mocks.getProviderCollectionSync,
}))
vi.mock("@core/storage/disk", () => ({
GlobalFileNames: {
groqModels: "groq_models.json",
},
ensureCacheDirectoryExists: vi.fn(async () => "/tmp/cline-cache"),
}))
vi.mock("@/core/storage/StateManager", () => ({
StateManager: {
get: () => ({
getModelsCache: mocks.getModelsCache,
setModelsCache: mocks.setModelsCache,
}),
},
}))
vi.mock("@/services/telemetry", () => ({
telemetryService: {
captureProviderApiError: mocks.captureProviderApiError,
},
}))
vi.mock("@/shared/net", () => ({
getAxiosSettings: () => ({}),
}))
vi.mock("@/shared/services/Logger", () => ({
Logger: {
error: vi.fn(),
log: vi.fn(),
},
}))
vi.mock("@utils/fs", () => ({
fileExistsAtPath: vi.fn(async () => false),
}))
vi.mock("axios", () => ({
default: {
get: mocks.axiosGet,
isAxiosError: vi.fn(() => false),
},
}))
vi.mock("fs/promises", () => ({
default: {
readFile: vi.fn(),
writeFile: mocks.writeFile,
},
}))
describe("refreshGroqModels", () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.getModelsCache.mockReturnValue(null)
mocks.getProviderCollectionSync.mockReturnValue({ models: {} })
mocks.getSecretKey.mockReturnValue("gsk_test_key")
mocks.axiosGet.mockResolvedValue({
data: {
data: [
{
id: "groq-new-chat-model",
object: "model",
active: true,
max_completion_tokens: 4096,
context_window: 8192,
owned_by: "Groq",
},
],
},
})
})
it("defaults cache pricing for live models missing SDK catalog metadata", async () => {
const controller = {
stateManager: {
getSecretKey: mocks.getSecretKey,
},
task: {
ulid: "task-1",
},
} as unknown as Parameters<typeof refreshGroqModels>[0]
const models = await refreshGroqModels(controller)
expect(models["groq-new-chat-model"]).toMatchObject({
maxTokens: 4096,
contextWindow: 8192,
cacheWritesPrice: 0,
cacheReadsPrice: 0,
description: "Groq model with 8,192 token context window",
})
expect(mocks.captureProviderApiError).not.toHaveBeenCalled()
expect(mocks.setModelsCache).toHaveBeenCalledWith("groq", expect.objectContaining(models))
})
})
@@ -139,8 +139,8 @@ async function fetchAndCacheModels(controller: Controller): Promise<Record<strin
supportsPromptCache: staticModelInfo?.supportsPromptCache || false,
inputPrice: staticModelInfo?.inputPrice || 0,
outputPrice: staticModelInfo?.outputPrice || 0,
cacheWritesPrice: (staticModelInfo as any)?.cacheWritesPrice || 0,
cacheReadsPrice: (staticModelInfo as any).cacheReadsPrice || 0,
cacheWritesPrice: staticModelInfo?.cacheWritesPrice || 0,
cacheReadsPrice: staticModelInfo?.cacheReadsPrice || 0,
description: generateModelDescription(rawModel, staticModelInfo),
}
@@ -21,6 +21,23 @@ export async function getAvailableSlashCommands(controller: Controller, _request
)
}
// Add plugin-registered commands
try {
const pluginCommands = await controller.getPluginSlashCommands()
for (const cmd of pluginCommands) {
commands.push(
SlashCommandInfo.create({
name: cmd.name,
description: cmd.description ?? `Plugin command: ${cmd.name}`,
section: "custom",
cliCompatible: true,
}),
)
}
} catch {
// Plugin command discovery is best-effort; don't fail the whole list.
}
// Get workflow toggles from state
const localWorkflowToggles = controller.stateManager.getWorkspaceStateKey("workflowToggles") ?? {}
const globalWorkflowToggles = controller.stateManager.getGlobalSettingsKey("globalWorkflowToggles") ?? {}
@@ -27,6 +27,7 @@ export async function getStateToPostToWebview(controller: {
backgroundCommandTaskId?: string
workspaceManager?: any
checkpointRestoreInput?: ExtensionState["checkpointRestoreInput"]
getPluginSlashCommands?: () => Promise<{ name: string; description?: string }[]>
}): Promise<ExtensionState> {
const stateManager = controller.stateManager
@@ -108,6 +109,15 @@ export async function getStateToPostToWebview(controller: {
// Codex OAuth not available
}
// Plugin slash commands are fetched best-effort so autocomplete failures
// don't block state posting.
let pluginSlashCommands: { name: string; description?: string }[] = []
try {
pluginSlashCommands = (await controller.getPluginSlashCommands?.()) ?? []
} catch {
// Plugin command discovery is best-effort.
}
return {
version,
apiConfiguration,
@@ -155,6 +165,7 @@ export async function getStateToPostToWebview(controller: {
taskHistory: processedTaskHistory,
shouldShowAnnouncement,
favoritedModelIds,
pluginSlashCommands,
backgroundCommandRunning: controller.backgroundCommandRunning ?? false,
backgroundCommandTaskId: controller.backgroundCommandTaskId,
workspaceRoots: controller.workspaceManager?.getRoots?.() ?? [],
@@ -156,7 +156,7 @@ export abstract class WebviewProvider {
*/
protected async getHMRHtmlContent(): Promise<string> {
const localPort = await this.getDevServerPort()
const localServerUrl = `localhost:${localPort}`
const localServerUrl = `127.0.0.1:${localPort}`
// Check if local dev server is running.
try {
+54 -4
View File
@@ -71,6 +71,7 @@ import { SdkInteractionCoordinator } from "./sdk-interaction-coordinator"
import { SdkMcpCoordinator } from "./sdk-mcp-coordinator"
import { SdkMessageCoordinator, type SessionEventListener } from "./sdk-message-coordinator"
import { SdkModeCoordinator } from "./sdk-mode-coordinator"
import { type PluginSlashCommand, SdkPluginCommandCoordinator } from "./sdk-plugin-commands"
import { SdkProviderChangeCoordinator } from "./sdk-provider-change-coordinator"
import { SdkSessionConfigBuilder } from "./sdk-session-config-builder"
import { SdkSessionEventCoordinator } from "./sdk-session-event-coordinator"
@@ -166,6 +167,7 @@ export class Controller {
private compaction: SdkCompactionCoordinator
private sessionEvents: SdkSessionEventCoordinator
private sessionHistory: SdkSessionHistoryLoader
private pluginCommands: SdkPluginCommandCoordinator
private readonly sdkTelemetry: VscodeSdkTelemetryHandle
private readonly providerFailureTelemetryTurnGate = new ProviderFailureTelemetryTurnGate()
private readonly providerConfigStore: ProviderConfigStore
@@ -258,7 +260,7 @@ export class Controller {
)
// Initialize SDK-backed auth and account services.
this.authService = AuthService.getInstance(this)
this.authService = AuthService.getInstance(this, this.sdkTelemetry.telemetry)
this.ocaAuthService = OcaAuthService.initialize(this)
this.accountService = ClineAccountService.getInstance()
@@ -480,6 +482,9 @@ export class Controller {
},
postStateToWebview: () => this.postStateToWebview(),
})
this.pluginCommands = new SdkPluginCommandCoordinator({
getWorkspaceRoot: () => this.getWorkspaceRoot(),
})
this.taskStart = new SdkTaskStartCoordinator({
stateManager: this.stateManager,
sessions: this.sessions,
@@ -572,6 +577,15 @@ export class Controller {
this.providerCatalog.invalidateProviderListings()
}
/**
* Return plugin-registered slash commands for autocomplete. Used by the
* getAvailableSlashCommands gRPC handler to surface plugin commands in the
* webview's slash command picker.
*/
getPluginSlashCommands(): Promise<PluginSlashCommand[]> {
return this.pluginCommands.getSlashCommands()
}
private handleProviderConfigChange(event: ProviderConfigChange): void {
this.scheduleProviderConfigStatePost()
@@ -674,6 +688,7 @@ export class Controller {
// are disposed below — see StatePostDebouncer.dispose().
await this.statePostDebouncer.dispose()
await this.invalidateUserInstructionService()
await this.pluginCommands.dispose()
this.messages.cancelPendingSave()
// Clear MCP tool list change callback before disposing McpHub
this.mcpHub?.clearToolListChangeCallback()
@@ -734,14 +749,48 @@ export class Controller {
}
/**
* Expand a leading `/workflow` or `/skill` slash command into its instruction
* body. Mirrors the CLI's `buildUserInputMessage`. Returns the input unchanged
* if it is not a known command or expansion fails.
* Expand a leading slash command. First checks plugin-registered commands
* (e.g. `/goal`), then falls back to workflow/skill expansion via the
* user-instruction service. For plugin commands:
* - If the handler returns `submitPrompt`, that becomes the prompt text.
* - If the handler returns `reply`, it is emitted as a say message.
* - If only `reply` is returned (no `submitPrompt`), returns empty string
* so the agent turn is suppressed (the reply was already shown).
* Returns the input unchanged if it is not a known command.
*/
private async resolveSlashCommands(text: string): Promise<string> {
if (this.isDisposed) {
return text
}
// Check plugin commands first — they take precedence over
// workflow/skill expansion so plugin names cannot be shadowed.
try {
const result = await this.pluginCommands.resolveCommand(text)
if (result) {
if (result.reply) {
this.messages.emitSessionEvents(
[
{
ts: Date.now(),
type: "say",
say: "text",
text: result.reply,
partial: false,
},
],
{
type: "status",
payload: { sessionId: this.sessions.getActiveSession()?.sessionId ?? "", status: "running" },
},
)
}
return result.submitPrompt ?? ""
}
} catch (error) {
Logger.warn("[SdkController] Plugin command resolution failed, falling through:", error)
}
try {
const workspaceRoot = await this.getWorkspaceRoot()
const service = await this.ensureUserInstructionService(workspaceRoot)
@@ -1800,6 +1849,7 @@ export class Controller {
mcpHub: this.mcpHub,
backgroundCommandRunning: this.backgroundCommandRunning,
backgroundCommandTaskId: this.backgroundCommandTaskId,
getPluginSlashCommands: () => this.pluginCommands.getSlashCommands(),
})
const sdkTaskHistory = (await this.taskHistory.listHistory({ limit: 100, hydrate: false }))
.map(sessionHistoryRecordToHistoryItem)
+14 -3
View File
@@ -8,7 +8,7 @@
// - Streaming subscription management
// - workos: prefix handling
import { getValidClineCredentials, type OAuthCredentials } from "@cline/core"
import { getValidClineCredentials, type ITelemetryService, type OAuthCredentials } from "@cline/core"
import { beforeEach, describe, expect, it, vi } from "vitest"
import { AuthService, type ClineAuthInfo, LogoutReason } from "./auth-service"
@@ -18,6 +18,8 @@ import { AuthService, type ClineAuthInfo, LogoutReason } from "./auth-service"
const mockFeatureFlagsPoll = vi.hoisted(() => vi.fn().mockResolvedValue(undefined))
const mockIdentifyAccount = vi.hoisted(() => vi.fn().mockResolvedValue(undefined))
const mockCaptureAuthLoggedOut = vi.hoisted(() => vi.fn())
const mockSdkTelemetry = { capture: vi.fn() } as unknown as ITelemetryService
// Mock StateManager
const mockSecrets = new Map<string, string>()
@@ -90,6 +92,7 @@ vi.mock("@/services/feature-flags", () => ({
vi.mock("@/services/telemetry", () => ({
telemetryService: {
identifyAccount: mockIdentifyAccount,
captureAuthLoggedOut: mockCaptureAuthLoggedOut,
},
}))
@@ -103,7 +106,9 @@ vi.mock("axios", () => ({
const mockLoginClineOAuth = vi.hoisted(() => vi.fn())
// Mock @cline/core OAuth functions
vi.mock("@cline/core", () => ({
vi.mock("@cline/core", async () => ({
sdkDebug: () => {},
hashSecret: () => "hashed",
createOAuthClientCallbacks: (opts: {
onOutput?: (message: string) => void
onPrompt: () => void
@@ -208,7 +213,7 @@ describe("AuthService", () => {
beforeEach(() => {
// Reset the singleton between tests
resetSingleton()
authService = AuthService.getInstance()
authService = AuthService.getInstance(undefined, mockSdkTelemetry)
mockSecrets.clear()
mockProviderSettings.clear()
vi.clearAllMocks()
@@ -378,6 +383,7 @@ describe("AuthService", () => {
// Persisted credentials should be cleared from providers.json.
expect(mockProviderSettings.get("cline")?.auth).toBeUndefined()
expect(mockCaptureAuthLoggedOut).toHaveBeenCalledWith("cline", LogoutReason.USER_INITIATED)
})
})
@@ -407,6 +413,11 @@ describe("AuthService", () => {
expect(testAccess(authService)._authenticated).toBe(true)
expect(testAccess(authService)._clineAuthInfo?.idToken).toBe("persisted-access-token")
expect(getValidClineCredentials).toHaveBeenCalledWith(
expect.any(Object),
expect.objectContaining({ telemetry: mockSdkTelemetry }),
expect.any(Object),
)
})
it("sets unauthenticated state when providers.json has no Cline auth", async () => {
+36 -6
View File
@@ -8,13 +8,15 @@
// disk — it's fetched from the Cline API on startup and cached in memory.
// This matches the CLI's pattern (see apps/cli/src/runtime/interactive-welcome.ts).
import type { OAuthCredentials } from "@cline/core"
import type { ITelemetryService, OAuthCredentials } from "@cline/core"
import {
createOAuthClientCallbacks,
getValidClineCredentials,
hashSecret,
loginClineOAuth,
loginOcaOAuth,
loginOpenAICodex,
sdkDebug,
} from "@cline/core"
import type { ApiProvider } from "@shared/api"
import { AuthState, UserInfo } from "@shared/proto/cline/account"
@@ -99,7 +101,10 @@ function readClineCredentials(): {
try {
const manager = getProviderSettingsManager()
const settings = manager.getProviderSettings("cline")
if (!settings?.auth?.accessToken) return null
if (!settings?.auth?.accessToken) {
sdkDebug("[SdkAuthService] readClineCredentials: no auth.accessToken found")
return null
}
// Strip workos: prefix if present (providers.json stores it with prefix)
let accessToken = settings.auth.accessToken
@@ -107,12 +112,16 @@ function readClineCredentials(): {
accessToken = accessToken.slice(WORKOS_TOKEN_PREFIX.length)
}
return {
const result = {
accessToken,
refreshToken: settings.auth.refreshToken,
expiresAt: (settings.auth as { expiresAt?: number }).expiresAt,
accountId: settings.auth.accountId,
}
sdkDebug(
`[SdkAuthService] readClineCredentials: found credentials (accessHash=${hashSecret(result.accessToken)}, refreshHash=${hashSecret(result.refreshToken)}, expiresAt=${result.expiresAt})`,
)
return result
} catch (error) {
Logger.error("[SdkAuthService] Failed to read credentials from providers.json:", error)
return null
@@ -150,6 +159,9 @@ function writeClineCredentials(credentials: {
},
{ tokenSource: "oauth", setLastUsed: true },
)
sdkDebug(
`[SdkAuthService] writeClineCredentials: wrote (accessHash=${hashSecret(credentials.accessToken)}, refreshHash=${hashSecret(credentials.refreshToken)}, expiresAt=${credentials.expiresAt})`,
)
} catch (error) {
Logger.error("[SdkAuthService] Failed to write credentials to providers.json:", error)
}
@@ -163,6 +175,7 @@ function clearClineCredentials(): void {
const manager = getProviderSettingsManager()
const existing = manager.getProviderSettings("cline")
if (existing) {
sdkDebug("[SdkAuthService] clearClineCredentials: clearing auth from providers.json")
manager.saveProviderSettings(
{
...existing,
@@ -189,6 +202,7 @@ export class AuthService {
private _activeAuthStatusUpdateHandlers = new Set<StreamingResponseHandler<AuthState>>()
private _handlerToController = new Map<StreamingResponseHandler<AuthState>, Controller>()
private _refreshPromise: Promise<string | undefined> | null = null
private _telemetry?: ITelemetryService
private constructor() {}
@@ -196,10 +210,13 @@ export class AuthService {
* Gets the singleton instance of AuthService.
* On first call with a controller, initializes BannerService.
*/
public static getInstance(controller?: Controller): AuthService {
public static getInstance(controller?: Controller, telemetry?: ITelemetryService): AuthService {
if (!AuthService.instance) {
AuthService.instance = new AuthService()
}
if (telemetry) {
AuthService.instance._telemetry = telemetry
}
// Initialize BannerService on first call with a controller
// (mirrors classic AuthService behavior)
if (controller) {
@@ -251,6 +268,9 @@ export class AuthService {
const bearerToken = accessToken.toLowerCase().startsWith(WORKOS_TOKEN_PREFIX)
? accessToken
: `${WORKOS_TOKEN_PREFIX}${accessToken}`
sdkDebug(
`[SdkAuthService] fetchUserInfoFromApi: GET ${apiBaseUrl}/api/v1/users/me (tokenHash=${hashSecret(accessToken)})`,
)
const response = await axios.get(`${apiBaseUrl}/api/v1/users/me`, {
headers: {
Authorization: `Bearer ${bearerToken}`,
@@ -259,6 +279,7 @@ export class AuthService {
},
...getAxiosSettings(),
})
sdkDebug(`[SdkAuthService] fetchUserInfoFromApi: response status=${response.status}`)
return response.data?.data ?? null
} catch (error) {
Logger.error("[SdkAuthService] Failed to fetch user info from API:", error)
@@ -286,7 +307,7 @@ export class AuthService {
return getValidClineCredentials(
this.toOAuthCredentials(authInfo),
{ apiBaseUrl: ClineEnv.config().apiBaseUrl },
{ apiBaseUrl: ClineEnv.config().apiBaseUrl, telemetry: this._telemetry },
{ forceRefresh: options?.forceRefresh },
)
}
@@ -335,9 +356,11 @@ export class AuthService {
}
if (!this._clineAuthInfo?.refreshToken) {
sdkDebug("[SdkAuthService] refreshAccessToken: no refresh token available")
return false
}
sdkDebug(`[SdkAuthService] refreshAccessToken: starting (currentTokenHash=${hashSecret(this._clineAuthInfo.idToken)})`)
this._refreshPromise = (async () => {
try {
const currentInfo = this._clineAuthInfo
@@ -346,6 +369,7 @@ export class AuthService {
}
const newCredentials = await this.resolveValidClineCredentials(currentInfo, { forceRefresh: true })
if (!newCredentials) {
sdkDebug("[SdkAuthService] refreshAccessToken: refresh returned null — clearing credentials")
this._clineAuthInfo = null
this._authenticated = false
clearClineCredentials()
@@ -375,6 +399,9 @@ export class AuthService {
this._authenticated = true
if (credentialsChanged) {
sdkDebug(
`[SdkAuthService] refreshAccessToken: credentials changed (newTokenHash=${hashSecret(newCredentials.access)})`,
)
writeClineCredentials({
accessToken: newCredentials.access,
refreshToken: newCredentials.refresh,
@@ -387,6 +414,8 @@ export class AuthService {
Logger.error("[SdkAuthService] Error sending auth status update after refresh:", err)
})
})
} else {
sdkDebug("[SdkAuthService] refreshAccessToken: credentials unchanged after refresh")
}
return this._clineAuthInfo.idToken
@@ -722,8 +751,9 @@ export class AuthService {
/**
* Handle deauthentication clear tokens from providers.json and push unauthenticated state.
*/
async handleDeauth(_reason: LogoutReason = LogoutReason.UNKNOWN): Promise<void> {
async handleDeauth(reason: LogoutReason = LogoutReason.UNKNOWN): Promise<void> {
try {
telemetryService.captureAuthLoggedOut("cline", reason)
this._clineAuthInfo = null
this._authenticated = false
clearClineCredentials()
@@ -783,6 +783,101 @@ describe("translateSessionEvent — agent_event content_end", () => {
expect(second.path).toBe("/src/package.json")
})
it("content_end for read_files carries the requested line range into the readFile payload", () => {
const state = new MessageTranslatorState()
translateSessionEvent(
{
type: "agent_event",
payload: {
sessionId: "s1",
event: {
type: "content_start",
contentType: "tool",
toolName: "read_files",
toolCallId: "c1",
input: { files: [{ path: "/src/big-file.ts", start_line: 100, end_line: 200 }] },
} as AgentEvent,
},
},
state,
)
const endResult = translateSessionEvent(
{
type: "agent_event",
payload: {
sessionId: "s1",
event: {
type: "content_end",
contentType: "tool",
toolName: "read_files",
toolCallId: "c1",
} as AgentEvent,
},
},
state,
)
const endTool = JSON.parse(endResult.messages[0].text!)
expect(endTool.tool).toBe("readFile")
expect(endTool.path).toBe("/src/big-file.ts")
expect(endTool.readLineStart).toBe(100)
expect(endTool.readLineEnd).toBe(200)
})
it("content_end for read_files treats a start_line-only read as open-ended and defaults a missing start_line to 1", () => {
const state = new MessageTranslatorState()
translateSessionEvent(
{
type: "agent_event",
payload: {
sessionId: "s1",
event: {
type: "content_start",
contentType: "tool",
toolName: "read_files",
toolCallId: "c1",
input: {
files: [
{ path: "/src/paged.ts", start_line: 500, end_line: null },
{ path: "/src/head.ts", end_line: 50 },
{ path: "/src/whole.ts" },
],
},
} as AgentEvent,
},
},
state,
)
const endResult = translateSessionEvent(
{
type: "agent_event",
payload: {
sessionId: "s1",
event: {
type: "content_end",
contentType: "tool",
toolName: "read_files",
toolCallId: "c1",
} as AgentEvent,
},
},
state,
)
expect(endResult.messages).toHaveLength(3)
const [paged, head, whole] = endResult.messages.map((m) => JSON.parse(m.text!))
expect(paged.readLineStart).toBe(500)
expect(paged.readLineEnd).toBeUndefined()
expect(head.readLineStart).toBe(1)
expect(head.readLineEnd).toBe(50)
expect(whole.readLineStart).toBeUndefined()
expect(whole.readLineEnd).toBeUndefined()
})
it("content_end without prior content_start still works (graceful fallback)", () => {
const state = new MessageTranslatorState()
+51 -20
View File
@@ -452,10 +452,11 @@ function sdkToolToClineSayTool(toolName: string, input?: unknown): ClineSayTool
switch (toolName) {
case "read_files":
case "read_file": {
const filePath = extractFirstFilePath(parsedInput)
const fileRead = extractFileReads(parsedInput)[0]
return {
tool: "readFile",
path: filePath,
path: fileRead?.path ?? "",
...readLineRangeFields(fileRead),
}
}
@@ -665,32 +666,53 @@ function getCompletionResultText(input: unknown): string {
return getStringField(parsed, "summary") ?? getStringField(parsed, "result") ?? ""
}
/** Extract file paths from a read_files/read_file input */
function extractFilePaths(input: Record<string, unknown> | undefined): string[] {
/** A single file read request parsed from a read_files/read_file input */
interface FileReadRequest {
path: string
startLine?: number
endLine?: number
}
/** Extract file read requests (path + optional one-based inclusive line range) from a read_files/read_file input */
function extractFileReads(input: Record<string, unknown> | undefined): FileReadRequest[] {
if (!input) return []
const files = input.files
if (Array.isArray(files) && files.length > 0) {
const paths = files
.map((f) => {
if (typeof f === "string") return f
const reads = files
.map((f): FileReadRequest => {
if (typeof f === "string") return { path: f }
if (typeof f === "object" && f !== null) {
return ((f as Record<string, unknown>).path as string) ?? ""
const entry = f as Record<string, unknown>
return {
path: (entry.path as string) ?? "",
startLine: getNumberField(entry, "start_line"),
endLine: getNumberField(entry, "end_line"),
}
}
return ""
return { path: "" }
})
.filter(Boolean)
if (paths.length > 0) {
return paths
.filter((read) => read.path)
if (reads.length > 0) {
return reads
}
}
const singlePath =
(input.path as string) ?? (input.file_path as string) ?? (input.filePath as string) ?? (input.filename as string) ?? ""
return singlePath ? [singlePath] : []
return singlePath
? [{ path: singlePath, startLine: getNumberField(input, "start_line"), endLine: getNumberField(input, "end_line") }]
: []
}
/** Extract the first file path from a read_files input */
function extractFirstFilePath(input: Record<string, unknown> | undefined): string {
return extractFilePaths(input)[0] ?? ""
/**
* Map a read request's line range onto ClineSayTool fields. An omitted start_line with an
* explicit end_line means the read began at line 1; an omitted end_line stays undefined
* (open-ended read the UI renders it as "start+").
*/
function readLineRangeFields(read: FileReadRequest | undefined): Pick<ClineSayTool, "readLineStart" | "readLineEnd"> {
if (!read || (read.startLine == null && read.endLine == null)) {
return {}
}
return { readLineStart: read.startLine ?? 1, readLineEnd: read.endLine }
}
/** Get a string field from a parsed input object */
@@ -701,6 +723,14 @@ function getStringField(input: Record<string, unknown> | undefined, field: strin
return undefined
}
/** Get a finite number field from a parsed input object (null/non-number → undefined) */
function getNumberField(input: Record<string, unknown> | undefined, field: string): number | undefined {
if (!input) return undefined
const value = input[field]
if (typeof value === "number" && Number.isFinite(value)) return value
return undefined
}
/** Get an array field from a parsed input object */
function getArrayField(input: Record<string, unknown> | undefined, field: string): string[] | undefined {
if (!input) return undefined
@@ -1295,16 +1325,17 @@ function translateAgentEvent(event: AgentEvent, state: MessageTranslatorState):
// list reflect what was actually read.
if (toolName === "read_files" || toolName === "read_file") {
const parsedInput = parseToolInput(storedInput)
const filePaths = extractFilePaths(parsedInput)
if (filePaths.length > 1) {
filePaths.forEach((filePath, index) => {
const fileReads = extractFileReads(parsedInput)
if (fileReads.length > 1) {
fileReads.forEach((fileRead, index) => {
messages.push({
ts: index === 0 ? ts : state.nextTs(),
type: "say",
say: "tool",
text: JSON.stringify({
tool: "readFile",
path: filePath,
path: fileRead.path,
...readLineRangeFields(fileRead),
} satisfies ClineSayTool),
partial: false,
})
@@ -0,0 +1,31 @@
import { describe, expect, it, vi } from "vitest"
import { normalizePluginCommandName, SdkPluginCommandCoordinator } from "./sdk-plugin-commands"
describe("SdkPluginCommandCoordinator", () => {
it("loads plugins from the active workspace", async () => {
const loadPlugins = vi.fn(async () => ({
extensions: [],
pluginPaths: [],
failures: [],
warnings: [],
}))
const coordinator = new SdkPluginCommandCoordinator({
getWorkspaceRoot: async () => "/workspace/project",
loadPlugins,
})
await coordinator.getSlashCommands()
expect(loadPlugins).toHaveBeenCalledWith(
expect.objectContaining({
cwd: "/workspace/project",
workspacePath: "/workspace/project",
}),
)
})
it("normalizes plugin command names like the CLI", () => {
expect(normalizePluginCommandName(" /Goal ")).toBe("goal")
expect(normalizePluginCommandName("GOAL")).toBe("goal")
})
})
+174
View File
@@ -0,0 +1,174 @@
// SdkPluginCommandCoordinator — discovers and executes plugin-registered
// slash commands, mirroring the CLI's createWorkspaceChatCommandHost.
//
// Plugins register commands via `api.registerCommand({ name, handler })` in
// their setup(). The ContributionRegistry runs setup() and collects the
// registered commands. This coordinator:
// 1. Lazily loads plugins via resolveAndLoadAgentPlugins (sandbox mode)
// 2. Initializes a ContributionRegistry to run setup() and gather commands
// 3. Exposes getSlashCommands() for autocomplete
// 4. Exposes resolveCommand(text) to execute a /command and return its result
import {
type AgentExtensionCommand,
type AgentExtensionCommandResult,
createContributionRegistry,
noopBasicLogger,
resolveAndLoadAgentPlugins,
} from "@cline/core"
import type { AgentTool, Message } from "@cline/shared"
import { Logger } from "@shared/services/Logger"
export interface PluginSlashCommand {
name: string
description?: string
}
export interface PluginCommandResult {
reply?: string
submitPrompt?: string
}
interface LoadedPlugins {
commands: AgentExtensionCommand[]
shutdown: () => Promise<void>
}
export interface SdkPluginCommandCoordinatorOptions {
getWorkspaceRoot: () => Promise<string>
loadPlugins?: typeof resolveAndLoadAgentPlugins
}
export function normalizePluginCommandName(name: string): string {
const trimmed = name.trim()
return (trimmed.startsWith("/") ? trimmed.slice(1) : trimmed).toLowerCase()
}
export class SdkPluginCommandCoordinator {
private loadedPromise: Promise<LoadedPlugins | undefined> | undefined
constructor(private readonly options: SdkPluginCommandCoordinatorOptions) {}
/**
* Lazily load plugins and initialize the contribution registry. The result
* is cached so subsequent calls reuse the same sandbox process. Returns
* undefined if no plugins are installed or loading fails.
*/
private ensureLoaded(): Promise<LoadedPlugins | undefined> {
if (this.loadedPromise) {
return this.loadedPromise
}
this.loadedPromise = (async () => {
let loaded: Awaited<ReturnType<typeof resolveAndLoadAgentPlugins>>
try {
const workspaceRoot = await this.options.getWorkspaceRoot()
loaded = await (this.options.loadPlugins ?? resolveAndLoadAgentPlugins)({
cwd: workspaceRoot,
workspacePath: workspaceRoot,
logger: noopBasicLogger,
})
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
Logger.warn(`[PluginCommands] Plugin loading failed; continuing without plugin commands (${message})`)
return undefined
}
if (!loaded.extensions.length) {
await loaded.shutdown?.().catch(() => {})
return undefined
}
const registry = createContributionRegistry<(typeof loaded.extensions)[number], AgentTool, Message[]>({
extensions: loaded.extensions,
})
try {
await registry.initialize()
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
Logger.warn(`[PluginCommands] Contribution registry initialization failed (${message})`)
await loaded.shutdown?.().catch(() => {})
return undefined
}
return {
commands: registry.getRegistrySnapshot().commands,
shutdown: async () => {
await loaded.shutdown?.().catch(() => {})
},
}
})()
return this.loadedPromise
}
/**
* Return plugin-registered slash commands for autocomplete. Returns an
* empty array if no plugins are installed or loading fails.
*/
async getSlashCommands(): Promise<PluginSlashCommand[]> {
const loaded = await this.ensureLoaded()
if (!loaded) {
return []
}
return loaded.commands
.filter((cmd) => typeof cmd.handler === "function")
.map((cmd) => ({
name: normalizePluginCommandName(cmd.name),
description: cmd.description,
}))
.filter((cmd) => cmd.name.length > 0)
}
/**
* Resolve a leading /command from a plugin. Returns null if the text does
* not match a plugin command. Returns { reply?, submitPrompt? } from the
* command handler.
*/
async resolveCommand(text: string): Promise<PluginCommandResult | null> {
if (!text.startsWith("/") || text.length < 2) {
return null
}
const match = text.match(/^\/(\S+)/)
if (!match?.[1]) {
return null
}
const name = normalizePluginCommandName(match[1])
const remainder = text.slice(name.length + 1).trim()
const loaded = await this.ensureLoaded()
if (!loaded) {
return null
}
const command = loaded.commands.find(
(cmd) => normalizePluginCommandName(cmd.name) === name && typeof cmd.handler === "function",
)
if (!command?.handler) {
return null
}
try {
const result: AgentExtensionCommandResult = await command.handler(remainder)
if (typeof result === "string") {
return { reply: result }
}
return {
reply: result.reply,
submitPrompt: result.submitPrompt,
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
Logger.warn(`[PluginCommands] Command "/${name}" failed: ${message}`)
return { reply: `Command /${name} failed: ${message}` }
}
}
/**
* Shut down the plugin sandbox process. Called on extension disposal.
*/
async dispose(): Promise<void> {
const promise = this.loadedPromise
this.loadedPromise = undefined
if (promise) {
const loaded = await promise.catch(() => undefined)
await loaded?.shutdown().catch(() => {})
}
}
}
@@ -140,9 +140,9 @@ export class SdkTaskStartCoordinator {
await this.options.taskHistory.updateTaskHistoryItem(newHistoryItem)
await this.options.postStateToWebview()
if (prompt?.trim()) {
if (prompt?.trim() || images?.length || files?.length) {
Logger.log(`[SdkController] Sending prompt to session: ${taskSessionId}`)
const resolvedTask = await this.options.resolveContextMentions(prompt)
const resolvedTask = await this.options.resolveContextMentions(prompt || "")
this.options.sessions.fireAndForgetSend(sdkHost, taskSessionId, resolvedTask, images, files)
}
+4 -1
View File
@@ -16,6 +16,7 @@ import { OnboardingModelGroup } from "./proto/cline/state"
import { Mode } from "./storage/types"
import { TelemetrySetting } from "./TelemetrySetting"
import { UserInfo } from "./UserInfo"
import type { SlashCommand } from "./slashCommands"
// webview will hold state
export interface ExtensionMessage {
type: "grpc_response" // New type for gRPC responses
@@ -114,6 +115,8 @@ export interface ExtensionState {
worktreesEnabled?: ClineFeatureSetting
customPrompt?: string
favoritedModelIds: string[]
/** Plugin-registered slash commands surfaced for autocomplete. */
pluginSlashCommands?: SlashCommand[]
// NEW: Add workspace information
workspaceRoots: WorkspaceRoot[]
primaryRootIndex: number
@@ -275,7 +278,7 @@ export interface ClineSayTool {
operationIsLocatedInWorkspace?: boolean
/** Starting line numbers in the original file where each SEARCH block matched */
startLineNumbers?: number[]
/** Inclusive line range actually returned by read_file (for UI summaries). */
/** One-based inclusive line range requested by read_file; readLineEnd omitted = open-ended read (for UI summaries). */
readLineStart?: number
readLineEnd?: number
}
+2 -1
View File
@@ -54,7 +54,8 @@ export class Logger {
fullMessage += ` ${args.map((arg) => JSON.stringify(arg)).join(" ")}`
}
const errorSuffix = error?.message ? ` ${error.message}` : ""
Logger.output(`${level} ${fullMessage}${errorSuffix}`.trimEnd())
const ts = new Date().toISOString()
Logger.output(`${ts} ${level} ${fullMessage}${errorSuffix}`.trimEnd())
} catch {
// do nothing if Logger fails
}
@@ -0,0 +1,67 @@
import { existsSync } from "node:fs"
import { readFile } from "node:fs/promises"
import { createRequire } from "node:module"
import { join } from "node:path"
import { describe, expect, it } from "bun:test"
import { $ } from "bun"
/**
* Integration test for CLINE-2584: the plugin sandbox bootstrap
* (`plugin-sandbox-bootstrap.js`) must be shipped with the VS Code extension.
*
* The bootstrap runs in an isolated child process spawned by
* `SubprocessSandbox` it cannot be inlined into `extension.js` because the
* sandbox spawns it via `node <bootstrapFile>`. The CLI build copies this
* file (`apps/cli/bun.mts`); the extension build (`esbuild.mjs`) must do the
* same.
*
* The bootstrap also has external runtime dependencies that must be resolvable
* from its on-disk location via Node's standard module resolution:
* - jiti (TypeScript transpilation of .ts plugins)
* - @cline/shared, @cline/sdk (host-provided SDK packages that plugins import)
*
* This test runs the real `bun esbuild.mjs` build and checks the real
* `dist/` output, exercising the same build pipeline CI uses.
*/
const projectRoot = join(import.meta.dir, "..", "..")
const distDir = join(projectRoot, "dist")
const bootstrapPath = join(distDir, "extensions", "plugin-sandbox-bootstrap.js")
describe("plugin-sandbox bootstrap build artifact (CLINE-2584)", () => {
it("esbuild.mjs emits plugin-sandbox-bootstrap.js into dist/", async () => {
const result = await $`bun esbuild.mjs`.cwd(projectRoot).quiet()
expect(result.exitCode).toBe(0)
expect(existsSync(join(distDir, "extension.js"))).toBe(true)
expect(existsSync(bootstrapPath)).toBe(true)
}, 60_000)
it("the bootstrap is a real executable script with IPC handling", async () => {
expect(existsSync(bootstrapPath)).toBe(true)
const content = await readFile(bootstrapPath, "utf8")
expect(content.length).toBeGreaterThan(1000)
expect(content).toMatch(/process\.on\(.process\.message|process\.send|type:\s*["']response["']/)
}, 60_000)
it("the bootstrap's runtime dependencies resolve from dist/", () => {
expect(existsSync(bootstrapPath)).toBe(true)
// The bootstrap is spawned as a standalone Node child process. It
// imports jiti (for TypeScript transpilation) and @cline/shared as
// external modules, and plugins import @cline/sdk. Node resolves
// these by walking up from the bootstrap's directory. All must be
// direct dependencies of the extension so they appear in
// node_modules and are resolvable.
const requireFromBootstrap = createRequire(bootstrapPath)
expect(() => requireFromBootstrap.resolve("jiti")).not.toThrow()
expect(() => requireFromBootstrap.resolve("@cline/shared")).not.toThrow()
// @cline/sdk is a host-provided SDK specifier that plugins import.
// The bootstrap's findHostPackageRoot walks up from dist/extensions/
// looking for node_modules/@cline/sdk/package.json.
expect(
existsSync(join(projectRoot, "node_modules", "@cline", "sdk", "package.json")),
).toBe(true)
}, 60_000)
})
+1
View File
@@ -20,6 +20,7 @@ export default defineConfig({
"src/core/controller/models/__tests__/resolveModelInfo.test.ts",
"src/core/controller/models/__tests__/providerCatalogSmoke.test.ts",
"src/core/controller/models/__tests__/refreshClineRecommendedModels.test.ts",
"src/core/controller/models/__tests__/refreshGroqModels.test.ts",
],
environment: "node",
// Several suites lazily `await import()` their subject inside the first test
@@ -507,10 +507,11 @@ export const ChatRowContent = memo(
{tool.path && !tool.path.startsWith(".") && <span>/</span>}
<span className="ph-no-capture whitespace-nowrap overflow-hidden text-ellipsis mr-2 text-left [direction: rtl]">
{cleanPathPrefix(tool.path ?? "") + "\u200E"}
{tool.readLineStart != null && tool.readLineEnd != null ? (
{tool.readLineStart != null ? (
<span className="opacity-80">
{" "}
({tool.readLineStart}-{tool.readLineEnd})
({tool.readLineStart}
{tool.readLineEnd != null ? `-${tool.readLineEnd}` : "+"})
</span>
) : null}
</span>
@@ -224,6 +224,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
remoteConfigSettings,
navigateToSettingsModelPicker,
mcpServers,
pluginSlashCommands,
} = useExtensionState()
const [isTextAreaFocused, setIsTextAreaFocused] = useState(false)
const [isDraggingOver, setIsDraggingOver] = useState(false)
@@ -489,6 +490,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
remoteWorkflowToggles,
remoteConfigSettings?.remoteGlobalWorkflows,
mcpServers,
pluginSlashCommands,
)
if (allCommands.length === 0) {
@@ -514,6 +516,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
remoteWorkflowToggles,
remoteConfigSettings?.remoteGlobalWorkflows,
mcpServers,
pluginSlashCommands,
)
if (commands.length > 0) {
handleSlashCommandsSelect(commands[selectedSlashCommandsIndex])
@@ -673,6 +676,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
slashCommandsQuery,
handleSlashCommandsSelect,
sendingDisabled,
pluginSlashCommands,
],
)
@@ -984,6 +988,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
globalWorkflowToggles,
remoteWorkflowToggles,
remoteConfigSettings?.remoteGlobalWorkflows,
mcpServers,
pluginSlashCommands,
)
if (isValidCommand) {
@@ -997,7 +1003,14 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
highlightLayerRef.current.innerHTML = processedText
highlightLayerRef.current.scrollTop = textAreaRef.current.scrollTop
highlightLayerRef.current.scrollLeft = textAreaRef.current.scrollLeft
}, [localWorkflowToggles, globalWorkflowToggles, remoteWorkflowToggles, remoteConfigSettings])
}, [
localWorkflowToggles,
globalWorkflowToggles,
remoteWorkflowToggles,
remoteConfigSettings,
mcpServers,
pluginSlashCommands,
])
useLayoutEffect(() => {
updateHighlights()
@@ -1406,6 +1419,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
mcpServers={mcpServers}
onMouseDown={handleMenuMouseDown}
onSelect={handleSlashCommandsSelect}
pluginSlashCommands={pluginSlashCommands}
query={slashCommandsQuery}
remoteWorkflows={remoteConfigSettings?.remoteGlobalWorkflows}
remoteWorkflowToggles={remoteWorkflowToggles}
@@ -16,6 +16,7 @@ interface SlashCommandMenuProps {
remoteWorkflowToggles?: Record<string, boolean>
remoteWorkflows?: any[]
mcpServers?: McpServer[]
pluginSlashCommands?: SlashCommand[]
}
const SlashCommandMenu: React.FC<SlashCommandMenuProps> = ({
@@ -29,6 +30,7 @@ const SlashCommandMenu: React.FC<SlashCommandMenuProps> = ({
remoteWorkflowToggles,
remoteWorkflows,
mcpServers = [],
pluginSlashCommands = [],
}) => {
const menuRef = useRef<HTMLDivElement>(null)
@@ -40,6 +42,7 @@ const SlashCommandMenu: React.FC<SlashCommandMenuProps> = ({
remoteWorkflowToggles,
remoteWorkflows,
mcpServers,
pluginSlashCommands,
)
const defaultCommands = filteredCommands.filter((cmd) => cmd.section === "default" || !cmd.section)
const workflowCommands = filteredCommands.filter((cmd) => cmd.section === "custom")
@@ -46,7 +46,9 @@ const getActivityText = (tool: ClineSayTool): string | null => {
return null
}
const lineHint =
tool.readLineStart != null && tool.readLineEnd != null ? ` (lines ${tool.readLineStart}-${tool.readLineEnd})` : ""
tool.readLineStart != null
? ` (lines ${tool.readLineStart}${tool.readLineEnd != null ? `-${tool.readLineEnd}` : "+"})`
: ""
return `Reading ${cleanedPath}${lineHint}...`
}
case "listFilesTopLevel":
@@ -302,7 +304,9 @@ function getToolDisplayInfo(tool: ClineSayTool) {
switch (tool.tool) {
case "readFile": {
const lineNote =
tool.readLineStart != null && tool.readLineEnd != null ? `lines ${tool.readLineStart}-${tool.readLineEnd}` : null
tool.readLineStart != null
? `lines ${tool.readLineStart}${tool.readLineEnd != null ? `-${tool.readLineEnd}` : "+"}`
: null
return {
icon,
path: filePath,
@@ -281,13 +281,13 @@ const UserTypeSelectionStep = ({ userType, onSelectUserType, userTypeSelections
{" "}
<VSCodeLink
className="inline"
style={{ fontSize: "inherit" }}
onClick={(e) => {
e.stopPropagation()
UiServiceClient.openUrl(
StringRequest.create({ value: option.learnMoreUrl }),
).catch((err) => console.error("Failed to open learn more link:", err))
}}>
}}
style={{ fontSize: "inherit" }}>
Learn more
</VSCodeLink>
</>
@@ -289,6 +289,7 @@ export const ExtensionStateContextProvider: React.FC<{
localAgentsRulesToggles: {},
localWorkflowToggles: {},
globalWorkflowToggles: {},
pluginSlashCommands: [],
shellIntegrationTimeout: 4000,
terminalReuseEnabled: true,
vscodeTerminalExecutionMode: "vscodeTerminal",
@@ -906,6 +907,7 @@ export const ExtensionStateContextProvider: React.FC<{
localAgentsRulesToggles: state.localAgentsRulesToggles || {},
localWorkflowToggles: state.localWorkflowToggles || {},
globalWorkflowToggles: state.globalWorkflowToggles || {},
pluginSlashCommands: state.pluginSlashCommands || [],
remoteRulesToggles: state.remoteRulesToggles || {},
remoteWorkflowToggles: state.remoteWorkflowToggles || {},
enableCheckpointsSetting: state.enableCheckpointsSetting,
@@ -181,6 +181,7 @@ export function getMatchingSlashCommands(
remoteWorkflowToggles?: Record<string, boolean>,
remoteWorkflows?: any[],
mcpServers: McpServer[] = [],
pluginSlashCommands: SlashCommand[] = [],
): SlashCommand[] {
const workflowCommands = getWorkflowCommands(
localWorkflowToggles,
@@ -189,7 +190,7 @@ export function getMatchingSlashCommands(
remoteWorkflows,
)
const mcpPromptCommands = getMcpPromptCommands(mcpServers)
const allCommands = [...DEFAULT_SLASH_COMMANDS, ...workflowCommands, ...mcpPromptCommands]
const allCommands = [...DEFAULT_SLASH_COMMANDS, ...workflowCommands, ...mcpPromptCommands, ...pluginSlashCommands]
if (!query) {
return allCommands
@@ -233,6 +234,7 @@ export function validateSlashCommand(
remoteWorkflowToggles?: Record<string, boolean>,
remoteWorkflows?: any[],
mcpServers: McpServer[] = [],
pluginSlashCommands: SlashCommand[] = [],
): "full" | "partial" | null {
if (!command) {
return null
@@ -245,7 +247,7 @@ export function validateSlashCommand(
remoteWorkflows,
)
const mcpPromptCommands = getMcpPromptCommands(mcpServers)
const allCommands = [...DEFAULT_SLASH_COMMANDS, ...workflowCommands, ...mcpPromptCommands]
const allCommands = [...DEFAULT_SLASH_COMMANDS, ...workflowCommands, ...mcpPromptCommands, ...pluginSlashCommands]
// case insensitive matching
const exactMatch = allCommands.some((cmd) => cmd.name.toLowerCase() === command.toLowerCase())
+3 -2
View File
@@ -108,12 +108,13 @@ export default defineConfig({
chunkSizeWarningLimit: 100000,
},
server: {
host: "127.0.0.1",
port: 25463,
fs: {
allow: [resolve(__dirname, "../src/shared")],
allow: [resolve(__dirname), resolve(__dirname, "../src/shared")],
},
hmr: {
host: "localhost",
host: "127.0.0.1",
protocol: "ws",
},
cors: {
+130 -151
View File
@@ -19,7 +19,7 @@
},
"apps/cli": {
"name": "@cline/cli",
"version": "3.0.38",
"version": "3.0.39",
"bin": {
"cline": "src/index.ts",
},
@@ -167,7 +167,8 @@
"@cline/core": "workspace:*",
"@cline/llms": "workspace:*",
"@cline/shared": "workspace:*",
"@fontsource-variable/geist": "^5.2.8",
"@fontsource-variable/schibsted-grotesk": "^5.2.8",
"@fontsource/azeret-mono": "^5.2.9",
"@hookform/resolvers": "^3.9.1",
"@radix-ui/react-accordion": "1.2.12",
"@radix-ui/react-alert-dialog": "1.1.15",
@@ -353,6 +354,7 @@
"@cline/agents": "workspace:*",
"@cline/core": "workspace:*",
"@cline/llms": "workspace:*",
"@cline/sdk": "workspace:*",
"@cline/shared": "workspace:*",
"@google/genai": "^1.30.0",
"@grpc/grpc-js": "^1.9.15",
@@ -405,6 +407,7 @@
"ignore": "^7.0.3",
"image-size": "^2.0.2",
"isbinaryfile": "^5.0.2",
"jiti": "^2.7.0",
"js-yaml": "^4.1.1",
"jschardet": "^3.1.4",
"json5": "^2.2.3",
@@ -616,7 +619,7 @@
},
"sdk/packages/agents": {
"name": "@cline/agents",
"version": "0.0.59",
"version": "0.0.60",
"dependencies": {
"@cline/llms": "workspace:*",
"@cline/shared": "workspace:*",
@@ -625,7 +628,7 @@
},
"sdk/packages/core": {
"name": "@cline/core",
"version": "0.0.59",
"version": "0.0.60",
"dependencies": {
"@cline/agents": "workspace:*",
"@cline/llms": "workspace:*",
@@ -663,7 +666,7 @@
},
"sdk/packages/llms": {
"name": "@cline/llms",
"version": "0.0.59",
"version": "0.0.60",
"dependencies": {
"@ai-sdk/amazon-bedrock": "^4.0.89",
"@ai-sdk/anthropic": "^3.0.68",
@@ -697,14 +700,14 @@
},
"sdk/packages/sdk": {
"name": "@cline/sdk",
"version": "0.0.59",
"version": "0.0.60",
"dependencies": {
"@cline/core": "workspace:*",
},
},
"sdk/packages/shared": {
"name": "@cline/shared",
"version": "0.0.59",
"version": "0.0.60",
"dependencies": {
"aws4fetch": "^1.0.20",
"jsonrepair": "^3.13.2",
@@ -741,15 +744,15 @@
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.16.1", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-1ad+Sc/0sCtZGHthxxvgEUo5Wsbw16I+aF+YwdiLnPwkZG8KAGUEAPK6LM6Pf69lCyJPt1Aomk1d+8oE3C4ZEw=="],
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.128", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.92", "@ai-sdk/openai": "3.0.80", "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-T6mvUEYjTCkRAYByOwCwHT12J8r2U9fxJqfEf6k04OceGFjERCfmDdc2wmEaZ4VLo62KpUKJJf6gdf9Bcrx1gw=="],
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.129", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.93", "@ai-sdk/openai": "3.0.80", "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z+JAe15rDrsK5tdguGJFFV/CGAlBBTHcjA1FCgdH34F5+Qo9raMRc1fyTeVICoWJ2/Lf9g+A3yuGf6S8UVBdzg=="],
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.92", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-dFrf4xhx2yM686KHFm76Nn7nBekjkjiw1btqOyR26/kXz58QMguhNsjyvMqPktD8AW/wwTAq0fDRVyDvF2gH1w=="],
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.93", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-RHLRn6TIfxvUOZyVztfWFSnYMZrmoUmqtaHCP2sWmWSLQkRZTOqD97PLk+oZdod5BQGAcSgzqVz8OTaXTADwcQ=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.142", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Y1iwdxdebYXpoK5y/4CrcCfJGeFwEJWlEx+pMWIg/ZVWGi9KA+JXM7YBkhxcshpq/jAXx8YyQeFMyZr0TGfXZQ=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.143", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-RCH60KsUaNiZkI/fBuyau4yvYrVBIEgAcN+Ain94QpL1kVm28GduQzFKfGffAiJU2We0ZrmN4BHkoCZzACK96Q=="],
"@ai-sdk/google": ["@ai-sdk/google@3.0.88", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CN3PHCz5pa2sBowwZG4sNqE+7YfHWZT6+5KU12YMWuBssZ03s143Jr2jThkN5Fgemy8Kyg+ub2XbpHGhtIZ2yQ=="],
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.155", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.92", "@ai-sdk/google": "3.0.88", "@ai-sdk/openai-compatible": "2.0.56", "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DMH5rhnN2AcwDmLWcRqUwF/Mj5SlgaAK5uy9ke7SNZ/BCNUcnI7MBVr1urteBdro1P3BAfiNSXuTfjIlOhwl/Q=="],
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.156", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.93", "@ai-sdk/google": "3.0.88", "@ai-sdk/openai-compatible": "2.0.56", "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-5Vkgl/wV7GCvb5alX5IpgdWAWfUpr8VNBDfhOYDRIZshaq8xe+99VMIFvLsAbcCXiXdgdIdwKXhKQmzyaRBxVg=="],
"@ai-sdk/mistral": ["@ai-sdk/mistral@3.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-IyOPiBqPkd6RPqTvhDN6/JcoF7VVP2499FY4acnMz08Y7a1c1ZplsL6lPRTOmcFEwto1491ePilD9H0GtY1QEg=="],
@@ -761,7 +764,7 @@
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.35", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bjYld/2KGPLt78kpqbya+fD4LYS7BqVQJyUjE3qAHrYB0FR2Q90BaWEVIBZaguTWXf/A8L6uG1zO1v9TxVlGWg=="],
"@ai-sdk/react": ["@ai-sdk/react@3.0.220", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.35", "ai": "6.0.218", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-mUqM13WXUT2jNhoU0Kf7YxAtIS+atCUtzZ1LNkhF1q42oYs4g2gWFtq4gNCejACvFMu1KusGRQU2S7ONXtVLZw=="],
"@ai-sdk/react": ["@ai-sdk/react@3.0.221", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.35", "ai": "6.0.219", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-5Zd+rF0YbjOqre0NEzdNE+FvNPrEuqfHm4KGxw9ovbAHv0ut/7sgYIn2kzr6ekvXHNqWz0HEmi+zYOXOZCJxJw=="],
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
@@ -769,63 +772,63 @@
"@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.3.198", "", { "optionalDependencies": { "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.198", "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.198", "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.198", "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.198", "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.198", "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.198", "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.198", "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.198" }, "peerDependencies": { "@anthropic-ai/sdk": ">=0.93.0", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.0.0" } }, "sha512-xt469sSCyclTPtzpLAg0Aschy665GiRMgZKabSmESbGUA5/H56HcILVOiFxclXswkeMUk2fQxfHJUY9UZfiTnA=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.3.201", "", { "optionalDependencies": { "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.201", "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.201", "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.201", "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.201", "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.201", "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.201", "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.201", "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.201" }, "peerDependencies": { "@anthropic-ai/sdk": ">=0.93.0", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.0.0" } }, "sha512-InT1XLmf2QpldWdtznKDWEoGJT4p+sXh24yxbeBQ++lMJCzMrI0W27MEmmmDWx0otpa+ubdHCF5YQ6oiNt7cmg=="],
"@anthropic-ai/claude-agent-sdk-darwin-arm64": ["@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.198", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZmiAybQKIKcP1qEAE/vfXvfxtKxG9CnJn98QTXC5Zxiwuy7Mllx2ALXh9dfmsf0V87CGEodlZQmMgUJotNIsUw=="],
"@anthropic-ai/claude-agent-sdk-darwin-arm64": ["@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.201", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8Mcb3BDyKUGfJWFFTWwt+at37lbDH3ZwVtUNPWGG1toZ75RDCJry5U4kXRvQ2xokvJQlA0E+eNp6keWe5ZH22Q=="],
"@anthropic-ai/claude-agent-sdk-darwin-x64": ["@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.198", "", { "os": "darwin", "cpu": "x64" }, "sha512-XwH5vgN46WSwg8aC1OagNofnJpV/G1ciEu118GEKer8ZhVkq/dvK/DqShxMkb6r1jV7u5IJ7zPXu9uKliyNJAw=="],
"@anthropic-ai/claude-agent-sdk-darwin-x64": ["@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.201", "", { "os": "darwin", "cpu": "x64" }, "sha512-TFR2bu0+ml3RHoMrtsgD0qDK5Oknw8kYGBV7qpQHn+IWmE96gnHhogG1LpJwpHtni08XkJIjfWk1DdlsUYtRkQ=="],
"@anthropic-ai/claude-agent-sdk-linux-arm64": ["@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.198", "", { "os": "linux", "cpu": "arm64" }, "sha512-qmz8dxEtDIlKntU5qYe0R4aWTxTue5S7zIQknatLX7aJ6HN/nq1aCNXWn5smTH2FViBkUPPR+sCIsNwSk6AT6Q=="],
"@anthropic-ai/claude-agent-sdk-linux-arm64": ["@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.201", "", { "os": "linux", "cpu": "arm64" }, "sha512-mShTo3MwF0gkN4dDw78wWJiB6aBDVRkl81cnApvoBofpdyUBYgm9Gw16CCjDTgelMKeBFqN6ErJpwjI3wbP00A=="],
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": ["@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.198", "", { "os": "linux", "cpu": "arm64" }, "sha512-Q7lKVNjIrUQ2B/AR77OvRf0zeOdEjonFVaR9FYrrwtzGeEqum69WSht5nM7Y7el3wjbNi0/eV0QTUM0DlsTEfw=="],
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": ["@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.201", "", { "os": "linux", "cpu": "arm64" }, "sha512-EiqbpfJIpChfkn+8Uj061Qjyw0eaRcOXtdrvVuHANyj8ZErVOr8HlH6op9PSeIUa9TX0m2+tNgKPQvOGseQckA=="],
"@anthropic-ai/claude-agent-sdk-linux-x64": ["@anthropic-ai/claude-agent-sdk-linux-x64@0.3.198", "", { "os": "linux", "cpu": "x64" }, "sha512-Zqxyz2AT1UM5WlOOoLJhLssZDgZo8rBK5ku6daveK12zp+UTJGZhGsjFghz1/ASxH08KqOTbUePNTORnPhHAEQ=="],
"@anthropic-ai/claude-agent-sdk-linux-x64": ["@anthropic-ai/claude-agent-sdk-linux-x64@0.3.201", "", { "os": "linux", "cpu": "x64" }, "sha512-jrJBrRWrSuoFKIgjyqxHqmfd6Pb3Bs5Bvakg0knXCTC4fbUXGnC9Q6u7gdDwgXohUNP6/DD+s8U7bivvvVv0dg=="],
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": ["@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.198", "", { "os": "linux", "cpu": "x64" }, "sha512-h1SrWVIMjLInYNPlf+TxXuKTOdoiOfJLBSoQG97315Z2Nh0IpBfqWExlqYTtPCgKE7q2iga31U283QfHpIDlSQ=="],
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": ["@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.201", "", { "os": "linux", "cpu": "x64" }, "sha512-IbxnzO5UCbqbm2TnzCHkSyJorAFw2isdKdIsFCTxJJjSs3ZC+v3LC1QSUiVCx0qi+CV6w3MKx6mLI11mrvhbbQ=="],
"@anthropic-ai/claude-agent-sdk-win32-arm64": ["@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.198", "", { "os": "win32", "cpu": "arm64" }, "sha512-mjIHf1HFiRuXefewWTaNZFlTZlCaEt/xsRjc1nSTCEEpFolZayVhrDKz+O2QFVcDtPl8x8GeYSL0kiikg1DZjQ=="],
"@anthropic-ai/claude-agent-sdk-win32-arm64": ["@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.201", "", { "os": "win32", "cpu": "arm64" }, "sha512-UsoytRJ/037uHpb3ATrIoe+AgwTf+PwKuFLGjddHAV/11wERJs0hlrnSmcnp43kf0PFxoSNinngme96YYASmQg=="],
"@anthropic-ai/claude-agent-sdk-win32-x64": ["@anthropic-ai/claude-agent-sdk-win32-x64@0.3.198", "", { "os": "win32", "cpu": "x64" }, "sha512-y3HLuCCz1kDwUrhd6OnqO+d5BUpTFSzNUsPT9kf3r1vk9HYKF+eMC9eIlcOhiW2kX491kxEvuEOfqgIkGx15cg=="],
"@anthropic-ai/claude-agent-sdk-win32-x64": ["@anthropic-ai/claude-agent-sdk-win32-x64@0.3.201", "", { "os": "win32", "cpu": "x64" }, "sha512-PhalN/0cWcqDfbx7iwoLNR2gurjTiqhBk1G6K+NRScxEcQjWuu5xKXCcdbX8ePVpT+nbEMmFEFpn2y+8V8hIdA=="],
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.37.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-tHjX2YbkUBwEgg0JZU3EFSSAQPoK4qQR/NFYa8Vtzd5UAyXzZksCw2In69Rml4R/TyHPBfRYaLK35XiOe33pjw=="],
"@asamuzakjp/css-color": ["@asamuzakjp/css-color@3.2.0", "", { "dependencies": { "@csstools/css-calc": "^2.1.3", "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", "lru-cache": "^10.4.3" } }, "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw=="],
"@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1078.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.26", "@aws-sdk/credential-provider-node": "^3.972.61", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-BYy0X/+GMXlitKShxkdTsCexWwDrn8usY2Y2Z06M5MSi4aRT3Ce5ilyA6OubQUqOWfsmDMYrm8oBNaTIcQFyrg=="],
"@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1079.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/credential-provider-node": "^3.972.62", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-HIScdAc8q/upCY/f3TPW0pNq1K1LL7tn5fEifKf1K+zs3NRPXLultta96ZwvcZ9Ax503JKKTo9f3xGpR3fpCxQ=="],
"@aws-sdk/core": ["@aws-sdk/core@3.974.26", "", { "dependencies": { "@aws-sdk/types": "^3.973.15", "@aws-sdk/xml-builder": "^3.972.33", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.29.0", "@smithy/signature-v4": "^5.6.1", "@smithy/types": "^4.15.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-wRj7Pthvjk3anees97pUWlxlTa0DUjeGrEQU5fKDZVdWZV0ekaprbof0df2uaE9g8u67t035v2j+ne2AW2UMkA=="],
"@aws-sdk/core": ["@aws-sdk/core@3.974.27", "", { "dependencies": { "@aws-sdk/types": "^3.973.15", "@aws-sdk/xml-builder": "^3.972.33", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.29.0", "@smithy/signature-v4": "^5.6.1", "@smithy/types": "^4.15.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-WRWEgIq6vx+NU6ot3VrRu4Jovj9MIObitSi6of/GV5THDDPccBhivCRNkWJutMM+m3GvdeI3l/UbGNcoOobxOA=="],
"@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.51", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.997.26", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-nXzAwRz0NOiHlG/HHea7oJ2ew2m21XZUU6h2cZMCrlNQqcWjMHCkun4D6E7CWqOxiFG0MeN8Gg5Iakrjv/UXrQ=="],
"@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.52", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.997.27", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-m+akZFJsghShferf2xsMw0Hogl1jNIJl2zUoZBNTFyWvlaOj1aK5sMTzcnw8m1dICvlQ+lC4T1OPGGsmZ+ezXA=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.52", "", { "dependencies": { "@aws-sdk/core": "^3.974.26", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-sxuaHZGHqOgKB8OdL3doXa1NJjqmO60FPfyTnYVKGjX9taRsIEGS9pd+2yALmo06hijZ8L94uSK0kfXZsRmVyA=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.53", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-+KDA3uc/HZ1vIneGu5QMQb0gAXDYrm2vOE60+BJ7lS0YinMQ5i2oV4PR1A16XkF6K1IbSwjEHd1hQIIgMsK48w=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.54", "", { "dependencies": { "@aws-sdk/core": "^3.974.26", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-e6yz52nq3SpR1oPLcvfsDM7H7k2gIYk/NSn/rwsFqzGXEwr3g0mRMlPbLaKCPCGNZJMU/gZg6/64B3eSam+gBw=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.55", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-1gBfkWY3RWeBlCoB9lIJjXMx45/54wxcgfzv6BY9otTmMrZPcNPi1v+MwZxxaCUg441NV3jsr1efnFNCXiW70g=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.59", "", { "dependencies": { "@aws-sdk/core": "^3.974.26", "@aws-sdk/credential-provider-env": "^3.972.52", "@aws-sdk/credential-provider-http": "^3.972.54", "@aws-sdk/credential-provider-login": "^3.972.58", "@aws-sdk/credential-provider-process": "^3.972.52", "@aws-sdk/credential-provider-sso": "^3.972.58", "@aws-sdk/credential-provider-web-identity": "^3.972.58", "@aws-sdk/nested-clients": "^3.997.26", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/credential-provider-imds": "^4.4.5", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-9Um/UpruN76AdpiLnvwChVkJJwJ9Vx9ykk/2AeLxxSCM/YYRD8Kkq2towUk9fZQLV7dd9ATlsi87U7hKs0z/iQ=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.60", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/credential-provider-env": "^3.972.53", "@aws-sdk/credential-provider-http": "^3.972.55", "@aws-sdk/credential-provider-login": "^3.972.59", "@aws-sdk/credential-provider-process": "^3.972.53", "@aws-sdk/credential-provider-sso": "^3.972.59", "@aws-sdk/credential-provider-web-identity": "^3.972.59", "@aws-sdk/nested-clients": "^3.997.27", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/credential-provider-imds": "^4.4.5", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-CV2md+PXvABwRjApWGhQ0wACy9WSFIhnUGrovLcjnjBCd/46TbuivLADtkF8IWNjtCQmQ+2IagSaxqBYqXBNAQ=="],
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.58", "", { "dependencies": { "@aws-sdk/core": "^3.974.26", "@aws-sdk/nested-clients": "^3.997.26", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-H3q96qF8/DJsPsXMVtMRqSWOc85K5O4zos32untdw+vE5vw0f3a6qJo1YqbND4BsEIKd4iZmzzVUq9kV4LjbHg=="],
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.59", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/nested-clients": "^3.997.27", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-JG4S9yyA1GFzJdJXqLKrUzZbyK+VDp2QIsJD7YOicJHAhqymfHpDJIok2dLnhOdVB0I37RjdC53uOwCMVS00gw=="],
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.61", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.52", "@aws-sdk/credential-provider-http": "^3.972.54", "@aws-sdk/credential-provider-ini": "^3.972.59", "@aws-sdk/credential-provider-process": "^3.972.52", "@aws-sdk/credential-provider-sso": "^3.972.58", "@aws-sdk/credential-provider-web-identity": "^3.972.58", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/credential-provider-imds": "^4.4.5", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-2U2KHMRCt1dlZoLU3KZR5g5EL4b0h2HHw96SkaUBK7qvEXPZj5rGRO/3ZTeJmh37dIYQuCnA2273rZOQvmsiHw=="],
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.62", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.53", "@aws-sdk/credential-provider-http": "^3.972.55", "@aws-sdk/credential-provider-ini": "^3.972.60", "@aws-sdk/credential-provider-process": "^3.972.53", "@aws-sdk/credential-provider-sso": "^3.972.59", "@aws-sdk/credential-provider-web-identity": "^3.972.59", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/credential-provider-imds": "^4.4.5", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-S6Slq3Tx7bvFk5yc34XNADyZYTX2HUXvaFAnowGRQnhjBO8J/mP62Fn7lxvJwjaDyYm/7gh9h6HEHaltRyMFXw=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.52", "", { "dependencies": { "@aws-sdk/core": "^3.974.26", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-Aff9Ebs42lz+Ep1wkS+Nlwh5S0eahakpyskPsuKGjiBJ6ExOjNtxbfKJTKovQtQNgJ7oG1BH6esJwGrbs7qgSA=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.53", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-EhfH+MQlqOMCkXIVa8MMObPzAQqwTTtxA7KhEJiyPeuNVA8PLOOUpgK7nBrgaDaGiIDLN/9LpGdaHuDjomeRTw=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.58", "", { "dependencies": { "@aws-sdk/core": "^3.974.26", "@aws-sdk/nested-clients": "^3.997.26", "@aws-sdk/token-providers": "3.1078.0", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-syloC58mXOacUqM2toPNfwd7X3jT+tWj0F/cN7qdW1FQyI0q41J0tPf6DIZ56BF0x82iS9j3ALP45MoBz79YuQ=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.59", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/nested-clients": "^3.997.27", "@aws-sdk/token-providers": "3.1079.0", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-h8793pOjcImx0SB+VcLONcaQQ57VAvKVuqyewQMRKqqH+CSXsG2dwOeLMUJPMxLdNvL7dXOM0ueTukyNUnu5mA=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.58", "", { "dependencies": { "@aws-sdk/core": "^3.974.26", "@aws-sdk/nested-clients": "^3.997.26", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-pTBImKzcGK+pcMKjL0fAJbnYzzYd1c0UDc7BSIOGNQhF9Nuk66vWlIXfYTYyzNSs+w8Q/vfbbNDDU8zdrouwLg=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.59", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/nested-clients": "^3.997.27", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-VoyO9+vl3XVmpZwn4obskrWIkrA/Jf3lSe1E3ZERlaN9u0D4YZ6+HywC3+L98QOXqZesEfedk67gRER8tK8+8w=="],
"@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1078.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.1078.0", "@aws-sdk/core": "^3.974.26", "@aws-sdk/credential-provider-cognito-identity": "^3.972.51", "@aws-sdk/credential-provider-env": "^3.972.52", "@aws-sdk/credential-provider-http": "^3.972.54", "@aws-sdk/credential-provider-ini": "^3.972.59", "@aws-sdk/credential-provider-login": "^3.972.58", "@aws-sdk/credential-provider-node": "^3.972.61", "@aws-sdk/credential-provider-process": "^3.972.52", "@aws-sdk/credential-provider-sso": "^3.972.58", "@aws-sdk/credential-provider-web-identity": "^3.972.58", "@aws-sdk/nested-clients": "^3.997.26", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/credential-provider-imds": "^4.4.5", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-V9Tr3MrNWUfTGgTMIr+WJaMC/VDbXY57BzrGDuyDZn7+vgZjAEG6nI5nMdfnTGdvV9wq1n0zZPYW2RDfcsWNCw=="],
"@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1079.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.1079.0", "@aws-sdk/core": "^3.974.27", "@aws-sdk/credential-provider-cognito-identity": "^3.972.52", "@aws-sdk/credential-provider-env": "^3.972.53", "@aws-sdk/credential-provider-http": "^3.972.55", "@aws-sdk/credential-provider-ini": "^3.972.60", "@aws-sdk/credential-provider-login": "^3.972.59", "@aws-sdk/credential-provider-node": "^3.972.62", "@aws-sdk/credential-provider-process": "^3.972.53", "@aws-sdk/credential-provider-sso": "^3.972.59", "@aws-sdk/credential-provider-web-identity": "^3.972.59", "@aws-sdk/nested-clients": "^3.997.27", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/credential-provider-imds": "^4.4.5", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-emoshJjvvyJDjoMlognc1BtdsTDbe/8NQhXM2wIOz/6/vx4lynUYbwhcNdP6rXuT1q0HzugEDkQK9EvbzB94fA=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.26", "", { "dependencies": { "@aws-sdk/core": "^3.974.26", "@aws-sdk/signature-v4-multi-region": "^3.996.38", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-Lwe3F6K7bs+jEubp1LbrvzeMBYb5fMazJ1IxV9TtKWPF8CSh67Fmwyq9fLz3NL/k55Dfpuph5Dimw76JFgr+SA=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.27", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/signature-v4-multi-region": "^3.996.38", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-A8PIePF9NIIOJ/4Lg1rl9xm/+QaKkHGetq+Z9wb5B+3Da31YYXRo8n7IDMh5C+HQI5eyEmjrwkGWVdYtnLtbXQ=="],
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.38", "", { "dependencies": { "@aws-sdk/types": "^3.973.15", "@smithy/signature-v4": "^5.6.1", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-C379Sk+MiFZCfWZphKlMyLHKxV22OjoGM5KJjj5IJNJcOCWL4IGIpnEGzv1FQiRwhYXfq55SJMfxlqPE08JJ9g=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1078.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.26", "@aws-sdk/nested-clients": "^3.997.26", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-/uyXLBGu3Lw1GbBA2X66hcOMnKtMcqAIF+3/eHfxBQmUeXF2sdqozDPrTfEr/TnSd0D6deZar+eVyhEqqWu29w=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1079.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/nested-clients": "^3.997.27", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-cbietrLlHPhhmbnMPTuDS4Zj/KNGhY+3vVhn6dwjO6Dqzrwothzg2srtcY34T9mlICsTXn34avDoWLHSntP54A=="],
"@aws-sdk/types": ["@aws-sdk/types@3.973.15", "", { "dependencies": { "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-IULn8uBV/SMtmOIANsm4WHXIOtVPBWfOWs3WGL0j/sI+KhaYehvOw0ET+9urnn8MBpiijuU/0JOpuwKOE451PQ=="],
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.33", "", { "dependencies": { "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-ezbwz9WpuLctm6o7P2t2naDhVVPI5jFGrVefVybhcKGjU57VIyT46pQVO0RI2RYkUdhdj2Z9uSIlAzGZE9NW9A=="],
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="],
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
"@azu/format-text": ["@azu/format-text@1.0.2", "", {}, "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg=="],
@@ -971,9 +974,9 @@
"@chevrotain/types": ["@chevrotain/types@11.1.2", "", {}, "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw=="],
"@clack/core": ["@clack/core@1.4.2", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-0Ty/1Gfm+Kb07sXcuESjyKfwEhSy4Ns1AgeEisHb/bDY5fWme0tTeTkU14T1Gmcs17YIjB/teiDe4uaCghbYqQ=="],
"@clack/core": ["@clack/core@1.4.3", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ=="],
"@clack/prompts": ["@clack/prompts@1.6.0", "", { "dependencies": { "@clack/core": "1.4.2", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-EYlRokl8szrP9Z25qT5aepMdBjzBvHF9ZEhzIiUBc9guz/T31EqRgvD0QSgZcpE93xiwrr+OkB4nz0BZyF6fSA=="],
"@clack/prompts": ["@clack/prompts@1.7.0", "", { "dependencies": { "@clack/core": "1.4.3", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A=="],
"@cline/agents": ["@cline/agents@workspace:sdk/packages/agents"],
@@ -1433,7 +1436,7 @@
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
"@iconify/utils": ["@iconify/utils@3.1.3", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw=="],
"@iconify/utils": ["@iconify/utils@3.1.4", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw=="],
"@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
@@ -1657,9 +1660,9 @@
"@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA=="],
"@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.8.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-/3FIraneMcng67SUJCxvyInk/oxzwsxyadufk0wwfOBLf5wqtAGX4MoQASwSbndBPeARzBryUM9Azr5kHIdWLw=="],
"@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.9.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-OQ0vzvbZBiUhjqLnUaoNfYmP8553Crr3aggB4y0ZUi815mZ7idpdJXQmoKdeBKJelYttoBlLSSHubmyw3wvX4w=="],
"@opentelemetry/core": ["@opentelemetry/core@2.8.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww=="],
"@opentelemetry/core": ["@opentelemetry/core@2.9.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw=="],
"@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.56.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "1.29.0", "@opentelemetry/otlp-grpc-exporter-base": "0.56.0", "@opentelemetry/otlp-transformer": "0.56.0", "@opentelemetry/sdk-logs": "0.56.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-/ef8wcphVKZ0uI7A1oqQI/gEMiBUlkeBkM9AGx6AviQFIbgPVSdNK3+bHBkyq5qMkyWgkeQCSJ0uhc5vJpf0dw=="],
@@ -1697,17 +1700,19 @@
"@opentelemetry/propagator-jaeger": ["@opentelemetry/propagator-jaeger@1.30.1", "", { "dependencies": { "@opentelemetry/core": "1.30.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg=="],
"@opentelemetry/resources": ["@opentelemetry/resources@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg=="],
"@opentelemetry/resources": ["@opentelemetry/resources@2.9.0", "", { "dependencies": { "@opentelemetry/core": "2.9.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-jyA5MBLQ+Dkl3+JsZkUoUvL7yHvU64kLsvpXKarWm6347Sl1t1bXFTFykUePNpT5WH5pm9a2Qtt03iIYQhZ1Fg=="],
"@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA=="],
"@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg=="],
"@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.9.0", "", { "dependencies": { "@opentelemetry/core": "2.9.0", "@opentelemetry/resources": "2.9.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Xx8RGS4H5XEBl01WuCreMIpiah9cCXMbSkeuIePPdD2cUpq/vUzYmj8E/MK1OsbOc93FuAD4jfn2WOacKwLn7Q=="],
"@opentelemetry/sdk-node": ["@opentelemetry/sdk-node@0.56.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.56.0", "@opentelemetry/core": "1.29.0", "@opentelemetry/exporter-logs-otlp-grpc": "0.56.0", "@opentelemetry/exporter-logs-otlp-http": "0.56.0", "@opentelemetry/exporter-logs-otlp-proto": "0.56.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.56.0", "@opentelemetry/exporter-trace-otlp-http": "0.56.0", "@opentelemetry/exporter-trace-otlp-proto": "0.56.0", "@opentelemetry/exporter-zipkin": "1.29.0", "@opentelemetry/instrumentation": "0.56.0", "@opentelemetry/resources": "1.29.0", "@opentelemetry/sdk-logs": "0.56.0", "@opentelemetry/sdk-metrics": "1.29.0", "@opentelemetry/sdk-trace-base": "1.29.0", "@opentelemetry/sdk-trace-node": "1.29.0", "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-FOY7tWboBBxqftLNHPJFmDXo9fRoPd2PlzfEvSd6058BJM9gY4pWCg8lbVlu03aBrQjcfCTAhXk/tz1Yqd/m6g=="],
"@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ=="],
"@opentelemetry/sdk-trace": ["@opentelemetry/sdk-trace@2.9.0", "", { "dependencies": { "@opentelemetry/core": "2.9.0", "@opentelemetry/resources": "2.9.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-sGA19HvtrrSKYsseHphluH6j3p6Xa3fqc7c7y8f/7mYWejc1lyDFcpSdD1kYa50HCLUeEo4zA5bW0pniaPszuw=="],
"@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.8.0", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.8.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-nZt9OGufioAc3AfoLTqA9bsAeaMJAictYDdI2VcNQ+PmT+3rfKjAZDZvgPfd8VPX0O5Bw1hdQF6kDK8VSpZiWg=="],
"@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.9.0", "", { "dependencies": { "@opentelemetry/core": "2.9.0", "@opentelemetry/resources": "2.9.0", "@opentelemetry/sdk-trace": "2.9.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-cp9zmTl62R8PJrpvFcmc8N2JQU/xfa0S+61q511Nji+QxCfZ8Ifvg7H27G8cANe4crg4RTrWsVvanHiXjSp6ag=="],
"@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.9.0", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.9.0", "@opentelemetry/core": "2.9.0", "@opentelemetry/sdk-trace-base": "2.9.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-ec9a7ps37huy5itYk0MalaZdSLlM6AXWp/FhtEjgMpp5leEGojBDvAl/UWttQnkMZOvFHKzRESn8TD3yKTF5nQ=="],
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.41.1", "", {}, "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA=="],
@@ -1741,9 +1746,9 @@
"@playwright/test": ["@playwright/test@1.61.1", "", { "dependencies": { "playwright": "1.61.1" }, "bin": { "playwright": "cli.js" } }, "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig=="],
"@posthog/core": ["@posthog/core@1.39.3", "", { "dependencies": { "@posthog/types": "^1.392.0" } }, "sha512-oR+B8Q5O61N+W2+HVOBG9dbxAT/+OVxX+XvpNj6KYgptN2EB14JiKQ9Rm7DNpErNTLV7WApZEK/URkZgldOxfg=="],
"@posthog/core": ["@posthog/core@1.39.6", "", { "dependencies": { "@posthog/types": "^1.392.0" } }, "sha512-o6ajIwN5zXoNP0D4H/QPmOyibNTUkSyOR6ya7AG5U2ywXx4awo72L2KnCoiZPQM5x/bXv6jPBdimH8M18Ax0aw=="],
"@posthog/types": ["@posthog/types@1.392.0", "", {}, "sha512-nctNujXL3FC1v99FktaTMSugSD9ZOZekEpahUSafkU2TSvW+XGKNkQZbokuJtiWvPBK208dwMJva8UfBkChqpw=="],
"@posthog/types": ["@posthog/types@1.392.1", "", {}, "sha512-Qg6Gl7/1vlr8+gPtBi5gwnLgAgiyFoKOVmTvTtDcvya9cpTwZfna7rQmkGQ4B63CunUYNNbOlqcwiUwUDyTK6w=="],
"@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
@@ -1763,7 +1768,7 @@
"@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
"@protobufjs/utf8": ["@protobufjs/utf8@1.1.1", "", {}, "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg=="],
"@protobufjs/utf8": ["@protobufjs/utf8@1.1.2", "", {}, "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug=="],
"@puppeteer/browsers": ["@puppeteer/browsers@2.6.1", "", { "dependencies": { "debug": "^4.4.0", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.6.3", "tar-fs": "^3.0.6", "unbzip2-stream": "^1.4.3", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg=="],
@@ -2209,19 +2214,19 @@
"@secretlint/types": ["@secretlint/types@10.2.2", "", {}, "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg=="],
"@shikijs/core": ["@shikijs/core@4.3.0", "", { "dependencies": { "@shikijs/primitive": "4.3.0", "@shikijs/types": "4.3.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-EooU3i9F6IAE8kEu+AnGf9DFZWkQBZ+hJn3tLVbsH+61mtQiva5biai66fAA6nvFPXkLgvrh7BrR7YcJU83xQQ=="],
"@shikijs/core": ["@shikijs/core@4.3.1", "", { "dependencies": { "@shikijs/primitive": "4.3.1", "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-ANMDxuaPsNMdDC1m4vfvhlDmJweMwkE5XitTwrq2rWHx5jM+dlm4MmHt2PP6t0uejfR77SuhrhJ0zEijIF/uhA=="],
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.3.0", "", { "dependencies": { "@shikijs/types": "4.3.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-hTv/KiFf2tpiqlACPiztGGurEARWIutB8YUhcrA1pUC7VzzwKO+g5crUocrLztrZ5ro5Z4hbXg7bYclETn3gSQ=="],
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.3.1", "", { "dependencies": { "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-JBItcnPuYq7jVJdZo/vMj94r+szT7XEjHFX+mvFDGSEIbVAXAGyHAHzhbWzpGOwYidCZrErJLLgn2PVeiokHnQ=="],
"@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.3.0", "", { "dependencies": { "@shikijs/types": "4.3.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-1vMdN3gHfnKfLYwecUI2ITJI4RhHt96xEaJumVn7Heb0IlJ8WQMIH0Voak+2j22BpSNKdnOfB/pCTPnPm2gq7A=="],
"@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.3.1", "", { "dependencies": { "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-OXyNMzg0pews+msMj4cHeqT4xiYKKvbnn6VbdAXxfoFl3SSx4fJTc8FadECuc5/H9p3BzhNAoAUXKwAu9rWYhg=="],
"@shikijs/langs": ["@shikijs/langs@4.3.0", "", { "dependencies": { "@shikijs/types": "4.3.0" } }, "sha512-rnlqFbBRSys9bT4gl/5rw9RnS0W/I84ZldXPkO7cvlEMoV85TyF/aU01N7/NbSR776RNLjrJKjfFUXJR6wN1Cg=="],
"@shikijs/langs": ["@shikijs/langs@4.3.1", "", { "dependencies": { "@shikijs/types": "4.3.1" } }, "sha512-m0l9nsDqgBHvbZbk7A0/kXz/impK3uB/c6rAn6Gpg/uPtdZRQ+alsN/17MU5thb68XTj/4DxkZAotrM0GGSpDQ=="],
"@shikijs/primitive": ["@shikijs/primitive@4.3.0", "", { "dependencies": { "@shikijs/types": "4.3.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-CPkz64PTa5diRW1ggzMZH9VM/du4RNChYgVtgqrFcgruvIybmCvySv8GkiHSczUHXYuuR8TdKEwFx+UnZMpgdg=="],
"@shikijs/primitive": ["@shikijs/primitive@4.3.1", "", { "dependencies": { "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-CXQRQOYy1leqQ8ceTeJdmXv/bsUY++6QyLpXJ94LZAAYj5X2SKRdc5ipguv4NPyGVKItB2PPwUpRNe0Sjh5S1A=="],
"@shikijs/themes": ["@shikijs/themes@4.3.0", "", { "dependencies": { "@shikijs/types": "4.3.0" } }, "sha512-Avgt05YiT+Y3prjIc9lmQxhJzHBcCfR6cjiFW4OyaMBbt2A6trX5rfjUzx+Vj/mE9qpArYjatnqo9XPjQNW/AQ=="],
"@shikijs/themes": ["@shikijs/themes@4.3.1", "", { "dependencies": { "@shikijs/types": "4.3.1" } }, "sha512-dgpoJ4WqNi2yTmizQHBJ5zcX6j2lE6icN/0yt4l1kkf16jrY/pwPLoTb1ETsWMz0OBLf9ZNvwmxft+cH+N9qSA=="],
"@shikijs/types": ["@shikijs/types@4.3.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oc8b9U2SYvofKZk8e/737nIX0qwf6eV2vHFATeObAu7r+mUVpLs8Re0BmVkIjAWAYgkmG/CzLNo7rzuBzRu/wQ=="],
"@shikijs/types": ["@shikijs/types@4.3.1", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-CHFxE0jztBIZRHH6gxXE7DXUCFXjReEGxZ/j0rfSLGKZuwp2xBYycEP14875DSa9KLL/6700oxIq6oO6ef9K2g=="],
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
@@ -2243,25 +2248,25 @@
"@slack/socket-mode": ["@slack/socket-mode@2.0.7", "", { "dependencies": { "@slack/logger": "^4.0.1", "@slack/web-api": "^7.15.0", "@types/node": ">=18", "@types/ws": "^8", "eventemitter3": "^5", "ws": "^8" } }, "sha512-qYy07je71WnEHgRwmw12DlAnZLi5HXmdlI2WUzUK2LH/rYXQpP6uEg462S5CwfE8FoCKUdIigHtYnOOfzZH1lQ=="],
"@slack/types": ["@slack/types@2.21.1", "", {}, "sha512-I8vmSjNYWsaxuWPx6dz4yeh0h7vRBWbgAMK14LEmblbZ404BtrPbXs6jDPx4cYgGf8msDGF4A9opLZBu21FViQ=="],
"@slack/types": ["@slack/types@2.22.0", "", {}, "sha512-sZ9lIgJhPX2qft/tKWiklFlc0o1FWeI7QtciZJfW1+ErH1eGGHvOZ8e73sleTCFEFJp1q/R0WeS8Oa7AsiDprg=="],
"@slack/web-api": ["@slack/web-api@7.18.0", "", { "dependencies": { "@slack/logger": "^4.0.1", "@slack/types": "^2.21.0", "@types/node": ">=18", "@types/retry": "0.12.0", "axios": "^1.16.0", "eventemitter3": "^5.0.1", "form-data": "^4.0.4", "is-electron": "2.2.2", "is-stream": "^2", "p-queue": "^6", "p-retry": "^4", "retry": "^0.13.1" } }, "sha512-EWBsKUhOFFp87beQg/ToSC+asWB7BrGHuh7uPC1ZI9vr41GjS+3WmmyWIMqs+mF6U+mh4d6HhtFlv67TrJFsvw=="],
"@smithy/core": ["@smithy/core@3.29.0", "", { "dependencies": { "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-sEvpvkBVoMxjoek35XyJFn2ZD3EJ1RpiZrT47WaZodxzAIWS44zkdvbqGE/ZlugtjiQp62cffYZ9ldyRkjAGnA=="],
"@smithy/core": ["@smithy/core@3.29.1", "", { "dependencies": { "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-qoiY4nrk5OCu1+eIR1VB8l5DmON/oKiqrd5zZFAhXJXjJlLWQusKEW/SkBDAtGDcPaz86m9kfcE1lngU0GlM6A=="],
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.4.5", "", { "dependencies": { "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-LnjUTNG0GgQlKIq7IioeOrPaEmC5xOd1WtAz24TLSiYQnWX2uHr53GrFuQhkrJBktPYCMga/NbUOW7hFbSA2Cg=="],
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.4.6", "", { "dependencies": { "@smithy/core": "^3.29.1", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-B2WQ/PV/H6Jeg3lrIq6bKUfa6Hy01mtK7CGs6lhjzHA6k4aagldH6T6eEjnzKl4HI0cJnAsxfJ19pgb5PV+CVQ=="],
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.4.5", "", { "dependencies": { "@smithy/core": "^3.29.0", "tslib": "^2.6.2" } }, "sha512-rasV+96Obv6tEhWPKWaehEhR+MEd2/lE/rdOYcmSMh6tJiQdoZg4v21p37Y1A7DMZOFgFDUX/3eNl/J3+MJDdA=="],
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.4.6", "", { "dependencies": { "@smithy/core": "^3.29.1", "tslib": "^2.6.2" } }, "sha512-4N/HbPptAjynK/FDrTiSAEXmpEDcQw54SeY7qnKbNMOcbHK0B0nEq+OKDYXrnqyp5YhZGzXFDUJANGkdzz6H5Q=="],
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.6.2", "", { "dependencies": { "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-q96PSDOAGw+X+nuELd7Cjebps0SYr+YlPbviEX9sLVw+VM4M7VV8hn1nL1mGS6urDu33eQ5A7WhlphaDO6kUyQ=="],
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.6.3", "", { "dependencies": { "@smithy/core": "^3.29.1", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-CwCc/7SMTj45y97MUnDTbTaxvtAsiNNRm81z3abROIuMbMsC2Iy5EKfkkVdsKrz8WExQAAMx1EJapq+9j4fFTQ=="],
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.9.2", "", { "dependencies": { "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-s0yAIRj6TVfHgl+QzVyqal1KMGZ9B5512IrxKc6+dOpw8fUmFL3CvuAhjv0J+aNjUPfVZ2IhqPEDvkB5Ncx9oA=="],
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.9.3", "", { "dependencies": { "@smithy/core": "^3.29.1", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-qZTa4gQFUo8RM02rk6q5UVTDLNrQ1oS20LsepBzqq1QBVc/EHJ03OOUADcqMZiXHArW+Y7+OGY0BpdTwZRq/Yg=="],
"@smithy/signature-v4": ["@smithy/signature-v4@5.6.1", "", { "dependencies": { "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-SqvuP75p/DmgWWI7jv4kf/UW+V4LFmlUn19s604SgAcRuJRB1vDnWwzZMYCLUcmKxko9wDn6iLgGEIpTNgZbIQ=="],
"@smithy/signature-v4": ["@smithy/signature-v4@5.6.2", "", { "dependencies": { "@smithy/core": "^3.29.1", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-QgHflghMoPxCJ9axiCVh8KZfbC9fuP6vkXXyK//E3cq7nLaSSyyLj0GAoqVWezYeDQmXIZhmlRvLE16jsqDK6g=="],
"@smithy/types": ["@smithy/types@4.15.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-x3L0XSACF6UYzKpa9biqiRMgvH5+wnFFew9Tm/grFYqgaupPwx/+ojDPpPJM8dZON3S9tjz5U+PQYsCBd1Mw5Q=="],
"@smithy/util-utf8": ["@smithy/util-utf8@4.4.5", "", { "dependencies": { "@smithy/core": "^3.29.0", "tslib": "^2.6.2" } }, "sha512-ZS7Y5X8mU9qRSsqwOeGKw86WWtPsRxa396kX2HyhYwADRqFHJ0cb3p2uFk6QnFF3BluUUBHTZJpPA9QuEl0tlg=="],
"@smithy/util-utf8": ["@smithy/util-utf8@4.4.6", "", { "dependencies": { "@smithy/core": "^3.29.1", "tslib": "^2.6.2" } }, "sha512-gncTZwzB/RTzm29VvK1nZhCHdPjBkR8pNaFtUMbQpkG6vFMjPHe/RsnmMXfrYj8Qs5s6QH5f1Mp9CBXFOHxqlQ=="],
"@so-ric/colorspace": ["@so-ric/colorspace@1.1.6", "", { "dependencies": { "color": "^5.0.2", "text-hex": "1.0.x" } }, "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw=="],
@@ -2489,7 +2494,7 @@
"@types/d3-quadtree": ["@types/d3-quadtree@3.0.6", "", {}, "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="],
"@types/d3-random": ["@types/d3-random@3.0.3", "", {}, "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ=="],
"@types/d3-random": ["@types/d3-random@3.0.4", "", {}, "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA=="],
"@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="],
@@ -2609,25 +2614,25 @@
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.62.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.62.1", "@typescript-eslint/type-utils": "8.62.1", "@typescript-eslint/utils": "8.62.1", "@typescript-eslint/visitor-keys": "8.62.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.62.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.63.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.63.0", "@typescript-eslint/type-utils": "8.63.0", "@typescript-eslint/utils": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.63.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.62.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.62.1", "@typescript-eslint/types": "8.62.1", "@typescript-eslint/typescript-estree": "8.62.1", "@typescript-eslint/visitor-keys": "8.62.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.63.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/typescript-estree": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.62.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.62.1", "@typescript-eslint/types": "^8.62.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.63.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.63.0", "@typescript-eslint/types": "^8.63.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.62.1", "", { "dependencies": { "@typescript-eslint/types": "8.62.1", "@typescript-eslint/visitor-keys": "8.62.1" } }, "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0" } }, "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.62.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.63.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.62.1", "", { "dependencies": { "@typescript-eslint/types": "8.62.1", "@typescript-eslint/typescript-estree": "8.62.1", "@typescript-eslint/utils": "8.62.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "@typescript-eslint/typescript-estree": "8.63.0", "@typescript-eslint/utils": "8.63.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.62.1", "", {}, "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.63.0", "", {}, "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.62.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.62.1", "@typescript-eslint/tsconfig-utils": "8.62.1", "@typescript-eslint/types": "8.62.1", "@typescript-eslint/visitor-keys": "8.62.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.63.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.63.0", "@typescript-eslint/tsconfig-utils": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.62.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.62.1", "@typescript-eslint/types": "8.62.1", "@typescript-eslint/typescript-estree": "8.62.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.63.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/typescript-estree": "8.63.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.62.1", "", { "dependencies": { "@typescript-eslint/types": "8.62.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw=="],
"@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.6", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og=="],
@@ -2641,21 +2646,21 @@
"@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@4.3.1", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.0", "@swc/core": "^1.15.11" }, "peerDependencies": { "vite": "^4 || ^5 || ^6 || ^7 || ^8" } }, "sha512-PaeokKjAGraNN+s5SIApgsktnJprIyt3zgEIu7awnEdfn29QiB2crTcCzyi2XGpX9rUnTc0cKU07Wm0N0g7H2w=="],
"@vitest/coverage-v8": ["@vitest/coverage-v8@3.2.6", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^1.0.2", "ast-v8-to-istanbul": "^0.3.3", "debug": "^4.4.1", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", "magic-string": "^0.30.17", "magicast": "^0.3.5", "std-env": "^3.9.0", "test-exclude": "^7.0.1", "tinyrainbow": "^2.0.0" }, "peerDependencies": { "@vitest/browser": "3.2.6", "vitest": "3.2.6" }, "optionalPeers": ["@vitest/browser"] }, "sha512-LsAdmUapA0qSN306d8+zOyawM0hFm2m2Hg9IwVNIKBm+qJV8cijiq2c+gxKZcB1HCfIWAy+0qEZDCUQA58A1cw=="],
"@vitest/coverage-v8": ["@vitest/coverage-v8@3.2.7", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^1.0.2", "ast-v8-to-istanbul": "^0.3.3", "debug": "^4.4.1", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", "magic-string": "^0.30.17", "magicast": "^0.3.5", "std-env": "^3.9.0", "test-exclude": "^7.0.1", "tinyrainbow": "^2.0.0" }, "peerDependencies": { "@vitest/browser": "3.2.7", "vitest": "3.2.7" }, "optionalPeers": ["@vitest/browser"] }, "sha512-NEGWJS2XNu2PfRLQwOO3CTKj1tTETxNBdk454vDxVBhxJYhPaA/eS0nAI0c+1El1P7a60z8+i+ZrQoGESweGKg=="],
"@vitest/expect": ["@vitest/expect@4.1.9", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA=="],
"@vitest/expect": ["@vitest/expect@4.1.10", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA=="],
"@vitest/mocker": ["@vitest/mocker@4.1.9", "", { "dependencies": { "@vitest/spy": "4.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw=="],
"@vitest/mocker": ["@vitest/mocker@4.1.10", "", { "dependencies": { "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow=="],
"@vitest/pretty-format": ["@vitest/pretty-format@4.1.9", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A=="],
"@vitest/pretty-format": ["@vitest/pretty-format@4.1.10", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q=="],
"@vitest/runner": ["@vitest/runner@4.1.9", "", { "dependencies": { "@vitest/utils": "4.1.9", "pathe": "^2.0.3" } }, "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg=="],
"@vitest/runner": ["@vitest/runner@4.1.10", "", { "dependencies": { "@vitest/utils": "4.1.10", "pathe": "^2.0.3" } }, "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg=="],
"@vitest/snapshot": ["@vitest/snapshot@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "@vitest/utils": "4.1.9", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA=="],
"@vitest/snapshot": ["@vitest/snapshot@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw=="],
"@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="],
"@vitest/spy": ["@vitest/spy@4.1.10", "", {}, "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw=="],
"@vitest/utils": ["@vitest/utils@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA=="],
"@vitest/utils": ["@vitest/utils@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA=="],
"@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.7", "", {}, "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g=="],
@@ -2699,9 +2704,9 @@
"@xterm/headless": ["@xterm/headless@5.5.0", "", {}, "sha512-5xXB7kdQlFBP82ViMJTwwEc3gKCLGKR/eoxQm4zge7GPBl86tCdI0IdPJjoKd8mUSFXz5V7i/25sfsEkP4j46g=="],
"@xyflow/react": ["@xyflow/react@12.11.1", "", { "dependencies": { "@xyflow/system": "0.0.78", "classcat": "^5.0.3", "zustand": "^4.4.0" }, "peerDependencies": { "@types/react": ">=17", "@types/react-dom": ">=17", "react": ">=17", "react-dom": ">=17" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-L+zBoLGSXham0MnlY8QqjfR7/C5JNw0zxkaey5aZ5XmCgJBAdH4+WRIu8CR40d3l/BdU635V6YbhBK1jMo8/6Q=="],
"@xyflow/react": ["@xyflow/react@12.11.2", "", { "dependencies": { "@xyflow/system": "0.0.79", "classcat": "^5.0.3", "zustand": "^4.4.0" }, "peerDependencies": { "@types/react": ">=17", "@types/react-dom": ">=17", "react": ">=17", "react-dom": ">=17" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-eLAlDWJfWnQEhJwGMjlWdAXO9eYllKpliUmPQlAmOLxz6mExXuzMVDUKLMquixgkrtmMFFtug3jGKmYYld12cA=="],
"@xyflow/system": ["@xyflow/system@0.0.78", "", { "dependencies": { "@types/d3-drag": "^3.0.7", "@types/d3-interpolate": "^3.0.4", "@types/d3-selection": "^3.0.10", "@types/d3-transition": "^3.0.8", "@types/d3-zoom": "^3.0.8", "d3-drag": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" } }, "sha512-lY0z2qP33fUhTva9Vaxrk0lqZta2pkbxB1trHAx1omnJqRtPvDlAQYV2r5fhS6AdpkulYmbNW0svy+A4/t4B/g=="],
"@xyflow/system": ["@xyflow/system@0.0.79", "", { "dependencies": { "@types/d3-drag": "^3.0.7", "@types/d3-interpolate": "^3.0.4", "@types/d3-selection": "^3.0.10", "@types/d3-transition": "^3.0.8", "@types/d3-zoom": "^3.0.8", "d3-drag": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" } }, "sha512-czLyOh91NF0hIzbNzwi8I6GlqG23BHh2435OddfI6uiaLH3xdrdygO93gqgH1Bv9mhy8XPFQJOBn1FTq4LvEWA=="],
"abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="],
@@ -2723,7 +2728,7 @@
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
"ai": ["ai@6.0.218", "", { "dependencies": { "@ai-sdk/gateway": "3.0.142", "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35", "@opentelemetry/api": "^1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HsyCUNaaYgX/b/kGOoYfKkqfT1HvpUKKDb8YkN1FKeCNZjKdqXLGY+cKBpYGIRAvsPuOHskxLxZ46cK1dTBWQQ=="],
"ai": ["ai@6.0.219", "", { "dependencies": { "@ai-sdk/gateway": "3.0.143", "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35", "@opentelemetry/api": "^1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rtTDz99Rc9HsstSJ7YdO8DX7DQwS442N2vQ5jOopXDdo4qfkLrtIRpORdztFMOZxX/XjBzHRlvqF6eMg3cpyLA=="],
"ai-sdk-provider-claude-code": ["ai-sdk-provider-claude-code@3.5.0", "", { "dependencies": { "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.1", "@anthropic-ai/claude-agent-sdk": "^0.3.170" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-7SZrTGkuR4G4zeNrjnJgDzYkrKZqzq7GUQJcFBKCxFH5dJpS9lbs+g9BCt7bwT1gDm4SAUmOQ0T5rKqcC2HkVQ=="],
@@ -2805,7 +2810,7 @@
"bare-events": ["bare-events@2.9.1", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg=="],
"bare-fs": ["bare-fs@4.7.2", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg=="],
"bare-fs": ["bare-fs@4.7.3", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-xRgplks8SvcKkdlv2M6Z2LZmRsmqd+x0nXXGXeMEjwdibj1HSDrlnqBRLeYdMvsgCox7Bq0e+DHwfczOfsn6IA=="],
"bare-os": ["bare-os@3.9.3", "", {}, "sha512-fF4Q7QsyKVF5Rj0qvI8BgUNjqzC2JvQlpTaPLjVJVxYVUX5Zr9un+y3w1HmA4nNKdFmRBT8z/WmrjvXzXVerKQ=="],
@@ -2817,7 +2822,7 @@
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.40", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.42", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q=="],
"basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="],
@@ -2857,7 +2862,7 @@
"browser-stdout": ["browser-stdout@1.3.1", "", {}, "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw=="],
"browserslist": ["browserslist@4.28.4", "", { "dependencies": { "baseline-browser-mapping": "^2.10.38", "caniuse-lite": "^1.0.30001799", "electron-to-chromium": "^1.5.376", "node-releases": "^2.0.48", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw=="],
"browserslist": ["browserslist@4.28.5", "", { "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001800", "electron-to-chromium": "^1.5.387", "node-releases": "^2.0.50", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ=="],
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
@@ -2901,7 +2906,7 @@
"camelize": ["camelize@1.0.1", "", {}, "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ=="],
"caniuse-lite": ["caniuse-lite@1.0.30001800", "", {}, "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA=="],
"caniuse-lite": ["caniuse-lite@1.0.30001802", "", {}, "sha512-vmv8ub2xwTNmljSKf82mtCk5JH7hC+YgzLj3P5zotvA0tPQ9016tdNNOG8WRca1IxOnhSsivB+J0z5FeE5LOUw=="],
"case-anything": ["case-anything@2.1.13", "", {}, "sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng=="],
@@ -2985,7 +2990,7 @@
"color-support": ["color-support@1.1.3", "", { "bin": { "color-support": "bin.js" } }, "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="],
"color2k": ["color2k@2.0.3", "", {}, "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog=="],
"color2k": ["color2k@2.0.4", "", {}, "sha512-OXAPGFRNeLFnUfqDtloYdxkwsJoIdXe28+bjbpJiPqyei2HPa3VHmMCWa0Qe62+U4Ftf9Hj7hRssOkxz7WiWbg=="],
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
@@ -3243,7 +3248,7 @@
"eight-colors": ["eight-colors@1.3.3", "", {}, "sha512-4B54S2Qi4pJjeHmCbDIsveQZWQ/TSSQng4ixYJ9/SYHHpeS5nYK0pzcHvWzWUfRsvJQjwoIENhAwqg59thQceg=="],
"electron-to-chromium": ["electron-to-chromium@1.5.384", "", {}, "sha512-g6KAKY1vkYsADvSPWvdJsuYT0ixdcu6lUtD9P/wJKGBEDlZVXh2AX42j1mPqqaQPDluWjara9ziQ7xqAeXCt5A=="],
"electron-to-chromium": ["electron-to-chromium@1.5.387", "", {}, "sha512-TaxwufTFDufvPEoXdhwVrA3UdFWBeWGkYoJ1K8ldF1xe6gKfth6iRNS5lTQ5JPNOHdGQm8PT1QYKUqFLCiUefQ=="],
"embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="],
@@ -3473,7 +3478,7 @@
"gauge": ["gauge@5.0.2", "", { "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", "console-control-strings": "^1.1.0", "has-unicode": "^2.0.1", "signal-exit": "^4.0.1", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.5" } }, "sha512-pMaFftXPtiGIHCJHdcUUx9Rby/rFT/Kkt3fIIGCs+9PMDIljSyRiqraTlxNtBReJRDfUefpa263RQ3vnp5G/LQ=="],
"gaxios": ["gaxios@7.1.5", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg=="],
"gaxios": ["gaxios@7.1.6", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-aIQ0QL8Or8vsUhHyXGA6AohOFRrAAiHhrvsAG6myzcSlfhxSXtnwXA/pRuQTilFgjhLe30swK5rg1d7E1f8Izw=="],
"gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
@@ -3521,7 +3526,7 @@
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"graphql": ["graphql@17.0.1", "", {}, "sha512-8eWbg5Zcv/8o20nzEjHUGPTj20MLFJjc5kagbIPxbaeGxvFwpitJhemEC/k17n5+UD4M/9ea5rTuce78mELujQ=="],
"graphql": ["graphql@17.0.2", "", {}, "sha512-FRWbddMxfkjiB7z+aQDWIR+E34xo9I8c9mtK2RPv8PmMzKRvrdsreHL/Ui/TmwHJfhHChEtsFPyMHKI+xuarQQ=="],
"grpc-health-check": ["grpc-health-check@2.1.0", "", { "dependencies": { "@grpc/proto-loader": "^0.7.13" } }, "sha512-HH3WjwNtusMTEQAtRelFgsFyNcOdihvpjusNDIrGYfWG8tPNSHqELrSyriIjm70k65YSxetsKG1y4H1L5gi1wQ=="],
@@ -3591,7 +3596,7 @@
"highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="],
"hono": ["hono@4.12.27", "", {}, "sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q=="],
"hono": ["hono@4.12.28", "", {}, "sha512-YwUvVpSF7m1yOblFPrU3Hbo8XhPheBoiyfGuII6z19LnOr6JpDnyyp7LFNrfV56wS8tpvtBFGRISHN02pDdLOA=="],
"hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="],
@@ -3793,7 +3798,7 @@
"jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
"jsonrepair": ["jsonrepair@3.14.1", "", { "bin": { "jsonrepair": "bin/cli.js" } }, "sha512-NpGgMhmzG/fajkBEFlS9jZvMSGDvc2xN/9wNCHZ+Nx32GZfLRELU6UE6dQkebvrQUct9S+7bvnpX29NB36Qbdw=="],
"jsonrepair": ["jsonrepair@3.15.0", "", { "bin": { "jsonrepair": "bin/cli.js" } }, "sha512-wy8OTjwsJwQRnQJkKnMJJ9vcytRdBPAgIF/Hy6+s1dAj42BHMKiyL8JzEieIl3JY7idt8eyHwBWTO8mh/+mtwA=="],
"jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="],
@@ -4145,7 +4150,7 @@
"nice-grpc-common": ["nice-grpc-common@2.0.3", "", { "dependencies": { "ts-error": "^1.0.6" } }, "sha512-MEhnD3JMah0mgyivpb9hpRDbOBuXBxI/TVO+OK1h6rC97WM42HsPMR+zzRNQ0C5BqYJTw1nyWiQRD0DucO+pjQ=="],
"node-abi": ["node-abi@3.93.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-Cu6yUpX5Iavugm8BeX7c0wgU9CvOqfd1yM6A1d2q2ZMjym7GjpASv2GdRcTq3Fx+Sb5OgBkEEpw4VnAbY6Y5RA=="],
"node-abi": ["node-abi@3.94.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g=="],
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
@@ -4309,7 +4314,7 @@
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
"picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
"pino": ["pino@10.3.1", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg=="],
@@ -4343,13 +4348,13 @@
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
"posthog-js": ["posthog-js@1.396.4", "", { "dependencies": { "@posthog/core": "^1.39.3", "@posthog/types": "^1.392.0", "core-js": "^3.38.1", "dompurify": "^3.3.2", "fflate": "^0.4.8", "preact": "^10.29.2", "query-selector-shadow-dom": "^1.0.1", "web-vitals": "^5.3.0" } }, "sha512-PycBmwKQD1T7YFYrGRb8rjQET/UVnexgUy8gVe6UBEhwHXEIhZF4na5VakJbn4zu1wg4tzjt8r7PA4VLu6bDjg=="],
"posthog-js": ["posthog-js@1.396.9", "", { "dependencies": { "@posthog/core": "^1.39.6", "@posthog/types": "^1.392.1", "core-js": "^3.38.1", "dompurify": "^3.3.2", "fflate": "^0.4.8", "preact": "^10.29.2", "query-selector-shadow-dom": "^1.0.1", "web-vitals": "^5.3.0" } }, "sha512-tORn1rcL78MauLsibfID2IcXDqn9+r6cHswOdRWwVOJOVjesIdkvQwV2qwXbNOXqqlMl0jox0PErTTy5cv5TUA=="],
"posthog-node": ["posthog-node@5.39.2", "", { "dependencies": { "@posthog/core": "^1.39.3" }, "peerDependencies": { "rxjs": "^7.0.0" }, "optionalPeers": ["rxjs"] }, "sha512-5piMedjlQ2x+UKLvHWTC5ls5/T1dDZKE1Pu5AKkYh9EkbZOjvu0cac6lWFB7mgbGkKQ0I1bhbjDx1QAYRJ7Unw=="],
"posthog-node": ["posthog-node@5.39.4", "", { "dependencies": { "@posthog/core": "^1.39.5" }, "peerDependencies": { "rxjs": "^7.0.0" }, "optionalPeers": ["rxjs"] }, "sha512-+fCQ7htBFRQQFbIzl1T0TA7bDwYyaB9XP308ZFMCUoB5LzTzOFxBa6TYVrxdH/VQl43WXTp6sf0QsG2Z4XlNBg=="],
"powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
"preact": ["preact@10.29.3", "", {}, "sha512-D9NL1GAnJZhc3RndVs4gDdxEeU9TcHgywMrhhOsnpdlvFjdbx0gAsLUnH6JEhlJH5giL7Tx5biWPUSEXE/HPzw=="],
"preact": ["preact@10.29.4", "", {}, "sha512-GMpwh9+NJ8tSmqwIaVyFRQkiKfBEzQ+k7r7tle4W+kaJ+7wJiB9hFz9BixAomMtenPPSBfM4bZhXozGxhf0uFQ=="],
"prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
@@ -4431,7 +4436,7 @@
"react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
"react-hook-form": ["react-hook-form@7.80.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-4P+fk6oXsxY+6xSj7Euhc2sumQD8zQqCuVHoJwoyp9EchP+IUW9OESB7uHFJOKsIBQ4MQqYE84INJFqUCYNoOg=="],
"react-hook-form": ["react-hook-form@7.81.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-ocbmr2p5KBMoAfj4WCUvped33lVi1Kd5DuDUvQDnB6VEAacOjPI/jMbtDdbhco4y9ct4xUuCmMY0b/C9L0QHjw=="],
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
@@ -4611,7 +4616,7 @@
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"shadcn": ["shadcn@4.12.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "kleur": "^4.1.5", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "undici": "^7.27.2", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-o781ieQziCnXH2FKsEqxp1fnbHdbgAPO9inTSPeZ59hQfsZXuMGp3ul8oFSV5KQS4nbUK9b+DrDE6C7OvfKKQQ=="],
"shadcn": ["shadcn@4.13.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "kleur": "^4.1.5", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "undici": "^7.27.2", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-5fuJ4jI/GcPeA/iTL4cJivCZuYQGXz/N3bIzyd+Gd/FM6xUCy2MxGG+LaDQuw2cjNy9zGPSFPTEmI048UwPTZA=="],
"shallow-clone": ["shallow-clone@3.0.1", "", { "dependencies": { "kind-of": "^6.0.2" } }, "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA=="],
@@ -4623,7 +4628,7 @@
"shell-quote": ["shell-quote@1.9.0", "", {}, "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA=="],
"shiki": ["shiki@4.3.0", "", { "dependencies": { "@shikijs/core": "4.3.0", "@shikijs/engine-javascript": "4.3.0", "@shikijs/engine-oniguruma": "4.3.0", "@shikijs/langs": "4.3.0", "@shikijs/themes": "4.3.0", "@shikijs/types": "4.3.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-NKKjWzR6LIGL3sXBrWDw9sDS9cxx42/DkysaNqJEeOWE8Kix5gpak0bc00OfDVEO4oyXSyz8+aRaqKoBD1yo7A=="],
"shiki": ["shiki@4.3.1", "", { "dependencies": { "@shikijs/core": "4.3.1", "@shikijs/engine-javascript": "4.3.1", "@shikijs/engine-oniguruma": "4.3.1", "@shikijs/langs": "4.3.1", "@shikijs/themes": "4.3.1", "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw=="],
"shimmer": ["shimmer@1.2.1", "", {}, "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="],
@@ -4775,7 +4780,7 @@
"symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
"systeminformation": ["systeminformation@5.31.11", "", { "os": "!aix", "bin": { "systeminformation": "lib/cli.js" } }, "sha512-I6O7iaUj23AXRgCPDDnvi3xHvdOLp4+1YMbF+X194lJwY1NeWojgHJPhslVKcmTtrLTguRk3QJK+xEdTiI3P0w=="],
"systeminformation": ["systeminformation@5.31.13", "", { "os": "!aix", "bin": { "systeminformation": "lib/cli.js" } }, "sha512-iUJXJoKzm4vtLSeT3nwe2s9QjoJAxHg7wYJ0KaQ54Xy2u9jsTq0ULWQQ0+T72FXjX2XnGqubazNx9lUfng7ELw=="],
"tabbable": ["tabbable@6.5.0", "", {}, "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA=="],
@@ -4887,7 +4892,7 @@
"ts-poet": ["ts-poet@6.12.0", "", { "dependencies": { "dprint-node": "^1.0.8" } }, "sha512-xo+iRNMWqyvXpFTaOAvLPA5QAWO6TZrSUs5s4Odaya3epqofBu/fMLHEWl8jPmjhA0s9sgj9sNvF1BmaQlmQkA=="],
"ts-proto": ["ts-proto@2.11.10", "", { "dependencies": { "@bufbuild/protobuf": "^2.10.2", "case-anything": "^2.1.13", "ts-poet": "^6.12.0", "ts-proto-descriptors": "2.1.0" }, "bin": { "protoc-gen-ts_proto": "protoc-gen-ts_proto" } }, "sha512-7mvz2RbOZc0J/+x8biIcVHJ0nx7xjH79vtoEnkpKT5l8yFF5ERe03dee2W3CbB5vb1QgnbQXGzeBGGryJ+Q9EA=="],
"ts-proto": ["ts-proto@2.12.0", "", { "dependencies": { "@bufbuild/protobuf": "^2.10.2", "case-anything": "^2.1.13", "ts-poet": "^6.12.0", "ts-proto-descriptors": "2.1.0" }, "bin": { "protoc-gen-ts_proto": "protoc-gen-ts_proto" } }, "sha512-ezMxg57ZiK/ZTW14U7y38+qyWHJr8cn8ELKuppANER666YnUteuNFO/mM1qI+9/wAHoTAfRadnIwtVdp1xw0jQ=="],
"ts-proto-descriptors": ["ts-proto-descriptors@2.1.0", "", { "dependencies": { "@bufbuild/protobuf": "^2.0.0" } }, "sha512-S5EZYEQ6L9KLFfjSRpZWDIXDV/W7tAj8uW7pLsihIxyr62EAVSiKuVPwE8iWnr849Bqa53enex1jhDUcpgquzA=="],
@@ -4917,7 +4922,7 @@
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"typescript-eslint": ["typescript-eslint@8.62.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.62.1", "@typescript-eslint/parser": "8.62.1", "@typescript-eslint/typescript-estree": "8.62.1", "@typescript-eslint/utils": "8.62.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw=="],
"typescript-eslint": ["typescript-eslint@8.63.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.63.0", "@typescript-eslint/parser": "8.63.0", "@typescript-eslint/typescript-estree": "8.63.0", "@typescript-eslint/utils": "8.63.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ=="],
"uc.micro": ["uc.micro@1.0.6", "", {}, "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="],
@@ -5013,11 +5018,11 @@
"victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="],
"vite": ["vite@8.1.2", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.16", "rolldown": "~1.1.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-6YYPbRXTxx6bRXmOn7XdnQAy5DQNHhDgtjhDHI13oe4pY93kkcdGJWxpGwOm++/Wh0QpQhDrpIoVMrmrsI5AGQ=="],
"vite": ["vite@8.1.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.16", "rolldown": "~1.1.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA=="],
"vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
"vitest": ["vitest@4.1.9", "", { "dependencies": { "@vitest/expect": "4.1.9", "@vitest/mocker": "4.1.9", "@vitest/pretty-format": "4.1.9", "@vitest/runner": "4.1.9", "@vitest/snapshot": "4.1.9", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.9", "@vitest/browser-preview": "4.1.9", "@vitest/browser-webdriverio": "4.1.9", "@vitest/coverage-istanbul": "4.1.9", "@vitest/coverage-v8": "4.1.9", "@vitest/ui": "4.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ=="],
"vitest": ["vitest@4.1.10", "", { "dependencies": { "@vitest/expect": "4.1.10", "@vitest/mocker": "4.1.10", "@vitest/pretty-format": "4.1.10", "@vitest/runner": "4.1.10", "@vitest/snapshot": "4.1.10", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.10", "@vitest/browser-preview": "4.1.10", "@vitest/browser-webdriverio": "4.1.10", "@vitest/coverage-istanbul": "4.1.10", "@vitest/coverage-v8": "4.1.10", "@vitest/ui": "4.1.10", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw=="],
"voca": ["voca@1.4.1", "", {}, "sha512-NJC/BzESaHT1p4B5k4JykxedeltmNbau4cummStd4RjFojgq/kLew5TzYge9N2geeWyI2w8T30wUET5v+F7ZHA=="],
@@ -5109,7 +5114,7 @@
"yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="],
"yocto-spinner": ["yocto-spinner@1.2.0", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-Yw0hUB6UA3o4YUgKy3oSe9a4cxoaZ9sBfYDw+JSxo6Id0KoJGoxzPA24qqUXYKBWABs/zDSGTz9kww7t3F0XGw=="],
"yocto-spinner": ["yocto-spinner@1.2.1", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-9cbFWLhbiZp+820O4pkHGNncI7+MrUGzBOjw8NMG+ewsY+aG0DdEXnr19Smxao32YOjLZRMdn1UtaxcrXOYOIg=="],
"yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="],
@@ -5213,6 +5218,8 @@
"@heroui/use-is-mobile/@react-aria/ssr": ["@react-aria/ssr@3.9.10", "", { "dependencies": { "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ=="],
"@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="],
"@jest/types/@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
"@jest/types/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -5679,9 +5686,9 @@
"@streamdown/code/shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
@@ -5753,7 +5760,7 @@
"body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
"body-parser/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"body-parser/iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="],
"chai/assertion-error": ["assertion-error@1.1.0", "", {}, "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw=="],
@@ -5965,7 +5972,7 @@
"onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"open-graph-scraper/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"open-graph-scraper/iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="],
"ora/cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
@@ -6095,7 +6102,7 @@
"radix-ui/@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.7" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1wNZBggTDK3GRuuQ6nP4k2yi7a6l7I5qbMPbZcRsrGsGVead/f/d5FhEzUvqFs0bcrDLx7n1zKQ3JvLR6whaaw=="],
"raw-body/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"raw-body/iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="],
"rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
@@ -6197,8 +6204,6 @@
"unzipper/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
"vaul/@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.18", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.14", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.11", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-portal": "1.1.13", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-slot": "1.3.0", "@radix-ui/react-use-controllable-state": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-apa28mldjMgORmE6g/w3sCcA0Y9UAVeeDVoozN4i7kOw12mLl9RBchfzK3Nn6qxOWjrZhK1Lfy7f07kyzxtnBw=="],
"verror/core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="],
"vite-node/es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
@@ -6231,7 +6236,7 @@
"webview-ui/vite": ["vite@7.3.6", "", { "dependencies": { "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg=="],
"webview-ui/vitest": ["vitest@3.2.6", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.6", "@vitest/mocker": "3.2.6", "@vitest/pretty-format": "^3.2.6", "@vitest/runner": "3.2.6", "@vitest/snapshot": "3.2.6", "@vitest/spy": "3.2.6", "@vitest/utils": "3.2.6", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.6", "@vitest/ui": "3.2.6", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw=="],
"webview-ui/vitest": ["vitest@3.2.7", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.7", "@vitest/mocker": "3.2.7", "@vitest/pretty-format": "^3.2.7", "@vitest/runner": "3.2.7", "@vitest/snapshot": "3.2.7", "@vitest/spy": "3.2.7", "@vitest/utils": "3.2.7", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.7", "@vitest/ui": "3.2.7", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg=="],
"wide-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
@@ -6773,7 +6778,7 @@
"googleapis-common/gaxios/rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
"googleapis-common/google-auth-library/gaxios": ["gaxios@7.1.5", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg=="],
"googleapis-common/google-auth-library/gaxios": ["gaxios@7.1.6", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-aIQ0QL8Or8vsUhHyXGA6AohOFRrAAiHhrvsAG6myzcSlfhxSXtnwXA/pRuQTilFgjhLe30swK5rg1d7E1f8Izw=="],
"hast-to-hyperscript/style-to-object/inline-style-parser": ["inline-style-parser@0.1.1", "", {}, "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q=="],
@@ -6975,28 +6980,6 @@
"unzipper/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
"vaul/@radix-ui/react-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.1.4", "", {}, "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-context": ["@radix-ui/react-context@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-effect-event": "0.0.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4lUhWTWAjbDIqFrAPWJ3WqBOpO5YchVZ88X3nh6H9Lu5AFi5nCUeTPj3D8FSDmabmFeRe9ME0BDA4MwKTha5GQ=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.11", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-use-callback-ref": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Mn88Vg2whaRocGJNOH+DKFqYm6ySFPQaiwHNxZPyjn99B52KAEJWWY9NP83+nWdk2HM3rdov+STu9AG471Rt9w=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-id": ["@radix-ui/react-id@1.1.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.13", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.6", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.7", "", { "dependencies": { "@radix-ui/react-slot": "1.3.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.3.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA=="],
"webview-ui/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"webview-ui/@vitejs/plugin-react-swc/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
@@ -7009,19 +6992,19 @@
"webview-ui/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
"webview-ui/vitest/@vitest/expect": ["@vitest/expect@3.2.6", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.6", "@vitest/utils": "3.2.6", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ=="],
"webview-ui/vitest/@vitest/expect": ["@vitest/expect@3.2.7", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.7", "@vitest/utils": "3.2.7", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w=="],
"webview-ui/vitest/@vitest/mocker": ["@vitest/mocker@3.2.6", "", { "dependencies": { "@vitest/spy": "3.2.6", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw=="],
"webview-ui/vitest/@vitest/mocker": ["@vitest/mocker@3.2.7", "", { "dependencies": { "@vitest/spy": "3.2.7", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA=="],
"webview-ui/vitest/@vitest/pretty-format": ["@vitest/pretty-format@3.2.6", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA=="],
"webview-ui/vitest/@vitest/pretty-format": ["@vitest/pretty-format@3.2.7", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA=="],
"webview-ui/vitest/@vitest/runner": ["@vitest/runner@3.2.6", "", { "dependencies": { "@vitest/utils": "3.2.6", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q=="],
"webview-ui/vitest/@vitest/runner": ["@vitest/runner@3.2.7", "", { "dependencies": { "@vitest/utils": "3.2.7", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA=="],
"webview-ui/vitest/@vitest/snapshot": ["@vitest/snapshot@3.2.6", "", { "dependencies": { "@vitest/pretty-format": "3.2.6", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw=="],
"webview-ui/vitest/@vitest/snapshot": ["@vitest/snapshot@3.2.7", "", { "dependencies": { "@vitest/pretty-format": "3.2.7", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g=="],
"webview-ui/vitest/@vitest/spy": ["@vitest/spy@3.2.6", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg=="],
"webview-ui/vitest/@vitest/spy": ["@vitest/spy@3.2.7", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ=="],
"webview-ui/vitest/@vitest/utils": ["@vitest/utils@3.2.6", "", { "dependencies": { "@vitest/pretty-format": "3.2.6", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg=="],
"webview-ui/vitest/@vitest/utils": ["@vitest/utils@3.2.7", "", { "dependencies": { "@vitest/pretty-format": "3.2.7", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw=="],
"webview-ui/vitest/chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
@@ -7243,10 +7226,6 @@
"test-exclude/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-dismissable-layer/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-focus-scope/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw=="],
"webview-ui/vitest/@vitest/utils/loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
"webview-ui/vitest/chai/check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="],
+9
View File
@@ -1,5 +1,14 @@
# Cline SDK Changelog
## 0.0.60
- Fixed an issue where a transient network or server error during token refresh could log you out — transient failures no longer clear your credentials
- Added the ClinePass usage-limit error so limit-reached responses are surfaced clearly
- Session id is now preserved when continuing within the same session
- Fixed infinite loading when initializing a task with an image
- Hardened compaction budget handling
- Added telemetry for auth-refresh outcomes and Cline credential lifecycle debug logging
## 0.0.59
- You can now select Cline free models on the ClinePass provider
+38 -6
View File
@@ -5,7 +5,7 @@ import {
readFileSync,
writeFileSync,
} from "node:fs";
import { dirname, join, resolve } from "node:path";
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
import { fileURLToPath } from "node:url";
import {
type AgentPlugin,
@@ -90,6 +90,8 @@ const GLOBAL_SKILLS_DIR = join(resolveClineDataDirPath(), "settings", "skills");
/** Safe identifier pattern for conversation IDs used in filesystem paths. */
const SAFE_ID_RE = /^[A-Za-z0-9_-]+$/;
const HANDOFF_PATH_ALLOWED_RE = /^[A-Za-z0-9._/-]+$/;
const HANDOFF_PATH_MAX_LENGTH = 240;
const envOr = (key: string, fallback: string): string =>
process.env[key]?.trim() || fallback;
@@ -298,14 +300,45 @@ function resolveHandoffPath(
ctx: AgentToolContext,
relativePath: string,
): string {
const handoffPath = validateHandoffRelativePath(relativePath);
const dir = handoffsDir(ctx);
const resolved = resolve(dir, relativePath);
if (!resolved.startsWith(`${dir}/`)) {
const resolved = resolve(dir, handoffPath);
const pathFromHandoffsDir = relative(dir, resolved);
if (
!pathFromHandoffsDir ||
pathFromHandoffsDir === ".." ||
pathFromHandoffsDir.startsWith(`..${sep}`) ||
isAbsolute(pathFromHandoffsDir)
) {
throw new Error(`Handoff path escapes directory: ${relativePath}`);
}
return resolved;
}
function validateHandoffRelativePath(relativePath: string): string {
const trimmed = relativePath.trim();
if (!trimmed) {
throw new Error("Handoff path must not be empty");
}
if (trimmed.length > HANDOFF_PATH_MAX_LENGTH) {
throw new Error(
`Handoff path must be ${HANDOFF_PATH_MAX_LENGTH} characters or fewer`,
);
}
if (trimmed.startsWith("/")) {
throw new Error(`Handoff path must be relative: ${relativePath}`);
}
if (!HANDOFF_PATH_ALLOWED_RE.test(trimmed)) {
throw new Error(
"Use a relative file path with letters, numbers, '.', '_', '-', or '/'.",
);
}
if (trimmed.split("/").includes("..")) {
throw new Error(`Handoff path must not contain '..': ${relativePath}`);
}
return trimmed;
}
function emitSteer(sessionId: string | undefined, prompt: string): void {
if (sessionId && prompt.trim()) {
globalThis.__clinePluginHost?.emitEvent?.("steer_message", {
@@ -407,9 +440,8 @@ const HandoffPathInput = z
.trim()
.min(1)
.max(240)
.regex(
/^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$))[A-Za-z0-9._/-]+$/,
"Use a relative file path with letters, numbers, '.', '_', '-', or '/'.",
.describe(
"Relative file path using letters, numbers, '.', '_', '-', or '/'. Must not be absolute or contain '..' segments.",
);
const StartSubagentInput = z
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@cline/agents",
"version": "0.0.59",
"version": "0.0.60",
"repository": {
"type": "git",
"url": "https://github.com/cline/cline",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@cline/core",
"description": "Cline Core SDK for Node Runtime",
"version": "0.0.59",
"version": "0.0.60",
"repository": {
"type": "git",
"url": "https://github.com/cline/cline",
+76 -5
View File
@@ -93,8 +93,22 @@ describe("auth/cline getValidClineCredentials", () => {
),
) as unknown as typeof fetch;
const result = await getValidClineCredentials(current, PROVIDER_OPTIONS);
const capture = vi.fn();
const result = await getValidClineCredentials(current, {
...PROVIDER_OPTIONS,
telemetry: { capture } as never,
});
expect(result).toBeNull();
expect(capture).toHaveBeenCalledWith(
expect.objectContaining({
event: "user.auth_logged_out",
properties: expect.objectContaining({
reason: "invalid_grant",
status: 401,
errorCode: "invalid_grant",
}),
}),
);
nowSpy.mockRestore();
});
@@ -115,11 +129,68 @@ describe("auth/cline getValidClineCredentials", () => {
),
) as unknown as typeof fetch;
const result = await getValidClineCredentials(current, PROVIDER_OPTIONS, {
refreshBufferMs: 60_000,
retryableTokenGraceMs: 30_000,
});
const capture = vi.fn();
const result = await getValidClineCredentials(
current,
{ ...PROVIDER_OPTIONS, telemetry: { capture } as never },
{
refreshBufferMs: 60_000,
retryableTokenGraceMs: 30_000,
},
);
expect(result).toBe(current);
expect(capture).toHaveBeenCalledWith(
expect.objectContaining({
event: "user.auth_refresh_soft_failure",
properties: expect.objectContaining({
status: 500,
tokenExpired: false,
}),
}),
);
nowSpy.mockRestore();
});
it("throws on transient refresh error when the token is already expired", async () => {
// A network blip landing after expiry is NOT an invalid grant; returning
// null here is what made clients wipe stored credentials over an outage.
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(100_000);
const current = createCredentials({ expires: 90_000 });
globalThis.fetch = vi.fn(
async () =>
new Response(
JSON.stringify({
error: "server_error",
error_description: "temporary issue",
}),
{
status: 500,
headers: { "Content-Type": "application/json" },
},
),
) as unknown as typeof fetch;
const capture = vi.fn();
await expect(
getValidClineCredentials(current, {
...PROVIDER_OPTIONS,
telemetry: { capture } as never,
}),
).rejects.toThrow("Token refresh failed: 500");
// The "prevented logout" counter: this exact situation used to wipe
// stored credentials.
expect(capture).toHaveBeenCalledWith(
expect.objectContaining({
event: "user.auth_refresh_soft_failure",
properties: expect.objectContaining({
status: 500,
tokenExpired: true,
}),
}),
);
expect(capture).not.toHaveBeenCalledWith(
expect.objectContaining({ event: "user.auth_logged_out" }),
);
nowSpy.mockRestore();
});
});
+80 -18
View File
@@ -2,9 +2,11 @@ import {
getClineEnvironmentConfig,
type ITelemetryService,
} from "@cline/shared";
import { hashSecret, sdkDebug } from "../logging/early-logger";
import {
captureAuthFailed,
captureAuthLoggedOut,
captureAuthRefreshSoftFailure,
captureAuthStarted,
captureAuthSucceeded,
identifyAccount,
@@ -621,27 +623,34 @@ export async function refreshClineToken(
current: ClineOAuthCredentials,
options: ClineOAuthProviderOptions,
): Promise<ClineOAuthCredentials> {
const response = await fetch(
resolveUrl(options.apiBaseUrl, DEFAULT_AUTH_ENDPOINTS.refresh),
{
method: "POST",
headers: {
"Content-Type": "application/json",
...(await resolveHeaders(options.headers)),
},
body: JSON.stringify({
refreshToken: current.refresh,
grantType: "refresh_token",
}),
signal: AbortSignal.timeout(
options.requestTimeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS,
),
},
const refreshUrl = resolveUrl(
options.apiBaseUrl,
DEFAULT_AUTH_ENDPOINTS.refresh,
);
sdkDebug(
`cline.refresh.request url=${refreshUrl} refreshTokenHash=${hashSecret(current.refresh)}`,
);
const response = await fetch(refreshUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
...(await resolveHeaders(options.headers)),
},
body: JSON.stringify({
refreshToken: current.refresh,
grantType: "refresh_token",
}),
signal: AbortSignal.timeout(
options.requestTimeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS,
),
});
if (!response.ok) {
const text = await response.text().catch(() => "");
const details = parseOAuthError(text);
sdkDebug(
`cline.refresh.error status=${response.status} errorCode=${details.code ?? "none"} message=${details.message ?? "none"}`,
);
throw new ClineOAuthTokenError(
`Token refresh failed: ${response.status}${details.message ? ` - ${details.message}` : ""}`,
{ status: response.status, errorCode: details.code },
@@ -651,19 +660,32 @@ export async function refreshClineToken(
const json = (await response.json()) as ClineTokenResponse;
const provider =
(current.metadata?.provider as string | undefined) ?? options.provider;
return toClineCredentials(
const result = toClineCredentials(
requireClineTokenResponse(json, "Invalid token refresh response"),
provider,
current,
);
sdkDebug(
`cline.refresh.success newAccessTokenHash=${hashSecret(result.access)} newRefreshTokenHash=${hashSecret(result.refresh)} expires=${result.expires}`,
);
return result;
}
/**
* Resolve valid Cline credentials, refreshing when needed.
*
* Contract: returns refreshed/current credentials when usable; returns `null`
* ONLY when the refresh token was rejected (invalid grant re-auth required);
* THROWS on transient failures (network, timeout, 5xx) so callers can leave
* stored credentials untouched and retry later.
*/
export async function getValidClineCredentials(
currentCredentials: ClineOAuthCredentials | null,
providerOptions: ClineOAuthProviderOptions,
options?: ClineTokenResolution,
): Promise<ClineOAuthCredentials | null> {
if (!currentCredentials) {
sdkDebug("cline.getCredentials outcome=no_current_credentials");
return null;
}
@@ -676,24 +698,64 @@ export async function getValidClineCredentials(
!forceRefresh &&
!isCredentialLikelyExpired(currentCredentials, refreshBufferMs)
) {
sdkDebug(
`cline.getCredentials outcome=still_valid forceRefresh=${forceRefresh}`,
);
return currentCredentials;
}
sdkDebug(
`cline.getCredentials outcome=needs_refresh forceRefresh=${forceRefresh} accessTokenHash=${hashSecret(currentCredentials.access)}`,
);
try {
return await refreshClineToken(currentCredentials, providerOptions);
} catch (error) {
const failureDetails = {
status: error instanceof ClineOAuthTokenError ? error.status : undefined,
errorCode:
error instanceof ClineOAuthTokenError ? error.errorCode : undefined,
errorName: error instanceof Error ? error.name : undefined,
};
if (error instanceof ClineOAuthTokenError && error.isLikelyInvalidGrant()) {
sdkDebug(
`cline.getCredentials outcome=invalid_grant status=${error.status} errorCode=${error.errorCode ?? "none"}`,
);
captureAuthLoggedOut(
providerOptions.telemetry,
providerOptions.provider ?? "cline",
"invalid_grant",
{ status: error.status, errorCode: error.errorCode },
);
return null;
}
if (currentCredentials.expires - Date.now() > retryableTokenGraceMs) {
// Keep current token on transient refresh failures while still valid.
sdkDebug(
`cline.getCredentials outcome=transient_failure_kept_current error=${error instanceof Error ? error.message : String(error)}`,
);
captureAuthRefreshSoftFailure(
providerOptions.telemetry,
providerOptions.provider ?? "cline",
{ ...failureDetails, tokenExpired: false },
);
return currentCredentials;
}
return null;
// Transient failure with an already-expired token: rethrow instead of
// returning null. A null from this function means the refresh token was
// REJECTED (re-auth required); a network blip that happens to land after
// expiry must not be mistaken for that — callers were wiping stored
// credentials over it, logging out every Cline process on the machine.
sdkDebug(
`cline.getCredentials outcome=transient_failure_rethrown error=${error instanceof Error ? error.message : String(error)}`,
);
// Every one of these events was a hard logout before the
// transient-vs-invalid_grant fix — the "prevented logout" counter.
captureAuthRefreshSoftFailure(
providerOptions.telemetry,
providerOptions.provider ?? "cline",
{ ...failureDetails, tokenExpired: true },
);
throw error;
}
}
@@ -6,6 +6,10 @@ import type {
CoreCompactionSummarizerConfig,
} from "../../types/config";
import type { ProviderConfig } from "../../types/provider-settings";
import {
buildBudgetProjection,
type BudgetProjectionResult,
} from "./budget-projection";
import {
buildSummaryMessage,
buildSummaryRequest,
@@ -20,6 +24,43 @@ import {
serializeConversation,
} from "./compaction-shared";
const MIN_AGENTIC_SUMMARY_INPUT_TOKENS = 1_024;
function resolveProviderMaxInputTokens(
providerConfig: ProviderConfig,
): number | undefined {
const explicit = providerConfig.maxInputTokens;
if (typeof explicit === "number" && Number.isFinite(explicit)) {
return explicit;
}
const modelInfoLimit =
providerConfig.modelInfo?.maxInputTokens ??
providerConfig.modelInfo?.contextWindow;
if (typeof modelInfoLimit === "number" && Number.isFinite(modelInfoLimit)) {
return modelInfoLimit;
}
const knownModelInfo = providerConfig.knownModels?.[providerConfig.modelId];
const knownModelLimit =
knownModelInfo?.maxInputTokens ?? knownModelInfo?.contextWindow;
if (typeof knownModelLimit === "number" && Number.isFinite(knownModelLimit)) {
return knownModelLimit;
}
return undefined;
}
export function buildAgenticSummaryInputBudget(options: {
messages: CoreCompactionContext["messages"];
targetTokens: number;
estimateMessageTokens: EstimateMessageTokens;
}): BudgetProjectionResult {
return buildBudgetProjection({
messages: options.messages,
targetTokens: Math.max(1, options.targetTokens),
policyIntent: "agentic_summary",
estimateMessageTokens: options.estimateMessageTokens,
});
}
async function generateSummary(options: {
providerConfig: ProviderConfig;
request: string;
@@ -92,8 +133,80 @@ export async function runAgenticCompaction(options: {
return undefined;
}
const fileOps = extractFileOps(messagesToSummarize);
const conversationText = serializeConversation(newMessagesToFold);
const preProjectionFileOps = extractFileOps(messagesToSummarize);
const summarizerProviderConfig = resolveSummarizerConfig({
activeProviderConfig: options.providerConfig,
summarizer: options.summarizer,
});
const resolvedSummarizerInputLimit = resolveProviderMaxInputTokens(
summarizerProviderConfig,
);
const canUseActiveContextLimit = options.summarizer === undefined;
const activeCompactionInputLimit = Math.max(
options.context.maxInputTokens,
options.context.triggerTokens,
MIN_AGENTIC_SUMMARY_INPUT_TOKENS,
);
if (resolvedSummarizerInputLimit === undefined && !canUseActiveContextLimit) {
options.logger?.log(
"Agentic compaction summarizer has no known input limit; using conservative summary budget",
{
severity: "warn",
summarizerProviderId: summarizerProviderConfig.providerId,
summarizerModelId: summarizerProviderConfig.modelId,
fallbackInputLimit: MIN_AGENTIC_SUMMARY_INPUT_TOKENS,
},
);
}
const summarizerInputLimit =
resolvedSummarizerInputLimit ??
(canUseActiveContextLimit
? activeCompactionInputLimit
: MIN_AGENTIC_SUMMARY_INPUT_TOKENS);
const summaryRequestOverheadTokens = estimateTokens(
buildSummaryRequest({
previousSummary,
conversationText: "",
fileOps: preProjectionFileOps,
}).length,
);
const availableSummaryInputTokens =
summarizerInputLimit - summaryRequestOverheadTokens;
if (availableSummaryInputTokens <= 0) {
options.logger?.debug(
"Skipped agentic compaction: summarizer budget exhausted",
{
summarizerProviderId: summarizerProviderConfig.providerId,
summarizerModelId: summarizerProviderConfig.modelId,
summarizerInputLimit,
summaryRequestOverheadTokens,
},
);
return undefined;
}
const summaryInputBudget = buildAgenticSummaryInputBudget({
messages: newMessagesToFold,
targetTokens: availableSummaryInputTokens,
estimateMessageTokens: options.estimateMessageTokens,
});
if (summaryInputBudget.status === "failed") {
options.logger?.log(
"Skipped agentic compaction: summary input budget failed",
{
severity: "warn",
budgetWarnings: summaryInputBudget.warnings.map(
(warning) => warning.code,
),
summaryInputEstimatedTokens: summaryInputBudget.estimatedTokens,
targetTokens: availableSummaryInputTokens,
summarizerProviderId: summarizerProviderConfig.providerId,
summarizerModelId: summarizerProviderConfig.modelId,
},
);
return undefined;
}
const fileOps = extractFileOps(summaryInputBudget.messages);
const conversationText = serializeConversation(summaryInputBudget.messages);
const summaryRequest = buildSummaryRequest({
previousSummary,
conversationText,
@@ -108,14 +221,20 @@ export async function runAgenticCompaction(options: {
summaryRequestChars: summaryRequest.length,
summaryRequestEstimatedTokens: estimateTokens(summaryRequest.length),
newMessagesJsonChars: safeJsonSize(newMessagesToFold),
summaryInputEstimatedTokens: summaryInputBudget.estimatedTokens,
summaryInputActions: summaryInputBudget.actions.length,
summaryInputWarnings: summaryInputBudget.warnings.map(
(warning) => warning.code,
),
summaryRequestOverheadTokens,
summarizerProviderId: summarizerProviderConfig.providerId,
summarizerModelId: summarizerProviderConfig.modelId,
summarizerInputLimit,
maxInputTokens: options.context.maxInputTokens,
triggerTokens: options.context.triggerTokens,
});
const rawSummary = await generateSummary({
providerConfig: resolveSummarizerConfig({
activeProviderConfig: options.providerConfig,
summarizer: options.summarizer,
}),
providerConfig: summarizerProviderConfig,
request: summaryRequest,
logger: options.logger,
});
@@ -149,5 +268,17 @@ export async function runAgenticCompaction(options: {
tokensAfter,
maxInputTokens: options.context.maxInputTokens,
});
return { messages: resultMessages };
const budgetActionCount = summaryInputBudget.actions.filter(
(action) =>
action.reason === "over_budget" || action.reason === "tool_pair_boundary",
).length;
return {
messages: resultMessages,
budget: {
policyIntent: "agentic_summary",
actionCount: budgetActionCount,
warningCount: summaryInputBudget.warnings.length,
liveTailHandling: summaryInputBudget.liveTailHandling,
},
};
}
@@ -7,6 +7,7 @@ import type {
CoreCompactionContext,
CoreCompactionResult,
} from "../../types/config";
import { buildBudgetProjection } from "./budget-projection";
import {
DEFAULT_TARGET_RATIO,
type EstimateMessageTokens,
@@ -444,21 +445,58 @@ export function runBasicCompaction(options: {
...candidates.map((candidate) => candidate.message),
...protectedTail,
];
if (!haveMessagesChanged(options.context.messages, nextMessages)) {
const budgeted = buildBudgetProjection({
messages: nextMessages,
targetTokens: totalTargetTokens,
policyIntent: "basic_compaction_projection",
estimateMessageTokens: options.estimateMessageTokens,
});
// This final projection owns the hard output budget. Unlike the earlier
// basic candidate passes, it can drop completed tool pairs after the latest
// typed prompt while preserving coherent tool closures.
if (budgeted.status === "failed") {
options.logger?.debug("Basic compaction returned best-effort projection", {
budgetWarnings: budgeted.warnings.map((warning) => warning.code),
projectedTokens: budgeted.estimatedTokens,
targetTokens: totalTargetTokens,
maxInputTokens: options.context.maxInputTokens,
});
}
const resultMessages = budgeted.messages;
if (!haveMessagesChanged(options.context.messages, resultMessages)) {
return undefined;
}
const beforeTokens = beforeCompactableTokens + protectedTailTokens;
const afterTokens = totalTokens + protectedTailTokens;
const afterTokens = getTotalTokens(
resultMessages,
options.estimateMessageTokens,
);
const budgetActionCount = budgeted.actions.filter(
(action) =>
action.reason === "over_budget" || action.reason === "tool_pair_boundary",
).length;
options.logger?.debug("Performed basic compaction", {
messagesBefore: options.context.messages.length,
messagesAfter: nextMessages.length,
messagesRemoved: options.context.messages.length - nextMessages.length,
messagesAfter: resultMessages.length,
messagesRemoved: options.context.messages.length - resultMessages.length,
tokensBefore: beforeTokens,
tokensAfter: afterTokens,
budgetStatus: budgeted.status,
budgetActions: budgetActionCount,
budgetWarnings: budgeted.warnings.map((warning) => warning.code),
targetTokens: totalTargetTokens,
maxInputTokens: options.context.maxInputTokens,
});
return { messages: nextMessages };
return {
messages: resultMessages,
budget: {
policyIntent: "basic_compaction_projection",
actionCount: budgetActionCount,
warningCount: budgeted.warnings.length,
liveTailHandling: budgeted.liveTailHandling,
},
};
}
@@ -0,0 +1,17 @@
export {
buildBudgetProjection,
findLatestTypedUserMessageIndex,
} from "./project";
export type {
BlockBudgetClass,
BudgetAction,
BudgetActionKind,
BudgetActionReason,
BudgetPath,
BudgetPolicyIntent,
BudgetProjectionOptions,
BudgetProjectionResult,
BudgetProjectionWarning,
ContentBlockBudgetClassification,
LiveTailHandling,
} from "./types";
@@ -0,0 +1,478 @@
import type { MessageWithMetadata } from "@cline/shared";
import { describe, expect, it } from "vitest";
import {
buildBudgetProjection,
findLatestTypedUserMessageIndex,
} from "./project";
const estimateChars = (message: MessageWithMetadata) =>
JSON.stringify(message).length;
describe("buildBudgetProjection", () => {
it("fails explicitly for impossible budgets", () => {
const result = buildBudgetProjection({
messages: [{ role: "user", content: "keep me" }],
targetTokens: 0,
policyIntent: "agentic_summary",
estimateMessageTokens: estimateChars,
});
expect(result.status).toBe("failed");
expect(result.messages).toHaveLength(1);
expect(result.warnings[0]?.code).toBe("budget_impossible");
});
it("drops unsafe image and redacted thinking blocks instead of truncating them", () => {
const result = buildBudgetProjection({
messages: [
{
role: "assistant",
content: [
{ type: "text", text: "old context" },
{
type: "redacted_thinking",
data: "x".repeat(500),
},
],
},
{
role: "assistant",
content: [
{
type: "image",
data: "y".repeat(500),
mediaType: "image/png",
},
],
},
{ role: "user", content: "latest task" },
],
targetTokens: 150,
policyIntent: "agentic_summary",
estimateMessageTokens: estimateChars,
});
const serialized = JSON.stringify(result.messages);
expect(serialized).not.toContain("redacted_thinking");
expect(serialized).not.toContain("image/png");
expect(result.actions).toEqual(
expect.arrayContaining([
expect.objectContaining({
kind: "dropped_block",
reason: "unsafe_to_truncate",
}),
]),
);
expect(result.liveTailHandling).toBe("included_degraded");
});
it("keeps unsafe blocks when input is already under budget", () => {
const result = buildBudgetProjection({
messages: [
{
role: "user",
content: [
{ type: "text", text: "look at this" },
{
type: "image",
data: "small-image",
mediaType: "image/png",
},
],
},
],
targetTokens: 1_000,
policyIntent: "agentic_summary",
estimateMessageTokens: estimateChars,
});
expect(result.status).toBe("ok");
expect(result.actions).toEqual([]);
expect(result.liveTailHandling).toBe("included_verbatim");
expect(JSON.stringify(result.messages)).toContain("small-image");
});
it("preserves unsafe blocks in the latest typed user message", () => {
const result = buildBudgetProjection({
messages: [
{ role: "user", content: "old task " + "x".repeat(500) },
{
role: "user",
content: [
{ type: "text", text: "what is in this image?" },
{
type: "image",
data: "live-image",
mediaType: "image/png",
},
],
},
],
targetTokens: 120,
policyIntent: "basic_compaction_projection",
estimateMessageTokens: estimateChars,
});
expect(JSON.stringify(result.messages)).toContain("live-image");
expect(result.actions).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ kind: "dropped_block" }),
]),
);
});
it("protects latest typed user after thinking-only messages are pruned", () => {
const result = buildBudgetProjection({
messages: [
{ role: "user", content: "old task" },
{
role: "assistant",
content: [{ type: "thinking", thinking: "discard me" }],
},
{
role: "user",
content: [
{ type: "text", text: "what is in this image?" },
{
type: "image",
data: "live-image",
mediaType: "image/png",
},
],
},
],
targetTokens: 1_000,
policyIntent: "agentic_summary",
estimateMessageTokens: estimateChars,
});
const serialized = JSON.stringify(result.messages);
expect(serialized).toContain("live-image");
expect(serialized).not.toContain("discard me");
});
it("keeps tool-use and tool-result pairs coherent when dropping history", () => {
const result = buildBudgetProjection({
messages: [
{ role: "user", content: "original task" },
{
role: "assistant",
content: [
{
type: "tool_use",
id: "tool_1",
name: "read_files",
input: { file_paths: ["/tmp/a.ts"] },
},
],
},
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "tool_1",
name: "read_files",
content: "x".repeat(1000),
},
],
},
{ role: "user", content: "latest task" },
],
targetTokens: 140,
policyIntent: "basic_compaction_projection",
estimateMessageTokens: estimateChars,
});
const serialized = JSON.stringify(result.messages);
expect(serialized).not.toContain("tool_1");
expect(serialized).toContain("latest task");
expect(result.actions).toEqual(
expect.arrayContaining([
expect.objectContaining({ reason: "tool_pair_boundary" }),
]),
);
});
it("records budget action paths against original message indexes", () => {
const result = buildBudgetProjection({
messages: [
{
role: "assistant",
content: [{ type: "image", data: "x", mediaType: "image/png" }],
},
{ role: "user", content: "old task " + "x".repeat(500) },
{ role: "assistant", content: "old answer " + "y".repeat(500) },
{ role: "user", content: "latest task" },
],
targetTokens: 80,
policyIntent: "basic_compaction_projection",
estimateMessageTokens: estimateChars,
});
expect(result.actions).toEqual(
expect.arrayContaining([
expect.objectContaining({
kind: "preserved",
path: expect.objectContaining({ messageIndex: 1 }),
}),
expect.objectContaining({
kind: "dropped_message",
path: expect.objectContaining({ messageIndex: 2 }),
}),
]),
);
});
it("detects the latest typed user message when tool results follow it", () => {
const messages: MessageWithMetadata[] = [
{ role: "user", content: "old task" },
{ role: "user", content: "latest typed prompt" },
{
role: "assistant",
content: [{ type: "tool_use", id: "tool_1", name: "read", input: {} }],
},
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "tool_1",
name: "read",
content: "result",
},
],
},
];
expect(findLatestTypedUserMessageIndex(messages)).toBe(1);
});
it("preserves the latest typed prompt under pressure", () => {
const result = buildBudgetProjection({
messages: [
{ role: "user", content: "old task " + "x".repeat(500) },
{ role: "user", content: "latest typed prompt" },
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "tool_1",
name: "read",
content: "result " + "y".repeat(500),
},
],
},
],
targetTokens: 120,
policyIntent: "agentic_summary",
estimateMessageTokens: estimateChars,
});
expect(JSON.stringify(result.messages)).toContain("latest typed prompt");
expect(result.actions).toEqual(
expect.arrayContaining([
expect.objectContaining({ reason: "protected_live_tail" }),
]),
);
});
it("drops completed tool pairs after the latest typed prompt", () => {
const result = buildBudgetProjection({
messages: [
{ role: "user", content: "old task " + "x".repeat(500) },
{ role: "user", content: "latest typed prompt" },
{
role: "assistant",
content: [
{ type: "tool_use", id: "tool_after", name: "read", input: {} },
],
},
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "tool_after",
name: "read",
content: "huge result " + "y".repeat(2_000),
},
],
},
],
targetTokens: 140,
policyIntent: "agentic_summary",
estimateMessageTokens: estimateChars,
});
const serialized = JSON.stringify(result.messages);
expect(serialized).toContain("latest typed prompt");
expect(serialized).not.toContain("tool_after");
expect(result.actions).toEqual(
expect.arrayContaining([
expect.objectContaining({
kind: "dropped_message",
reason: "tool_pair_boundary",
path: expect.objectContaining({ messageIndex: 2 }),
}),
expect.objectContaining({
kind: "dropped_message",
reason: "tool_pair_boundary",
path: expect.objectContaining({ messageIndex: 3 }),
}),
]),
);
});
it("preserves unresolved tool use after the latest typed prompt", () => {
const result = buildBudgetProjection({
messages: [
{ role: "user", content: "old task " + "x".repeat(500) },
{ role: "user", content: "latest typed prompt" },
{
role: "assistant",
content: [
{
type: "tool_use",
id: "tool_live",
name: "run_command",
input: { command: "sleep 1" },
},
],
},
],
targetTokens: 80,
policyIntent: "agentic_summary",
estimateMessageTokens: estimateChars,
});
const serialized = JSON.stringify(result.messages);
expect(serialized).toContain("latest typed prompt");
expect(serialized).toContain("tool_live");
expect(result.status).toBe("failed");
expect(result.warnings[0]?.code).toBe(
"budget_unachievable_with_protections",
);
});
it("does not preserve later text or file blocks after tool-result budget is exhausted", () => {
const result = buildBudgetProjection({
messages: [
{ role: "user", content: "latest typed prompt" },
{
role: "assistant",
content: [
{ type: "tool_use", id: "tool_live", name: "read", input: {} },
],
},
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "tool_live",
name: "read",
content: [
{ type: "text", text: "a".repeat(200) },
{
type: "file",
path: "/tmp/huge.txt",
content: "b".repeat(1_000),
},
],
},
],
},
],
targetTokens: 260,
policyIntent: "agentic_summary",
estimateMessageTokens: estimateChars,
});
const serialized = JSON.stringify(result.messages);
expect(serialized).toContain("latest typed prompt");
expect(serialized).not.toContain("b".repeat(100));
expect(result.actions).toEqual(
expect.arrayContaining([
expect.objectContaining({
kind: "truncated_text",
reason: "over_budget",
}),
]),
);
});
it("drops thinking blocks instead of mutating provider-native reasoning", () => {
const result = buildBudgetProjection({
messages: [
{ role: "user", content: "latest typed prompt" },
{
role: "assistant",
content: [
{ type: "text", text: "a".repeat(1_000) },
{ type: "thinking", thinking: "b".repeat(1_000) },
],
},
],
targetTokens: 900,
policyIntent: "agentic_summary",
estimateMessageTokens: estimateChars,
});
const assistant = result.messages.find(
(message) => message.role === "assistant",
);
expect(JSON.stringify(assistant)).not.toContain("b".repeat(100));
expect(JSON.stringify(assistant)).not.toContain('"thinking"');
expect(result.actions).toEqual(
expect.arrayContaining([
expect.objectContaining({
kind: "dropped_block",
reason: "unsafe_to_truncate",
}),
]),
);
});
it("drops nested unsafe tool-result blocks outside the protected tail", () => {
const result = buildBudgetProjection({
messages: [
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "tool_old",
name: "read",
content: [
{ type: "text", text: "old output" },
{
type: "image",
data: "old-image-data",
mediaType: "image/png",
},
],
},
],
},
{ role: "user", content: "latest typed prompt" },
],
targetTokens: 1_000,
policyIntent: "agentic_summary",
estimateMessageTokens: estimateChars,
});
const serialized = JSON.stringify(result.messages);
expect(serialized).toContain("old output");
expect(serialized).not.toContain("old-image-data");
expect(result.actions).toEqual(
expect.arrayContaining([
expect.objectContaining({
kind: "dropped_block",
reason: "unsafe_to_truncate",
}),
]),
);
});
});
@@ -0,0 +1,672 @@
import type {
ContentBlock,
MessageWithMetadata,
ToolResultContent,
} from "@cline/shared";
import type {
BudgetAction,
BudgetMutationAction,
BudgetProjectionOptions,
BudgetProjectionResult,
BudgetProjectionWarning,
BudgetPolicyIntent,
} from "./types";
type EstimateMessageTokens = (message: MessageWithMetadata) => number;
interface ProjectionPolicy {
protectLatestTypedUser: boolean;
protectLiveTailFromDrop: boolean;
dropUnsafeOutsideLiveTail: boolean;
dropThinkingBlocks: boolean;
}
function resolveProjectionPolicy(intent: BudgetPolicyIntent): ProjectionPolicy {
switch (intent) {
case "agentic_summary":
case "basic_compaction_projection":
return {
protectLatestTypedUser: true,
protectLiveTailFromDrop: true,
dropUnsafeOutsideLiveTail: true,
dropThinkingBlocks: true,
};
case "normal_provider_request":
return {
protectLatestTypedUser: true,
protectLiveTailFromDrop: true,
dropUnsafeOutsideLiveTail: false,
dropThinkingBlocks: false,
};
}
}
function cloneMessages(messages: MessageWithMetadata[]): MessageWithMetadata[] {
return messages.map((message) => ({
...message,
content: Array.isArray(message.content)
? message.content.map((block) => ({ ...block }) as ContentBlock)
: message.content,
...(message.metadata ? { metadata: { ...message.metadata } } : {}),
}));
}
function safeJsonSize(value: unknown): number {
try {
return JSON.stringify(value).length;
} catch {
return String(value).length;
}
}
function totalTokens(
messages: MessageWithMetadata[],
estimateMessageTokens: EstimateMessageTokens,
): number {
return messages.reduce(
(total, message) => total + estimateMessageTokens(message),
0,
);
}
function isToolResultOnlyUserMessage(message: MessageWithMetadata): boolean {
return (
message.role === "user" &&
Array.isArray(message.content) &&
message.content.length > 0 &&
message.content.every((block) => block.type === "tool_result")
);
}
export function findLatestTypedUserMessageIndex(
messages: MessageWithMetadata[],
): number {
for (let index = messages.length - 1; index >= 0; index -= 1) {
const message = messages[index];
if (message.role === "user" && !isToolResultOnlyUserMessage(message)) {
return index;
}
}
return -1;
}
function findFirstTypedUserMessageIndex(
messages: MessageWithMetadata[],
): number {
for (let index = 0; index < messages.length; index += 1) {
const message = messages[index];
if (message.role === "user" && !isToolResultOnlyUserMessage(message)) {
return index;
}
}
return -1;
}
function collectToolIds(message: MessageWithMetadata): Set<string> {
const ids = new Set<string>();
if (!Array.isArray(message.content)) {
return ids;
}
for (const block of message.content) {
if (block.type === "tool_use") {
ids.add(block.id);
} else if (block.type === "tool_result") {
ids.add(block.tool_use_id);
}
}
return ids;
}
function buildToolPairIndex(
messages: MessageWithMetadata[],
): Map<string, Set<number>> {
const index = new Map<string, Set<number>>();
for (
let messageIndex = 0;
messageIndex < messages.length;
messageIndex += 1
) {
for (const id of collectToolIds(messages[messageIndex])) {
const existing = index.get(id);
if (existing) {
existing.add(messageIndex);
} else {
index.set(id, new Set([messageIndex]));
}
}
}
return index;
}
function findProtectedTailStartIndex(messages: MessageWithMetadata[]): number {
const resolvedToolUseIds = new Set<string>();
for (const message of messages) {
if (!Array.isArray(message.content)) {
continue;
}
for (const block of message.content) {
if (block.type === "tool_result") {
resolvedToolUseIds.add(block.tool_use_id);
}
}
}
for (let index = messages.length - 1; index >= 0; index -= 1) {
const message = messages[index];
if (!Array.isArray(message.content)) {
continue;
}
if (
message.content.some(
(block) =>
block.type === "tool_use" && !resolvedToolUseIds.has(block.id),
)
) {
return index;
}
}
return messages.length;
}
function collectMessageClosure(
messages: MessageWithMetadata[],
startIndex: number,
): Set<number> {
const pairIndex = buildToolPairIndex(messages);
const removal = new Set<number>();
const queue = [startIndex];
while (queue.length > 0) {
const index = queue.shift();
if (index === undefined || removal.has(index)) {
continue;
}
removal.add(index);
for (const id of collectToolIds(messages[index])) {
for (const linked of pairIndex.get(id) ?? []) {
if (!removal.has(linked)) {
queue.push(linked);
}
}
}
}
return removal;
}
function isUnsafeBlock(block: ContentBlock): boolean {
return block.type === "image" || block.type === "redacted_thinking";
}
function isNestedUnsafeToolResultBlock(
block: Extract<ToolResultContent["content"], unknown[]>[number],
): boolean {
return block.type === "image";
}
function shouldDropWholeBlock(
block: ContentBlock,
policy: ProjectionPolicy,
isProtected: boolean,
): boolean {
if (policy.dropThinkingBlocks && block.type === "thinking") {
return true;
}
return (
policy.dropUnsafeOutsideLiveTail && !isProtected && isUnsafeBlock(block)
);
}
function pruneEmptyMessages(
messages: MessageWithMetadata[],
originalIndexes: number[],
actions: BudgetAction[],
reason: BudgetMutationAction["reason"] = "over_budget",
): { messages: MessageWithMetadata[]; originalIndexes: number[] } {
const next: MessageWithMetadata[] = [];
const nextOriginalIndexes: number[] = [];
for (let index = 0; index < messages.length; index += 1) {
const message = messages[index];
if (Array.isArray(message.content) && message.content.length === 0) {
actions.push({
kind: "dropped_message",
path: { messageIndex: originalIndexes[index] },
reason,
originalSize: safeJsonSize(message),
finalSize: 0,
});
continue;
}
next.push(message);
nextOriginalIndexes.push(originalIndexes[index]);
}
return { messages: next, originalIndexes: nextOriginalIndexes };
}
function dropUnsafeBlocks(
messages: MessageWithMetadata[],
originalIndexes: number[],
actions: BudgetAction[],
latestTypedUserIndex: number,
protectedTailStartIndex: number,
policy: ProjectionPolicy,
): MessageWithMetadata[] {
return messages.map((message, messageIndex) => {
if (!Array.isArray(message.content)) {
return message;
}
let changed = false;
const protectedBlock =
messageIndex === latestTypedUserIndex ||
messageIndex >= protectedTailStartIndex;
const content = message.content.flatMap((block, blockIndex) => {
if (shouldDropWholeBlock(block, policy, protectedBlock)) {
changed = true;
actions.push({
kind: "dropped_block",
path: { messageIndex: originalIndexes[messageIndex], blockIndex },
reason: "unsafe_to_truncate",
originalSize: safeJsonSize(block),
finalSize: 0,
});
return [];
}
if (block.type === "tool_result" && Array.isArray(block.content)) {
const nestedContent = block.content.filter((nestedBlock) => {
if (
policy.dropUnsafeOutsideLiveTail &&
!protectedBlock &&
isNestedUnsafeToolResultBlock(nestedBlock)
) {
return false;
}
return true;
});
if (nestedContent.length !== block.content.length) {
changed = true;
const nextBlock = { ...block, content: nestedContent };
actions.push({
kind: "dropped_block",
path: { messageIndex: originalIndexes[messageIndex], blockIndex },
reason: "unsafe_to_truncate",
originalSize: safeJsonSize(block),
finalSize: safeJsonSize(nextBlock),
});
return [nextBlock];
}
}
return [block];
});
return changed ? { ...message, content } : message;
});
}
function dropThinkingBlocks(
messages: MessageWithMetadata[],
originalIndexes: number[],
actions: BudgetAction[],
): MessageWithMetadata[] {
return messages.map((message, messageIndex) => {
if (!Array.isArray(message.content)) {
return message;
}
let changed = false;
const content = message.content.filter((block, blockIndex) => {
if (block.type !== "thinking") {
return true;
}
changed = true;
actions.push({
kind: "dropped_block",
path: { messageIndex: originalIndexes[messageIndex], blockIndex },
reason: "unsafe_to_truncate",
originalSize: safeJsonSize(block),
finalSize: 0,
});
return false;
});
return changed ? { ...message, content } : message;
});
}
function truncateText(text: string, maxChars: number): string {
if (maxChars <= 0) {
return "";
}
if (text.length <= maxChars) {
return text;
}
if (maxChars <= 16) {
return text.slice(0, Math.max(1, maxChars));
}
const estimateMarker = `\n...[truncated ${text.length - maxChars} chars]`;
const keep = Math.max(1, maxChars - estimateMarker.length);
const marker = `\n...[truncated ${text.length - keep} chars]`;
return `${text.slice(0, keep)}${marker}`;
}
function truncateToolResultContent(
content: ToolResultContent["content"],
maxChars: number,
): ToolResultContent["content"] {
if (typeof content === "string") {
return truncateText(content, maxChars);
}
let remaining = maxChars;
return content.map((block) => {
if (remaining <= 0) {
if (block.type === "text") {
return { ...block, text: "" };
}
if (block.type === "file") {
return { ...block, content: "" };
}
return block;
}
if (block.type === "text") {
const text = truncateText(block.text, remaining);
remaining -= text.length;
return { ...block, text };
}
if (block.type === "file") {
const content = truncateText(block.content, remaining);
remaining -= content.length;
return { ...block, content };
}
return block;
});
}
function toolResultTextLength(content: ToolResultContent["content"]): number {
if (typeof content === "string") {
return content.length;
}
return content.reduce((total, block) => {
if (block.type === "text") {
return total + block.text.length;
}
if (block.type === "file") {
return total + block.content.length;
}
return total;
}, 0);
}
function truncateMessageText(
message: MessageWithMetadata,
maxChars: number,
): MessageWithMetadata {
if (typeof message.content === "string") {
return { ...message, content: truncateText(message.content, maxChars) };
}
let remaining = maxChars;
return {
...message,
content: message.content.map((block) => {
if (remaining <= 0) {
if (block.type === "text") {
return { ...block, text: "" };
}
if (block.type === "file") {
return { ...block, content: "" };
}
if (block.type === "tool_result") {
return {
...block,
content: truncateToolResultContent(block.content, 0),
};
}
return block;
}
if (block.type === "text") {
const text = truncateText(block.text, remaining);
remaining -= text.length;
return { ...block, text };
}
if (block.type === "file") {
const content = truncateText(block.content, remaining);
remaining -= content.length;
return { ...block, content };
}
if (block.type === "tool_result") {
const content = truncateToolResultContent(block.content, remaining);
remaining -= toolResultTextLength(content);
return { ...block, content };
}
return block;
}),
};
}
function hasTruncatableText(message: MessageWithMetadata): boolean {
if (typeof message.content === "string") {
return message.content.length > 0;
}
return message.content.some(
(block) =>
block.type === "text" ||
block.type === "file" ||
block.type === "tool_result",
);
}
function removeMessagesAt(
messages: MessageWithMetadata[],
originalIndexes: number[],
removal: Set<number>,
): { messages: MessageWithMetadata[]; originalIndexes: number[] } {
return {
messages: messages.filter((_, index) => !removal.has(index)),
originalIndexes: originalIndexes.filter((_, index) => !removal.has(index)),
};
}
function closureTouchesProtectedTail(
closure: Set<number>,
protectedStartIndex: number,
): boolean {
if (protectedStartIndex < 0) {
return false;
}
for (const removalIndex of closure) {
if (removalIndex >= protectedStartIndex) {
return true;
}
}
return false;
}
function closureTouchesPinnedMessage(
closure: Set<number>,
pinnedIndex: number,
): boolean {
return pinnedIndex >= 0 && closure.has(pinnedIndex);
}
export function buildBudgetProjection(
options: BudgetProjectionOptions,
): BudgetProjectionResult {
const actions: BudgetAction[] = [];
const warnings: BudgetProjectionWarning[] = [];
const policy = resolveProjectionPolicy(options.policyIntent);
if (options.targetTokens <= 0) {
return {
status: "failed",
messages: cloneMessages(options.messages),
actions,
liveTailHandling: "preserved_out_of_band",
estimatedTokens: totalTokens(
options.messages,
options.estimateMessageTokens,
),
warnings: [
{
code: "budget_impossible",
message: "Target budget must be greater than zero.",
},
],
};
}
let messages = cloneMessages(options.messages);
let originalIndexes = messages.map((_, index) => index);
if (policy.dropThinkingBlocks) {
const prunedThinking = pruneEmptyMessages(
dropThinkingBlocks(messages, originalIndexes, actions),
originalIndexes,
actions,
"unsafe_to_truncate",
);
messages = prunedThinking.messages;
originalIndexes = prunedThinking.originalIndexes;
}
const latestTypedUserIndex = policy.protectLatestTypedUser
? findLatestTypedUserMessageIndex(messages)
: -1;
const protectedTailStartIndex = policy.protectLiveTailFromDrop
? findProtectedTailStartIndex(messages)
: messages.length;
if (policy.dropUnsafeOutsideLiveTail) {
const prunedUnsafe = pruneEmptyMessages(
dropUnsafeBlocks(
messages,
originalIndexes,
actions,
latestTypedUserIndex,
protectedTailStartIndex,
policy,
),
originalIndexes,
actions,
);
messages = prunedUnsafe.messages;
originalIndexes = prunedUnsafe.originalIndexes;
}
let estimatedTokens = totalTokens(messages, options.estimateMessageTokens);
if (estimatedTokens <= options.targetTokens) {
return {
status: "ok",
messages,
actions,
liveTailHandling:
actions.length > 0 ? "included_degraded" : "included_verbatim",
estimatedTokens,
warnings,
};
}
for (
let index = messages.length - 1;
index >= 0 && estimatedTokens > options.targetTokens;
index -= 1
) {
const latestTypedUserIndex = findLatestTypedUserMessageIndex(messages);
if (index === latestTypedUserIndex) {
continue;
}
if (
policy.protectLiveTailFromDrop &&
index >= findProtectedTailStartIndex(messages)
) {
continue;
}
if (!hasTruncatableText(messages[index])) {
continue;
}
const originalSize = safeJsonSize(messages[index]);
const charsPerToken = Math.max(
1,
originalSize /
Math.max(1, options.estimateMessageTokens(messages[index])),
);
const targetChars = Math.max(
16,
Math.floor(
(options.targetTokens * charsPerToken) / Math.max(1, messages.length),
),
);
messages[index] = truncateMessageText(messages[index], targetChars);
actions.push({
kind: "truncated_text",
path: { messageIndex: originalIndexes[index] },
reason: "over_budget",
originalSize,
finalSize: safeJsonSize(messages[index]),
});
estimatedTokens = totalTokens(messages, options.estimateMessageTokens);
}
for (
let index = 0;
index < messages.length && estimatedTokens > options.targetTokens;
) {
const firstTypedUserIndex = findFirstTypedUserMessageIndex(messages);
const latestTypedUserIndex = findLatestTypedUserMessageIndex(messages);
const protectedStartIndex = policy.protectLiveTailFromDrop
? findProtectedTailStartIndex(messages)
: messages.length;
if (index === firstTypedUserIndex || index === latestTypedUserIndex) {
actions.push({
kind: "preserved",
path: { messageIndex: originalIndexes[index] },
reason: "protected_live_tail",
originalSize: safeJsonSize(messages[index]),
finalSize: safeJsonSize(messages[index]),
});
index += 1;
continue;
}
const closure = collectMessageClosure(messages, index);
if (closureTouchesPinnedMessage(closure, firstTypedUserIndex)) {
index += 1;
continue;
}
if (closureTouchesPinnedMessage(closure, latestTypedUserIndex)) {
index += 1;
continue;
}
if (closureTouchesProtectedTail(closure, protectedStartIndex)) {
index += 1;
continue;
}
for (const removalIndex of closure) {
actions.push({
kind: "dropped_message",
path: { messageIndex: originalIndexes[removalIndex] },
reason:
closure.size > 1 || collectToolIds(messages[removalIndex]).size > 0
? "tool_pair_boundary"
: "over_budget",
originalSize: safeJsonSize(messages[removalIndex]),
finalSize: 0,
});
}
const removed = removeMessagesAt(messages, originalIndexes, closure);
messages = removed.messages;
originalIndexes = removed.originalIndexes;
estimatedTokens = totalTokens(messages, options.estimateMessageTokens);
}
if (estimatedTokens > options.targetTokens) {
warnings.push({
code: "budget_unachievable_with_protections",
message:
"Projection could not reach budget without violating protected content.",
});
return {
status: "failed",
messages,
actions,
liveTailHandling: "included_degraded",
estimatedTokens,
warnings,
};
}
return {
status: "ok",
messages,
actions,
liveTailHandling:
actions.length > 0 ? "included_degraded" : "included_verbatim",
estimatedTokens,
warnings,
};
}
@@ -0,0 +1,103 @@
import type { ContentBlock, MessageWithMetadata } from "@cline/shared";
export type BudgetPolicyIntent =
| "agentic_summary"
| "basic_compaction_projection"
| "normal_provider_request";
export type BudgetActionKind =
| "truncated_text"
| "dropped_block"
| "dropped_message"
| "preserved";
export type BudgetActionReason =
| "over_budget"
| "unsafe_to_truncate"
| "tool_pair_boundary"
| "protected_live_tail";
export type LiveTailHandling =
| "included_verbatim"
| "included_degraded"
| "summarized_as_context"
| "omitted_with_warning"
| "preserved_out_of_band";
export type BlockBudgetClass =
| "text"
| "thinking"
| "tool_use"
| "tool_result"
| "unsafe_binary"
| "unsafe_encrypted"
| "opaque";
export interface BudgetPath {
messageIndex: number;
blockIndex?: number;
}
interface BaseBudgetAction {
path: BudgetPath;
originalSize: number;
finalSize: number;
}
export type BudgetMutationAction =
| (BaseBudgetAction & {
kind: "truncated_text";
reason: Extract<BudgetActionReason, "over_budget">;
})
| (BaseBudgetAction & {
kind: "dropped_block";
path: Required<BudgetPath>;
reason: Exclude<BudgetActionReason, "protected_live_tail">;
})
| (BaseBudgetAction & {
kind: "dropped_message";
reason: Exclude<BudgetActionReason, "protected_live_tail">;
});
export interface BudgetPreservedAction extends BaseBudgetAction {
kind: "preserved";
reason: Extract<
BudgetActionReason,
"protected_live_tail" | "tool_pair_boundary"
>;
}
export type BudgetAction = BudgetMutationAction | BudgetPreservedAction;
export type BudgetProjectionWarningCode =
| "budget_impossible"
| "budget_unachievable_with_protections";
export interface BudgetProjectionWarning {
code: BudgetProjectionWarningCode;
message: string;
path?: BudgetPath;
}
export interface BudgetProjectionOptions {
messages: MessageWithMetadata[];
targetTokens: number;
policyIntent: BudgetPolicyIntent;
estimateMessageTokens: (message: MessageWithMetadata) => number;
}
export interface BudgetProjectionResult {
status: "ok" | "failed";
messages: MessageWithMetadata[];
actions: BudgetAction[];
liveTailHandling: LiveTailHandling;
estimatedTokens: number;
warnings: BudgetProjectionWarning[];
}
export interface ContentBlockBudgetClassification {
block: ContentBlock;
budgetClass: BlockBudgetClass;
canStringTruncate: boolean;
canDropWholeBlock: boolean;
}
@@ -485,6 +485,7 @@ export function resolveSummarizerConfig(options: {
apiKey: summarizer.apiKey ?? baseProviderConfig?.apiKey,
baseUrl: summarizer.baseUrl ?? baseProviderConfig?.baseUrl,
headers: summarizer.headers ?? baseProviderConfig?.headers,
modelInfo: summarizer.modelInfo ?? baseProviderConfig?.modelInfo,
knownModels: summarizer.knownModels ?? baseProviderConfig?.knownModels,
maxOutputTokens:
summarizer.maxOutputTokens ?? DEFAULT_SUMMARY_MAX_OUTPUT_TOKENS,
@@ -3,6 +3,7 @@ import type { MessageWithMetadata } from "@cline/shared";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createSessionCompactionState } from "../../session/models/session-compaction";
import type { CoreCompactionContext } from "../../types/config";
import { buildAgenticSummaryInputBudget } from "./agentic-compaction";
import { runBasicCompaction } from "./basic-compaction";
import {
createCompactionStateAwarePrepareTurn,
@@ -10,6 +11,7 @@ import {
} from "./compaction";
import {
createTokenEstimator,
estimateTokens,
resolveSummarizerConfig,
serializeMessage,
TOOL_RESULT_CHAR_LIMIT,
@@ -329,10 +331,10 @@ describe("createContextCompactionPrepareTurn", () => {
expectNoOrphanedToolPairs(compacted);
expect(pairs.get("tool-a")).toBeUndefined();
expect(pairs.get("tool-b")).toEqual({ hasResult: true, hasUse: true });
expect(JSON.stringify(compacted)).toContain("Read the latest file");
});
it("preserves the latest tool pair under aggressive basic compaction", () => {
it("may drop the latest completed tool pair under aggressive basic compaction", () => {
const messages: LlmsProviders.Message[] = [
{ role: "user", content: "Read the files" },
assistantToolUseMessage("tool-a"),
@@ -347,7 +349,8 @@ describe("createContextCompactionPrepareTurn", () => {
expectNoOrphanedToolPairs(compacted);
expect(pairs.get("tool-a")).toBeUndefined();
expect(pairs.get("tool-b")).toEqual({ hasResult: true, hasUse: true });
expect(pairs.get("tool-b")).toBeUndefined();
expect(JSON.stringify(compacted)).toContain("Read the latest file");
});
it("treats multi-tool assistant turns as one atomic group in basic compaction", () => {
@@ -386,7 +389,7 @@ describe("createContextCompactionPrepareTurn", () => {
expect(collectToolPairPresence(compacted).get("tool-a")).toBeUndefined();
});
it("preserves the latest typed user turn with its tool work during basic compaction", () => {
it("preserves the latest typed user prompt without requiring completed tool work", () => {
const messages: LlmsProviders.Message[] = [
{ role: "user", content: "Old request" },
{ role: "assistant", content: "Old answer that can be compacted" },
@@ -400,9 +403,24 @@ describe("createContextCompactionPrepareTurn", () => {
expect(compacted).toEqual([
{ role: "user", content: "Old request" },
{ role: "user", content: "Read the latest file" },
assistantToolUseMessage("tool-a"),
toolResultMessage("tool-a", "latest result"),
]);
expectNoOrphanedToolPairs(compacted);
});
it("budgets the complete basic compaction output including the latest turn", () => {
const messages: LlmsProviders.Message[] = [
{ role: "user", content: "original task" },
{ role: "assistant", content: "old assistant " + "x".repeat(10_000) },
{ role: "user", content: "latest typed prompt" },
assistantToolUseMessage("tool-live"),
toolResultMessage("tool-live", "live result " + "y".repeat(10_000)),
];
const compacted = runForcedBasicCompaction(messages, 700);
expect(totalJsonTokens(compacted)).toBeLessThanOrEqual(700);
expect(JSON.stringify(compacted)).toContain("latest typed prompt");
expectNoOrphanedToolPairs(compacted);
});
it("does not compact a single typed user message", () => {
@@ -520,6 +538,23 @@ describe("createContextCompactionPrepareTurn", () => {
expect(anthropicConfig.maxOutputTokens).toBe(1_024);
});
it("preserves summarizer modelInfo without a nested providerConfig", () => {
const resolved = resolveSummarizerConfig({
activeProviderConfig: {
providerId: "anthropic",
modelId: "primary-model",
modelInfo: { id: "primary-model", maxInputTokens: 100_000 },
} as LlmsProviders.ProviderConfig,
summarizer: {
providerId: "openai",
modelId: "small-summary",
modelInfo: { id: "small-summary", maxInputTokens: 600 },
},
});
expect(resolved.modelInfo?.maxInputTokens).toBe(600);
});
it("summarizes older messages and keeps recent messages", async () => {
const emitStatusNotice = vi.fn();
createHandlerMock.mockReturnValue({
@@ -772,6 +807,43 @@ describe("createContextCompactionPrepareTurn", () => {
expect(summarizerPrompt.length).toBeLessThan(longToolOutput.length);
});
it("budgets agentic summary input before serialization", () => {
const result = buildAgenticSummaryInputBudget({
messages: [
{ role: "user", content: "Run a large command" },
{
role: "assistant",
content: [
{
type: "tool_use",
id: "tool-large",
name: "execute_command",
input: { command: "print-large-output" },
},
],
},
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "tool-large",
name: "execute_command",
content: "x".repeat(50_000),
},
],
},
{ role: "user", content: "Latest typed prompt" },
],
targetTokens: 400,
estimateMessageTokens: estimateJsonTokens,
});
expect(result.estimatedTokens).toBeLessThanOrEqual(400);
expect(JSON.stringify(result.messages)).toContain("Latest typed prompt");
expect(result.actions.length).toBeGreaterThan(0);
});
it("never lands the agentic cut in the middle of a tool pair", async () => {
// Repro for the "No tool call found for function call output" provider
// error: findCutIndex used to walk back by token budget and could land
@@ -946,6 +1018,81 @@ describe("createContextCompactionPrepareTurn", () => {
);
});
it("budgets agentic summary input against the configured summarizer context window", async () => {
let summaryRequest = "";
createHandlerMock.mockReturnValue({
createMessage: vi.fn(
(_system: string, messages: LlmsProviders.Message[]) => {
summaryRequest = String(messages[0]?.content ?? "");
return streamChunks([
{ type: "text", id: "summary-small", text: "## Goal\nSummarized" },
{ type: "done", id: "summary-small", success: true },
]);
},
),
});
const summarizerLimit = 600;
const oversizedAssistant = "assistant details ".repeat(5_000);
const prepareTurn = createContextCompactionPrepareTurn({
providerId: "anthropic",
modelId: "primary-model",
providerConfig: {
providerId: "anthropic",
modelId: "primary-model",
modelInfo: { id: "primary-model", maxInputTokens: 10_000 },
} as LlmsProviders.ProviderConfig,
compaction: {
enabled: true,
strategy: "agentic",
preserveRecentTokens: 1,
reserveTokens: 5,
summarizer: {
providerId: "openai",
modelId: "small-summary",
modelInfo: {
id: "small-summary",
maxInputTokens: summarizerLimit,
},
},
},
logger: undefined,
});
await prepareTurn?.({
agentId: "agent-1",
conversationId: "conv-1",
parentAgentId: null,
iteration: 1,
abortSignal: new AbortController().signal,
systemPrompt: "You are helpful.",
tools: [],
messages: [
{ role: "user", content: "Old request" },
{ role: "assistant", content: oversizedAssistant },
{ role: "user", content: "Latest turn" },
{ role: "assistant", content: "Latest answer" },
],
apiMessages: [
{ role: "user", content: "Old request" },
{ role: "assistant", content: oversizedAssistant },
{ role: "user", content: "Latest turn" },
{ role: "assistant", content: "Latest answer" },
],
model: {
id: "primary-model",
provider: "anthropic",
info: { id: "primary-model", maxInputTokens: 10_000 },
},
});
expect(createHandlerMock).toHaveBeenCalledTimes(1);
expect(estimateTokens(summaryRequest.length)).toBeLessThanOrEqual(
summarizerLimit,
);
expect(summaryRequest).not.toContain(oversizedAssistant);
});
it("uses basic compaction without calling the summarizer", async () => {
const emitStatusNotice = vi.fn();
const prepareTurn = createContextCompactionPrepareTurn({
@@ -1798,7 +1945,7 @@ describe("createContextCompactionPrepareTurn", () => {
expect(result?.messages.length).toBeLessThan(4);
});
it("preserves user image blocks during basic compaction sanitization", () => {
it("drops old user image blocks during basic compaction sanitization", () => {
const messages: LlmsProviders.Message[] = [
{
role: "user",
@@ -1834,7 +1981,6 @@ describe("createContextCompactionPrepareTurn", () => {
expect(result?.messages).toBeDefined();
expect(result?.messages[0]?.content).toEqual([
{ type: "text", text: "Older user turn" },
{ type: "image", data: "abc", mediaType: "image/png" },
]);
expect(result?.messages.at(-1)).toEqual({
role: "user",
@@ -2065,6 +2211,83 @@ describe("createContextCompactionPrepareTurn", () => {
);
});
it("accounts executed compaction telemetry against compaction messages", async () => {
const captureCalls: Array<{
event: string;
properties?: Record<string, unknown>;
}> = [];
const telemetry = {
capture: (call: {
event: string;
properties?: Record<string, unknown>;
}) => captureCalls.push(call),
captureRequired: () => {},
setDistinctId: () => {},
updateCommonProperties: () => {},
identify: () => {},
} as unknown as Parameters<
typeof createContextCompactionPrepareTurn
>[0]["telemetry"];
const compact = vi.fn(async () => ({
messages: [{ role: "user" as const, content: "trimmed" }],
}));
const prepareTurn = createContextCompactionPrepareTurn({
providerId: "anthropic",
modelId: "mock-model",
providerConfig: {
providerId: "anthropic",
modelId: "mock-model",
} as LlmsProviders.ProviderConfig,
compaction: {
enabled: true,
strategy: "basic",
reserveTokens: 1,
compact,
},
telemetry,
});
const messages: LlmsProviders.Message[] = [
{ role: "user", content: "Original task" },
{ role: "assistant", content: "short answer" },
{ role: "user", content: "Latest" },
];
const apiMessages: LlmsProviders.Message[] = [
...messages,
{ role: "assistant", content: "provider-only payload ".repeat(1_000) },
];
await prepareTurn?.({
agentId: "agent-1",
conversationId: "conv-1",
parentAgentId: null,
iteration: 3,
abortSignal: new AbortController().signal,
systemPrompt: "",
tools: [],
messages,
apiMessages,
model: {
id: "mock-model",
provider: "anthropic",
info: { id: "mock-model", maxInputTokens: 100 },
},
});
expect(compact).toHaveBeenCalledTimes(1);
const executed = captureCalls.find(
(call) => call.event === "task.compaction_executed",
);
const props = executed?.properties as Record<string, unknown>;
expect(props.tokensBefore as number).toBeLessThan(
props.triggerTokens as number,
);
expect(props.tokensSaved).toBe(
(props.tokensBefore as number) - (props.tokensAfter as number),
);
expect(props.tokensSaved as number).toBeGreaterThanOrEqual(0);
});
it("emits task.compaction_skipped when the strategy returns undefined", async () => {
const captureCalls: Array<{
event: string;
@@ -2108,6 +2331,19 @@ describe("createContextCompactionPrepareTurn", () => {
{ role: "assistant", content: "x".repeat(500) },
{ role: "user", content: "Latest" },
];
const apiMessages: LlmsProviders.Message[] = [
{ role: "user", content: "api-shaped ".repeat(500) },
];
const estimateMessageTokens = createTokenEstimator();
const sessionInputTokens = messages.reduce(
(total, message) => total + estimateMessageTokens(message),
0,
);
const apiInputTokens = apiMessages.reduce(
(total, message) => total + estimateMessageTokens(message),
0,
);
expect(apiInputTokens).not.toBe(sessionInputTokens);
const result = await prepareTurn?.({
agentId: "agent-1",
@@ -2118,7 +2354,7 @@ describe("createContextCompactionPrepareTurn", () => {
systemPrompt: "",
tools: [],
messages,
apiMessages: messages,
apiMessages,
model: {
id: "mock-model",
provider: "anthropic",
@@ -2136,6 +2372,7 @@ describe("createContextCompactionPrepareTurn", () => {
expect(props.mode).toBe("auto");
expect(props.reason).toBe("no_result");
expect(props.ulid).toBe("ulid-test-skip");
expect(props.tokensBefore).toBe(sessionInputTokens);
expect(typeof props.durationMs).toBe("number");
expect(
captureCalls.find((call) => call.event === "task.compaction_executed"),
@@ -1,4 +1,5 @@
import {
captureCompactionBudgetEmergency,
captureCompactionExecuted,
captureCompactionSkipped,
type TelemetryCompactionStrategy,
@@ -430,6 +431,10 @@ export function createContextCompactionPrepareTurn(
);
const beforeMessageCount = context.messages.length;
const compactionInputTokens = context.messages.reduce(
(total: number, message) => total + estimateMessageTokens(message),
0,
);
const startedAt = Date.now();
const result = userCompaction?.compact
@@ -467,10 +472,11 @@ export function createContextCompactionPrepareTurn(
severity: "info",
strategy: strategy,
maxInputTokens,
inputTokens,
inputTokens: compactionInputTokens,
apiInputTokens: inputTokens,
afterTokens,
tokensSaved: inputTokens - afterTokens,
utilizationBefore: `${((inputTokens / maxInputTokens) * 100).toFixed(1)}%`,
tokensSaved: compactionInputTokens - afterTokens,
utilizationBefore: `${((compactionInputTokens / maxInputTokens) * 100).toFixed(1)}%`,
utilizationAfter: `${((afterTokens / maxInputTokens) * 100).toFixed(1)}%`,
thresholdTrigger: `${(targetState.thresholdRatio * 100).toFixed(1)}%`,
messagesBefore: beforeMessageCount,
@@ -484,9 +490,9 @@ export function createContextCompactionPrepareTurn(
messagesBefore: beforeMessageCount,
messagesAfter: result.messages.length,
messagesRemoved: beforeMessageCount - result.messages.length,
tokensBefore: inputTokens,
tokensBefore: compactionInputTokens,
tokensAfter: afterTokens,
tokensSaved: inputTokens - afterTokens,
tokensSaved: compactionInputTokens - afterTokens,
triggerTokens: targetState.triggerTokens,
maxInputTokens,
thresholdRatio: targetState.thresholdRatio,
@@ -497,13 +503,38 @@ export function createContextCompactionPrepareTurn(
modelId: config.modelId,
...telemetryIdentity,
});
if (
result.budget &&
(result.budget.actionCount > 0 || result.budget.warningCount > 0)
) {
captureCompactionBudgetEmergency(config.telemetry, {
ulid: telemetryUlid,
strategy: telemetryStrategy,
mode,
policyIntent: result.budget.policyIntent,
actionCount: result.budget.actionCount,
warningCount: result.budget.warningCount,
liveTailHandling: result.budget.liveTailHandling,
provider: config.providerId,
modelId: config.modelId,
...telemetryIdentity,
});
context.emitStatusNotice?.("compaction-budget-adjusted", {
kind: "compaction_budget_emergency",
reason: "compaction_budget_emergency",
iteration: context.iteration,
policyIntent: result.budget.policyIntent,
actionCount: result.budget.actionCount,
warningCount: result.budget.warningCount,
});
}
} else {
captureCompactionSkipped(config.telemetry, {
ulid: telemetryUlid,
strategy: telemetryStrategy,
mode,
reason: "no_result",
tokensBefore: inputTokens,
tokensBefore: compactionInputTokens,
triggerTokens: targetState.triggerTokens,
maxInputTokens,
thresholdRatio: targetState.thresholdRatio,
+33 -20
View File
@@ -9,6 +9,7 @@ export {
ClineNotSubscribedError,
ClineOrgIndividualInferenceSubscriptionError,
ClinePassLimitError,
extractClinePassLimitMessage,
getClineOrgIndividualInferenceSubscriptionMessage,
getClineNotSubscribedMessage,
getClinePassSubscriptionUrl,
@@ -131,10 +132,15 @@ export {
isClineAccountActionRequest,
type ProviderActionExecutor,
RpcClineAccountService,
type UserCurrentPlan,
type UserRemoteConfigOrganization,
type UserRemoteConfigResponse,
type UserCurrentPlan,
} from "./account";
export {
hashSecret,
setSdkLogger,
sdkDebug,
} from "./logging/early-logger";
export {
createOAuthClientCallbacks,
type OAuthClientCallbacksOptions,
@@ -302,7 +308,10 @@ export {
type McpServerTransportConfig,
type McpSettingsFile,
type McpSettingsLockOptions,
McpSettingsLockTimeoutError,
type McpSettingsMutator,
McpSettingsMutatorPurityError,
McpSettingsUpdateSkippedError,
type McpSseTransportConfig,
type McpStdioTransportConfig,
type McpStreamableHttpTransportConfig,
@@ -321,9 +330,6 @@ export {
updateMcpServerOAuthStateAsync,
updateMcpSettingsFile,
updateMcpSettingsFileSync,
McpSettingsLockTimeoutError,
McpSettingsMutatorPurityError,
McpSettingsUpdateSkippedError,
} from "./extensions/mcp";
export {
type AgentTask,
@@ -412,6 +418,11 @@ export {
} from "./remote-config/integration";
export type { RuntimeCapabilities } from "./runtime/capabilities";
export { normalizeRuntimeCapabilities } from "./runtime/capabilities";
export type {
ConnectionUpdate,
ConnectionUpdateInput,
} from "./runtime/config/connection-update";
export { buildConnectionUpdate } from "./runtime/config/connection-update";
export { listSessionHistoryFromBackend } from "./runtime/host/history";
export type { SessionBackend } from "./runtime/host/host";
export {
@@ -474,7 +485,10 @@ export {
type FeatureFlagsServiceOptions,
NoOpFeatureFlagsProvider,
} from "./services/feature-flags";
export type { GlobalSettings } from "./services/global-settings";
export type {
GlobalCompactionStrategy,
GlobalSettings,
} from "./services/global-settings";
export {
filterDisabledPluginPaths,
filterDisabledTools,
@@ -496,16 +510,6 @@ export {
toggleDisabledTool,
writeGlobalSettings,
} from "./services/global-settings";
export type { GlobalCompactionStrategy } from "./services/global-settings";
export type {
McpInstallOptions,
McpInstallResult,
} from "./services/mcp-install";
export {
buildMcpInstallTransport,
installMcpServer,
parseMcpInstallArgs,
} from "./services/mcp-install";
export type {
MarketplaceActionResult,
MarketplaceEntryInput,
@@ -526,6 +530,15 @@ export {
uninstallMarketplacePlugin,
uninstallMarketplaceSkill,
} from "./services/marketplace";
export type {
McpInstallOptions,
McpInstallResult,
} from "./services/mcp-install";
export {
buildMcpInstallTransport,
installMcpServer,
parseMcpInstallArgs,
} from "./services/mcp-install";
export type {
ParsedPluginSource,
PluginInstallOptions,
@@ -687,11 +700,11 @@ export {
} from "./services/workspace/workspace-manifest";
export {
buildCheckpointWorkspaceDiff,
compareCheckpointToWorkspace,
createCheckpointComparePlan,
type CheckpointComparePlan,
type CheckpointContentDiff,
type CheckpointWorkspaceCompareResult,
compareCheckpointToWorkspace,
createCheckpointComparePlan,
} from "./session/checkpoint-diff";
export {
findCheckpointForRun,
@@ -791,17 +804,17 @@ export {
getCoreHeadlessToolNames,
MAX_COMMAND_OUTPUT_CHARS,
resolveCoreSelectedToolIds,
type ShellExecutor,
type ShellExecutorOptions,
type StructuredCommandInput,
StructuredCommandInputSchema,
TEAM_TOOL_NAMES,
truncateCommandOutput,
type ToolCatalogEntry,
type ToolExecutors,
type ShellExecutor,
type ShellExecutorOptions,
type ToolPolicyPresetName,
type ToolPresetName,
ToolPresets,
truncateCommandOutput,
} from "./extensions/tools";
export {
type ClineRecommendedModel,
@@ -0,0 +1,60 @@
/**
* Early SDK logger for components that operate before/outside of `ClineCore`
* sessions, plus a secret-hashing helper for credential diagnostics.
*
* `ClineCore.create({ logger })` receives a `BasicLogger` but it is only
* threaded to session-scoped components. `ProviderSettingsManager`,
* `RuntimeOAuthTokenManager`, and the Cline auth functions in `cline.ts` are
* constructed or called before `ClineCore` exists or outside a session.
* Hosts call `setSdkLogger()` once at startup and every early component picks
* it up without threading loggers through every constructor.
*
* When no logger is registered, every call is a no-op.
*
* Secrets are never logged in cleartext; {@link hashSecret} produces an
* 8-hex-digit fingerprint that is stable for the same value but irreversible.
*/
import { createHash } from "node:crypto";
import type { BasicLogger } from "@cline/shared";
let earlyLogger: BasicLogger | undefined;
/**
* Register the logger used by {@link sdkDebug}. Pass `undefined` to disable.
*/
export function setSdkLogger(logger: BasicLogger | undefined): void {
earlyLogger = logger;
}
/** @internal Returns the currently registered early logger (for testing). */
export function getSdkLogger(): BasicLogger | undefined {
return earlyLogger;
}
/**
* Short, stable fingerprint of a secret for debug logging (8 hex digits of a
* SHA-256 digest). The same input always yields the same hash, making it easy
* to eyeball whether a token changed across log entries without leaking it.
*
* Returns `"unset"` for undefined/null/empty so absence is still visible.
*/
export function hashSecret(value: unknown): string {
if (typeof value !== "string" || value.length === 0) {
return "unset";
}
return createHash("sha256").update(value).digest("hex").slice(0, 8);
}
/**
* Emit a debug-level SDK diagnostic message (already a formatted string).
* Best-effort: swallows errors from the underlying logger so logging never
* breaks auth/storage flows.
*/
export function sdkDebug(message: string): void {
try {
earlyLogger?.debug(message);
} catch {
// Never let logging break the app.
}
}
@@ -0,0 +1,103 @@
import { describe, expect, it } from "vitest";
import { buildConnectionUpdate } from "./connection-update";
describe("buildConnectionUpdate", () => {
const base = {
providerId: "openai",
modelId: "codex-test",
apiKey: "sk-test",
};
it("includes only the connection fields that are defined", () => {
expect(buildConnectionUpdate(base)).toEqual(base);
expect(
buildConnectionUpdate({
...base,
baseUrl: "https://example.test",
headers: { "x-a": "1" },
}),
).toEqual({
...base,
baseUrl: "https://example.test",
headers: { "x-a": "1" },
});
});
it("passes an empty string through so callers can clear a field", () => {
expect(buildConnectionUpdate({ ...base, apiKey: "" })).toEqual({
...base,
apiKey: "",
});
});
it("clears reasoning when thinking is explicitly false", () => {
expect(
buildConnectionUpdate({
...base,
thinking: false,
reasoningEffort: "high",
thinkingBudgetTokens: 1024,
}),
).toEqual({
...base,
thinking: false,
reasoningEffort: null,
thinkingBudgetTokens: null,
});
});
it("enables reasoning with the selected effort", () => {
expect(
buildConnectionUpdate({
...base,
thinking: true,
reasoningEffort: "low",
}),
).toEqual({
...base,
thinking: true,
reasoningEffort: "low",
thinkingBudgetTokens: null,
});
});
it("clears stale effort and budget when thinking is enabled without them", () => {
expect(buildConnectionUpdate({ ...base, thinking: true })).toEqual({
...base,
thinking: true,
reasoningEffort: null,
thinkingBudgetTokens: null,
});
});
it("keeps a provided budget when thinking is enabled explicitly", () => {
expect(
buildConnectionUpdate({
...base,
thinking: true,
thinkingBudgetTokens: 1024,
}),
).toEqual({
...base,
thinking: true,
reasoningEffort: null,
thinkingBudgetTokens: 1024,
});
});
it("enables thinking when only an effort is provided", () => {
expect(
buildConnectionUpdate({ ...base, reasoningEffort: "medium" }),
).toEqual({ ...base, thinking: true, reasoningEffort: "medium" });
});
it("enables thinking and truncates a fractional budget", () => {
expect(
buildConnectionUpdate({ ...base, thinkingBudgetTokens: 2048.7 }),
).toEqual({ ...base, thinking: true, thinkingBudgetTokens: 2048 });
});
it("leaves reasoning untouched when thinking is unset", () => {
expect(buildConnectionUpdate(base)).toEqual(base);
});
});
@@ -12,6 +12,72 @@ export interface ConnectionUpdate {
thinkingBudgetTokens?: CoreSessionConfig["thinkingBudgetTokens"] | null;
}
export interface ConnectionUpdateInput {
providerId?: string;
modelId?: string;
apiKey?: string;
baseUrl?: string;
headers?: Record<string, string>;
providerConfig?: CoreSessionConfig["providerConfig"];
thinking?: boolean;
reasoningEffort?: CoreSessionConfig["reasoningEffort"];
thinkingBudgetTokens?: number;
}
/**
* Builds a ConnectionUpdate for switching a live session's provider/model
* connection. Shared by clients (CLI, desktop sidecar, ) so the
* thinking/reasoning transition rules stay consistent:
* - `thinking: false` disables reasoning and clears effort/budget.
* - `thinking: true` (or a provided effort/budget) enables reasoning; an
* explicit `thinking: true` resets the reasoning state, so a stale effort or
* token budget from the previous connection is cleared unless a replacement
* accompanies it. A bare effort/budget without a `thinking` flag updates
* only the provided field.
* - `thinking: undefined` leaves the session's reasoning state untouched.
* Connection fields are included only when defined; an empty string is passed
* through (it means "clear"), so callers that want to skip blanks must drop
* them before calling.
*/
export function buildConnectionUpdate(
input: ConnectionUpdateInput,
): ConnectionUpdate {
const update: ConnectionUpdate = {};
if (input.providerId !== undefined) update.providerId = input.providerId;
if (input.modelId !== undefined) update.modelId = input.modelId;
if (input.apiKey !== undefined) update.apiKey = input.apiKey;
if (input.baseUrl !== undefined) update.baseUrl = input.baseUrl;
if (input.headers !== undefined) update.headers = input.headers;
if (input.providerConfig !== undefined) {
update.providerConfig = input.providerConfig;
}
if (input.thinking === false) {
update.thinking = false;
update.reasoningEffort = null;
update.thinkingBudgetTokens = null;
return update;
}
if (input.thinking === true) {
update.thinking = true;
update.reasoningEffort = input.reasoningEffort ?? null;
// Cleared symmetrically with reasoningEffort; a valid budget below
// overwrites the null.
update.thinkingBudgetTokens = null;
} else if (input.reasoningEffort !== undefined) {
update.thinking = true;
update.reasoningEffort = input.reasoningEffort;
}
if (
typeof input.thinkingBudgetTokens === "number" &&
Number.isFinite(input.thinkingBudgetTokens) &&
input.thinkingBudgetTokens > 0
) {
update.thinking = true;
update.thinkingBudgetTokens = Math.trunc(input.thinkingBudgetTokens);
}
return update;
}
export function normalizeConnectionUpdate(
updates: ConnectionUpdate,
): ConnectionUpdate {
@@ -342,6 +342,87 @@ describe("LocalRuntimeHost", () => {
);
});
it("persists provider/model connection updates to the session manifest", async () => {
const sessionId = "sess-connection-manifest-update";
const manifest = createManifest(sessionId);
const sessionService = {
ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
manifestPath: "/tmp/manifest.json",
messagesPath: "/tmp/messages.json",
manifest,
}),
persistSessionMessages: vi.fn(),
updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
writeSessionManifest: vi.fn(),
listSessions: vi.fn().mockResolvedValue([]),
deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
};
const runtimeBuilder = {
build: vi.fn().mockReturnValue({ tools: [], shutdown: vi.fn() }),
};
const agent = {
run: vi.fn().mockResolvedValue(createResult()),
continue: vi.fn().mockResolvedValue(createResult()),
getMessages: vi.fn().mockReturnValue([]),
getAgentId: vi.fn().mockReturnValue("agent-root-1"),
getConversationId: vi.fn().mockReturnValue("conv-root-1"),
abort: vi.fn(),
subscribeEvents: vi.fn().mockReturnValue(() => {}),
updateConnection: vi.fn(),
canStartRun: vi.fn().mockReturnValue(true),
shutdown: vi.fn().mockResolvedValue(undefined),
};
const createAgent = vi.fn(() => agent as never);
const manager = new RuntimeHostUnderTest({
distinctId,
sessionService: sessionService as never,
runtimeBuilder: runtimeBuilder as never,
createAgent,
});
await manager.startSession(
normalizeStartInput({
config: createConfig({ sessionId }),
prompt: "hello",
interactive: true,
}),
);
sessionService.writeSessionManifest.mockClear();
// A disk-only writer (compaction path, rename) updated the manifest
// behind the in-memory copy's back; the connection update must re-read
// and preserve it instead of clobbering it with the stale copy.
const diskManifest = {
...manifest,
compaction_path: "/tmp/compaction.json",
title: "renamed session",
};
const readSessionManifest = vi.fn().mockResolvedValue(diskManifest);
(sessionService as Record<string, unknown>).readSessionManifest =
readSessionManifest;
await manager.updateSessionConnection(sessionId, {
providerId: "openai",
modelId: "codex-test",
});
expect(sessionService.writeSessionManifest).toHaveBeenCalledWith(
"/tmp/manifest.json",
expect.objectContaining({
session_id: sessionId,
provider: "openai",
model: "codex-test",
compaction_path: "/tmp/compaction.json",
title: "renamed session",
}),
);
sessionService.writeSessionManifest.mockClear();
await manager.updateSessionConnection(sessionId, { thinking: true });
expect(sessionService.writeSessionManifest).not.toHaveBeenCalled();
});
it("persists thinking budget token connection updates", async () => {
const sessionId = "sess-thinking-budget-update";
const manifest = createManifest(sessionId);
@@ -226,6 +226,8 @@ export class LocalRuntimeHost implements RuntimeHost {
private readonly defaultFetch?: typeof fetch;
private readonly events = new RuntimeHostEventBus();
private readonly sessions = new Map<string, ActiveSession>();
// Serializes manifest read-modify-writes per session; see mutateSessionManifest.
private readonly manifestMutationQueues = new Map<string, Promise<void>>();
private readonly usageBySession = new Map<string, SessionAccumulatedUsage>();
private readonly aggregateUsageBySession = new Map<
string,
@@ -1331,6 +1333,60 @@ export class LocalRuntimeHost implements RuntimeHost {
);
session.agent.updateConnection(updates);
session.runtime.teamRuntime?.updateTeammateConnections(teammateUpdates);
// Keep the persisted manifest in sync so session history reflects the
// connection the session is now using, not the one it started with.
if (updates.providerId || updates.modelId) {
await this.mutateSessionManifest(session, (manifest) => {
if (updates.providerId) manifest.provider = updates.providerId;
if (updates.modelId) manifest.model = updates.modelId;
});
}
}
/**
* Serialized read-modify-write for a live session's manifest. Every
* manifest write for an active session MUST go through this helper: it
* re-reads the disk manifest first so disk-only writers (compaction-path
* updates, title/metadata renames) are never reverted by a stale in-memory
* copy, applies the field-level mutation, syncs the in-memory copy, and
* persists one mutation at a time per session.
*/
private async mutateSessionManifest(
session: ActiveSession,
mutate: (manifest: SessionManifest) => void,
): Promise<SessionManifest | undefined> {
const artifacts = session.artifacts;
if (!artifacts) return undefined;
const sessionId = session.sessionId;
const tail =
this.manifestMutationQueues.get(sessionId) ?? Promise.resolve();
const next = tail.then(async () => {
const latest =
(await this.invokeOptionalValue<SessionManifest>(
"readSessionManifest",
sessionId,
)) ?? artifacts.manifest;
mutate(latest);
artifacts.manifest = latest;
await this.invoke<void>(
"writeSessionManifest",
artifacts.manifestPath,
latest,
);
return latest;
});
const queued = next.then(
() => undefined,
() => undefined,
);
this.manifestMutationQueues.set(sessionId, queued);
try {
return await next;
} finally {
if (this.manifestMutationQueues.get(sessionId) === queued) {
this.manifestMutationQueues.delete(sessionId);
}
}
}
// Retained for unit tests that reach in via Reflect.
@@ -1361,7 +1417,9 @@ export class LocalRuntimeHost implements RuntimeHost {
): Promise<AgentResult> {
const preparedInput = await this.prepareTurnInput(session, input);
const prompt = preparedInput.prompt.trim();
if (!prompt) throw new Error("prompt cannot be empty");
const images = preparedInput?.userImages?.length;
const files = preparedInput?.userFiles?.length;
if (!prompt && !images && !files) throw new Error("prompt cannot be empty");
if (!session.artifacts && !session.pendingPrompt) {
session.pendingPrompt = prompt;
@@ -1901,31 +1959,26 @@ export class LocalRuntimeHost implements RuntimeHost {
exitCode,
);
if (!result.updated) return;
const latestManifest =
(await this.invokeOptionalValue<SessionManifest>(
"readSessionManifest",
session.sessionId,
)) ?? session.artifacts.manifest;
latestManifest.status = status;
if (isNonTerminalSessionStatus(status)) {
delete latestManifest.ended_at;
latestManifest.exit_code = null;
} else {
latestManifest.ended_at = result.endedAt ?? nowIso();
latestManifest.exit_code = typeof exitCode === "number" ? exitCode : null;
}
session.artifacts.manifest = latestManifest;
const latestManifest = await this.mutateSessionManifest(
session,
(manifest) => {
manifest.status = status;
if (isNonTerminalSessionStatus(status)) {
delete manifest.ended_at;
manifest.exit_code = null;
} else {
manifest.ended_at = result.endedAt ?? nowIso();
manifest.exit_code = typeof exitCode === "number" ? exitCode : null;
}
},
);
if (!latestManifest) return;
session.status = status;
session.updatedAt = result.endedAt ?? nowIso();
session.endedAt = isNonTerminalSessionStatus(status)
? null
: latestManifest.ended_at;
session.exitCode = latestManifest.exit_code;
await this.invoke<void>(
"writeSessionManifest",
session.artifacts.manifestPath,
latestManifest,
);
this.emitStatus(session.sessionId, status);
}
@@ -128,6 +128,55 @@ describe("RuntimeEventAdapter — suppressed events", () => {
});
});
describe("RuntimeEventAdapter — status notices", () => {
let adapter: RuntimeEventAdapter;
beforeEach(() => {
adapter = new RuntimeEventAdapter();
});
it("preserves bounded compaction reasons", () => {
for (const reason of [
"auto_compaction",
"manual_compaction",
"compaction_budget_emergency",
] as const) {
const out = adapter.translate({
type: "status-notice",
snapshot: makeSnapshot(),
message: "compaction status",
metadata: { reason },
});
expect(out).toEqual([
{
type: "notice",
noticeType: "status",
displayRole: "status",
message: "compaction status",
reason,
metadata: { reason },
},
]);
}
});
it("does not promote arbitrary status reasons", () => {
const out = adapter.translate({
type: "status-notice",
snapshot: makeSnapshot(),
message: "custom",
metadata: { reason: "surprise" },
});
expect(out[0]).toMatchObject({
type: "notice",
noticeType: "status",
message: "custom",
reason: undefined,
});
});
});
// ---------------------------------------------------------------------------
// Iteration lifecycle
// ---------------------------------------------------------------------------
@@ -66,6 +66,21 @@ import type {
// Helpers
// =============================================================================
type StatusNoticeReason = Extract<AgentEvent, { type: "notice" }>["reason"];
function resolveStatusNoticeReason(
reason: unknown,
): StatusNoticeReason | undefined {
switch (reason) {
case "auto_compaction":
case "manual_compaction":
case "compaction_budget_emergency":
return reason;
default:
return undefined;
}
}
function extractTextPart(message: AgentMessage): string | undefined {
const parts = message.content.filter(
(part): part is AgentTextPart => part.type === "text",
@@ -225,10 +240,7 @@ export class RuntimeEventAdapter {
noticeType: "status",
displayRole: "status",
message: event.message,
reason:
event.metadata?.reason === "auto_compaction"
? "auto_compaction"
: undefined,
reason: resolveStatusNoticeReason(event.metadata?.reason),
metadata: event.metadata,
},
];
@@ -1,4 +1,5 @@
import type { ITelemetryService } from "@cline/shared";
import { hashSecret, sdkDebug } from "../../logging/early-logger";
import {
getProviderAuthHandler,
getProviderOAuthCredentialsFromSettings,
@@ -113,6 +114,9 @@ export class RuntimeOAuthTokenManager {
const settings =
this.providerSettingsManager.getProviderSettings(storageProviderId);
if (!settings) {
sdkDebug(
`oauth.resolve providerId=${providerId} storageProviderId=${storageProviderId} outcome=no_settings`,
);
return null;
}
@@ -121,9 +125,16 @@ export class RuntimeOAuthTokenManager {
settings,
);
if (!currentCredentials) {
sdkDebug(
`oauth.resolve providerId=${providerId} storageProviderId=${storageProviderId} outcome=no_credentials`,
);
return null;
}
sdkDebug(
`oauth.resolve.start providerId=${providerId} storageProviderId=${storageProviderId} forceRefresh=${forceRefresh} accessTokenHash=${hashSecret(currentCredentials.access)} refreshTokenHash=${hashSecret(currentCredentials.refresh)}`,
);
const nextCredentials = await handler.refresh({
settings,
credentials: currentCredentials,
@@ -131,6 +142,9 @@ export class RuntimeOAuthTokenManager {
telemetry: this.telemetry,
});
if (!nextCredentials) {
sdkDebug(
`oauth.resolve providerId=${providerId} outcome=refresh_returned_null`,
);
throw new OAuthReauthRequiredError(providerId);
}
@@ -144,10 +158,15 @@ export class RuntimeOAuthTokenManager {
});
const wasRefreshed = !authSettingsEqual(settings.auth, nextSettings.auth);
if (wasRefreshed) {
sdkDebug(
`oauth.resolve.refreshed providerId=${providerId} newAccessTokenHash=${hashSecret(nextCredentials.access)} newRefreshTokenHash=${hashSecret(nextCredentials.refresh)} savingToDisk=true`,
);
this.providerSettingsManager.saveProviderSettings(nextSettings, {
setLastUsed: false,
tokenSource: "oauth",
});
} else {
sdkDebug(`oauth.resolve providerId=${providerId} outcome=not_refreshed`);
}
return {
@@ -1,4 +1,11 @@
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import {
mkdirSync,
mkdtempSync,
readdirSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import os from "node:os";
import path from "node:path";
import * as LlmsModels from "@cline/llms";
@@ -51,6 +58,50 @@ describe("ProviderSettingsManager", () => {
expect(reloaded.read().providers.anthropic?.tokenSource).toBe("manual");
});
it("writes atomically, leaving no temp file behind", () => {
const tempDir = mkdtempSync(
path.join(os.tmpdir(), "core-provider-settings-"),
);
tempDirs.push(tempDir);
const filePath = path.join(tempDir, "provider-settings.json");
const manager = new ProviderSettingsManager({ filePath });
manager.saveProviderSettings(
{ provider: "anthropic", apiKey: "test-key" },
{ setLastUsed: true },
);
const siblings = readdirSync(tempDir);
expect(siblings).toEqual(["provider-settings.json"]);
});
it("preserves the previous file when the staged write cannot be renamed", () => {
const tempDir = mkdtempSync(
path.join(os.tmpdir(), "core-provider-settings-"),
);
tempDirs.push(tempDir);
const filePath = path.join(tempDir, "provider-settings.json");
const manager = new ProviderSettingsManager({ filePath });
manager.saveProviderSettings(
{ provider: "anthropic", apiKey: "before" },
{ setLastUsed: true },
);
const before = readFileSync(filePath, "utf8");
// Occupying the temp path with a directory makes writeFileSync fail,
// simulating a mid-write crash: the destination must be untouched.
mkdirSync(`${filePath}.${process.pid}.tmp`);
expect(() =>
manager.saveProviderSettings(
{ provider: "anthropic", apiKey: "after" },
{ setLastUsed: true },
),
).toThrow();
rmSync(`${filePath}.${process.pid}.tmp`, { recursive: true, force: true });
expect(readFileSync(filePath, "utf8")).toBe(before);
});
it("resolves auth storage settings for providers registered with a storage provider id", () => {
const tempDir = mkdtempSync(
path.join(os.tmpdir(), "core-provider-settings-"),
@@ -3,12 +3,15 @@ import {
existsSync,
mkdirSync,
readFileSync,
renameSync,
rmSync,
writeFileSync,
} from "node:fs";
import { basename, dirname } from "node:path";
import { resolveProviderSettingsPath } from "@cline/shared/storage";
import { getLiveModelsCatalog } from "../..";
import { getProviderAuthHandler } from "../../auth/provider-auth-registry";
import { hashSecret, sdkDebug } from "../../logging/early-logger";
import {
emptyStoredProviderSettings,
type ProviderConfig,
@@ -99,6 +102,10 @@ export class ProviderSettingsManager {
const result = StoredProviderSettingsSchema.safeParse(parsed);
if (result.success) {
registerConfiguredProvidersFromSettings(result.data);
const clineAuth = result.data.providers["cline"]?.settings?.auth;
sdkDebug(
`providers.read providers=[${Object.keys(result.data.providers).join(",")}] lastUsed=${result.data.lastUsedProvider ?? "none"} clineAuthPresent=${!!clineAuth?.accessToken} clineAccessTokenHash=${hashSecret(clineAuth?.accessToken)} clineRefreshTokenHash=${hashSecret(clineAuth?.refreshToken)}`,
);
return result.data;
}
} catch {
@@ -114,11 +121,21 @@ export class ProviderSettingsManager {
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true, mode: 0o700 });
}
writeFileSync(
this.filePath,
`${JSON.stringify(normalized, null, 2)}\n`,
"utf8",
);
// Stage to a pid-unique temp file and rename into place. Concurrent
// Cline processes (CLI, extension, hub) share this file; a bare
// writeFileSync lets readers catch a partial file, which read() treats
// as empty settings — indistinguishable from being logged out.
const tempPath = `${this.filePath}.${process.pid}.tmp`;
try {
writeFileSync(tempPath, `${JSON.stringify(normalized, null, 2)}\n`, {
encoding: "utf8",
mode: 0o600,
});
renameSync(tempPath, this.filePath);
} catch (error) {
rmSync(tempPath, { force: true });
throw error;
}
// Restrict file to owner-only read/write (best-effort; no-op on Windows).
try {
chmodSync(this.filePath, 0o600);
@@ -154,6 +171,16 @@ export class ProviderSettingsManager {
: previous.lastUsedProvider,
};
this.write(next);
const prevClineAuth = previous.providers["cline"]?.settings?.auth;
const nextClineAuth =
validatedSettings.provider === "cline"
? validatedSettings.auth
: next.providers["cline"]?.settings?.auth;
const authDropped =
!!prevClineAuth?.accessToken && !nextClineAuth?.accessToken;
sdkDebug(
`providers.save providerId=${providerId} tokenSource=${tokenSource} clineAuthWasPresent=${!!prevClineAuth?.accessToken} clineAuthIsPresent=${!!nextClineAuth?.accessToken} authDropped=${authDropped}`,
);
return next;
}
@@ -5,6 +5,7 @@ import {
import { describe, expect, test, vi } from "vitest";
import {
CORE_TELEMETRY_EVENTS,
captureCompactionBudgetEmergency,
captureCompactionExecuted,
captureCompactionSkipped,
captureExtensionActivated,
@@ -453,6 +454,38 @@ describe("captureRunCommandsTimeout", () => {
});
});
describe("captureCompactionBudgetEmergency", () => {
test("emits task.compaction_budget_emergency with action metadata", () => {
const stub = createTelemetryStub();
captureCompactionBudgetEmergency(stub.telemetry, {
ulid: "ulid-1",
strategy: "basic",
mode: "auto",
policyIntent: "basic_compaction_projection",
actionCount: 2,
warningCount: 1,
liveTailHandling: "included_degraded",
provider: "anthropic",
modelId: "claude-sonnet-4",
});
const { event, properties } = captureCallAt(stub, 0);
expect(event).toBe("task.compaction_budget_emergency");
expect(properties).toMatchObject({
ulid: "ulid-1",
strategy: "basic",
mode: "auto",
policyIntent: "basic_compaction_projection",
actionCount: 2,
warningCount: 1,
liveTailHandling: "included_degraded",
});
expect(typeof (properties as Record<string, unknown>).timestamp).toBe(
"string",
);
});
});
/**
* Telemetry-policy regression coverage.
*
@@ -615,6 +648,24 @@ describe("telemetry policy: helpers respect telemetry opt-out", () => {
expect(emitRequired).not.toHaveBeenCalled();
});
test("captureCompactionBudgetEmergency never invokes captureRequired", () => {
const { adapter, emitRequired } = createDisabledAdapter();
const service = new TelemetryService({
distinctId: "test-distinct-id",
adapters: [adapter],
});
captureCompactionBudgetEmergency(service, {
ulid: "ulid-1",
strategy: "basic",
mode: "auto",
policyIntent: "basic_compaction_projection",
actionCount: 1,
warningCount: 0,
liveTailHandling: "included_degraded",
});
expect(emitRequired).not.toHaveBeenCalled();
});
test("a correctly-policed adapter drops these events when disabled", () => {
// This test layers on top of the previous four to assert the *full*
// end-to-end policy: when the adapter is disabled, a real adapter
@@ -693,6 +744,15 @@ describe("telemetry policy: helpers respect telemetry opt-out", () => {
command_count: 2,
duration_ms: 1502,
});
captureCompactionBudgetEmergency(service, {
ulid: "ulid-1",
strategy: "basic",
mode: "auto",
policyIntent: "basic_compaction_projection",
actionCount: 1,
warningCount: 0,
liveTailHandling: "included_degraded",
});
expect(observed).toEqual([]);
expect(dropped).toEqual([
"user.extension_activated",
@@ -703,6 +763,7 @@ describe("telemetry policy: helpers respect telemetry opt-out", () => {
"task.compaction_executed",
"task.compaction_skipped",
"sdk.tool_timeout",
"task.compaction_budget_emergency",
]);
});
});
@@ -6,6 +6,10 @@ import {
SDK_ERROR_TELEMETRY_EVENT,
type TelemetryProperties,
} from "@cline/shared";
import type {
CoreCompactionBudgetPolicyIntent,
CoreCompactionLiveTailHandling,
} from "../../types/config";
const MAX_ERROR_MESSAGE_LENGTH = 500;
@@ -44,6 +48,7 @@ export const CORE_TELEMETRY_EVENTS = {
AUTH_SUCCEEDED: "user.auth_succeeded",
AUTH_FAILED: "user.auth_failed",
AUTH_LOGGED_OUT: "user.auth_logged_out",
AUTH_REFRESH_SOFT_FAILURE: "user.auth_refresh_soft_failure",
PROVIDER_CONFIGURED: "user.provider_configured",
TELEMETRY_OPT_OUT: "user.opt_out",
},
@@ -67,6 +72,7 @@ export const CORE_TELEMETRY_EVENTS = {
SUBAGENT_COMPLETED: "task.subagent_completed",
COMPACTION_EXECUTED: "task.compaction_executed",
COMPACTION_SKIPPED: "task.compaction_skipped",
COMPACTION_BUDGET_EMERGENCY: "task.compaction_budget_emergency",
},
HOOKS: {
DISCOVERY_COMPLETED: "hooks.discovery_completed",
@@ -247,10 +253,40 @@ export function captureAuthLoggedOut(
telemetry: ITelemetryService | undefined,
provider?: string,
reason?: string,
details?: { status?: number; errorCode?: string },
): void {
emit(telemetry, CORE_TELEMETRY_EVENTS.USER.AUTH_LOGGED_OUT, {
provider,
reason,
status: details?.status,
errorCode: details?.errorCode,
});
}
/**
* Fires when a token refresh fails for a reason that does NOT invalidate the
* session (network error, timeout, 5xx) and stored credentials were kept.
* Before the transient-vs-invalid_grant fix, `tokenExpired: true` instances
* were misclassified as invalid grants and wiped stored credentials this
* event is the "prevented logout" counter for tracking that fix in
* production.
*/
export function captureAuthRefreshSoftFailure(
telemetry: ITelemetryService | undefined,
provider?: string,
details?: {
status?: number;
errorCode?: string;
errorName?: string;
tokenExpired?: boolean;
},
): void {
emit(telemetry, CORE_TELEMETRY_EVENTS.USER.AUTH_REFRESH_SOFT_FAILURE, {
provider,
status: details?.status,
errorCode: details?.errorCode,
errorName: details?.errorName,
tokenExpired: details?.tokenExpired,
});
}
@@ -693,3 +729,26 @@ export function captureCompactionSkipped(
timestamp: new Date().toISOString(),
});
}
export interface CaptureCompactionBudgetEmergencyProperties {
ulid: string;
strategy: TelemetryCompactionStrategy;
mode: TelemetryCompactionMode;
policyIntent: CoreCompactionBudgetPolicyIntent;
actionCount: number;
warningCount: number;
liveTailHandling: CoreCompactionLiveTailHandling;
provider?: string;
modelId?: string;
}
export function captureCompactionBudgetEmergency(
telemetry: ITelemetryService | undefined,
properties: CaptureCompactionBudgetEmergencyProperties &
Partial<TelemetryAgentIdentityProperties>,
): void {
emit(telemetry, CORE_TELEMETRY_EVENTS.TASK.COMPACTION_BUDGET_EMERGENCY, {
...properties,
timestamp: new Date().toISOString(),
});
}
+31
View File
@@ -73,8 +73,32 @@ export interface CoreCompactionContext {
utilizationRatio: number;
}
// Mirrors BudgetPolicyIntent in extensions/context/budget-projection/types.ts.
// Keep this public API type decoupled from the internal projection module.
export type CoreCompactionBudgetPolicyIntent =
| "agentic_summary"
| "basic_compaction_projection"
| "normal_provider_request";
// Mirrors LiveTailHandling in extensions/context/budget-projection/types.ts.
// Keep this public API type decoupled from the internal projection module.
export type CoreCompactionLiveTailHandling =
| "included_verbatim"
| "included_degraded"
| "summarized_as_context"
| "omitted_with_warning"
| "preserved_out_of_band";
export interface CoreCompactionBudgetMetadata {
policyIntent: CoreCompactionBudgetPolicyIntent;
actionCount: number;
warningCount: number;
liveTailHandling: CoreCompactionLiveTailHandling;
}
export interface CoreCompactionResult {
messages: MessageWithMetadata[];
budget?: CoreCompactionBudgetMetadata;
}
export interface CoreCompactionSummarizerConfig {
@@ -83,6 +107,13 @@ export interface CoreCompactionSummarizerConfig {
apiKey?: string;
baseUrl?: string;
headers?: Record<string, string>;
/**
* Optional pre-resolved model metadata for the summarizer. Supplying either
* this or `knownModels` lets agentic compaction budget summary input against
* the summarizer model's actual context window instead of falling back to the
* active model's window.
*/
modelInfo?: ModelInfo;
knownModels?: Record<string, ModelInfo>;
providerConfig?: ProviderConfig;
maxOutputTokens?: number;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@cline/llms",
"version": "0.0.59",
"version": "0.0.60",
"description": "Config-driven SDK for selecting, extending, and instantiating LLM providers and models",
"repository": {
"type": "git",
File diff suppressed because it is too large Load Diff

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