# 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.

> 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.