# whale **Repository Path**: ooeyusea/whale ## Basic Information - **Project Name**: whale - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-17 - **Last Updated**: 2026-06-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Whale — Desktop AI Agent A personal-MVP desktop AI agent for coding tasks, with multi-project isolation, single-Agent chat, and main-Agent-driven sub-Agent orchestration. Built with Tauri 2 + React 19 + Rust. ![Reference screenshot](sceenshot/codex的截图.png) > The UI is informed by a Codex-style desktop app. The screenshot > above is the visual reference; features visible in the screenshot > that are not yet implemented are listed under > [Out of scope](#out-of-scope). ## Features - **Multi-project workspaces** — each project is a local folder with its own conversation history and optional system prompt. - **Live streaming chat** — assistant text streams in token-by-token via the Tauri event bus. - **Tool calling** — file / shell / git / web / `find_files` / `list_dir` / `grep` / `read_file` / `write_file` / `edit_file` / `run_shell` / `git_status` / `git_diff` / `git_log` / `git_commit`. - **Sub-Agent orchestration** — the main agent can spawn worker sub-agents with their own role label and system prompt; their execution shows as nested cards in the UI. - **Sandbox + permission flow** — file operations are scoped to the project root; high-risk shell commands (e.g. `rm -rf`, `git push`, `sudo`) require explicit user approval. - **Cancellation** — in-flight runs (including shell processes) can be cancelled mid-stream via a red Stop button. - **Theme** — Light / Dark / System. ## Tech stack | Layer | Choice | | --- | --- | | Shell | Tauri 2 | | Frontend | React 19 + Vite 7 + Tailwind 4 + shadcn/ui | | Markdown | react-markdown + remark-gfm + rehype-highlight | | Backend | Rust (stable) + tokio + sqlx | | LLM | DeepSeek via OpenAI-compatible API | | Testing (frontend) | vitest + @testing-library/react | | Testing (backend) | `#[cfg(test)]` + `cargo test` + httpmock | | E2E (this MVP) | vitest in-process E2E with Tauri API stubs | ## Prerequisites - **Rust** ≥ 1.95 (`rustup install stable`) - **Node.js** ≥ 22 and **pnpm** ≥ 11 (`corepack enable && corepack prepare pnpm@latest --activate`) - **Tauri 2 system dependencies** — see . - **Windows**: Microsoft C++ Build Tools + WebView2 (preinstalled on Windows 10/11). - **macOS**: Xcode Command Line Tools. - **Linux**: `libwebkit2gtk-4.1-dev`, `libssl-dev`, `libgtk-3-dev`, etc. ## Setup ```sh pnpm install pnpm tauri dev ``` The first `pnpm tauri dev` will compile the Rust crate, which can take 1–3 minutes. Subsequent runs are incremental. ## Build ```sh pnpm tauri build ``` This produces a platform-specific installer (`.exe` / `.dmg` / `.deb` / `.AppImage`) under `src-tauri/target/release/bundle/`. ## Tests ```sh # Frontend unit + component tests (64 tests) pnpm test # Backend unit + integration tests (146 tests) cd src-tauri && cargo test # Real end-to-end: drives the built whale.exe via WebDriver # (Playwright + tauri-driver + msedgedriver). See e2e/README.md. pnpm test:e2e ``` ### Test matrix (spec §9.3) All 10 spec-required tests pass: | # | Description | Where | | --- | --- | --- | | 1 | Agent loop with mock LLM that returns `tool_call` then `stop` | `loop_agent::tests::one_iteration_with_tool_call_then_stop` | | 2 | Agent loop terminates at iteration 50 | `loop_agent::tests::max_iterations_reached_at_50` | | 3 | Main agent calls `spawn_agent` → child `AgentRun` created | `subagent_spawn::spawn_agent_creates_child_with_parent_run_id` | | 4 | Depth check: child agent's `spawn_agent` returns an error | `subagent_spawn::depth_one_child_cannot_spawn_agent` | | 5 | Sandbox: `read_file` outside project root returns `PathEscape` | `tools/file/read_file.rs::tests::*` | | 6 | Symlink escape: symlink in project pointing outside is rejected | `tools/file/read_file.rs::tests::symlink_escape_rejected` | | 7 | High-risk: `rm -rf /` matches a Deny pattern | `permissions::matcher::tests::*` | | 8 | High-risk: `rm -rf node_modules/.cache` matches Ask pattern | `tests/permission_flow.rs::end_to_end_permission_flow_for_run_shell` + `e2e/webdriver/permission.spec.ts` | | 9 | Cancellation: cancel token fires mid-stream; shell process is killed | `loop_agent::tests::cancellation_*` + `e2e/webdriver/cancellation.spec.ts` | | 10 | E2E: user lists `.ts` files → tool card → final reply | `e2e/webdriver/happy-path.spec.ts` | ### Real E2E (Playwright + tauri-driver) The `e2e/` directory contains a real end-to-end suite that drives the **built `whale.exe`** binary via the WebDriver protocol. The old in-process vitest stubs in `src/e2e/` (test 4, 8, 9, 10) are marked `@deprecated` and kept in place for reference, but the real suite is the canonical one. Architecture: - `e2e/playwright.config.ts` — WebDriver mode, port 14555 for tauri-driver, 90s timeout, single worker. - `e2e/fixtures.ts` — spawns `tauri-driver` once per worker, and creates a fresh WebDriver session per test (each test gets a fresh `whale.exe` process). - `e2e/mock-llm-server.ts` — a small Node `http` server that serves OpenAI-compatible SSE responses. Per-test scripts (happy_path, permission, cancel, depth) are picked from the `?script=` query parameter on the chat-completions endpoint. - `e2e/webdriver/_base.ts` — Playwright test extension providing the `whale` fixture (DriverHandle for the running whale.exe) and the `mockLlm` / `project` / `dataDir` helpers. - `e2e/webdriver/{happy-path,permission,cancellation,subagent-depth}.spec.ts` — the four spec §9.3 tests against the running binary. #### How the mock LLM is plugged in - The Rust startup reads `deepseek_base_url` (and `deepseek_api_key` and `default_model`) from the SQLite `settings` table. `WHALE_DATA_DIR` and a `--whale-data-dir` CLI arg override the app data dir. - The E2E fixture wipes `WHALE_DATA_DIR` before each test, then pre-populates the `settings` table with the mock LLM URL (e.g. `http://127.0.0.1:8765/v1?script=happy_path`). - `whale.exe` starts, reads the settings, and every chat call hits the local mock. - The DeepSeek provider preserves query strings on the base URL so the script selector reaches the chat-completions endpoint correctly. #### Prerequisites - Build the binary: `pnpm tauri build` (or set `--features tauri/custom-protocol` on the cargo build). - Install tauri-driver: `cargo install tauri-driver --version "^2.0" --locked`. The binary must be on PATH or at `~/.cargo/bin/tauri-driver` (or `CARGO_HOME/bin/`). - Install msedgedriver matching your WebView2 runtime version (148.x as of this writing). Get it from or via `winget install Microsoft.EdgeWebDriver2`. The driver must be on PATH or at `C:\tools\msedgedriver.exe`. #### Known caveats - The bundled WebView2 + React 19 controlled-input synthetic event system does not always pick up W3C `/value` or `/actions` keyboard events. The fixtures therefore drive project / conversation creation through the Tauri IPC (which is what the input form's Send button ultimately invokes). The agent behaviour — tool cards, permission dialog, stop button, depth-exceeded error — is what the spec actually exercises, and that *is* driven by Rust events that the WebView renders. See `e2e/README.md` for more details. ## Configuration Open the in-app **Settings** page (`/settings`): - **DeepSeek API key** — stored in the SQLite `settings` table. - **Default model** — e.g. `deepseek-chat`. - **Theme** — Light / Dark / System. - **High-risk patterns** — JSON array of `{ name, regex, default, description }`. Each pattern's `default` is `allow`, `ask`, or `deny`. Per-project settings live at `/projects/:id/settings` and include the system-prompt override and per-project model configuration. ## Architecture Three layers (spec §2): 1. **Frontend** (`src/`) — React 19 + shadcn/ui + Tailwind 4. Subscribes to Tauri events to stream messages and tool calls. 2. **Agent core** (`src-tauri/src/agent/`) — the LLM loop, tool registry, sub-agent manager, cancellation, JSONL session log. 3. **Persistence** — SQLite (sqlx) + filesystem (JSONL session logs + per-project `.whale/` config). Event flow: ``` AgentCore (Rust) --AgentEvent--> Tauri Event Bus --listen()--> React components ^ | | v tool registry store/agent.ts (zustand) ``` - `run_agent_message` Tauri command: starts a new run, registers a `CancellationToken`, spawns the agent loop in the background, and **forwards every `AgentEvent` from the in-process mpsc channel to the Tauri event bus** so the frontend can stream. - `useAgentRun(runId)` hook: subscribes to all event names, dispatches them into the `useAgentStore` (zustand) reducer. - Components read from the store and re-render as events arrive. The full design is in [`docs/superpowers/specs/2026-06-08-whale-desktop-agent-design.md`](docs/superpowers/specs/2026-06-08-whale-desktop-agent-design.md). ## File layout ``` . ├── src/ # React frontend │ ├── components/ # streaming + UI components │ ├── e2e/ # in-process E2E tests (Test 8, 9, 10) │ ├── lib/ # tauri wrappers, events, agent-events │ ├── routes/ # React Router routes │ ├── store/ # zustand stores │ └── types/ # shared TypeScript types ├── src-tauri/ │ ├── src/agent/ # LLM loop, tool dispatcher, events │ ├── src/commands/ # Tauri command layer │ ├── src/dao/ # SQLite DAOs │ ├── src/llm/ # DeepSeek adapter + mock │ ├── src/permissions/ # high-risk matcher + flow │ ├── src/sandbox/ # path sandbox │ ├── src/tools/ # 14 tools │ └── tests/ # Rust integration tests ├── docs/superpowers/ # design spec + implementation plans └── sceenshot/ # reference screenshot ``` ## Keyboard shortcuts | Key | Action | | --- | --- | | `Enter` | Send | | `Shift+Enter` | Newline | | `Cmd/Ctrl+K` | Focus input | | `Esc` | Cancel active run | ## Out of scope The reference screenshot contains features that are visible but intentionally deferred (see spec §10): sidebar entries 快速对话 / 搜索 / 插件 / 自动化, 用量 quota bar, mobile companion, plugin marketplace, cloud sync, multi-LLM provider, recursive sub-agents beyond depth 2. ## License Internal project — see repository owner.