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