feat(*): svelte 5 panel view sharing the gateway ui, obsidian theme, preview and tests
This commit is contained in:
@@ -1,73 +1,57 @@
|
||||
# Beaver — Obsidian plugin
|
||||
# Beaver - Obsidian plugin
|
||||
|
||||
[](https://sladge.net)
|
||||
|
||||
Two things:
|
||||
|
||||
- **Send the current note** to Beaver's `markdown frontend` (`POST /chat/stream` on `beaver-gateway`) and stream the agent's reply back into the buffer. Skips the Obsidian Sync round-trip by POSTing the buffer contents directly.
|
||||
- **Activity panel** (v0): a side view that follows the active note's `conversation_id` and shows, live over SSE, what the agent is doing — tool calls, subagents as a tree, turn status, usage of each turn.
|
||||
- **The panel**: Beaver's conversations next to your notes - the master thread, branches, deep chats - with the live tree of tool calls and subagents, the composer, and the same actions the admin has (branch with a seed, return a branch to Telegram or hide it, the memory flag, close with a digest). A narrow sidedock column, a full tab, or the phone screen. It follows the active note's `conversation_id` unless you pin it.
|
||||
- **Send the current note** to Beaver's markdown frontend (`POST /chat/stream`) and stream the agent's reply back into the buffer.
|
||||
|
||||
## Commands
|
||||
|
||||
- **Beaver: Send using selected agent** — uses `frontmatter.agent`. Only when the active note has an `agent:` field.
|
||||
- **Beaver: Send using different agent** — fetches the agent list from the gateway and opens a fuzzy picker. Same condition.
|
||||
- **Beaver: Open chat in panel** — reveals the activity panel in the right sidebar (creates it on first use) and points it at the active note.
|
||||
- **Beaver: Open panel** - the right sidedock (also the ribbon icon).
|
||||
- **Beaver: Open panel in a tab** - the wide layout with the conversation rail on the left.
|
||||
- **Beaver: Open chat in panel** - the active note has `conversation_id` in its frontmatter; the panel shows that conversation.
|
||||
- **Beaver: Send using selected agent** / **Send using different agent** - the note has `agent:` in its frontmatter.
|
||||
|
||||
## Activity panel
|
||||
## The panel
|
||||
|
||||
Subscribes to `GET /api/conversations/{id}/events` (SSE, bearer) where `{id}` is the `conversation_id` from the active note's frontmatter. Switching notes switches the subscription; a note without `conversation_id` shows a hint instead. On connect it fetches `GET /api/conversations/{id}` for the snapshot (title, agent, kind, status, whether a turn is running), then renders bus events:
|
||||
The UI is `beaver-gateway/ui/src/lib/panel`, compiled into this plugin by `esbuild.config.mjs` (`$lib/*` points at the gateway's `ui/src/lib`; every bare import resolves from this plugin's `node_modules`, so there is one Svelte runtime; `svelte-sonner` becomes Obsidian notices). The same components render the browser admin and `/admin/panel`.
|
||||
|
||||
- `turn.start` / `turn.end` — a turn block with status (running / done / interrupted / error), origin (`user` / `inject`), start time, duration and usage (`in · out · cache read/write · $ · time`).
|
||||
- `tool` / `tool.result` — a tool row (name, input summary, result status); clicking it expands the raw input and the result preview. Rows with `parent_tool_use_id` nest under the `Task`/`Agent` tool that spawned the subagent.
|
||||
- `stream` `content_block_start` of type `tool_use` — pre-creates the row so a long-running tool shows up before its input is complete.
|
||||
- `say` — text the dispatcher sent out of an inject turn.
|
||||
Theme: `src/tailwind.css` maps the panel's tokens onto Obsidian's variables on `.beaver-root`, exactly as beaver-calendar does with `.bcal-root` - the app's font, accent, radii and the current theme, light or dark. Menus and dialogs portal to a `.beaver-root.beaver-portal` layer on `body`, above the sidedocks.
|
||||
|
||||
The stream reconnects with exponential backoff (1 s → 30 s, jittered) and resets on `hello`. `401`/`403`/`404` are not retried: the panel shows the error and waits for you to fix the token or the id. When it reconnects and the snapshot says nothing is running, turns still shown as running are marked interrupted — their `turn.end` was lost with the connection.
|
||||
Markdown in the thread goes through Obsidian's `MarkdownRenderer`: `[[links]]` open the note, external links open the browser. A deep chat's row offers **Open note** (its markdown binding, under the vault subpath).
|
||||
|
||||
Plain DOM and CSS on Obsidian's theme variables; no framework. It is the v0 of the panel from `архитектура.md` §2 — the full Svelte UI (M5) replaces it.
|
||||
Layout: below 48rem of panel width the conversation switcher sits in the header (kind, title, live dot, follow toggle); wider than that the grouped rail (master, branches, deep chats, jobs) sits on the left. Right-click a row for its actions; on touch the `…` button is always visible. `↑`/`↓` walk the rows, `/` jumps to the search, `⌘↩` sends.
|
||||
|
||||
## Settings
|
||||
|
||||
- **Base URL** — markdown-frontend root, e.g. `http://localhost:62993` or `https://host/md`.
|
||||
- **API origin** — where `/api/…` lives, e.g. `http://localhost:62994` or `https://host`. Empty = derived from Base URL (`…/md` → `…`, port `62993` → `62994`).
|
||||
- **Bearer token** — needs the `messages` scope for sending and the `api` scope for the panel; a `*` bootstrap token covers both.
|
||||
- **Vault subpath** — folder in this vault that maps to the gateway's vault root.
|
||||
- **Test connection** / **Test API** — `GET /agents` on the markdown frontend, `GET /api/agents` on the API origin.
|
||||
- **Base URL** - markdown frontend root, e.g. `https://host/md`.
|
||||
- **API origin** - where `/api/…` lives; empty derives it from the base URL (`…/md` → `…`).
|
||||
- **Bearer token** - `messages` scope for sending, `api` scope for the panel (or a `*` bootstrap token).
|
||||
- **Vault subpath** - the folder in this vault that maps to the gateway's vault root.
|
||||
|
||||
## Note format
|
||||
Changing settings remounts open panels.
|
||||
|
||||
A minimal note that the plugin can send:
|
||||
## Build
|
||||
|
||||
```markdown
|
||||
---
|
||||
agent: beaver-opus-medium
|
||||
---
|
||||
|
||||
hi there
|
||||
```
|
||||
bun install
|
||||
bun run dev # css + esbuild watch
|
||||
bun run build # svelte-check + css (minified) + bundle
|
||||
bun run test # vitest (theme rules) + tests/smoke.mjs (jsdom, fake gateway)
|
||||
bun run preview # the panel in a browser, no Obsidian: http://localhost:4174/#<scene>
|
||||
make check # ultracite + svelte-check
|
||||
make install VAULT=~/Obsidian/my-vault
|
||||
make zip
|
||||
```
|
||||
|
||||
The gateway parses `### User:` / `### Assistant:` markers; a note with no markers is treated as a single user turn. After the first turn the gateway adds `conversation_id` to the frontmatter — that is what the activity panel keys off.
|
||||
Preview scenes (`#thread` default, `#switcher`, `#closed`, `#menu`, `#actions`, `#activity`, `#branch`, `#question`, `#deep`, `#empty`, `#offline`, `#stream`; suffix `-light` for the light theme). Width is the window: run headless Chrome with `--window-size=340,720` for the sidedock, `1100,720` for a tab, `390,800` for a phone. Set `VAULT=` or `THEME=` to preview against a community theme; `app.css` is read from the Obsidian install.
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
make install VAULT=~/Obsidian/my-vault # build + copy manifest.json, main.js, styles.css, versions.json into <vault>/.obsidian/plugins/beaver
|
||||
make zip # build → beaver-plugin-<version>.zip you can carry to any vault
|
||||
```
|
||||
`make install VAULT=…` copies `manifest.json`, `main.js`, `styles.css`, `versions.json` into `<vault>/.obsidian/plugins/beaver`; then enable **Beaver** in Settings → Community plugins. On a phone, sync the four files with Obsidian Sync ("Installed community plugins") or any file sync, and point the settings at a URL the phone can reach.
|
||||
|
||||
Then enable **Beaver** in Settings → Community plugins (or reload it if it was already enabled).
|
||||
## Version
|
||||
|
||||
### Mobile
|
||||
|
||||
The plugin is desktop-and-mobile (`isDesktopOnly: false`). Two things to get right on a phone:
|
||||
|
||||
- The base URL / API origin in plugin settings have to be reachable from the phone — `http://localhost:…` won't work; use the gateway's LAN IP, a tunnel, or a public hostname.
|
||||
- Get the plugin files onto the phone vault via Obsidian Sync (toggle "Installed community plugins" in your sync settings) or any file-sync tool you already use (Working Copy, Syncthing, etc.). `make install` only knows how to write to a local path.
|
||||
|
||||
### Hand-rolled
|
||||
|
||||
Four files are all Obsidian needs — `manifest.json`, `main.js`, `styles.css`, `versions.json`. Drop them in `<vault>/.obsidian/plugins/beaver/` however you like (unzip, `scp`, `rsync`, your own git, …).
|
||||
|
||||
## Bumping the version
|
||||
|
||||
Edit the version field in `manifest.json`, `package.json`, and `versions.json` by hand. Three files, one change each.
|
||||
Edit `manifest.json`, `package.json` and `versions.json` by hand.
|
||||
|
||||
Reference in New Issue
Block a user