diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index aa8980fc..8110df51 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -222,7 +222,7 @@ Notes can be opened in separate Tauri windows for focused editing. Secondary win Full agent mode — spawns the selected local CLI agent as a subprocess with tool access and MCP vault integration. -1. **Frontend** (`AiPanel` + `useCliAiAgent` + `aiAgents.ts`) — streaming UI with reasoning blocks, tool action cards, response display, onboarding, and default-agent selection +1. **Frontend** (`AiPanel` + `useCliAiAgent` + `aiAgentSession.ts` + `aiAgents.ts`) — one normalized session lifecycle for message state, reasoning blocks, tool action cards, response display, onboarding, and default-agent selection 2. **Backend** (`ai_agents.rs`) — normalizes agent availability and streaming, dispatching to per-agent adapters 3. **Agent adapters** — Claude Code still uses `claude_cli.rs` with `acceptEdits`, strict Tolaria MCP config, a file/search-only built-in tool list, hidden Windows subprocess launches, and closed stdin for print-mode subprocesses so Windows launches receive EOF; Codex runs through `codex --sandbox workspace-write --ask-for-approval never exec --json`; OpenCode runs through `opencode run --format json`; Pi runs through `pi --mode json --no-session` with `npm:pi-mcp-adapter`. OpenCode and Pi both launch from the active vault cwd with closed stdin and transient MCP config. All app-launched paths use hidden Windows launches and avoid dangerous permission-bypass flags. 4. **MCP Integration** — Claude receives the generated MCP config file path, Codex receives the same Tolaria MCP server via transient `-c mcp_servers.tolaria.*` config overrides, OpenCode receives it through `OPENCODE_CONFIG_CONTENT`, and Pi receives it through a temporary `PI_CODING_AGENT_DIR/mcp.json` consumed by `pi-mcp-adapter` @@ -268,7 +268,7 @@ sequenceDiagram #### File Operation Detection -When the agent writes or edits vault files, `useCliAiAgent` detects this from normalized tool inputs and calls `onFileCreated` or `onFileModified` callbacks to trigger vault reload. +When the agent writes or edits vault files, `aiAgentFileOperations.ts` detects this from normalized tool inputs and calls `onFileCreated` or `onFileModified` callbacks to trigger vault reload. Unrecognized write-like operations fall back to a full vault refresh. ### Context Building @@ -718,10 +718,9 @@ The vault backend (`src-tauri/src/vault/`) is split into focused submodules: | Command | Description | |---------|-------------| | `stream_claude_chat` | Claude CLI chat mode (streaming) | -| `stream_claude_agent` | Claude CLI agent mode (streaming + tools) | | `check_claude_cli` | Check if Claude CLI is available | | `get_ai_agents_status` | Check Claude Code + Codex + OpenCode + Pi availability | -| `stream_ai_agent` | Stream the selected CLI agent through the normalized event layer | +| `stream_ai_agent` | Stream Claude Code, Codex, OpenCode, or Pi through the normalized agent event layer | | `register_mcp_tools` | Register MCP in Claude/Cursor/generic config for the active vault | | `remove_mcp_tools` | Remove Tolaria's MCP entry from Claude/Cursor/generic config | | `check_mcp_status` | Check whether the active vault is explicitly registered in Claude/Cursor/generic config | @@ -783,7 +782,7 @@ No Redux or global context. State lives in the root `App.tsx` and custom hooks: | `useTabManagement` | Navigation history, note switching | Note navigation lifecycle | | `useVaultSwitcher` | `vaultPath`, `extraVaults` | Vault switching | | `useTheme` | Editor theme CSS vars and theme-mode bridge | Editor typography and app theme runtime | -| `useCliAiAgent` | `messages`, `status`, tool actions | Selected AI agent conversation | +| `useCliAiAgent` | `messages`, `status`, tool actions | Selected AI agent conversation backed by the shared session pipeline | | `useAutoSync` | Sync interval, pull/push state | Git auto-sync | | `useAutoGit` | Last activity timestamp, idle/inactive checkpoint triggers | Automatic commit/push checkpoints | | `useCommitFlow` | Commit dialog state, shared manual/automatic checkpoint runner | Git commit/push orchestration | diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md index 0a18abf2..1b1ed0c7 100644 --- a/docs/GETTING-STARTED.md +++ b/docs/GETTING-STARTED.md @@ -106,15 +106,14 @@ tolaria/ │ │ └── ui/ # shadcn/ui primitives │ │ ├── button.tsx, dialog.tsx, input.tsx, ... │ │ -│ ├── hooks/ # Custom React hooks (~87 files) +│ ├── hooks/ # Custom React hooks (~86 files) │ │ ├── useVaultLoader.ts # Loads vault entries + content │ │ ├── useVaultSwitcher.ts # Multi-vault management │ │ ├── useVaultConfig.ts # Per-vault UI settings │ │ ├── useNoteActions.ts # Composes creation + rename + frontmatter │ │ ├── useNoteCreation.ts # Note/type creation │ │ ├── useNoteRename.ts # Note renaming + wikilink updates -│ │ ├── useAiAgent.ts # Legacy Claude-specific stream helpers reused by the shared agent hook -│ │ ├── useCliAiAgent.ts # Selected AI agent state + normalized tool tracking +│ │ ├── useCliAiAgent.ts # Selected AI agent state + normalized session pipeline │ │ ├── useAiAgentsStatus.ts # Claude/Codex/OpenCode/Pi availability polling │ │ ├── useAiAgentPreferences.ts # Default-agent persistence + cycling │ │ ├── useAiActivity.ts # MCP UI bridge listener @@ -284,7 +283,9 @@ tolaria/ | File | Why it matters | |------|---------------| | `src/components/AiPanel.tsx` | AI agent panel — selected CLI agent with tool execution, reasoning, and actions. | -| `src/hooks/useCliAiAgent.ts` | Agent state: messages, streaming, tool tracking, file detection. | +| `src/hooks/useCliAiAgent.ts` | Thin React owner for the selected CLI agent session state. | +| `src/lib/aiAgentSession.ts` | Single message/session lifecycle for prompt normalization, history, streaming, and reset behavior. | +| `src/lib/aiAgentFileOperations.ts` | Detects agent-created or modified vault files from normalized tool inputs. | | `src/lib/aiAgents.ts` | Supported agent definitions, status normalization, and default-agent helpers. | | `src/utils/ai-context.ts` | Context snapshot builder for AI conversations. | diff --git a/src-tauri/src/claude_cli.rs b/src-tauri/src/claude_cli.rs index d5f2ce96..025d11d0 100644 --- a/src-tauri/src/claude_cli.rs +++ b/src-tauri/src/claude_cli.rs @@ -50,7 +50,7 @@ pub struct ChatStreamRequest { pub session_id: Option, } -/// Parameters accepted by `stream_claude_agent`. +/// Parameters accepted by Claude Code agent streams. #[derive(Debug, Deserialize)] pub struct AgentStreamRequest { pub message: String, diff --git a/src-tauri/src/commands/ai.rs b/src-tauri/src/commands/ai.rs index 4819ebef..810af17b 100644 --- a/src-tauri/src/commands/ai.rs +++ b/src-tauri/src/commands/ai.rs @@ -1,6 +1,6 @@ #[cfg(desktop)] use crate::ai_agents::{AiAgentStreamRequest, AiAgentsStatus}; -use crate::claude_cli::{AgentStreamRequest, ChatStreamRequest, ClaudeCliStatus}; +use crate::claude_cli::{ChatStreamRequest, ClaudeCliStatus}; use crate::vault::VaultAiGuidanceStatus; use super::expand_tilde; @@ -81,14 +81,6 @@ define_desktop_stream_command!( crate::claude_cli::run_chat_stream ); -#[cfg(desktop)] -define_desktop_stream_command!( - stream_claude_agent, - AgentStreamRequest, - "claude-agent-stream", - crate::claude_cli::run_agent_stream -); - #[cfg(desktop)] define_desktop_stream_command!( stream_ai_agent, @@ -140,15 +132,6 @@ pub async fn stream_claude_chat( Err("Claude CLI is not available on mobile".into()) } -#[cfg(mobile)] -#[tauri::command] -pub async fn stream_claude_agent( - _app_handle: tauri::AppHandle, - _request: AgentStreamRequest, -) -> Result { - Err("Claude CLI is not available on mobile".into()) -} - #[cfg(mobile)] #[tauri::command] pub async fn stream_ai_agent( diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2ab222c6..e08f3045 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -401,7 +401,6 @@ macro_rules! app_invoke_handler { commands::get_vault_ai_guidance_status, commands::restore_vault_ai_guidance, commands::stream_claude_chat, - commands::stream_claude_agent, commands::stream_ai_agent, commands::reload_vault, commands::reload_vault_entry, diff --git a/src/components/AiPanel.test.tsx b/src/components/AiPanel.test.tsx index 3e4af01a..abd3425a 100644 --- a/src/components/AiPanel.test.tsx +++ b/src/components/AiPanel.test.tsx @@ -57,9 +57,10 @@ describe('AiPanel', () => { mockClearConversation.mockReset() }) - it('renders panel with AI Chat header', () => { + it('renders panel with the default CLI agent header', () => { render() - expect(screen.getByText('AI Chat')).toBeTruthy() + expect(screen.getByText('AI Agent')).toBeTruthy() + expect(screen.getByText('Claude Code')).toBeTruthy() }) it('renders data-testid ai-panel', () => { @@ -86,7 +87,7 @@ describe('AiPanel', () => { it('renders empty state without context', () => { render() - expect(screen.getByText('Open a note, then ask the AI about it')).toBeTruthy() + expect(screen.getByText('Open a note, then ask Claude Code about it')).toBeTruthy() }) it('renders contextual empty state when active entry is provided', () => { @@ -94,7 +95,7 @@ describe('AiPanel', () => { render( ) - expect(screen.getByText('Ask about this note and its linked context')).toBeTruthy() + expect(screen.getByText('Ask Claude Code about this note and its linked context')).toBeTruthy() }) it('shows context bar with active entry title', () => { diff --git a/src/components/AiPanel.tsx b/src/components/AiPanel.tsx index 6c751ccc..4ac41575 100644 --- a/src/components/AiPanel.tsx +++ b/src/components/AiPanel.tsx @@ -56,7 +56,6 @@ export function AiPanelView({ }: AiPanelViewProps) { const defaultAiAgent = providedDefaultAiAgent ?? DEFAULT_AI_AGENT const defaultAiAgentReady = providedDefaultAiAgentReady ?? true - const useLegacyAiExperience = providedDefaultAiAgent === undefined && providedDefaultAiAgentReady === undefined const inputRef = useRef(null) const panelRef = useRef(null) const agentLabel = getAiAgentDefinition(defaultAiAgent).label @@ -100,7 +99,6 @@ export function AiPanelView({ @@ -110,7 +108,6 @@ export function AiPanelView({ void onNewChat: () => void } @@ -23,7 +22,6 @@ interface AiPanelContextBarProps { interface AiPanelMessageHistoryProps { agentLabel: string agentReady: boolean - legacyCopy: boolean messages: AiAgentMessage[] isActive: boolean onOpenNote?: (path: string) => void @@ -58,8 +56,7 @@ function AiPanelEmptyState({ agentLabel, agentReady, hasContext, - legacyCopy, -}: Pick) { +}: Pick) { if (!agentReady) { return (

{hasContext - ? legacyCopy ? 'Ask about this note and its linked context' : `Ask ${agentLabel} about this note and its linked context` - : legacyCopy ? 'Open a note, then ask the AI about it' : `Open a note, then ask ${agentLabel} about it` + ? `Ask ${agentLabel} about this note and its linked context` + : `Open a note, then ask ${agentLabel} about it` }

@@ -102,7 +99,6 @@ function AiPanelEmptyState({ export function AiPanelHeader({ agentLabel, agentReady, - legacyCopy, onClose, onNewChat, }: AiPanelHeaderProps) { @@ -114,14 +110,12 @@ export function AiPanelHeader({

- {legacyCopy ? 'AI Chat' : 'AI Agent'} + AI Agent + + + {agentLabel} + {!agentReady ? ' · not installed' : ''} - {!legacyCopy && ( - - {agentLabel} - {!agentReady ? ' · not installed' : ''} - - )}