From 5c4f79fb39badba31e552e86f281027d83887428 Mon Sep 17 00:00:00 2001
From: RAIT-09 <51452399+RAIT-09@users.noreply.github.com>
Date: Sat, 21 Mar 2026 22:07:55 +0900
Subject: [PATCH] docs: symlink CLAUDE.md and GEMINI.md to AGENTS.md to keep in
sync
---
AGENTS.md | 248 +++++++++++++++++++++++++++++++++---------------
CLAUDE.md | 279 +-----------------------------------------------------
GEMINI.md | 185 +-----------------------------------
3 files changed, 173 insertions(+), 539 deletions(-)
mode change 100644 => 120000 CLAUDE.md
mode change 100644 => 120000 GEMINI.md
diff --git a/AGENTS.md b/AGENTS.md
index 72e4abf..9b88bd5 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,7 +1,7 @@
# Agent Client Plugin - LLM Developer Guide
## Overview
-Obsidian plugin for AI agent interaction (Claude Code, Gemini CLI, custom agents). **React Hooks Architecture**.
+Obsidian plugin for AI agent interaction (Claude Code, Codex, Gemini CLI, custom agents). **ChatPanel + React Hooks Architecture**.
**Tech**: React 19, TypeScript, Obsidian API, Agent Client Protocol (ACP)
@@ -9,96 +9,164 @@ Obsidian plugin for AI agent interaction (Claude Code, Gemini CLI, custom agents
```
src/
-├── domain/ # Pure domain models + ports (interfaces)
-│ ├── models/ # agent-config, agent-error, chat-message, chat-session
-│ └── ports/ # IAgentClient, ISettingsAccess, IVaultAccess
-├── adapters/ # Interface implementations
-│ ├── acp/ # ACP protocol (acp.adapter.ts, acp-type-converter.ts)
-│ └── obsidian/ # Platform adapters (vault, settings, mention-service)
+├── types/ # Type definitions (no logic, no dependencies)
+│ ├── chat.ts # ChatMessage, MessageContent, PromptContent, AttachedFile
+│ ├── session.ts # ChatSession, SessionUpdate, SessionInfo, Capabilities
+│ ├── agent.ts # AgentConfig, agent settings (Claude/Codex/Gemini/Custom)
+│ └── errors.ts # AcpError, ProcessError, ErrorInfo
+├── acp/ # ACP protocol (SDK dependency confined here)
+│ ├── acp-client.ts # Process lifecycle, JSON-RPC, IAgentClient + ITerminalClient
+│ ├── type-converter.ts # ACP SDK ↔ internal type conversion
+│ ├── permission-handler.ts # Permission queue, auto-approve, Promise resolution
+│ └── terminal-handler.ts # Terminal process create/output/kill
+├── services/ # Business logic (non-React)
+│ ├── vault-service.ts # Vault access + fuzzy search + CM6 selection tracking
+│ ├── settings-service.ts # Settings persistence, session storage, normalization
+│ ├── session-helpers.ts # Agent config building, API key injection (pure functions)
+│ ├── message-sender.ts # Prompt preparation + sending (pure functions)
+│ ├── chat-exporter.ts # Markdown export with frontmatter
+│ ├── view-registry.ts # Multi-view management, focus, broadcast
+│ └── update-checker.ts # Agent/plugin version checking
├── hooks/ # React custom hooks (state + logic)
-│ ├── useAgentSession.ts # Session lifecycle, agent switching
-│ ├── useChat.ts # Message sending, callbacks
+│ ├── useSession.ts # Session lifecycle, config options, optimistic updates
+│ ├── useMessages.ts # Message state, streaming updates, send/receive
│ ├── usePermission.ts # Permission handling
-│ ├── useMentions.ts # @[[note]] suggestions
+│ ├── useMentions.ts # @[[note]] suggestions + auto-mention active note
│ ├── useSlashCommands.ts # /command suggestions
-│ ├── useAutoMention.ts # Auto-mention active note
-│ ├── useAutoExport.ts # Auto-export on new/close
+│ ├── useSessionHistory.ts # Session list/load/resume/fork
│ └── useSettings.ts # Settings subscription
-├── components/ # UI components
-│ ├── chat/ # ChatView, ChatHeader, ChatMessages, ChatInput, etc.
-│ └── settings/ # AgentClientSettingTab
-├── shared/ # Utilities
-│ ├── message-service.ts # prepareMessage, sendPreparedMessage (pure functions)
-│ ├── terminal-manager.ts # Process spawn, stdout/stderr capture
-│ ├── logger.ts, chat-exporter.ts, mention-utils.ts, etc.
+├── ui/ # React components
+│ ├── ChatContext.ts # React Context (plugin, acpClient, vaultService)
+│ ├── ChatPanel.tsx # Hook aggregation + rendering (core orchestrator)
+│ ├── ChatView.tsx # Sidebar view (ItemView wrapper)
+│ ├── FloatingChatView.tsx # Floating window (position/drag/resize)
+│ ├── ChatHeader.tsx # Header (sidebar + floating variants)
+│ ├── MessageList.tsx # Message list with auto-scroll
+│ ├── MessageBubble.tsx # Single message rendering (content dispatch)
+│ ├── ToolCallBlock.tsx # Tool call + diff display
+│ ├── TerminalBlock.tsx # Terminal output polling
+│ ├── InputArea.tsx # Textarea, attachments, mentions, history
+│ ├── InputToolbar.tsx # Mode/model selectors, usage, send button
+│ └── ... # SuggestionPopup, PermissionBanner, ErrorBanner, etc.
+├── utils/ # Shared utilities
+│ ├── platform.ts # Shell, WSL, Windows env, command building
+│ ├── paths.ts # Path resolution, file:// URI
+│ ├── error-utils.ts # ACP error conversion
+│ ├── mention-parser.ts # @[[note]] detection/extraction
+│ └── logger.ts # Debug-mode logger
├── plugin.ts # Obsidian plugin lifecycle, settings persistence
└── main.ts # Entry point
```
## Key Components
-### ChatView (`components/chat/ChatView.tsx`)
-- **Hook Composition**: Combines all hooks (useAgentSession, useChat, usePermission, etc.)
-- **Adapter Instantiation**: Creates AcpAdapter, VaultAdapter, MentionService via useMemo
-- **Rendering**: Delegates to ChatHeader, ChatMessages, ChatInput
+### ChatPanel (`ui/ChatPanel.tsx`)
+Central orchestrator component. Replaces the former `useChatController` hook.
+- **Hook Composition**: Calls all hooks (useSession, useMessages, usePermission, etc.)
+- **Session Update Routing**: Routes ACP updates to useMessages (message-level) or useSession (session-level)
+- **Callback Registration**: Provides IChatViewContainer callbacks via ref pattern
+- **Workspace Events**: Handles toggle-auto-mention, new-chat-requested, approve/reject-permission, cancel, export
+- **Rendering**: Renders ChatHeader, MessageList, InputArea directly (no prop bag)
+
+### ChatView / FloatingChatView (`ui/ChatView.tsx`, `ui/FloatingChatView.tsx`)
+Thin wrappers that:
+- Create services (AcpClient, VaultService) in lifecycle methods
+- Provide ChatContext (plugin, acpClient, vaultService, settingsService)
+- Render ``
+- Implement IChatViewContainer for broadcast commands
### Hooks (`hooks/`)
-**useAgentSession**: Session lifecycle
-- `createSession()`: Load config, inject API keys, initialize + newSession
-- `switchAgent()`: Change active agent, restart session
+**useSession**: Session lifecycle
+- `createSession()`: Build config, inject API keys, initialize + newSession
+- `restartSession()`: Change active agent, restart session
- `closeSession()`: Cancel session, disconnect
+- `setConfigOption()`: Optimistic update + rollback on error
+- `handleSessionUpdate()`: Handle session-level updates (commands, config, usage)
-**useChat**: Messaging
-- `sendMessage()`: Prepare (auto-mention, path conversion) → send via IAgentClient
-- `handleNewChat()`: Export if enabled, restart session
-- Callbacks: addMessage, updateLastMessage, updateMessage
+**useMessages**: Messaging and streaming updates
+- `sendMessage()`: Prepare (auto-mention, path conversion) → send via AcpClient
+- `handleSessionUpdate()`: Handle message-level updates (agent_message_chunk, tool_call, etc.)
+- `upsertToolCall()`: Create or update tool call in single `setMessages` callback (avoids race conditions)
+- `updateLastMessage()`: Append text/thought chunks to last assistant message
+- `updateMessage()`: Update specific message by tool call ID
**usePermission**: Permission handling
-- `handlePermissionResponse()`: Respond with selected option
+- `approvePermission()` / `rejectPermission()`: Respond with selected option
- Auto-approve logic based on settings
-**useMentions / useSlashCommands**: Input suggestions
-- Dropdown state management
-- Selection handlers
+**useMentions**: @mention + auto-mention (merged)
+- Dropdown state management + selection
+- Active note tracking + toggle for slash commands
-### AcpAdapter (`adapters/acp/acp.adapter.ts`)
-Implements IAgentClient + IAcpClient (terminal ops)
+**useSessionHistory**: Session persistence
+- `restoreSession()`: Load/resume with local message fallback
+- `forkSession()`: Create new branch from existing session
+
+### ACP Client (`acp/acp-client.ts`)
+Implements IAgentClient + ITerminalClient
- **Process**: spawn() with login shell (macOS/Linux -l, Windows shell:true)
- **Protocol**: JSON-RPC over stdin/stdout via ndJsonStream
-- **Flow**: initialize() → newSession() → sendMessage() → sessionUpdate() callbacks
-- **Updates**: agent_message_chunk, agent_thought_chunk, tool_call, tool_call_update, plan, available_commands_update
-- **Permissions**: Promise-based Map
-- **Terminal**: createTerminal, terminalOutput, killTerminal, releaseTerminal
+- **Flow**: initialize() → newSession() → sendPrompt() → sessionUpdate via `onSessionUpdate`
+- **Updates**: agent_message_chunk, agent_thought_chunk, user_message_chunk, tool_call, tool_call_update, plan, available_commands_update, current_mode_update, session_info_update, usage_update, config_option_update
+- **Unified Callback**: Single `onSessionUpdate(callback)` for all agent events
+- **Permissions**: Promise-based via PermissionHandler
+- **Terminal**: TerminalHandler manages create/output/kill/release
-### Obsidian Adapters (`adapters/obsidian/`)
+### Services (`services/`)
-**VaultAdapter**: IVaultAccess - searchNotes (fuzzy), getActiveNote, readNote
-**SettingsStore**: ISettingsAccess - Observer pattern, getSnapshot(), subscribe()
-**MentionService**: File index, fuzzy search (basename, path, aliases)
+**VaultService**: Vault access + file index + fuzzy search + CM6 selection tracking
+**SettingsService**: Observer pattern settings store, session storage (data.json + sessions/*.json)
+**session-helpers**: Pure functions — buildAgentConfigWithApiKey, findAgentSettings, getAvailableAgents
+**message-sender**: Pure functions — preparePrompt (auto-mention, context), sendPreparedPrompt (auth retry)
-### Message Service (`shared/message-service.ts`)
-Pure functions (non-React):
-- `prepareMessage()`: Auto-mention, convert @[[note]] → paths
-- `sendPreparedMessage()`: Send via IAgentClient, auth retry
+## Types
-## Ports (Interfaces)
+### SessionUpdate (`types/session.ts`)
+Union type for all session update events from the agent:
```typescript
+type SessionUpdate =
+ | AgentMessageChunk // Text chunk from agent's response
+ | AgentThoughtChunk // Text chunk from agent's reasoning
+ | UserMessageChunk // Text chunk from user message (session/load)
+ | ToolCall // New tool call event
+ | ToolCallUpdate // Update to existing tool call
+ | Plan // Agent's task plan
+ | AvailableCommandsUpdate // Slash commands changed
+ | CurrentModeUpdate // Mode changed
+ | SessionInfoUpdate // Session metadata changed
+ | UsageUpdate // Context window usage
+ | ConfigOptionUpdate; // Config options changed
+```
+
+### Key Interfaces
+
+Interfaces are defined in their implementation files (no separate port files):
+
+```typescript
+// acp/acp-client.ts
interface IAgentClient {
initialize(config: AgentConfig): Promise;
- newSession(workingDirectory: string): Promise;
+ newSession(workingDirectory: string): Promise;
authenticate(methodId: string): Promise;
- sendMessage(sessionId: string, message: string): Promise;
+ sendPrompt(sessionId: string, content: PromptContent[]): Promise;
cancel(sessionId: string): Promise;
disconnect(): Promise;
- onMessage(callback: (message: ChatMessage) => void): void;
- onError(callback: (error: AgentError) => void): void;
- onPermissionRequest(callback: (request: PermissionRequest) => void): void;
+ onSessionUpdate(callback: (update: SessionUpdate) => void): void;
+ onError(callback: (error: ProcessError) => void): void;
respondToPermission(requestId: string, optionId: string): Promise;
+ isInitialized(): boolean;
+ getCurrentAgentId(): string | null;
+ setSessionConfigOption(sessionId: string, configId: string, value: string): Promise;
+ listSessions(cwd?: string, cursor?: string): Promise;
+ loadSession(sessionId: string, cwd: string): Promise;
+ resumeSession(sessionId: string, cwd: string): Promise;
+ forkSession(sessionId: string, cwd: string): Promise;
}
+// services/vault-service.ts
interface IVaultAccess {
readNote(path: string): Promise;
searchNotes(query: string): Promise;
@@ -106,20 +174,29 @@ interface IVaultAccess {
listNotes(): Promise;
}
+// services/settings-service.ts
interface ISettingsAccess {
getSnapshot(): AgentClientPluginSettings;
updateSettings(updates: Partial): Promise;
subscribe(listener: () => void): () => void;
+ saveSession(info: SavedSessionInfo): Promise;
+ getSavedSessions(agentId?: string, cwd?: string): SavedSessionInfo[];
+ deleteSession(sessionId: string): Promise;
+ saveSessionMessages(sessionId: string, agentId: string, messages: ChatMessage[]): Promise;
+ loadSessionMessages(sessionId: string): Promise;
+ deleteSessionMessages(sessionId: string): Promise;
}
```
## Development Rules
### Architecture
-1. **Hooks for state + logic**: No ViewModel, no Use Cases classes
-2. **Pure functions in shared/**: Non-React business logic
-3. **Ports for ACP resistance**: IAgentClient interface isolates protocol changes
-4. **Domain has zero deps**: No `obsidian`, `@agentclientprotocol/sdk`
+1. **ChatPanel as orchestrator**: All hooks called in ChatPanel, renders children directly
+2. **Services for non-React logic**: Pure functions and classes in `services/`
+3. **ACP isolation**: All `@agentclientprotocol/sdk` imports confined to `acp/`
+4. **Types have zero deps**: No `obsidian`, no SDK, no React in `types/`
+5. **Unified callbacks**: Use `onSessionUpdate` for all agent events
+6. **Context for services**: plugin, acpClient, vaultService via ChatContext
### Obsidian Plugin Review (CRITICAL)
1. No innerHTML/outerHTML - use createEl/createDiv/createSpan
@@ -129,18 +206,22 @@ interface ISettingsAccess {
5. Minimize `any` - use proper types
### Naming Conventions
-- Ports: `*.port.ts`
-- Adapters: `*.adapter.ts`
-- Hooks: `use*.ts`
-- Components: `PascalCase.tsx`
-- Utils/Models: `kebab-case.ts`
+- Types: `kebab-case.ts` in `types/`
+- ACP: `kebab-case.ts` in `acp/`
+- Services: `kebab-case.ts` in `services/`
+- Hooks: `use*.ts` in `hooks/`
+- Components: `PascalCase.tsx` in `ui/`
+- Utils: `kebab-case.ts` in `utils/`
### Code Patterns
1. React hooks for state management
2. useCallback/useMemo for performance
-3. useRef for cleanup function access
+3. useRef for cleanup function access and stale closure prevention
4. Error handling: try-catch async ops
5. Logging: Logger class (respects debugMode)
+6. **Upsert pattern**: Use `setMessages` functional updates to avoid race conditions with tool_call updates
+7. **Ref pattern for callbacks**: IChatViewContainer callbacks use refs for latest values
+8. **Context value stability**: ChatContext value created once (service instances), wrapped in useMemo
## Common Tasks
@@ -148,37 +229,50 @@ interface ISettingsAccess {
1. Create `hooks/use[Feature].ts`
2. Define state with useState/useReducer
3. Export functions and state
-4. Compose in ChatView.tsx
+4. Call the hook in `ui/ChatPanel.tsx`
+5. Pass state/callbacks to child components as props
### Add Agent Type
-1. **Optional**: Define config in `domain/models/agent-config.ts`
-2. **Adapter**: Implement IAgentClient in `adapters/[agent]/[agent].adapter.ts`
-3. **Settings**: Add to AgentClientPluginSettings in plugin.ts
-4. **UI**: Update AgentClientSettingTab
+1. Add settings type in `types/agent.ts`
+2. Add config and defaults in `plugin.ts`
+3. Add API key injection in `services/session-helpers.ts`
+4. Update `ui/SettingsTab.ts` for configuration UI
### Modify Message Types
-1. Update `ChatMessage`/`MessageContent` in `domain/models/chat-message.ts`
-2. Update `AcpAdapter.sessionUpdate()` to handle new type
-3. Update `MessageContentRenderer` to render new type
+1. Update `ChatMessage`/`MessageContent` in `types/chat.ts`
+2. If adding new session update type:
+ - Add to `SessionUpdate` union in `types/session.ts`
+ - Handle in `hooks/useMessages.ts` `handleSessionUpdate()`
+3. Update `acp/acp-client.ts` `sessionUpdate()` to emit the new type
+4. Update `ui/MessageBubble.tsx` `ContentBlock` to render new type
+
+### Add New Session Update Type
+1. Define interface in `types/session.ts`
+2. Add to `SessionUpdate` union type
+3. Handle in `hooks/useMessages.ts` `handleSessionUpdate()` (for message-level)
+4. Or handle in `hooks/useSession.ts` `handleSessionUpdate()` (for session-level)
+5. Route in `ui/ChatPanel.tsx` session update effect
### Debug
1. Settings → Developer Settings → Debug Mode ON
2. Open DevTools (Cmd+Option+I / Ctrl+Shift+I)
-3. Filter logs: `[AcpAdapter]`, `[useChat]`, `[NoteMentionService]`
+3. Filter logs: `[AcpAdapter]`, `[useMessages]`, `[VaultService]`, `[ChatPanel]`
## ACP Protocol
**Communication**: JSON-RPC 2.0 over stdin/stdout
-**Methods**: initialize, newSession, authenticate, prompt, cancel
-**Notifications**: session/update (agent_message_chunk, agent_thought_chunk, tool_call, tool_call_update, plan, available_commands_update)
+**Methods**: initialize, newSession, authenticate, prompt, cancel, setSessionConfigOption
+**Notifications**: session/update (agent_message_chunk, agent_thought_chunk, user_message_chunk, tool_call, tool_call_update, plan, available_commands_update, current_mode_update, session_info_update, usage_update, config_option_update)
**Requests**: requestPermission
+**Session Management** (unstable): session/list, session/load, session/resume, session/fork
**Agents**:
- Claude Code: `@zed-industries/claude-agent-acp` (ANTHROPIC_API_KEY)
-- Gemini CLI: `@anthropics/gemini-cli-acp` (GOOGLE_API_KEY)
+- Codex: `@zed-industries/codex-acp` (OPENAI_API_KEY)
+- Gemini CLI: `@anthropics/gemini-cli-acp` (GEMINI_API_KEY)
- Custom: Any ACP-compatible agent
---
-**Last Updated**: November 2025 | **Architecture**: React Hooks | **Version**: 0.3.0
+**Last Updated**: March 2026 | **Architecture**: ChatPanel + React Hooks | **Version**: 0.9.1
diff --git a/CLAUDE.md b/CLAUDE.md
deleted file mode 100644
index 9b88bd5..0000000
--- a/CLAUDE.md
+++ /dev/null
@@ -1,278 +0,0 @@
-# Agent Client Plugin - LLM Developer Guide
-
-## Overview
-Obsidian plugin for AI agent interaction (Claude Code, Codex, Gemini CLI, custom agents). **ChatPanel + React Hooks Architecture**.
-
-**Tech**: React 19, TypeScript, Obsidian API, Agent Client Protocol (ACP)
-
-## Architecture
-
-```
-src/
-├── types/ # Type definitions (no logic, no dependencies)
-│ ├── chat.ts # ChatMessage, MessageContent, PromptContent, AttachedFile
-│ ├── session.ts # ChatSession, SessionUpdate, SessionInfo, Capabilities
-│ ├── agent.ts # AgentConfig, agent settings (Claude/Codex/Gemini/Custom)
-│ └── errors.ts # AcpError, ProcessError, ErrorInfo
-├── acp/ # ACP protocol (SDK dependency confined here)
-│ ├── acp-client.ts # Process lifecycle, JSON-RPC, IAgentClient + ITerminalClient
-│ ├── type-converter.ts # ACP SDK ↔ internal type conversion
-│ ├── permission-handler.ts # Permission queue, auto-approve, Promise resolution
-│ └── terminal-handler.ts # Terminal process create/output/kill
-├── services/ # Business logic (non-React)
-│ ├── vault-service.ts # Vault access + fuzzy search + CM6 selection tracking
-│ ├── settings-service.ts # Settings persistence, session storage, normalization
-│ ├── session-helpers.ts # Agent config building, API key injection (pure functions)
-│ ├── message-sender.ts # Prompt preparation + sending (pure functions)
-│ ├── chat-exporter.ts # Markdown export with frontmatter
-│ ├── view-registry.ts # Multi-view management, focus, broadcast
-│ └── update-checker.ts # Agent/plugin version checking
-├── hooks/ # React custom hooks (state + logic)
-│ ├── useSession.ts # Session lifecycle, config options, optimistic updates
-│ ├── useMessages.ts # Message state, streaming updates, send/receive
-│ ├── usePermission.ts # Permission handling
-│ ├── useMentions.ts # @[[note]] suggestions + auto-mention active note
-│ ├── useSlashCommands.ts # /command suggestions
-│ ├── useSessionHistory.ts # Session list/load/resume/fork
-│ └── useSettings.ts # Settings subscription
-├── ui/ # React components
-│ ├── ChatContext.ts # React Context (plugin, acpClient, vaultService)
-│ ├── ChatPanel.tsx # Hook aggregation + rendering (core orchestrator)
-│ ├── ChatView.tsx # Sidebar view (ItemView wrapper)
-│ ├── FloatingChatView.tsx # Floating window (position/drag/resize)
-│ ├── ChatHeader.tsx # Header (sidebar + floating variants)
-│ ├── MessageList.tsx # Message list with auto-scroll
-│ ├── MessageBubble.tsx # Single message rendering (content dispatch)
-│ ├── ToolCallBlock.tsx # Tool call + diff display
-│ ├── TerminalBlock.tsx # Terminal output polling
-│ ├── InputArea.tsx # Textarea, attachments, mentions, history
-│ ├── InputToolbar.tsx # Mode/model selectors, usage, send button
-│ └── ... # SuggestionPopup, PermissionBanner, ErrorBanner, etc.
-├── utils/ # Shared utilities
-│ ├── platform.ts # Shell, WSL, Windows env, command building
-│ ├── paths.ts # Path resolution, file:// URI
-│ ├── error-utils.ts # ACP error conversion
-│ ├── mention-parser.ts # @[[note]] detection/extraction
-│ └── logger.ts # Debug-mode logger
-├── plugin.ts # Obsidian plugin lifecycle, settings persistence
-└── main.ts # Entry point
-```
-
-## Key Components
-
-### ChatPanel (`ui/ChatPanel.tsx`)
-Central orchestrator component. Replaces the former `useChatController` hook.
-- **Hook Composition**: Calls all hooks (useSession, useMessages, usePermission, etc.)
-- **Session Update Routing**: Routes ACP updates to useMessages (message-level) or useSession (session-level)
-- **Callback Registration**: Provides IChatViewContainer callbacks via ref pattern
-- **Workspace Events**: Handles toggle-auto-mention, new-chat-requested, approve/reject-permission, cancel, export
-- **Rendering**: Renders ChatHeader, MessageList, InputArea directly (no prop bag)
-
-### ChatView / FloatingChatView (`ui/ChatView.tsx`, `ui/FloatingChatView.tsx`)
-Thin wrappers that:
-- Create services (AcpClient, VaultService) in lifecycle methods
-- Provide ChatContext (plugin, acpClient, vaultService, settingsService)
-- Render ``
-- Implement IChatViewContainer for broadcast commands
-
-### Hooks (`hooks/`)
-
-**useSession**: Session lifecycle
-- `createSession()`: Build config, inject API keys, initialize + newSession
-- `restartSession()`: Change active agent, restart session
-- `closeSession()`: Cancel session, disconnect
-- `setConfigOption()`: Optimistic update + rollback on error
-- `handleSessionUpdate()`: Handle session-level updates (commands, config, usage)
-
-**useMessages**: Messaging and streaming updates
-- `sendMessage()`: Prepare (auto-mention, path conversion) → send via AcpClient
-- `handleSessionUpdate()`: Handle message-level updates (agent_message_chunk, tool_call, etc.)
-- `upsertToolCall()`: Create or update tool call in single `setMessages` callback (avoids race conditions)
-- `updateLastMessage()`: Append text/thought chunks to last assistant message
-- `updateMessage()`: Update specific message by tool call ID
-
-**usePermission**: Permission handling
-- `approvePermission()` / `rejectPermission()`: Respond with selected option
-- Auto-approve logic based on settings
-
-**useMentions**: @mention + auto-mention (merged)
-- Dropdown state management + selection
-- Active note tracking + toggle for slash commands
-
-**useSessionHistory**: Session persistence
-- `restoreSession()`: Load/resume with local message fallback
-- `forkSession()`: Create new branch from existing session
-
-### ACP Client (`acp/acp-client.ts`)
-Implements IAgentClient + ITerminalClient
-
-- **Process**: spawn() with login shell (macOS/Linux -l, Windows shell:true)
-- **Protocol**: JSON-RPC over stdin/stdout via ndJsonStream
-- **Flow**: initialize() → newSession() → sendPrompt() → sessionUpdate via `onSessionUpdate`
-- **Updates**: agent_message_chunk, agent_thought_chunk, user_message_chunk, tool_call, tool_call_update, plan, available_commands_update, current_mode_update, session_info_update, usage_update, config_option_update
-- **Unified Callback**: Single `onSessionUpdate(callback)` for all agent events
-- **Permissions**: Promise-based via PermissionHandler
-- **Terminal**: TerminalHandler manages create/output/kill/release
-
-### Services (`services/`)
-
-**VaultService**: Vault access + file index + fuzzy search + CM6 selection tracking
-**SettingsService**: Observer pattern settings store, session storage (data.json + sessions/*.json)
-**session-helpers**: Pure functions — buildAgentConfigWithApiKey, findAgentSettings, getAvailableAgents
-**message-sender**: Pure functions — preparePrompt (auto-mention, context), sendPreparedPrompt (auth retry)
-
-## Types
-
-### SessionUpdate (`types/session.ts`)
-Union type for all session update events from the agent:
-
-```typescript
-type SessionUpdate =
- | AgentMessageChunk // Text chunk from agent's response
- | AgentThoughtChunk // Text chunk from agent's reasoning
- | UserMessageChunk // Text chunk from user message (session/load)
- | ToolCall // New tool call event
- | ToolCallUpdate // Update to existing tool call
- | Plan // Agent's task plan
- | AvailableCommandsUpdate // Slash commands changed
- | CurrentModeUpdate // Mode changed
- | SessionInfoUpdate // Session metadata changed
- | UsageUpdate // Context window usage
- | ConfigOptionUpdate; // Config options changed
-```
-
-### Key Interfaces
-
-Interfaces are defined in their implementation files (no separate port files):
-
-```typescript
-// acp/acp-client.ts
-interface IAgentClient {
- initialize(config: AgentConfig): Promise;
- newSession(workingDirectory: string): Promise;
- authenticate(methodId: string): Promise;
- sendPrompt(sessionId: string, content: PromptContent[]): Promise;
- cancel(sessionId: string): Promise;
- disconnect(): Promise;
- onSessionUpdate(callback: (update: SessionUpdate) => void): void;
- onError(callback: (error: ProcessError) => void): void;
- respondToPermission(requestId: string, optionId: string): Promise;
- isInitialized(): boolean;
- getCurrentAgentId(): string | null;
- setSessionConfigOption(sessionId: string, configId: string, value: string): Promise;
- listSessions(cwd?: string, cursor?: string): Promise;
- loadSession(sessionId: string, cwd: string): Promise;
- resumeSession(sessionId: string, cwd: string): Promise;
- forkSession(sessionId: string, cwd: string): Promise;
-}
-
-// services/vault-service.ts
-interface IVaultAccess {
- readNote(path: string): Promise;
- searchNotes(query: string): Promise;
- getActiveNote(): Promise;
- listNotes(): Promise;
-}
-
-// services/settings-service.ts
-interface ISettingsAccess {
- getSnapshot(): AgentClientPluginSettings;
- updateSettings(updates: Partial): Promise;
- subscribe(listener: () => void): () => void;
- saveSession(info: SavedSessionInfo): Promise;
- getSavedSessions(agentId?: string, cwd?: string): SavedSessionInfo[];
- deleteSession(sessionId: string): Promise;
- saveSessionMessages(sessionId: string, agentId: string, messages: ChatMessage[]): Promise;
- loadSessionMessages(sessionId: string): Promise;
- deleteSessionMessages(sessionId: string): Promise;
-}
-```
-
-## Development Rules
-
-### Architecture
-1. **ChatPanel as orchestrator**: All hooks called in ChatPanel, renders children directly
-2. **Services for non-React logic**: Pure functions and classes in `services/`
-3. **ACP isolation**: All `@agentclientprotocol/sdk` imports confined to `acp/`
-4. **Types have zero deps**: No `obsidian`, no SDK, no React in `types/`
-5. **Unified callbacks**: Use `onSessionUpdate` for all agent events
-6. **Context for services**: plugin, acpClient, vaultService via ChatContext
-
-### Obsidian Plugin Review (CRITICAL)
-1. No innerHTML/outerHTML - use createEl/createDiv/createSpan
-2. NO detach leaves in onunload (antipattern)
-3. Styles in CSS only - no JS style manipulation
-4. Use Platform interface - not process.platform
-5. Minimize `any` - use proper types
-
-### Naming Conventions
-- Types: `kebab-case.ts` in `types/`
-- ACP: `kebab-case.ts` in `acp/`
-- Services: `kebab-case.ts` in `services/`
-- Hooks: `use*.ts` in `hooks/`
-- Components: `PascalCase.tsx` in `ui/`
-- Utils: `kebab-case.ts` in `utils/`
-
-### Code Patterns
-1. React hooks for state management
-2. useCallback/useMemo for performance
-3. useRef for cleanup function access and stale closure prevention
-4. Error handling: try-catch async ops
-5. Logging: Logger class (respects debugMode)
-6. **Upsert pattern**: Use `setMessages` functional updates to avoid race conditions with tool_call updates
-7. **Ref pattern for callbacks**: IChatViewContainer callbacks use refs for latest values
-8. **Context value stability**: ChatContext value created once (service instances), wrapped in useMemo
-
-## Common Tasks
-
-### Add New Feature Hook
-1. Create `hooks/use[Feature].ts`
-2. Define state with useState/useReducer
-3. Export functions and state
-4. Call the hook in `ui/ChatPanel.tsx`
-5. Pass state/callbacks to child components as props
-
-### Add Agent Type
-1. Add settings type in `types/agent.ts`
-2. Add config and defaults in `plugin.ts`
-3. Add API key injection in `services/session-helpers.ts`
-4. Update `ui/SettingsTab.ts` for configuration UI
-
-### Modify Message Types
-1. Update `ChatMessage`/`MessageContent` in `types/chat.ts`
-2. If adding new session update type:
- - Add to `SessionUpdate` union in `types/session.ts`
- - Handle in `hooks/useMessages.ts` `handleSessionUpdate()`
-3. Update `acp/acp-client.ts` `sessionUpdate()` to emit the new type
-4. Update `ui/MessageBubble.tsx` `ContentBlock` to render new type
-
-### Add New Session Update Type
-1. Define interface in `types/session.ts`
-2. Add to `SessionUpdate` union type
-3. Handle in `hooks/useMessages.ts` `handleSessionUpdate()` (for message-level)
-4. Or handle in `hooks/useSession.ts` `handleSessionUpdate()` (for session-level)
-5. Route in `ui/ChatPanel.tsx` session update effect
-
-### Debug
-1. Settings → Developer Settings → Debug Mode ON
-2. Open DevTools (Cmd+Option+I / Ctrl+Shift+I)
-3. Filter logs: `[AcpAdapter]`, `[useMessages]`, `[VaultService]`, `[ChatPanel]`
-
-## ACP Protocol
-
-**Communication**: JSON-RPC 2.0 over stdin/stdout
-
-**Methods**: initialize, newSession, authenticate, prompt, cancel, setSessionConfigOption
-**Notifications**: session/update (agent_message_chunk, agent_thought_chunk, user_message_chunk, tool_call, tool_call_update, plan, available_commands_update, current_mode_update, session_info_update, usage_update, config_option_update)
-**Requests**: requestPermission
-**Session Management** (unstable): session/list, session/load, session/resume, session/fork
-
-**Agents**:
-- Claude Code: `@zed-industries/claude-agent-acp` (ANTHROPIC_API_KEY)
-- Codex: `@zed-industries/codex-acp` (OPENAI_API_KEY)
-- Gemini CLI: `@anthropics/gemini-cli-acp` (GEMINI_API_KEY)
-- Custom: Any ACP-compatible agent
-
----
-
-**Last Updated**: March 2026 | **Architecture**: ChatPanel + React Hooks | **Version**: 0.9.1
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 120000
index 0000000..47dc3e3
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+AGENTS.md
\ No newline at end of file
diff --git a/GEMINI.md b/GEMINI.md
deleted file mode 100644
index 72e4abf..0000000
--- a/GEMINI.md
+++ /dev/null
@@ -1,184 +0,0 @@
-# Agent Client Plugin - LLM Developer Guide
-
-## Overview
-Obsidian plugin for AI agent interaction (Claude Code, Gemini CLI, custom agents). **React Hooks Architecture**.
-
-**Tech**: React 19, TypeScript, Obsidian API, Agent Client Protocol (ACP)
-
-## Architecture
-
-```
-src/
-├── domain/ # Pure domain models + ports (interfaces)
-│ ├── models/ # agent-config, agent-error, chat-message, chat-session
-│ └── ports/ # IAgentClient, ISettingsAccess, IVaultAccess
-├── adapters/ # Interface implementations
-│ ├── acp/ # ACP protocol (acp.adapter.ts, acp-type-converter.ts)
-│ └── obsidian/ # Platform adapters (vault, settings, mention-service)
-├── hooks/ # React custom hooks (state + logic)
-│ ├── useAgentSession.ts # Session lifecycle, agent switching
-│ ├── useChat.ts # Message sending, callbacks
-│ ├── usePermission.ts # Permission handling
-│ ├── useMentions.ts # @[[note]] suggestions
-│ ├── useSlashCommands.ts # /command suggestions
-│ ├── useAutoMention.ts # Auto-mention active note
-│ ├── useAutoExport.ts # Auto-export on new/close
-│ └── useSettings.ts # Settings subscription
-├── components/ # UI components
-│ ├── chat/ # ChatView, ChatHeader, ChatMessages, ChatInput, etc.
-│ └── settings/ # AgentClientSettingTab
-├── shared/ # Utilities
-│ ├── message-service.ts # prepareMessage, sendPreparedMessage (pure functions)
-│ ├── terminal-manager.ts # Process spawn, stdout/stderr capture
-│ ├── logger.ts, chat-exporter.ts, mention-utils.ts, etc.
-├── plugin.ts # Obsidian plugin lifecycle, settings persistence
-└── main.ts # Entry point
-```
-
-## Key Components
-
-### ChatView (`components/chat/ChatView.tsx`)
-- **Hook Composition**: Combines all hooks (useAgentSession, useChat, usePermission, etc.)
-- **Adapter Instantiation**: Creates AcpAdapter, VaultAdapter, MentionService via useMemo
-- **Rendering**: Delegates to ChatHeader, ChatMessages, ChatInput
-
-### Hooks (`hooks/`)
-
-**useAgentSession**: Session lifecycle
-- `createSession()`: Load config, inject API keys, initialize + newSession
-- `switchAgent()`: Change active agent, restart session
-- `closeSession()`: Cancel session, disconnect
-
-**useChat**: Messaging
-- `sendMessage()`: Prepare (auto-mention, path conversion) → send via IAgentClient
-- `handleNewChat()`: Export if enabled, restart session
-- Callbacks: addMessage, updateLastMessage, updateMessage
-
-**usePermission**: Permission handling
-- `handlePermissionResponse()`: Respond with selected option
-- Auto-approve logic based on settings
-
-**useMentions / useSlashCommands**: Input suggestions
-- Dropdown state management
-- Selection handlers
-
-### AcpAdapter (`adapters/acp/acp.adapter.ts`)
-Implements IAgentClient + IAcpClient (terminal ops)
-
-- **Process**: spawn() with login shell (macOS/Linux -l, Windows shell:true)
-- **Protocol**: JSON-RPC over stdin/stdout via ndJsonStream
-- **Flow**: initialize() → newSession() → sendMessage() → sessionUpdate() callbacks
-- **Updates**: agent_message_chunk, agent_thought_chunk, tool_call, tool_call_update, plan, available_commands_update
-- **Permissions**: Promise-based Map
-- **Terminal**: createTerminal, terminalOutput, killTerminal, releaseTerminal
-
-### Obsidian Adapters (`adapters/obsidian/`)
-
-**VaultAdapter**: IVaultAccess - searchNotes (fuzzy), getActiveNote, readNote
-**SettingsStore**: ISettingsAccess - Observer pattern, getSnapshot(), subscribe()
-**MentionService**: File index, fuzzy search (basename, path, aliases)
-
-### Message Service (`shared/message-service.ts`)
-Pure functions (non-React):
-- `prepareMessage()`: Auto-mention, convert @[[note]] → paths
-- `sendPreparedMessage()`: Send via IAgentClient, auth retry
-
-## Ports (Interfaces)
-
-```typescript
-interface IAgentClient {
- initialize(config: AgentConfig): Promise;
- newSession(workingDirectory: string): Promise;
- authenticate(methodId: string): Promise;
- sendMessage(sessionId: string, message: string): Promise;
- cancel(sessionId: string): Promise;
- disconnect(): Promise;
- onMessage(callback: (message: ChatMessage) => void): void;
- onError(callback: (error: AgentError) => void): void;
- onPermissionRequest(callback: (request: PermissionRequest) => void): void;
- respondToPermission(requestId: string, optionId: string): Promise;
-}
-
-interface IVaultAccess {
- readNote(path: string): Promise;
- searchNotes(query: string): Promise;
- getActiveNote(): Promise;
- listNotes(): Promise;
-}
-
-interface ISettingsAccess {
- getSnapshot(): AgentClientPluginSettings;
- updateSettings(updates: Partial): Promise;
- subscribe(listener: () => void): () => void;
-}
-```
-
-## Development Rules
-
-### Architecture
-1. **Hooks for state + logic**: No ViewModel, no Use Cases classes
-2. **Pure functions in shared/**: Non-React business logic
-3. **Ports for ACP resistance**: IAgentClient interface isolates protocol changes
-4. **Domain has zero deps**: No `obsidian`, `@agentclientprotocol/sdk`
-
-### Obsidian Plugin Review (CRITICAL)
-1. No innerHTML/outerHTML - use createEl/createDiv/createSpan
-2. NO detach leaves in onunload (antipattern)
-3. Styles in CSS only - no JS style manipulation
-4. Use Platform interface - not process.platform
-5. Minimize `any` - use proper types
-
-### Naming Conventions
-- Ports: `*.port.ts`
-- Adapters: `*.adapter.ts`
-- Hooks: `use*.ts`
-- Components: `PascalCase.tsx`
-- Utils/Models: `kebab-case.ts`
-
-### Code Patterns
-1. React hooks for state management
-2. useCallback/useMemo for performance
-3. useRef for cleanup function access
-4. Error handling: try-catch async ops
-5. Logging: Logger class (respects debugMode)
-
-## Common Tasks
-
-### Add New Feature Hook
-1. Create `hooks/use[Feature].ts`
-2. Define state with useState/useReducer
-3. Export functions and state
-4. Compose in ChatView.tsx
-
-### Add Agent Type
-1. **Optional**: Define config in `domain/models/agent-config.ts`
-2. **Adapter**: Implement IAgentClient in `adapters/[agent]/[agent].adapter.ts`
-3. **Settings**: Add to AgentClientPluginSettings in plugin.ts
-4. **UI**: Update AgentClientSettingTab
-
-### Modify Message Types
-1. Update `ChatMessage`/`MessageContent` in `domain/models/chat-message.ts`
-2. Update `AcpAdapter.sessionUpdate()` to handle new type
-3. Update `MessageContentRenderer` to render new type
-
-### Debug
-1. Settings → Developer Settings → Debug Mode ON
-2. Open DevTools (Cmd+Option+I / Ctrl+Shift+I)
-3. Filter logs: `[AcpAdapter]`, `[useChat]`, `[NoteMentionService]`
-
-## ACP Protocol
-
-**Communication**: JSON-RPC 2.0 over stdin/stdout
-
-**Methods**: initialize, newSession, authenticate, prompt, cancel
-**Notifications**: session/update (agent_message_chunk, agent_thought_chunk, tool_call, tool_call_update, plan, available_commands_update)
-**Requests**: requestPermission
-
-**Agents**:
-- Claude Code: `@zed-industries/claude-agent-acp` (ANTHROPIC_API_KEY)
-- Gemini CLI: `@anthropics/gemini-cli-acp` (GOOGLE_API_KEY)
-- Custom: Any ACP-compatible agent
-
----
-
-**Last Updated**: November 2025 | **Architecture**: React Hooks | **Version**: 0.3.0
diff --git a/GEMINI.md b/GEMINI.md
new file mode 120000
index 0000000..47dc3e3
--- /dev/null
+++ b/GEMINI.md
@@ -0,0 +1 @@
+AGENTS.md
\ No newline at end of file