mirror of
https://github.com/cline/cline.git
synced 2026-09-05 05:02:27 +08:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 18e2bdf194 | |||
| b83c9ec240 | |||
| 711a8c6ec0 | |||
| d68badae38 | |||
| d67c403392 | |||
| 02f405f6e2 | |||
| b4ed8a226e | |||
| cbf40961db | |||
| 2d05ba52da | |||
| 5d3778b5cf | |||
| 8d0eb54a1f | |||
| a3989acc38 | |||
| d199b1bff9 | |||
| d41eed1198 | |||
| 6309971089 | |||
| 2d2c669421 | |||
| c5f146a418 | |||
| 261ee4c313 | |||
| d45b051c04 | |||
| 78c83cdf33 | |||
| 3266121fa1 | |||
| 6467de65a2 | |||
| 65fe885638 | |||
| c3033d6f13 | |||
| cfb1327a1b | |||
| 264af96e1b | |||
| 10cb9bd97a |
Vendored
+1
-1
@@ -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]+/"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -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,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": {
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(() => ({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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",
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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")
|
||||
|
||||
+6
-2
@@ -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())
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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=="],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@cline/agents",
|
||||
"version": "0.0.59",
|
||||
"version": "0.0.60",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cline/cline",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,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
Reference in New Issue
Block a user