mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
feat: add session deletion with per-provider cleanup and path safety
Add delete_session Tauri command dispatching to provider-specific deletion logic for all 5 providers (Claude, Codex, Gemini, OpenCode, OpenClaw). Includes path traversal protection via canonicalize + starts_with validation, session ID verification against file contents, frontend confirmation dialog with optimistic cache updates, i18n keys (zh/en/ja), and component tests.
This commit is contained in:
+34
-4
@@ -4,9 +4,12 @@ import type { McpServer, Provider, Settings } from "@/types";
|
||||
import {
|
||||
addProvider,
|
||||
deleteProvider,
|
||||
deleteSession,
|
||||
getCurrentProviderId,
|
||||
getSessionMessages,
|
||||
getProviders,
|
||||
listProviders,
|
||||
listSessions,
|
||||
resetProviderState,
|
||||
setCurrentProviderId,
|
||||
updateProvider,
|
||||
@@ -37,7 +40,9 @@ const success = <T>(payload: T) => HttpResponse.json(payload as any);
|
||||
|
||||
export const handlers = [
|
||||
http.post(`${TAURI_ENDPOINT}/get_migration_result`, () => success(false)),
|
||||
http.post(`${TAURI_ENDPOINT}/get_skills_migration_result`, () => success(null)),
|
||||
http.post(`${TAURI_ENDPOINT}/get_skills_migration_result`, () =>
|
||||
success(null),
|
||||
),
|
||||
http.post(`${TAURI_ENDPOINT}/get_providers`, async ({ request }) => {
|
||||
const { app } = await withJson<{ app: AppId }>(request);
|
||||
return success(getProviders(app));
|
||||
@@ -105,6 +110,25 @@ export const handlers = [
|
||||
|
||||
http.post(`${TAURI_ENDPOINT}/open_external`, () => success(true)),
|
||||
|
||||
http.post(`${TAURI_ENDPOINT}/list_sessions`, () => success(listSessions())),
|
||||
|
||||
http.post(`${TAURI_ENDPOINT}/get_session_messages`, async ({ request }) => {
|
||||
const { providerId, sourcePath } = await withJson<{
|
||||
providerId: string;
|
||||
sourcePath: string;
|
||||
}>(request);
|
||||
return success(getSessionMessages(providerId, sourcePath));
|
||||
}),
|
||||
|
||||
http.post(`${TAURI_ENDPOINT}/delete_session`, async ({ request }) => {
|
||||
const { providerId, sessionId, sourcePath } = await withJson<{
|
||||
providerId: string;
|
||||
sessionId: string;
|
||||
sourcePath: string;
|
||||
}>(request);
|
||||
return success(deleteSession(providerId, sessionId, sourcePath));
|
||||
}),
|
||||
|
||||
// MCP APIs
|
||||
http.post(`${TAURI_ENDPOINT}/get_mcp_config`, async ({ request }) => {
|
||||
const { app } = await withJson<{ app: AppId }>(request);
|
||||
@@ -178,9 +202,13 @@ export const handlers = [
|
||||
},
|
||||
),
|
||||
|
||||
http.post(`${TAURI_ENDPOINT}/apply_claude_onboarding_skip`, () => success(true)),
|
||||
http.post(`${TAURI_ENDPOINT}/apply_claude_onboarding_skip`, () =>
|
||||
success(true),
|
||||
),
|
||||
|
||||
http.post(`${TAURI_ENDPOINT}/clear_claude_onboarding_skip`, () => success(true)),
|
||||
http.post(`${TAURI_ENDPOINT}/clear_claude_onboarding_skip`, () =>
|
||||
success(true),
|
||||
),
|
||||
|
||||
http.post(`${TAURI_ENDPOINT}/get_config_dir`, async ({ request }) => {
|
||||
const { app } = await withJson<{ app: AppId }>(request);
|
||||
@@ -280,7 +308,9 @@ export const handlers = [
|
||||
success([]),
|
||||
),
|
||||
http.post(`${TAURI_ENDPOINT}/add_to_failover_queue`, () => success(true)),
|
||||
http.post(`${TAURI_ENDPOINT}/remove_from_failover_queue`, () => success(true)),
|
||||
http.post(`${TAURI_ENDPOINT}/remove_from_failover_queue`, () =>
|
||||
success(true),
|
||||
),
|
||||
http.post(`${TAURI_ENDPOINT}/reorder_failover_queue`, () => success(true)),
|
||||
http.post(`${TAURI_ENDPOINT}/set_failover_item_enabled`, () => success(true)),
|
||||
|
||||
|
||||
+125
-5
@@ -1,5 +1,11 @@
|
||||
import type { AppId } from "@/lib/api/types";
|
||||
import type { McpServer, Provider, Settings } from "@/types";
|
||||
import type {
|
||||
McpServer,
|
||||
Provider,
|
||||
SessionMessage,
|
||||
SessionMeta,
|
||||
Settings,
|
||||
} from "@/types";
|
||||
|
||||
type ProvidersByApp = Record<AppId, Record<string, Provider>>;
|
||||
type CurrentProviderState = Record<AppId, string>;
|
||||
@@ -80,13 +86,69 @@ let settingsState: Settings = {
|
||||
language: "zh",
|
||||
};
|
||||
let appConfigDirOverride: string | null = null;
|
||||
const sessionMessageKey = (providerId: string, sourcePath: string) =>
|
||||
`${providerId}:${sourcePath}`;
|
||||
|
||||
const createDefaultSessions = (): SessionMeta[] => {
|
||||
const now = Date.now();
|
||||
return [
|
||||
{
|
||||
providerId: "codex",
|
||||
sessionId: "codex-session-1",
|
||||
title: "Codex Session One",
|
||||
summary: "Codex summary",
|
||||
projectDir: "/mock/codex",
|
||||
createdAt: now - 2000,
|
||||
lastActiveAt: now - 1000,
|
||||
sourcePath: "/mock/codex/session-1.jsonl",
|
||||
resumeCommand: "codex resume codex-session-1",
|
||||
},
|
||||
{
|
||||
providerId: "claude",
|
||||
sessionId: "claude-session-1",
|
||||
title: "Claude Session One",
|
||||
summary: "Claude summary",
|
||||
projectDir: "/mock/claude",
|
||||
createdAt: now - 4000,
|
||||
lastActiveAt: now - 3000,
|
||||
sourcePath: "/mock/claude/session-1.jsonl",
|
||||
resumeCommand: "claude --resume claude-session-1",
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const createDefaultSessionMessages = (): Record<string, SessionMessage[]> => ({
|
||||
[sessionMessageKey("codex", "/mock/codex/session-1.jsonl")]: [
|
||||
{
|
||||
role: "user",
|
||||
content: "First codex message",
|
||||
ts: Date.now() - 1000,
|
||||
},
|
||||
],
|
||||
[sessionMessageKey("claude", "/mock/claude/session-1.jsonl")]: [
|
||||
{
|
||||
role: "user",
|
||||
content: "First claude message",
|
||||
ts: Date.now() - 3000,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let sessionsState = createDefaultSessions();
|
||||
let sessionMessagesState = createDefaultSessionMessages();
|
||||
let mcpConfigs: McpConfigState = {
|
||||
claude: {
|
||||
sample: {
|
||||
id: "sample",
|
||||
name: "Sample Claude Server",
|
||||
enabled: true,
|
||||
apps: { claude: true, codex: false, gemini: false, opencode: false, openclaw: false },
|
||||
apps: {
|
||||
claude: true,
|
||||
codex: false,
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
openclaw: false,
|
||||
},
|
||||
server: {
|
||||
type: "stdio",
|
||||
command: "claude-server",
|
||||
@@ -98,7 +160,13 @@ let mcpConfigs: McpConfigState = {
|
||||
id: "httpServer",
|
||||
name: "HTTP Codex Server",
|
||||
enabled: false,
|
||||
apps: { claude: false, codex: true, gemini: false, opencode: false, openclaw: false },
|
||||
apps: {
|
||||
claude: false,
|
||||
codex: true,
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
openclaw: false,
|
||||
},
|
||||
server: {
|
||||
type: "http",
|
||||
url: "http://localhost:3000",
|
||||
@@ -116,6 +184,8 @@ const cloneProviders = (value: ProvidersByApp) =>
|
||||
export const resetProviderState = () => {
|
||||
providers = createDefaultProviders();
|
||||
current = createDefaultCurrent();
|
||||
sessionsState = createDefaultSessions();
|
||||
sessionMessagesState = createDefaultSessionMessages();
|
||||
settingsState = {
|
||||
showInTray: true,
|
||||
minimizeToTrayOnClose: true,
|
||||
@@ -131,7 +201,13 @@ export const resetProviderState = () => {
|
||||
id: "sample",
|
||||
name: "Sample Claude Server",
|
||||
enabled: true,
|
||||
apps: { claude: true, codex: false, gemini: false, opencode: false, openclaw: false },
|
||||
apps: {
|
||||
claude: true,
|
||||
codex: false,
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
openclaw: false,
|
||||
},
|
||||
server: {
|
||||
type: "stdio",
|
||||
command: "claude-server",
|
||||
@@ -143,7 +219,13 @@ export const resetProviderState = () => {
|
||||
id: "httpServer",
|
||||
name: "HTTP Codex Server",
|
||||
enabled: false,
|
||||
apps: { claude: false, codex: true, gemini: false, opencode: false, openclaw: false },
|
||||
apps: {
|
||||
claude: false,
|
||||
codex: true,
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
openclaw: false,
|
||||
},
|
||||
server: {
|
||||
type: "http",
|
||||
url: "http://localhost:3000",
|
||||
@@ -285,3 +367,41 @@ export const deleteMcpServer = (appType: AppId, id: string) => {
|
||||
if (!mcpConfigs[appType]) return;
|
||||
delete mcpConfigs[appType][id];
|
||||
};
|
||||
|
||||
export const listSessions = () =>
|
||||
JSON.parse(JSON.stringify(sessionsState)) as SessionMeta[];
|
||||
|
||||
export const getSessionMessages = (providerId: string, sourcePath: string) =>
|
||||
JSON.parse(
|
||||
JSON.stringify(
|
||||
sessionMessagesState[sessionMessageKey(providerId, sourcePath)] ?? [],
|
||||
),
|
||||
) as SessionMessage[];
|
||||
|
||||
export const deleteSession = (
|
||||
providerId: string,
|
||||
sessionId: string,
|
||||
sourcePath: string,
|
||||
) => {
|
||||
sessionsState = sessionsState.filter(
|
||||
(session) =>
|
||||
!(
|
||||
session.providerId === providerId &&
|
||||
session.sessionId === sessionId &&
|
||||
session.sourcePath === sourcePath
|
||||
),
|
||||
);
|
||||
delete sessionMessagesState[sessionMessageKey(providerId, sourcePath)];
|
||||
return true;
|
||||
};
|
||||
|
||||
export const setSessionFixtures = (
|
||||
sessions: SessionMeta[],
|
||||
messages: Record<string, SessionMessage[]>,
|
||||
) => {
|
||||
sessionsState = JSON.parse(JSON.stringify(sessions)) as SessionMeta[];
|
||||
sessionMessagesState = JSON.parse(JSON.stringify(messages)) as Record<
|
||||
string,
|
||||
SessionMessage[]
|
||||
>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user