From 2b828bd138e36718cab8008f149250334b1ba2b2 Mon Sep 17 00:00:00 2001 From: Ramnique Singh <30795890+ramnique@users.noreply.github.com> Date: Wed, 21 Jan 2026 23:24:42 +0530 Subject: [PATCH 01/10] remove curl from default allow-list --- apps/x/packages/core/src/config/security.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/x/packages/core/src/config/security.ts b/apps/x/packages/core/src/config/security.ts index c2b4f9fb..9419e76e 100644 --- a/apps/x/packages/core/src/config/security.ts +++ b/apps/x/packages/core/src/config/security.ts @@ -6,7 +6,6 @@ export const SECURITY_CONFIG_PATH = path.join(WorkDir, "config", "security.json" const DEFAULT_ALLOW_LIST = [ "cat", - "curl", "date", "echo", "grep", From 2d5a5fc8f7b4cb888375a8c711d168d4e5fdbc99 Mon Sep 17 00:00:00 2001 From: tusharmagar Date: Mon, 26 Jan 2026 01:51:48 +0530 Subject: [PATCH 02/10] add close button from settings dialog --- apps/x/apps/renderer/src/components/settings-dialog.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/x/apps/renderer/src/components/settings-dialog.tsx b/apps/x/apps/renderer/src/components/settings-dialog.tsx index ac5a24bf..b60ec736 100644 --- a/apps/x/apps/renderer/src/components/settings-dialog.tsx +++ b/apps/x/apps/renderer/src/components/settings-dialog.tsx @@ -137,7 +137,6 @@ export function SettingsDialog({ children }: SettingsDialogProps) { {children}
{/* Sidebar */} From 1b76d5a4866cc4cfa94e0721efa0ff4d3c557fa7 Mon Sep 17 00:00:00 2001 From: tusharmagar Date: Mon, 26 Jan 2026 01:56:43 +0530 Subject: [PATCH 03/10] add auto-focus feature to chat input and prompt textarea --- apps/x/apps/renderer/src/App.tsx | 1 + .../src/components/ai-elements/prompt-input.tsx | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index 3ac0b24f..ad3ee534 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -322,6 +322,7 @@ function ChatInputInner({ placeholder="Type your message..." disabled={isProcessing} onKeyDown={handleKeyDown} + autoFocus className="min-h-6 py-0 border-0 shadow-none focus-visible:ring-0 rounded-none" />
+ ) } diff --git a/apps/x/apps/renderer/src/components/google-client-id-modal.tsx b/apps/x/apps/renderer/src/components/google-client-id-modal.tsx new file mode 100644 index 00000000..5727d506 --- /dev/null +++ b/apps/x/apps/renderer/src/components/google-client-id-modal.tsx @@ -0,0 +1,85 @@ +"use client" + +import { useEffect, useState } from "react" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" + +interface GoogleClientIdModalProps { + open: boolean + onOpenChange: (open: boolean) => void + onSubmit: (clientId: string) => void + isSubmitting?: boolean +} + +export function GoogleClientIdModal({ + open, + onOpenChange, + onSubmit, + isSubmitting = false, +}: GoogleClientIdModalProps) { + const [clientId, setClientId] = useState("") + + useEffect(() => { + if (!open) { + setClientId("") + } + }, [open]) + + const trimmedClientId = clientId.trim() + const isValid = trimmedClientId.length > 0 + + const handleSubmit = () => { + if (!isValid || isSubmitting) return + onSubmit(trimmedClientId) + } + + return ( + + + + Enter Google Client ID + + This app does not store the client ID. You will be prompted each session. + + +
+ + setClientId(event.target.value)} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault() + handleSubmit() + } + }} + autoFocus + /> +
+
+ + +
+
+
+ ) +} diff --git a/apps/x/apps/renderer/src/components/onboarding-modal.tsx b/apps/x/apps/renderer/src/components/onboarding-modal.tsx index 48119934..e33654eb 100644 --- a/apps/x/apps/renderer/src/components/onboarding-modal.tsx +++ b/apps/x/apps/renderer/src/components/onboarding-modal.tsx @@ -14,6 +14,8 @@ import { import { Button } from "@/components/ui/button" import { Switch } from "@/components/ui/switch" import { cn } from "@/lib/utils" +import { GoogleClientIdModal } from "@/components/google-client-id-modal" +import { getGoogleClientId, setGoogleClientId } from "@/lib/google-client-id-store" import { toast } from "sonner" interface ProviderState { @@ -36,6 +38,7 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { const [providers, setProviders] = useState([]) const [providersLoading, setProvidersLoading] = useState(true) const [providerStates, setProviderStates] = useState>({}) + const [googleClientIdOpen, setGoogleClientIdOpen] = useState(false) // Granola state const [granolaEnabled, setGranolaEnabled] = useState(false) @@ -159,15 +162,14 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { return cleanup }, []) - // Connect to a provider - const handleConnect = useCallback(async (provider: string) => { + const startConnect = useCallback(async (provider: string, clientId?: string) => { setProviderStates(prev => ({ ...prev, [provider]: { ...prev[provider], isConnecting: true } })) try { - const result = await window.ipc.invoke('oauth:connect', { provider }) + const result = await window.ipc.invoke('oauth:connect', { provider, clientId }) if (!result.success) { toast.error(result.error || `Failed to connect to ${provider}`) @@ -186,6 +188,27 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { } }, []) + // Connect to a provider + const handleConnect = useCallback(async (provider: string) => { + if (provider === 'google') { + const existingClientId = getGoogleClientId() + if (!existingClientId) { + setGoogleClientIdOpen(true) + return + } + await startConnect(provider, existingClientId) + return + } + + await startConnect(provider) + }, [startConnect]) + + const handleGoogleClientIdSubmit = useCallback((clientId: string) => { + setGoogleClientId(clientId) + setGoogleClientIdOpen(false) + startConnect('google', clientId) + }, [startConnect]) + const handleNext = () => { if (currentStep < 2) { setCurrentStep((prev) => (prev + 1) as Step) @@ -429,6 +452,13 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { } return ( + <> + {}}> } + ) } diff --git a/apps/x/apps/renderer/src/hooks/useOAuth.ts b/apps/x/apps/renderer/src/hooks/useOAuth.ts index b2777c6c..f23f5975 100644 --- a/apps/x/apps/renderer/src/hooks/useOAuth.ts +++ b/apps/x/apps/renderer/src/hooks/useOAuth.ts @@ -50,10 +50,10 @@ export function useOAuth(provider: string) { return cleanup; }, [provider, checkConnection]); - const connect = useCallback(async () => { + const connect = useCallback(async (clientId?: string) => { try { setIsConnecting(true); - const result = await window.ipc.invoke('oauth:connect', { provider }); + const result = await window.ipc.invoke('oauth:connect', { provider, clientId }); if (result.success) { // OAuth flow started - keep isConnecting state, wait for event // Event listener will handle the actual completion diff --git a/apps/x/apps/renderer/src/lib/google-client-id-store.ts b/apps/x/apps/renderer/src/lib/google-client-id-store.ts new file mode 100644 index 00000000..78898325 --- /dev/null +++ b/apps/x/apps/renderer/src/lib/google-client-id-store.ts @@ -0,0 +1,17 @@ +let googleClientId: string | null = null; + +export function getGoogleClientId(): string | null { + return googleClientId; +} + +export function setGoogleClientId(clientId: string): void { + const trimmed = clientId.trim(); + if (!trimmed) { + return; + } + googleClientId = trimmed; +} + +export function clearGoogleClientId(): void { + googleClientId = null; +} diff --git a/apps/x/packages/core/src/auth/provider-client-id.ts b/apps/x/packages/core/src/auth/provider-client-id.ts new file mode 100644 index 00000000..6f8f6a90 --- /dev/null +++ b/apps/x/packages/core/src/auth/provider-client-id.ts @@ -0,0 +1,23 @@ +type ProviderClientIdOverrides = Map; + +const providerClientIdOverrides: ProviderClientIdOverrides = new Map(); + +export function setProviderClientIdOverride(provider: string, clientId: string): void { + const trimmed = clientId.trim(); + if (!trimmed) { + return; + } + providerClientIdOverrides.set(provider, trimmed); +} + +export function getProviderClientIdOverride(provider: string): string | undefined { + return providerClientIdOverrides.get(provider); +} + +export function hasProviderClientIdOverride(provider: string): boolean { + return providerClientIdOverrides.has(provider); +} + +export function clearProviderClientIdOverride(provider: string): void { + providerClientIdOverrides.delete(provider); +} diff --git a/apps/x/packages/core/src/auth/providers.ts b/apps/x/packages/core/src/auth/providers.ts index 1cf703d6..bb4bdd9f 100644 --- a/apps/x/packages/core/src/auth/providers.ts +++ b/apps/x/packages/core/src/auth/providers.ts @@ -22,7 +22,7 @@ const DiscoverySchema = z.discriminatedUnion('mode', [ const ClientSchema = z.discriminatedUnion('mode', [ z.object({ mode: z.literal('static'), - clientId: z.string().min(1), + clientId: z.string().min(1).optional(), }), z.object({ mode: z.literal('dcr'), @@ -58,7 +58,6 @@ const providerConfigs: ProviderConfig = { }, client: { mode: 'static', - clientId: '797410052581-ibmmvqec0l68stv5fmgh0juqfvbg08fc.apps.googleusercontent.com', }, scopes: [ 'https://www.googleapis.com/auth/gmail.readonly', diff --git a/apps/x/packages/core/src/knowledge/fireflies-client-factory.ts b/apps/x/packages/core/src/knowledge/fireflies-client-factory.ts index a56d39b5..d8c975c7 100644 --- a/apps/x/packages/core/src/knowledge/fireflies-client-factory.ts +++ b/apps/x/packages/core/src/knowledge/fireflies-client-factory.ts @@ -144,9 +144,13 @@ export class FirefliesClientFactory { if (providerConfig.client.mode === 'static') { // Discover endpoints, use static client ID console.log(`[Fireflies] Discovery mode: issuer with static client ID`); + const clientId = providerConfig.client.clientId; + if (!clientId) { + throw new Error('Fireflies client ID not configured.'); + } this.cache.config = await oauthClient.discoverConfiguration( providerConfig.discovery.issuer, - providerConfig.client.clientId + clientId ); } else { // DCR mode - need existing registration @@ -170,10 +174,14 @@ export class FirefliesClientFactory { } console.log(`[Fireflies] Using static endpoints (no discovery)`); + const clientId = providerConfig.client.clientId; + if (!clientId) { + throw new Error('Fireflies client ID not configured.'); + } this.cache.config = oauthClient.createStaticConfiguration( providerConfig.discovery.authorizationEndpoint, providerConfig.discovery.tokenEndpoint, - providerConfig.client.clientId, + clientId, providerConfig.discovery.revocationEndpoint ); } diff --git a/apps/x/packages/core/src/knowledge/google-client-factory.ts b/apps/x/packages/core/src/knowledge/google-client-factory.ts index f32f492c..9a1240fb 100644 --- a/apps/x/packages/core/src/knowledge/google-client-factory.ts +++ b/apps/x/packages/core/src/knowledge/google-client-factory.ts @@ -3,6 +3,7 @@ import container from '../di/container.js'; import { IOAuthRepo } from '../auth/repo.js'; import { IClientRegistrationRepo } from '../auth/client-repo.js'; import { getProviderConfig } from '../auth/providers.js'; +import { getProviderClientIdOverride } from '../auth/provider-client-id.js'; import * as oauthClient from '../auth/oauth-client.js'; import type { Configuration } from '../auth/oauth-client.js'; import { OAuthTokens } from '../auth/types.js'; @@ -17,12 +18,22 @@ export class GoogleClientFactory { config: Configuration | null; client: OAuth2Client | null; tokens: OAuthTokens | null; + clientId: string | null; } = { config: null, client: null, tokens: null, + clientId: null, }; + private static resolveClientId(): string { + const override = getProviderClientIdOverride(this.PROVIDER_NAME); + if (!override) { + throw new Error('Google client ID not provided for this session.'); + } + return override; + } + /** * Get or create OAuth2Client, reusing cached instance when possible */ @@ -36,7 +47,13 @@ export class GoogleClientFactory { } // Initialize config cache if needed - await this.initializeConfigCache(); + try { + await this.initializeConfigCache(); + } catch (error) { + console.error("[OAuth] Failed to initialize Google OAuth configuration:", error); + this.clearCache(); + return null; + } if (!this.cache.config) { return null; } @@ -95,6 +112,10 @@ export class GoogleClientFactory { return false; } + if (!getProviderClientIdOverride(this.PROVIDER_NAME)) { + return false; + } + const tokens = await oauthRepo.getTokens(this.PROVIDER_NAME); if (!tokens) { return false; @@ -116,14 +137,21 @@ export class GoogleClientFactory { this.cache.config = null; this.cache.client = null; this.cache.tokens = null; + this.cache.clientId = null; } /** * Initialize cached configuration (called once) */ private static async initializeConfigCache(): Promise { - if (this.cache.config) { - return; // Already initialized + const clientId = this.resolveClientId(); + + if (this.cache.config && this.cache.clientId === clientId) { + return; // Already initialized for this client ID + } + + if (this.cache.clientId && this.cache.clientId !== clientId) { + this.clearCache(); } console.log(`[OAuth] Initializing Google OAuth configuration...`); @@ -135,7 +163,7 @@ export class GoogleClientFactory { console.log(`[OAuth] Discovery mode: issuer with static client ID`); this.cache.config = await oauthClient.discoverConfiguration( providerConfig.discovery.issuer, - providerConfig.client.clientId + clientId ); } else { // DCR mode - need existing registration @@ -162,11 +190,12 @@ export class GoogleClientFactory { this.cache.config = oauthClient.createStaticConfiguration( providerConfig.discovery.authorizationEndpoint, providerConfig.discovery.tokenEndpoint, - providerConfig.client.clientId, + clientId, providerConfig.discovery.revocationEndpoint ); } + this.cache.clientId = clientId; console.log(`[OAuth] Google OAuth configuration initialized`); } @@ -174,17 +203,7 @@ export class GoogleClientFactory { * Create OAuth2Client from OAuthTokens */ private static createClientFromTokens(tokens: OAuthTokens): OAuth2Client { - const providerConfig = getProviderConfig(this.PROVIDER_NAME); - - // Get client ID from config - let clientId: string; - if (providerConfig.client.mode === 'static') { - clientId = providerConfig.client.clientId; - } else { - // For DCR, we'd need to look up the registered client ID - // This is a fallback - normally initializeConfigCache handles this - throw new Error('Cannot create client without static client ID'); - } + const clientId = this.resolveClientId(); // Create OAuth2Client directly (PKCE flow doesn't use client secret) const client = new OAuth2Client( diff --git a/apps/x/packages/shared/src/ipc.ts b/apps/x/packages/shared/src/ipc.ts index 93b797a9..28689dde 100644 --- a/apps/x/packages/shared/src/ipc.ts +++ b/apps/x/packages/shared/src/ipc.ts @@ -175,6 +175,7 @@ const ipcSchemas = { 'oauth:connect': { req: z.object({ provider: z.string(), + clientId: z.string().optional(), }), res: z.object({ success: z.boolean(), From 82a68e0acc1f827725d46343ae5c5708f53f682e Mon Sep 17 00:00:00 2001 From: Ramnique Singh <30795890+ramnique@users.noreply.github.com> Date: Tue, 27 Jan 2026 07:20:27 +0530 Subject: [PATCH 05/10] graph: build 1-by-1 --- apps/x/packages/core/src/knowledge/note_creation_high.ts | 7 +++++++ apps/x/packages/core/src/knowledge/note_creation_low.ts | 7 +++++++ apps/x/packages/core/src/knowledge/note_creation_medium.ts | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/apps/x/packages/core/src/knowledge/note_creation_high.ts b/apps/x/packages/core/src/knowledge/note_creation_high.ts index 9363206c..81d14dcf 100644 --- a/apps/x/packages/core/src/knowledge/note_creation_high.ts +++ b/apps/x/packages/core/src/knowledge/note_creation_high.ts @@ -997,6 +997,12 @@ If new info contradicts existing: ## 9a: Meetings — Create and Update Notes +**IMPORTANT: Write sequentially, one file at a time.** +- Generate content for exactly one note. +- Issue exactly one \`write\` command. +- Wait for the tool to return before generating the next note. +- Do NOT batch multiple \`write\` commands in a single response. + **For new entities (meetings only):** \`\`\`bash executeCommand("write '{knowledge_folder}/People/Jennifer.md' '{content}'") @@ -1103,6 +1109,7 @@ If you discovered new name variants during resolution, add them to Aliases field - Be concise: one line per activity entry - Note state changes with \`[Field → value]\` in activity - Escape quotes properly in shell commands +- Write only one file per response (no multi-file write batches) --- diff --git a/apps/x/packages/core/src/knowledge/note_creation_low.ts b/apps/x/packages/core/src/knowledge/note_creation_low.ts index 02eae480..ac8239f9 100644 --- a/apps/x/packages/core/src/knowledge/note_creation_low.ts +++ b/apps/x/packages/core/src/knowledge/note_creation_low.ts @@ -553,6 +553,12 @@ Before writing: ## 9a: Create and Update Notes +**IMPORTANT: Write sequentially, one file at a time.** +- Generate content for exactly one note. +- Issue exactly one \`write\` command. +- Wait for the tool to return before generating the next note. +- Do NOT batch multiple \`write\` commands in a single response. + **For new entities:** \`\`\`bash executeCommand("write '{knowledge_folder}/People/Jennifer.md' '{content}'") @@ -579,6 +585,7 @@ Add newly discovered name variants to Aliases field. - Use YYYY-MM-DD format for dates - Be concise: one line per activity entry - Escape quotes properly in shell commands +- Write only one file per response (no multi-file write batches) --- diff --git a/apps/x/packages/core/src/knowledge/note_creation_medium.ts b/apps/x/packages/core/src/knowledge/note_creation_medium.ts index 297e19fc..746d4fac 100644 --- a/apps/x/packages/core/src/knowledge/note_creation_medium.ts +++ b/apps/x/packages/core/src/knowledge/note_creation_medium.ts @@ -906,6 +906,12 @@ If new info contradicts existing: ## 9a: Create and Update Notes +**IMPORTANT: Write sequentially, one file at a time.** +- Generate content for exactly one note. +- Issue exactly one \`write\` command. +- Wait for the tool to return before generating the next note. +- Do NOT batch multiple \`write\` commands in a single response. + **For new entities (meetings and qualifying emails):** \`\`\`bash executeCommand("write '{knowledge_folder}/People/Jennifer.md' '{content}'") @@ -941,6 +947,7 @@ If you discovered new name variants during resolution, add them to Aliases field - Be concise: one line per activity entry - Note state changes with \`[Field → value]\` in activity - Escape quotes properly in shell commands +- Write only one file per response (no multi-file write batches) --- From 4d2fc01f88f574bd6f828d961e1bd03e52d3d9c4 Mon Sep 17 00:00:00 2001 From: tusharmagar Date: Tue, 27 Jan 2026 08:27:06 +0530 Subject: [PATCH 06/10] Enhance chat input and sidebar components with runId support and improved auto-focus behavior --- apps/x/apps/renderer/src/App.tsx | 21 ++++++++++++++++--- .../components/ai-elements/prompt-input.tsx | 8 ++++--- .../renderer/src/components/chat-sidebar.tsx | 15 ++++++++----- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index ad3ee534..884e4e95 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -282,6 +282,7 @@ interface ChatInputInnerProps { isProcessing: boolean presetMessage?: string onPresetMessageConsumed?: () => void + runId?: string | null } function ChatInputInner({ @@ -289,6 +290,7 @@ function ChatInputInner({ isProcessing, presetMessage, onPresetMessageConsumed, + runId, }: ChatInputInnerProps) { const controller = usePromptInputController() const message = controller.textInput.value @@ -320,9 +322,9 @@ function ChatInputInner({
diff --git a/apps/x/apps/renderer/src/components/ai-elements/prompt-input.tsx b/apps/x/apps/renderer/src/components/ai-elements/prompt-input.tsx index f0933d30..c27ab5c3 100644 --- a/apps/x/apps/renderer/src/components/ai-elements/prompt-input.tsx +++ b/apps/x/apps/renderer/src/components/ai-elements/prompt-input.tsx @@ -906,6 +906,7 @@ export type PromptInputTextareaProps = ComponentProps< typeof InputGroupTextarea > & { autoFocus?: boolean; + focusTrigger?: unknown; // When this value changes, focus the textarea }; export const PromptInputTextarea = ({ @@ -914,6 +915,7 @@ export const PromptInputTextarea = ({ placeholder = "What would you like to know?", onKeyDown: externalOnKeyDown, autoFocus = false, + focusTrigger, ...props }: PromptInputTextareaProps) => { const controller = useOptionalPromptInputController(); @@ -924,16 +926,16 @@ export const PromptInputTextarea = ({ const textareaRef = useRef(null); - // Auto-focus the textarea when requested + // Auto-focus the textarea when requested or when focusTrigger changes useEffect(() => { - if (autoFocus) { + if (autoFocus || focusTrigger !== undefined) { // Small delay to ensure the element is fully mounted and visible const timer = setTimeout(() => { textareaRef.current?.focus(); }, 50); return () => clearTimeout(timer); } - }, [autoFocus]); + }, [autoFocus, focusTrigger]); const containerRef = useRef(null); const highlightRef = useRef(null); diff --git a/apps/x/apps/renderer/src/components/chat-sidebar.tsx b/apps/x/apps/renderer/src/components/chat-sidebar.tsx index 0d9a7d7e..4cddfdf0 100644 --- a/apps/x/apps/renderer/src/components/chat-sidebar.tsx +++ b/apps/x/apps/renderer/src/components/chat-sidebar.tsx @@ -260,10 +260,16 @@ export function ChatSidebar({ document.addEventListener('mouseup', handleMouseUp) }, [width]) - // Auto-focus textarea when sidebar opens + // Auto-focus textarea when sidebar opens or when conversation is cleared (new chat) useEffect(() => { - textareaRef.current?.focus() - }, []) + // Focus when conversation is empty (new chat started) + if (conversation.length === 0) { + const timer = setTimeout(() => { + textareaRef.current?.focus() + }, 50) + return () => clearTimeout(timer) + } + }, [conversation.length]) // Auto-populate with @currentfile when switching knowledge files useEffect(() => { @@ -584,9 +590,8 @@ export function ChatSidebar({ onKeyDown={handleKeyDown} onScroll={syncHighlightScroll} placeholder="Ask anything..." - disabled={isProcessing} rows={1} - className="relative z-10 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground disabled:opacity-50 resize-none max-h-32 min-h-6" + className="relative z-10 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground resize-none max-h-32 min-h-6" style={{ fieldSizing: 'content' } as React.CSSProperties} /> From d05344861a3bee6e2187eb30f8fc49609e8bf28c Mon Sep 17 00:00:00 2001 From: tusharmagar Date: Tue, 27 Jan 2026 08:39:10 +0530 Subject: [PATCH 07/10] remove faulty undo/redo and replaced with back/forward buttons --- apps/x/apps/renderer/src/App.tsx | 60 +++++++++++++++++-- .../src/components/editor-toolbar.tsx | 36 +++++++---- .../src/components/markdown-editor.tsx | 18 +++++- 3 files changed, 97 insertions(+), 17 deletions(-) diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index 884e4e95..d1a6f1fe 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -382,6 +382,8 @@ function ChatInputWithMentions({ function App() { // File browser state (for Knowledge section) const [selectedPath, setSelectedPath] = useState(null) + const [fileHistoryBack, setFileHistoryBack] = useState([]) + const [fileHistoryForward, setFileHistoryForward] = useState([]) const [fileContent, setFileContent] = useState('') const [editorContent, setEditorContent] = useState('') const [tree, setTree] = useState([]) @@ -1075,6 +1077,52 @@ function App() { setIsGraphOpen(false) }, []) + // File navigation with history tracking + const navigateToFile = useCallback((path: string | null) => { + if (path === selectedPath) return + + // Push current path to back history (if we have one) + if (selectedPath) { + setFileHistoryBack(prev => [...prev, selectedPath]) + } + // Clear forward history when navigating to a new file + setFileHistoryForward([]) + setSelectedPath(path) + }, [selectedPath]) + + const navigateBack = useCallback(() => { + if (fileHistoryBack.length === 0) return + + const newBack = [...fileHistoryBack] + const previousPath = newBack.pop()! + + // Push current path to forward history + if (selectedPath) { + setFileHistoryForward(prev => [...prev, selectedPath]) + } + + setFileHistoryBack(newBack) + setSelectedPath(previousPath) + }, [fileHistoryBack, selectedPath]) + + const navigateForward = useCallback(() => { + if (fileHistoryForward.length === 0) return + + const newForward = [...fileHistoryForward] + const nextPath = newForward.pop()! + + // Push current path to back history + if (selectedPath) { + setFileHistoryBack(prev => [...prev, selectedPath]) + } + + setFileHistoryForward(newForward) + setSelectedPath(nextPath) + }, [fileHistoryForward, selectedPath]) + + const canNavigateBack = fileHistoryBack.length > 0 + const canNavigateForward = fileHistoryForward.length > 0 + // Handle image upload for the markdown editor const handleImageUpload = useCallback(async (file: File): Promise => { try { @@ -1128,7 +1176,7 @@ function App() { const toggleExpand = (path: string, kind: 'file' | 'dir') => { if (kind === 'file') { - setSelectedPath(path) + navigateToFile(path) setIsGraphOpen(false) return } @@ -1312,9 +1360,9 @@ function App() { const openWikiLink = useCallback(async (wikiPath: string) => { const resolvedPath = await ensureWikiFile(wikiPath) if (resolvedPath) { - setSelectedPath(resolvedPath) + navigateToFile(resolvedPath) } - }, [ensureWikiFile, setSelectedPath]) + }, [ensureWikiFile, navigateToFile]) const wikiLinkConfig = React.useMemo(() => ({ files: knowledgeFiles, @@ -1616,7 +1664,7 @@ function App() { error={graphStatus === 'error' ? (graphError ?? 'Failed to build graph') : null} onSelectNode={(path) => { setIsGraphOpen(false) - setSelectedPath(path) + navigateToFile(path) }} /> @@ -1629,6 +1677,10 @@ function App() { placeholder="Start writing..." wikiLinks={wikiLinkConfig} onImageUpload={handleImageUpload} + onNavigateBack={navigateBack} + onNavigateForward={navigateForward} + canNavigateBack={canNavigateBack} + canNavigateForward={canNavigateForward} /> ) : ( diff --git a/apps/x/apps/renderer/src/components/editor-toolbar.tsx b/apps/x/apps/renderer/src/components/editor-toolbar.tsx index 48b16156..95853e5c 100644 --- a/apps/x/apps/renderer/src/components/editor-toolbar.tsx +++ b/apps/x/apps/renderer/src/components/editor-toolbar.tsx @@ -22,8 +22,8 @@ import { MinusIcon, LinkIcon, CodeSquareIcon, - Undo2Icon, - Redo2Icon, + ChevronLeftIcon, + ChevronRightIcon, ExternalLinkIcon, Trash2Icon, ImageIcon, @@ -33,9 +33,21 @@ interface EditorToolbarProps { editor: Editor | null onSelectionHighlight?: (range: { from: number; to: number } | null) => void onImageUpload?: (file: File) => Promise | void + onNavigateBack?: () => void + onNavigateForward?: () => void + canNavigateBack?: boolean + canNavigateForward?: boolean } -export function EditorToolbar({ editor, onSelectionHighlight, onImageUpload }: EditorToolbarProps) { +export function EditorToolbar({ + editor, + onSelectionHighlight, + onImageUpload, + onNavigateBack, + onNavigateForward, + canNavigateBack, + canNavigateForward, +}: EditorToolbarProps) { const [linkUrl, setLinkUrl] = useState('') const [isLinkPopoverOpen, setIsLinkPopoverOpen] = useState(false) const fileInputRef = useRef(null) @@ -105,24 +117,24 @@ export function EditorToolbar({ editor, onSelectionHighlight, onImageUpload }: E return (
- {/* Undo / Redo */} + {/* Back / Forward Navigation */}
diff --git a/apps/x/apps/renderer/src/components/markdown-editor.tsx b/apps/x/apps/renderer/src/components/markdown-editor.tsx index 1bda92a9..a65f262a 100644 --- a/apps/x/apps/renderer/src/components/markdown-editor.tsx +++ b/apps/x/apps/renderer/src/components/markdown-editor.tsx @@ -30,6 +30,10 @@ interface MarkdownEditorProps { placeholder?: string wikiLinks?: WikiLinkConfig onImageUpload?: (file: File) => Promise + onNavigateBack?: () => void + onNavigateForward?: () => void + canNavigateBack?: boolean + canNavigateForward?: boolean } type WikiLinkMatch = { @@ -78,6 +82,10 @@ export function MarkdownEditor({ placeholder = 'Start writing...', wikiLinks, onImageUpload, + onNavigateBack, + onNavigateForward, + canNavigateBack, + canNavigateForward, }: MarkdownEditorProps) { const isInternalUpdate = useRef(false) const wrapperRef = useRef(null) @@ -318,7 +326,15 @@ export function MarkdownEditor({ return (
- +
{wikiLinks ? ( From d04a9cfa84957e54bfd3387ee03f46f24994aa8c Mon Sep 17 00:00:00 2001 From: tusharmagar Date: Tue, 27 Jan 2026 11:20:03 +0530 Subject: [PATCH 08/10] Update Suggestions component to be right alligned --- apps/x/apps/renderer/src/components/ai-elements/suggestions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/x/apps/renderer/src/components/ai-elements/suggestions.tsx b/apps/x/apps/renderer/src/components/ai-elements/suggestions.tsx index cd07ed7a..94eed585 100644 --- a/apps/x/apps/renderer/src/components/ai-elements/suggestions.tsx +++ b/apps/x/apps/renderer/src/components/ai-elements/suggestions.tsx @@ -51,7 +51,7 @@ export function Suggestions({ return (
{suggestions.map((suggestion) => ( From efa91f86276bfd8b9361ce8247bf5c2ee76c92e5 Mon Sep 17 00:00:00 2001 From: Ramnique Singh <30795890+ramnique@users.noreply.github.com> Date: Wed, 28 Jan 2026 07:03:32 +0530 Subject: [PATCH 09/10] Revert "ask for google client id" This reverts commit 941a56142b695e2070d1ba6cf2bc437ccbed8666. --- apps/x/apps/main/src/ipc.ts | 2 +- apps/x/apps/main/src/oauth-handler.ts | 43 +--------- .../src/components/connectors-popover.tsx | 40 +-------- .../src/components/google-client-id-modal.tsx | 85 ------------------- .../src/components/onboarding-modal.tsx | 37 +------- apps/x/apps/renderer/src/hooks/useOAuth.ts | 4 +- .../src/lib/google-client-id-store.ts | 17 ---- .../core/src/auth/provider-client-id.ts | 23 ----- apps/x/packages/core/src/auth/providers.ts | 3 +- .../src/knowledge/fireflies-client-factory.ts | 12 +-- .../src/knowledge/google-client-factory.ts | 51 ++++------- apps/x/packages/shared/src/ipc.ts | 1 - 12 files changed, 33 insertions(+), 285 deletions(-) delete mode 100644 apps/x/apps/renderer/src/components/google-client-id-modal.tsx delete mode 100644 apps/x/apps/renderer/src/lib/google-client-id-store.ts delete mode 100644 apps/x/packages/core/src/auth/provider-client-id.ts diff --git a/apps/x/apps/main/src/ipc.ts b/apps/x/apps/main/src/ipc.ts index d5420a3a..01644e90 100644 --- a/apps/x/apps/main/src/ipc.ts +++ b/apps/x/apps/main/src/ipc.ts @@ -305,7 +305,7 @@ export function setupIpcHandlers() { return runsCore.listRuns(args.cursor); }, 'oauth:connect': async (_event, args) => { - return await connectProvider(args.provider, args.clientId); + return await connectProvider(args.provider); }, 'oauth:disconnect': async (_event, args) => { return await disconnectProvider(args.provider); diff --git a/apps/x/apps/main/src/oauth-handler.ts b/apps/x/apps/main/src/oauth-handler.ts index a00d6e4f..3e694daa 100644 --- a/apps/x/apps/main/src/oauth-handler.ts +++ b/apps/x/apps/main/src/oauth-handler.ts @@ -3,12 +3,6 @@ import { createAuthServer } from './auth-server.js'; import * as oauthClient from '@x/core/dist/auth/oauth-client.js'; import type { Configuration } from '@x/core/dist/auth/oauth-client.js'; import { getProviderConfig, getAvailableProviders } from '@x/core/dist/auth/providers.js'; -import { - clearProviderClientIdOverride, - getProviderClientIdOverride, - hasProviderClientIdOverride, - setProviderClientIdOverride, -} from '@x/core/dist/auth/provider-client-id.js'; import container from '@x/core/dist/di/container.js'; import { IOAuthRepo } from '@x/core/dist/auth/repo.js'; import { IClientRegistrationRepo } from '@x/core/dist/auth/client-repo.js'; @@ -45,25 +39,14 @@ function getClientRegistrationRepo(): IClientRegistrationRepo { */ async function getProviderConfiguration(provider: string): Promise { const config = getProviderConfig(provider); - const resolveClientId = (): string => { - const override = getProviderClientIdOverride(provider); - if (override) { - return override; - } - if (config.client.mode === 'static' && config.client.clientId) { - return config.client.clientId; - } - throw new Error(`${provider} client ID not configured. Please provide a client ID.`); - }; if (config.discovery.mode === 'issuer') { if (config.client.mode === 'static') { // Discover endpoints, use static client ID console.log(`[OAuth] ${provider}: Discovery from issuer with static client ID`); - const clientId = resolveClientId(); return await oauthClient.discoverConfiguration( config.discovery.issuer, - clientId + config.client.clientId ); } else { // DCR mode - check for existing registration or register new @@ -100,11 +83,10 @@ async function getProviderConfiguration(provider: string): Promise { +export async function connectProvider(provider: string): Promise<{ success: boolean; error?: string }> { try { console.log(`[OAuth] Starting connection flow for ${provider}...`); const oauthRepo = getOAuthRepo(); const providerConfig = getProviderConfig(provider); - if (provider === 'google') { - const trimmedClientId = clientId?.trim(); - if (!trimmedClientId) { - return { success: false, error: 'Google client ID is required to connect.' }; - } - setProviderClientIdOverride(provider, trimmedClientId); - } - // Get or create OAuth configuration const config = await getProviderConfiguration(provider); @@ -235,9 +209,6 @@ export async function disconnectProvider(provider: string): Promise<{ success: b try { const oauthRepo = getOAuthRepo(); await oauthRepo.clearTokens(provider); - if (provider === 'google') { - clearProviderClientIdOverride(provider); - } return { success: true }; } catch (error) { console.error('OAuth disconnect failed:', error); @@ -251,9 +222,6 @@ export async function disconnectProvider(provider: string): Promise<{ success: b export async function isConnected(provider: string): Promise<{ isConnected: boolean }> { try { const oauthRepo = getOAuthRepo(); - if (provider === 'google' && !hasProviderClientIdOverride(provider)) { - return { isConnected: false }; - } const connected = await oauthRepo.isConnected(provider); return { isConnected: connected }; } catch (error) { @@ -310,10 +278,7 @@ export async function getConnectedProviders(): Promise<{ providers: string[] }> try { const oauthRepo = getOAuthRepo(); const providers = await oauthRepo.getConnectedProviders(); - const filteredProviders = providers.filter((provider) => - provider === 'google' ? hasProviderClientIdOverride(provider) : true - ); - return { providers: filteredProviders }; + return { providers }; } catch (error) { console.error('Get connected providers failed:', error); return { providers: [] }; diff --git a/apps/x/apps/renderer/src/components/connectors-popover.tsx b/apps/x/apps/renderer/src/components/connectors-popover.tsx index b252f281..ced6064f 100644 --- a/apps/x/apps/renderer/src/components/connectors-popover.tsx +++ b/apps/x/apps/renderer/src/components/connectors-popover.tsx @@ -17,8 +17,6 @@ import { import { Button } from "@/components/ui/button" import { Switch } from "@/components/ui/switch" import { Separator } from "@/components/ui/separator" -import { GoogleClientIdModal } from "@/components/google-client-id-modal" -import { getGoogleClientId, setGoogleClientId, clearGoogleClientId } from "@/lib/google-client-id-store" import { toast } from "sonner" interface ProviderState { @@ -37,7 +35,6 @@ export function ConnectorsPopover({ children, tooltip }: ConnectorsPopoverProps) const [providers, setProviders] = useState([]) const [providersLoading, setProvidersLoading] = useState(true) const [providerStates, setProviderStates] = useState>({}) - const [googleClientIdOpen, setGoogleClientIdOpen] = useState(false) // Granola state const [granolaEnabled, setGranolaEnabled] = useState(false) @@ -164,14 +161,15 @@ export function ConnectorsPopover({ children, tooltip }: ConnectorsPopoverProps) return cleanup }, [refreshAllStatuses]) - const startConnect = useCallback(async (provider: string, clientId?: string) => { + // Connect to a provider + const handleConnect = useCallback(async (provider: string) => { setProviderStates(prev => ({ ...prev, [provider]: { ...prev[provider], isConnecting: true } })) try { - const result = await window.ipc.invoke('oauth:connect', { provider, clientId }) + const result = await window.ipc.invoke('oauth:connect', { provider }) if (result.success) { // OAuth flow started - keep isConnecting state, wait for event @@ -194,27 +192,6 @@ export function ConnectorsPopover({ children, tooltip }: ConnectorsPopoverProps) } }, []) - // Connect to a provider - const handleConnect = useCallback(async (provider: string) => { - if (provider === 'google') { - const existingClientId = getGoogleClientId() - if (!existingClientId) { - setGoogleClientIdOpen(true) - return - } - await startConnect(provider, existingClientId) - return - } - - await startConnect(provider) - }, [startConnect]) - - const handleGoogleClientIdSubmit = useCallback((clientId: string) => { - setGoogleClientId(clientId) - setGoogleClientIdOpen(false) - startConnect('google', clientId) - }, [startConnect]) - // Disconnect from a provider const handleDisconnect = useCallback(async (provider: string) => { setProviderStates(prev => ({ @@ -226,9 +203,6 @@ export function ConnectorsPopover({ children, tooltip }: ConnectorsPopoverProps) const result = await window.ipc.invoke('oauth:disconnect', { provider }) if (result.success) { - if (provider === 'google') { - clearGoogleClientId() - } const displayName = provider === 'fireflies-ai' ? 'Fireflies' : provider.charAt(0).toUpperCase() + provider.slice(1) toast.success(`Disconnected from ${displayName}`) setProviderStates(prev => ({ @@ -315,13 +289,6 @@ export function ConnectorsPopover({ children, tooltip }: ConnectorsPopoverProps) } return ( - <> - {tooltip ? ( @@ -406,6 +373,5 @@ export function ConnectorsPopover({ children, tooltip }: ConnectorsPopoverProps)
- ) } diff --git a/apps/x/apps/renderer/src/components/google-client-id-modal.tsx b/apps/x/apps/renderer/src/components/google-client-id-modal.tsx deleted file mode 100644 index 5727d506..00000000 --- a/apps/x/apps/renderer/src/components/google-client-id-modal.tsx +++ /dev/null @@ -1,85 +0,0 @@ -"use client" - -import { useEffect, useState } from "react" -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" - -interface GoogleClientIdModalProps { - open: boolean - onOpenChange: (open: boolean) => void - onSubmit: (clientId: string) => void - isSubmitting?: boolean -} - -export function GoogleClientIdModal({ - open, - onOpenChange, - onSubmit, - isSubmitting = false, -}: GoogleClientIdModalProps) { - const [clientId, setClientId] = useState("") - - useEffect(() => { - if (!open) { - setClientId("") - } - }, [open]) - - const trimmedClientId = clientId.trim() - const isValid = trimmedClientId.length > 0 - - const handleSubmit = () => { - if (!isValid || isSubmitting) return - onSubmit(trimmedClientId) - } - - return ( - - - - Enter Google Client ID - - This app does not store the client ID. You will be prompted each session. - - -
- - setClientId(event.target.value)} - onKeyDown={(event) => { - if (event.key === "Enter") { - event.preventDefault() - handleSubmit() - } - }} - autoFocus - /> -
-
- - -
-
-
- ) -} diff --git a/apps/x/apps/renderer/src/components/onboarding-modal.tsx b/apps/x/apps/renderer/src/components/onboarding-modal.tsx index e33654eb..48119934 100644 --- a/apps/x/apps/renderer/src/components/onboarding-modal.tsx +++ b/apps/x/apps/renderer/src/components/onboarding-modal.tsx @@ -14,8 +14,6 @@ import { import { Button } from "@/components/ui/button" import { Switch } from "@/components/ui/switch" import { cn } from "@/lib/utils" -import { GoogleClientIdModal } from "@/components/google-client-id-modal" -import { getGoogleClientId, setGoogleClientId } from "@/lib/google-client-id-store" import { toast } from "sonner" interface ProviderState { @@ -38,7 +36,6 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { const [providers, setProviders] = useState([]) const [providersLoading, setProvidersLoading] = useState(true) const [providerStates, setProviderStates] = useState>({}) - const [googleClientIdOpen, setGoogleClientIdOpen] = useState(false) // Granola state const [granolaEnabled, setGranolaEnabled] = useState(false) @@ -162,14 +159,15 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { return cleanup }, []) - const startConnect = useCallback(async (provider: string, clientId?: string) => { + // Connect to a provider + const handleConnect = useCallback(async (provider: string) => { setProviderStates(prev => ({ ...prev, [provider]: { ...prev[provider], isConnecting: true } })) try { - const result = await window.ipc.invoke('oauth:connect', { provider, clientId }) + const result = await window.ipc.invoke('oauth:connect', { provider }) if (!result.success) { toast.error(result.error || `Failed to connect to ${provider}`) @@ -188,27 +186,6 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { } }, []) - // Connect to a provider - const handleConnect = useCallback(async (provider: string) => { - if (provider === 'google') { - const existingClientId = getGoogleClientId() - if (!existingClientId) { - setGoogleClientIdOpen(true) - return - } - await startConnect(provider, existingClientId) - return - } - - await startConnect(provider) - }, [startConnect]) - - const handleGoogleClientIdSubmit = useCallback((clientId: string) => { - setGoogleClientId(clientId) - setGoogleClientIdOpen(false) - startConnect('google', clientId) - }, [startConnect]) - const handleNext = () => { if (currentStep < 2) { setCurrentStep((prev) => (prev + 1) as Step) @@ -452,13 +429,6 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { } return ( - <> - {}}> } - ) } diff --git a/apps/x/apps/renderer/src/hooks/useOAuth.ts b/apps/x/apps/renderer/src/hooks/useOAuth.ts index f23f5975..b2777c6c 100644 --- a/apps/x/apps/renderer/src/hooks/useOAuth.ts +++ b/apps/x/apps/renderer/src/hooks/useOAuth.ts @@ -50,10 +50,10 @@ export function useOAuth(provider: string) { return cleanup; }, [provider, checkConnection]); - const connect = useCallback(async (clientId?: string) => { + const connect = useCallback(async () => { try { setIsConnecting(true); - const result = await window.ipc.invoke('oauth:connect', { provider, clientId }); + const result = await window.ipc.invoke('oauth:connect', { provider }); if (result.success) { // OAuth flow started - keep isConnecting state, wait for event // Event listener will handle the actual completion diff --git a/apps/x/apps/renderer/src/lib/google-client-id-store.ts b/apps/x/apps/renderer/src/lib/google-client-id-store.ts deleted file mode 100644 index 78898325..00000000 --- a/apps/x/apps/renderer/src/lib/google-client-id-store.ts +++ /dev/null @@ -1,17 +0,0 @@ -let googleClientId: string | null = null; - -export function getGoogleClientId(): string | null { - return googleClientId; -} - -export function setGoogleClientId(clientId: string): void { - const trimmed = clientId.trim(); - if (!trimmed) { - return; - } - googleClientId = trimmed; -} - -export function clearGoogleClientId(): void { - googleClientId = null; -} diff --git a/apps/x/packages/core/src/auth/provider-client-id.ts b/apps/x/packages/core/src/auth/provider-client-id.ts deleted file mode 100644 index 6f8f6a90..00000000 --- a/apps/x/packages/core/src/auth/provider-client-id.ts +++ /dev/null @@ -1,23 +0,0 @@ -type ProviderClientIdOverrides = Map; - -const providerClientIdOverrides: ProviderClientIdOverrides = new Map(); - -export function setProviderClientIdOverride(provider: string, clientId: string): void { - const trimmed = clientId.trim(); - if (!trimmed) { - return; - } - providerClientIdOverrides.set(provider, trimmed); -} - -export function getProviderClientIdOverride(provider: string): string | undefined { - return providerClientIdOverrides.get(provider); -} - -export function hasProviderClientIdOverride(provider: string): boolean { - return providerClientIdOverrides.has(provider); -} - -export function clearProviderClientIdOverride(provider: string): void { - providerClientIdOverrides.delete(provider); -} diff --git a/apps/x/packages/core/src/auth/providers.ts b/apps/x/packages/core/src/auth/providers.ts index bb4bdd9f..1cf703d6 100644 --- a/apps/x/packages/core/src/auth/providers.ts +++ b/apps/x/packages/core/src/auth/providers.ts @@ -22,7 +22,7 @@ const DiscoverySchema = z.discriminatedUnion('mode', [ const ClientSchema = z.discriminatedUnion('mode', [ z.object({ mode: z.literal('static'), - clientId: z.string().min(1).optional(), + clientId: z.string().min(1), }), z.object({ mode: z.literal('dcr'), @@ -58,6 +58,7 @@ const providerConfigs: ProviderConfig = { }, client: { mode: 'static', + clientId: '797410052581-ibmmvqec0l68stv5fmgh0juqfvbg08fc.apps.googleusercontent.com', }, scopes: [ 'https://www.googleapis.com/auth/gmail.readonly', diff --git a/apps/x/packages/core/src/knowledge/fireflies-client-factory.ts b/apps/x/packages/core/src/knowledge/fireflies-client-factory.ts index d8c975c7..a56d39b5 100644 --- a/apps/x/packages/core/src/knowledge/fireflies-client-factory.ts +++ b/apps/x/packages/core/src/knowledge/fireflies-client-factory.ts @@ -144,13 +144,9 @@ export class FirefliesClientFactory { if (providerConfig.client.mode === 'static') { // Discover endpoints, use static client ID console.log(`[Fireflies] Discovery mode: issuer with static client ID`); - const clientId = providerConfig.client.clientId; - if (!clientId) { - throw new Error('Fireflies client ID not configured.'); - } this.cache.config = await oauthClient.discoverConfiguration( providerConfig.discovery.issuer, - clientId + providerConfig.client.clientId ); } else { // DCR mode - need existing registration @@ -174,14 +170,10 @@ export class FirefliesClientFactory { } console.log(`[Fireflies] Using static endpoints (no discovery)`); - const clientId = providerConfig.client.clientId; - if (!clientId) { - throw new Error('Fireflies client ID not configured.'); - } this.cache.config = oauthClient.createStaticConfiguration( providerConfig.discovery.authorizationEndpoint, providerConfig.discovery.tokenEndpoint, - clientId, + providerConfig.client.clientId, providerConfig.discovery.revocationEndpoint ); } diff --git a/apps/x/packages/core/src/knowledge/google-client-factory.ts b/apps/x/packages/core/src/knowledge/google-client-factory.ts index 9a1240fb..f32f492c 100644 --- a/apps/x/packages/core/src/knowledge/google-client-factory.ts +++ b/apps/x/packages/core/src/knowledge/google-client-factory.ts @@ -3,7 +3,6 @@ import container from '../di/container.js'; import { IOAuthRepo } from '../auth/repo.js'; import { IClientRegistrationRepo } from '../auth/client-repo.js'; import { getProviderConfig } from '../auth/providers.js'; -import { getProviderClientIdOverride } from '../auth/provider-client-id.js'; import * as oauthClient from '../auth/oauth-client.js'; import type { Configuration } from '../auth/oauth-client.js'; import { OAuthTokens } from '../auth/types.js'; @@ -18,22 +17,12 @@ export class GoogleClientFactory { config: Configuration | null; client: OAuth2Client | null; tokens: OAuthTokens | null; - clientId: string | null; } = { config: null, client: null, tokens: null, - clientId: null, }; - private static resolveClientId(): string { - const override = getProviderClientIdOverride(this.PROVIDER_NAME); - if (!override) { - throw new Error('Google client ID not provided for this session.'); - } - return override; - } - /** * Get or create OAuth2Client, reusing cached instance when possible */ @@ -47,13 +36,7 @@ export class GoogleClientFactory { } // Initialize config cache if needed - try { - await this.initializeConfigCache(); - } catch (error) { - console.error("[OAuth] Failed to initialize Google OAuth configuration:", error); - this.clearCache(); - return null; - } + await this.initializeConfigCache(); if (!this.cache.config) { return null; } @@ -112,10 +95,6 @@ export class GoogleClientFactory { return false; } - if (!getProviderClientIdOverride(this.PROVIDER_NAME)) { - return false; - } - const tokens = await oauthRepo.getTokens(this.PROVIDER_NAME); if (!tokens) { return false; @@ -137,21 +116,14 @@ export class GoogleClientFactory { this.cache.config = null; this.cache.client = null; this.cache.tokens = null; - this.cache.clientId = null; } /** * Initialize cached configuration (called once) */ private static async initializeConfigCache(): Promise { - const clientId = this.resolveClientId(); - - if (this.cache.config && this.cache.clientId === clientId) { - return; // Already initialized for this client ID - } - - if (this.cache.clientId && this.cache.clientId !== clientId) { - this.clearCache(); + if (this.cache.config) { + return; // Already initialized } console.log(`[OAuth] Initializing Google OAuth configuration...`); @@ -163,7 +135,7 @@ export class GoogleClientFactory { console.log(`[OAuth] Discovery mode: issuer with static client ID`); this.cache.config = await oauthClient.discoverConfiguration( providerConfig.discovery.issuer, - clientId + providerConfig.client.clientId ); } else { // DCR mode - need existing registration @@ -190,12 +162,11 @@ export class GoogleClientFactory { this.cache.config = oauthClient.createStaticConfiguration( providerConfig.discovery.authorizationEndpoint, providerConfig.discovery.tokenEndpoint, - clientId, + providerConfig.client.clientId, providerConfig.discovery.revocationEndpoint ); } - this.cache.clientId = clientId; console.log(`[OAuth] Google OAuth configuration initialized`); } @@ -203,7 +174,17 @@ export class GoogleClientFactory { * Create OAuth2Client from OAuthTokens */ private static createClientFromTokens(tokens: OAuthTokens): OAuth2Client { - const clientId = this.resolveClientId(); + const providerConfig = getProviderConfig(this.PROVIDER_NAME); + + // Get client ID from config + let clientId: string; + if (providerConfig.client.mode === 'static') { + clientId = providerConfig.client.clientId; + } else { + // For DCR, we'd need to look up the registered client ID + // This is a fallback - normally initializeConfigCache handles this + throw new Error('Cannot create client without static client ID'); + } // Create OAuth2Client directly (PKCE flow doesn't use client secret) const client = new OAuth2Client( diff --git a/apps/x/packages/shared/src/ipc.ts b/apps/x/packages/shared/src/ipc.ts index 28689dde..93b797a9 100644 --- a/apps/x/packages/shared/src/ipc.ts +++ b/apps/x/packages/shared/src/ipc.ts @@ -175,7 +175,6 @@ const ipcSchemas = { 'oauth:connect': { req: z.object({ provider: z.string(), - clientId: z.string().optional(), }), res: z.object({ success: z.boolean(), From 7f91e8829c0993796c3aca3a308fbe1cddb1b26e Mon Sep 17 00:00:00 2001 From: Ramnique Singh <30795890+ramnique@users.noreply.github.com> Date: Wed, 28 Jan 2026 07:03:21 +0530 Subject: [PATCH 10/10] fix scopes --- apps/x/packages/core/src/auth/providers.ts | 2 +- apps/x/packages/core/src/knowledge/sync_calendar.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/x/packages/core/src/auth/providers.ts b/apps/x/packages/core/src/auth/providers.ts index 1cf703d6..b6afe2a2 100644 --- a/apps/x/packages/core/src/auth/providers.ts +++ b/apps/x/packages/core/src/auth/providers.ts @@ -62,7 +62,7 @@ const providerConfigs: ProviderConfig = { }, scopes: [ 'https://www.googleapis.com/auth/gmail.readonly', - 'https://www.googleapis.com/auth/calendar.readonly', + 'https://www.googleapis.com/auth/calendar.events.readonly', 'https://www.googleapis.com/auth/drive.readonly', ], }, diff --git a/apps/x/packages/core/src/knowledge/sync_calendar.ts b/apps/x/packages/core/src/knowledge/sync_calendar.ts index f2719357..46ec2e1e 100644 --- a/apps/x/packages/core/src/knowledge/sync_calendar.ts +++ b/apps/x/packages/core/src/knowledge/sync_calendar.ts @@ -11,7 +11,7 @@ const SYNC_DIR = path.join(WorkDir, 'calendar_sync'); const SYNC_INTERVAL_MS = 5 * 60 * 1000; // Check every 5 minutes const LOOKBACK_DAYS = 14; const REQUIRED_SCOPES = [ - 'https://www.googleapis.com/auth/calendar.readonly', + 'https://www.googleapis.com/auth/calendar.events.readonly', 'https://www.googleapis.com/auth/drive.readonly' ];