Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ Read the full vision in [docs/spacedrive.md](docs/spacedrive.md).
### Prerequisites

- **Rust** 1.85+ ([rustup](https://rustup.rs/))
- An LLM API key from any supported provider (Anthropic, OpenAI, OpenRouter, Z.ai, Groq, Together, Fireworks, DeepSeek, xAI, Mistral, or OpenCode Zen)
- An LLM API key from any supported provider (Anthropic, OpenAI, OpenRouter, Ollama Cloud, Z.ai, Groq, Together, Fireworks, DeepSeek, xAI, Mistral, or OpenCode Zen)

### Build and Run

Expand Down
12 changes: 9 additions & 3 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ fn main() {

// Skip if bun isn't installed or node_modules is missing (CI without frontend deps)
if !interface_dir.join("node_modules").exists() {
eprintln!("cargo:warning=interface/node_modules not found, skipping frontend build. Run `bun install` in interface/");
eprintln!(
"cargo:warning=interface/node_modules not found, skipping frontend build. Run `bun install` in interface/"
);
ensure_dist_dir();
return;
}
Expand All @@ -28,10 +30,14 @@ fn main() {
match status {
Ok(s) if s.success() => {}
Ok(s) => {
eprintln!("cargo:warning=frontend build exited with {s}, the binary will serve a stale or empty UI");
eprintln!(
"cargo:warning=frontend build exited with {s}, the binary will serve a stale or empty UI"
);
}
Err(e) => {
eprintln!("cargo:warning=failed to run `bun run build`: {e}. Install bun to build the frontend.");
eprintln!(
"cargo:warning=failed to run `bun run build`: {e}. Install bun to build the frontend."
);
ensure_dist_dir();
}
}
Expand Down
2 changes: 2 additions & 0 deletions docs/content/docs/(configuration)/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ spacebot --config /path/to.toml # CLI override
anthropic_key = "env:ANTHROPIC_API_KEY"
openai_key = "env:OPENAI_API_KEY"
openrouter_key = "env:OPENROUTER_API_KEY"
ollama_key = "env:OLLAMA_API_KEY"

# --- Instance Defaults ---
# All agents inherit these. Individual agents can override any field.
Expand Down Expand Up @@ -168,6 +169,7 @@ Model names include the provider as a prefix:
| Anthropic | `anthropic/<model>` | `anthropic/claude-sonnet-4-20250514` |
| OpenAI | `openai/<model>` | `openai/gpt-4o` |
| OpenRouter | `openrouter/<provider>/<model>` | `openrouter/anthropic/claude-sonnet-4-20250514` |
| Ollama Cloud | `ollama/<model>` | `ollama/gpt-oss:20b` |

You can mix providers across process types. See [Routing](/docs/routing) for the full routing system.

Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/(deployment)/roadmap.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The full message-in → LLM → response-out pipeline is wired end-to-end across
- **Config** — hierarchical TOML with `Config`, `AgentConfig`, `ResolvedAgentConfig`, `Binding`, `MessagingConfig`. File watcher with event filtering and content hash debounce for hot-reload.
- **Multi-agent** — per-agent database isolation, `Agent` struct bundles all dependencies
- **Database connections** — SQLite + LanceDB + redb per-agent, migrations for all tables
- **LLM**`SpacebotModel` implements Rig's `CompletionModel`, routes through `LlmManager` via HTTP with retries and fallback chains across 11 providers (Anthropic, OpenAI, OpenRouter, Z.ai, Groq, Together, Fireworks, DeepSeek, xAI, Mistral, OpenCode Zen)
- **LLM**`SpacebotModel` implements Rig's `CompletionModel`, routes through `LlmManager` via HTTP with retries and fallback chains across 12 providers (Anthropic, OpenAI, OpenRouter, Ollama Cloud, Z.ai, Groq, Together, Fireworks, DeepSeek, xAI, Mistral, OpenCode Zen)
- **Model routing**`RoutingConfig` with process-type defaults, task overrides, fallback chains
- **Memory** — full stack: types, SQLite store (CRUD + graph), LanceDB (embeddings + vector + FTS), fastembed, hybrid search (RRF fusion). `memory_type` filter wired end-to-end through SearchConfig. `total_cmp` for safe sorting.
- **Memory maintenance** — decay + prune implemented
Expand Down
10 changes: 10 additions & 0 deletions interface/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ export interface BranchCompletedEvent {
conclusion: string;
}

export interface BranchFailedEvent {
type: "branch_failed";
agent_id: string;
channel_id: string;
branch_id: string;
error: string;
}

export interface ToolStartedEvent {
type: "tool_started";
agent_id: string;
Expand Down Expand Up @@ -112,6 +120,7 @@ export type ApiEvent =
| WorkerCompletedEvent
| BranchStartedEvent
| BranchCompletedEvent
| BranchFailedEvent
| ToolStartedEvent
| ToolCompletedEvent;

Expand Down Expand Up @@ -645,6 +654,7 @@ export interface ProviderStatus {
anthropic: boolean;
openai: boolean;
openrouter: boolean;
ollama: boolean;
zhipu: boolean;
groq: boolean;
together: boolean;
Expand Down
27 changes: 27 additions & 0 deletions interface/src/hooks/useChannelLiveState.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import {
api,
type BranchFailedEvent,
type BranchCompletedEvent,
type BranchStartedEvent,
type InboundMessageEvent,
Expand Down Expand Up @@ -397,6 +398,31 @@ export function useChannelLiveState(channels: ChannelInfo[]) {
});
}, [updateItem]);

const handleBranchFailed = useCallback((data: unknown) => {
const event = data as BranchFailedEvent;

// Remove from active branches
setLiveStates((prev) => {
const state = prev[event.channel_id];
if (!state?.branches[event.branch_id]) return prev;
const { [event.branch_id]: _, ...remainingBranches } = state.branches;
return {
...prev,
[event.channel_id]: { ...state, branches: remainingBranches },
};
});

// Update timeline item with failure summary
updateItem(event.channel_id, event.branch_id, (item) => {
if (item.type !== "branch_run") return item;
return {
...item,
conclusion: `Branch failed: ${event.error}`,
completed_at: new Date().toISOString(),
};
});
}, [updateItem]);

const handleToolStarted = useCallback((data: unknown) => {
const event = data as ToolStartedEvent;
const channelId = event.channel_id;
Expand Down Expand Up @@ -612,6 +638,7 @@ export function useChannelLiveState(channels: ChannelInfo[]) {
worker_completed: handleWorkerCompleted,
branch_started: handleBranchStarted,
branch_completed: handleBranchCompleted,
branch_failed: handleBranchFailed,
tool_started: handleToolStarted,
tool_completed: handleToolCompleted,
};
Expand Down
1 change: 1 addition & 0 deletions interface/src/lib/providerIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function ProviderIcon({ provider, className = "text-ink-faint", size = 24
anthropic: Anthropic,
openai: OpenAI,
openrouter: OpenRouter,
ollama: OpenRouter,
groq: Groq,
mistral: Mistral,
deepseek: DeepSeek,
Expand Down
8 changes: 4 additions & 4 deletions interface/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {AgentMemories} from "@/routes/AgentMemories";
import {AgentConfig} from "@/routes/AgentConfig";
import {AgentCron} from "@/routes/AgentCron";
import {AgentIngest} from "@/routes/AgentIngest";
import {AgentWorkers} from "@/routes/AgentWorkers";
import {Settings} from "@/routes/Settings";
import {useLiveContext} from "@/hooks/useLiveContext";
import {AgentTabs} from "@/components/AgentTabs";
Expand Down Expand Up @@ -170,13 +171,12 @@ const agentWorkersRoute = createRoute({
path: "/agents/$agentId/workers",
component: function AgentWorkersPage() {
const {agentId} = agentWorkersRoute.useParams();
const {liveStates, channels} = useLiveContext();
return (
<div className="flex h-full flex-col">
<AgentHeader agentId={agentId} />
<div className="flex flex-1 items-center justify-center">
<p className="text-sm text-ink-faint">
Workers control interface coming soon
</p>
<div className="flex-1 overflow-hidden">
<AgentWorkers agentId={agentId} channels={channels} liveStates={liveStates} />
</div>
</div>
);
Expand Down
Loading