diff --git a/apps/desktop/src/main/lib/agent-manager/utils/anthropic/auth/auth.ts b/apps/desktop/src/main/lib/agent-manager/utils/anthropic/auth/auth.ts index 3a77a560b1..2ebda025d8 100644 --- a/apps/desktop/src/main/lib/agent-manager/utils/anthropic/auth/auth.ts +++ b/apps/desktop/src/main/lib/agent-manager/utils/anthropic/auth/auth.ts @@ -9,7 +9,7 @@ import { execSync } from "node:child_process"; import { existsSync, readFileSync } from "node:fs"; import { homedir, platform } from "node:os"; -import { delimiter, join } from "node:path"; +import { join } from "node:path"; interface ClaudeCredentials { apiKey: string; @@ -135,77 +135,3 @@ export function getCredentialsFromKeychain(): ClaudeCredentials | null { return null; } - -/** - * Get existing Claude credentials from any available source. - * - * Priority: - * 1. Config file (~/.claude.json, ~/.config/claude/credentials.json) - * 2. macOS Keychain - * - * Note: Environment variables are intentionally NOT checked — the desktop app - * must never read ANTHROPIC_API_KEY or OPENAI_API_KEY from process.env. - */ -export function getExistingClaudeCredentials(): ClaudeCredentials | null { - // 1. Check config file - const configCredentials = getCredentialsFromConfig(); - if (configCredentials) { - return configCredentials; - } - - // 2. Check macOS Keychain - const keychainCredentials = getCredentialsFromKeychain(); - if (keychainCredentials) { - return keychainCredentials; - } - - console.warn("[claude/auth] No Claude credentials found"); - return null; -} - -/** Keys that must never leak into spawned processes. */ -const STRIPPED_ENV_KEYS = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"]; - -/** - * Build environment variables for running Claude CLI. - * - * OAuth credentials are handled by the binary itself (from ~/.claude/.credentials.json). - * ANTHROPIC_API_KEY and OPENAI_API_KEY are explicitly stripped to prevent leakage. - */ -export function buildClaudeEnv(): Record { - const env: Record = { - ...process.env, - } as Record; - - // Strip secret API keys — the desktop app uses OAuth only - for (const key of STRIPPED_ENV_KEYS) { - delete env[key]; - } - - // Ensure PATH includes common binary locations (non-Windows only) - if (platform() !== "win32") { - const pathAdditions = ["/usr/local/bin", "/opt/homebrew/bin", "/usr/bin"]; - const currentPath = env.PATH || ""; - const pathParts = currentPath.split(delimiter); - - for (const addition of pathAdditions) { - if (!pathParts.includes(addition)) { - pathParts.push(addition); - } - } - - env.PATH = pathParts.join(delimiter); - } - - // Mark as SDK entry (like 1code does) - env.CLAUDE_CODE_ENTRYPOINT = "sdk-ts"; - - return env; -} - -/** - * Check if Claude credentials are available. - */ -export function hasClaudeCredentials(): boolean { - return getExistingClaudeCredentials() !== null; -} diff --git a/apps/desktop/src/main/lib/agent-manager/utils/anthropic/auth/index.ts b/apps/desktop/src/main/lib/agent-manager/utils/anthropic/auth/index.ts index 3852140f5f..c63232c274 100644 --- a/apps/desktop/src/main/lib/agent-manager/utils/anthropic/auth/index.ts +++ b/apps/desktop/src/main/lib/agent-manager/utils/anthropic/auth/index.ts @@ -1,5 +1,4 @@ export { - buildClaudeEnv, getCredentialsFromConfig, getCredentialsFromKeychain, } from "./auth"; diff --git a/packages/durable-session/src/react/index.ts b/packages/durable-session/src/react/index.ts index 72db894d58..ba4d2d6ed8 100644 --- a/packages/durable-session/src/react/index.ts +++ b/packages/durable-session/src/react/index.ts @@ -1,10 +1,3 @@ -export { - type ChatAgentPresence, - type ChatUserPresence, - type UseChatPresenceOptions, - type UseChatPresenceReturn, - useChatPresence, -} from "./useChatPresence"; export { type ModelOption, type SessionConfig, diff --git a/packages/durable-session/src/react/useChatPresence/index.ts b/packages/durable-session/src/react/useChatPresence/index.ts deleted file mode 100644 index c8f2c9e0ea..0000000000 --- a/packages/durable-session/src/react/useChatPresence/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { - type ChatAgentPresence, - type ChatUserPresence, - type UseChatPresenceOptions, - type UseChatPresenceReturn, - useChatPresence, -} from "./useChatPresence"; diff --git a/packages/durable-session/src/react/useChatPresence/useChatPresence.ts b/packages/durable-session/src/react/useChatPresence/useChatPresence.ts deleted file mode 100644 index 2c45850604..0000000000 --- a/packages/durable-session/src/react/useChatPresence/useChatPresence.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { useCallback, useSyncExternalStore } from "react"; -import type { AgentValue, RawPresenceRow } from "../../schema"; -import type { SessionDB } from "../../session-db/session-db"; - -export interface ChatUserPresence { - userId: string; - deviceId: string; - name?: string; - status: "active" | "idle" | "typing" | "offline"; - lastSeenAt: string; - draft?: string; - cursorPosition?: number; -} - -export interface ChatAgentPresence { - agentId: string; - name?: string; - endpoint: string; - triggers?: "all" | "user-messages"; - model?: string; - generationMessageId?: string; -} - -export interface UseChatPresenceOptions { - sessionDB: SessionDB; - proxyUrl?: string; - sessionId?: string; - headers?: Record; -} - -export interface UseChatPresenceReturn { - users: ChatUserPresence[]; - agents: ChatAgentPresence[]; - updateStatus: ( - userId: string, - deviceId: string, - status: ChatUserPresence["status"], - ) => void; - updateDraft: ( - userId: string, - deviceId: string, - text: string, - cursorPosition?: number, - ) => void; - drafts: Array<{ userId: string; name?: string; text: string }>; -} - -export function useChatPresence( - options: UseChatPresenceOptions, -): UseChatPresenceReturn { - const { sessionDB, proxyUrl, sessionId, headers } = options; - - const subscribeToPresence = useCallback( - (callback: () => void) => { - const subscription = sessionDB.collections.presence.subscribeChanges(() => - callback(), - ); - return () => subscription.unsubscribe(); - }, - [sessionDB], - ); - - const getPresenceSnapshot = useCallback((): ChatUserPresence[] => { - const rows = Array.from( - sessionDB.collections.presence.values(), - ) as RawPresenceRow[]; - return rows - .filter((r) => r.status !== "offline") - .map((r) => ({ - userId: r.userId, - deviceId: r.deviceId, - name: r.name, - status: r.status, - lastSeenAt: r.lastSeenAt, - draft: r.draft, - cursorPosition: r.cursorPosition, - })); - }, [sessionDB]); - - const subscribeToAgents = useCallback( - (callback: () => void) => { - const subscription = sessionDB.collections.agents.subscribeChanges(() => - callback(), - ); - return () => subscription.unsubscribe(); - }, - [sessionDB], - ); - - const getAgentsSnapshot = useCallback((): ChatAgentPresence[] => { - const rows = Array.from( - sessionDB.collections.agents.values(), - ) as AgentValue[]; - return rows.map((r) => ({ - agentId: r.agentId, - name: r.name, - endpoint: r.endpoint, - triggers: r.triggers, - model: r.model, - generationMessageId: r.generationMessageId, - })); - }, [sessionDB]); - - const users = useSyncExternalStore( - subscribeToPresence, - getPresenceSnapshot, - () => [], - ); - const agents = useSyncExternalStore( - subscribeToAgents, - getAgentsSnapshot, - () => [], - ); - - const updateStatus = useCallback( - (userId: string, deviceId: string, status: ChatUserPresence["status"]) => { - if (!proxyUrl || !sessionId) return; - const endpoint = status === "offline" ? "logout" : "login"; - fetch(`${proxyUrl}/v1/sessions/${sessionId}/${endpoint}`, { - method: "POST", - headers: { "Content-Type": "application/json", ...headers }, - body: JSON.stringify({ userId, deviceId, status }), - credentials: "include", - }).catch(console.error); - }, - [proxyUrl, sessionId, headers], - ); - - const updateDraft = useCallback( - ( - userId: string, - deviceId: string, - text: string, - cursorPosition?: number, - ) => { - if (!proxyUrl || !sessionId) return; - fetch(`${proxyUrl}/v1/sessions/${sessionId}/login`, { - method: "POST", - headers: { "Content-Type": "application/json", ...headers }, - body: JSON.stringify({ - userId, - deviceId, - status: "typing", - draft: text, - cursorPosition, - }), - credentials: "include", - }).catch(console.error); - }, - [proxyUrl, sessionId, headers], - ); - - const drafts = users - .filter((u) => u.draft && u.draft.length > 0) - .map((u) => ({ userId: u.userId, name: u.name, text: u.draft as string })); - - return { users, agents, updateStatus, updateDraft, drafts }; -} diff --git a/packages/local-db/src/schema/schema.ts b/packages/local-db/src/schema/schema.ts index bfef32f1e8..ff4a1f5546 100644 --- a/packages/local-db/src/schema/schema.ts +++ b/packages/local-db/src/schema/schema.ts @@ -169,19 +169,8 @@ export const settings = sqliteTable("settings", { terminalFontSize: integer("terminal_font_size"), editorFontFamily: text("editor_font_family"), editorFontSize: integer("editor_font_size"), - oauthCredentials: text("oauth_credentials", { mode: "json" }).$type< - Record - >(), }); -export interface OAuthCredentialEntry { - accessToken: string; - refreshToken?: string; - expiresAt?: number; - scope?: string; - tokenType?: string; -} - export type InsertSettings = typeof settings.$inferInsert; export type SelectSettings = typeof settings.$inferSelect;