From 49262c612c3e6b204f74b8e16b2676cbc348f4c4 Mon Sep 17 00:00:00 2001 From: Richard Zink Date: Sun, 8 Nov 2026 16:54:08 +0100 Subject: [PATCH] Users, file manager, image generation, budgets and agent workflow - User management: local accounts (Argon2) and OpenID Connect, admin/user roles, per-user folders plus admin-shared folders, read-only session sharing, event filtering per user - File manager with CodeMirror editor and git client (status, diff, stage, commit, branches, fetch/pull/push, log, clone/init) - Image generation via fal.ai: generate_image tool and image page - Budgets: monthly limits per provider, provider balances/costs (OpenRouter, Anthropic admin cost report, fal.ai), price discovery - Subagents on other models, background tasks, concurrency limits per host/model/subagent - MCP tools loaded on demand (mcp_search/mcp_call), read-only hints - Permission mode "auto", safe read-only commands always allowed - Persistent message queue with attachments, deletable - Live reasoning incl. tags, activity animations, CSS themes, mobile layout, statistics per user, upload size handling - Docker: named volume for state, localhost-bound port Co-Authored-By: Claude Opus 5.5 --- .env.example | 14 +- .gitignore | 1 + README.md | 261 ++++-- config.example.toml | 110 ++- docker-compose.yml | 29 +- docs/images/chat.png | Bin 0 -> 101308 bytes docs/images/mobile.png | Bin 0 -> 130339 bytes docs/images/settings.png | Bin 0 -> 102317 bytes docs/images/stats.png | Bin 0 -> 111081 bytes server/Cargo.lock | 129 ++- server/Cargo.toml | 6 +- server/src/api.rs | 527 ++++++++--- server/src/app.rs | 158 +++- server/src/auth.rs | 612 +++++++++++++ server/src/budget.rs | 223 +++++ server/src/config.rs | 197 +++- server/src/files.rs | 300 ++++++ server/src/git.rs | 348 +++++++ server/src/images.rs | 237 +++++ server/src/main.rs | 8 +- server/src/mcp.rs | 154 +++- server/src/permission.rs | 119 ++- server/src/prompts.rs | 81 +- server/src/provider/anthropic.rs | 3 + server/src/provider/mod.rs | 39 +- server/src/provider/openai.rs | 94 +- server/src/runner.rs | 365 ++++++-- server/src/store.rs | 491 +++++++++- server/src/tools/agent.rs | 6 +- server/src/tools/mod.rs | 25 +- server/src/types.rs | 32 + web/package-lock.json | 632 +++++++++++++ web/package.json | 8 + web/src/App.tsx | 55 +- web/src/api.ts | 33 +- web/src/components/Activity.tsx | 80 ++ web/src/components/Budget.tsx | 98 ++ web/src/components/Charts.tsx | 10 +- web/src/components/ChatView.tsx | 132 ++- web/src/components/CodeEditor.tsx | 96 ++ web/src/components/Composer.tsx | 39 +- web/src/components/Diff.tsx | 2 +- web/src/components/Files.tsx | 442 +++++++++ web/src/components/GitPanel.tsx | 252 +++++ web/src/components/Images.tsx | 176 ++++ web/src/components/Login.tsx | 48 +- web/src/components/ModelSelect.tsx | 3 +- web/src/components/NewSession.tsx | 12 + web/src/components/QueueList.tsx | 48 + web/src/components/Settings.tsx | 266 ++++-- web/src/components/ShareDialog.tsx | 72 ++ web/src/components/Sidebar.tsx | 20 +- web/src/components/Stats.tsx | 36 +- web/src/components/ToolCard.tsx | 38 +- web/src/components/settings/Account.tsx | 47 + web/src/components/settings/AgentsEditor.tsx | 183 ++++ web/src/components/settings/Folders.tsx | 75 ++ web/src/components/settings/Users.tsx | 117 +++ web/src/i18n/de.ts | 231 ++++- web/src/i18n/en.ts | 231 ++++- web/src/i18n/index.ts | 5 +- web/src/prefs.ts | 27 + web/src/store.ts | 26 +- web/src/styles.css | 909 ++++++++++++++++--- web/src/theme.ts | 35 +- web/src/themes/base.css | 6 + web/src/themes/gruvbox-dark.css | 55 ++ web/src/themes/gruvbox-light.css | 54 ++ web/src/themes/index.ts | 4 + web/src/types.ts | 37 +- 70 files changed, 8495 insertions(+), 714 deletions(-) create mode 100644 docs/images/chat.png create mode 100644 docs/images/mobile.png create mode 100644 docs/images/settings.png create mode 100644 docs/images/stats.png create mode 100644 server/src/auth.rs create mode 100644 server/src/budget.rs create mode 100644 server/src/files.rs create mode 100644 server/src/git.rs create mode 100644 server/src/images.rs create mode 100644 web/src/components/Activity.tsx create mode 100644 web/src/components/Budget.tsx create mode 100644 web/src/components/CodeEditor.tsx create mode 100644 web/src/components/Files.tsx create mode 100644 web/src/components/GitPanel.tsx create mode 100644 web/src/components/Images.tsx create mode 100644 web/src/components/QueueList.tsx create mode 100644 web/src/components/ShareDialog.tsx create mode 100644 web/src/components/settings/Account.tsx create mode 100644 web/src/components/settings/AgentsEditor.tsx create mode 100644 web/src/components/settings/Folders.tsx create mode 100644 web/src/components/settings/Users.tsx create mode 100644 web/src/prefs.ts create mode 100644 web/src/themes/base.css create mode 100644 web/src/themes/gruvbox-dark.css create mode 100644 web/src/themes/gruvbox-light.css create mode 100644 web/src/themes/index.ts diff --git a/.env.example b/.env.example index e181950..0e8f085 100644 --- a/.env.example +++ b/.env.example @@ -1,11 +1,17 @@ -# Access token for the web UI (required). Generate one with: openssl rand -hex 32 -CODECRAFT_TOKEN= +# Optional: create the first admin on first start. Otherwise the server logs a +# one-time setup code and you create the admin in the web UI. +CODECRAFT_ADMIN_USER= +CODECRAFT_ADMIN_PASSWORD= + +# Single sign-on client secret (see [auth.oidc] in config.toml). +OIDC_CLIENT_SECRET= # Directory with your projects, mounted as /workspace. -WORKSPACE=/home/zink/Projects +WORKSPACE=/path/to/your/projects -# Host port. +# Host port and address (127.0.0.1 = only reachable through a local reverse proxy). PORT=8787 +BIND_ADDR=127.0.0.1 # UID/GID that owns the project files (id -u / id -g). UID=1000 diff --git a/.gitignore b/.gitignore index dc2dddf..566295e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ *.db *.db-wal *.db-shm +docker-compose.override.yml diff --git a/README.md b/README.md index 67f3952..5f36564 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,226 @@ -# CodeCraft +

+ CodeCraft icon: angle brackets with a slash on a dark tile +

-A self-hosted, web-based coding harness for AI models – in the spirit of Claude Code and -opencode, usable from any browser and installable as a PWA. +

CodeCraft

-- **Any model**: Anthropic (native Messages API), Google Gemini, opencode Go, fal.ai, - OpenRouter and self-hosted models (Ollama, vLLM, llama.cpp, LM Studio) through - OpenAI-compatible endpoints. -- **Agent loop with tools**: read, write, edit, list, glob, grep, bash, web_fetch, - todo_write, ask_user, task (subagents), skill – plus every tool of your MCP servers. -- **Modes**: `build` (full access), `plan` (read-only, proposes a plan), `craft` - (study a target → build your own version → check visually → refine), and custom - agents. -- **Human in the loop**: permission requests (with diffs) and questions from agents *and* - MCP servers (elicitation) are forwarded to the UI; optional browser notifications. -- **MCP**: stdio and Streamable HTTP servers, configured in the UI or the config file. -- **Statistics**: every model request is recorded (tokens in/out, cache, time to first - token, prefill and output speed, cost per model price) – per chat and in a dashboard - with daily charts and a weekday × hour heatmap. -- **Claude Code compatible project files**: `AGENTS.md`, `CLAUDE.md`, - `.claude/{agents,commands,skills}` (and `.codecraft/…`). -- **Configuration** in a commented TOML file that the web UI edits in place (comments - are preserved). -- **i18n**: English and German; adding a language is one file. +

+ A self-hosted coding agent for your browser: any model, your tools, your rules.
+ Run Claude, Gemini, opencode Go, fal.ai or your own models as coding agents with files, shell, MCP and subagents,
+ approve what they do from any device, and see exactly what every request costs. +

-## Quick start (Docker) +

+ Rust backend + React PWA + Docker + Status: early alpha +

+ +

+ How it works · + Modes · + Models · + Users · + Statistics · + Get started · + Configuration · + Themes · + Development · + Status +

+ +
+ +

+ A CodeCraft session: the agent edits a file, a permission card shows the diff and asks before running a command, the task list shows what is left +
+ A session: the agent works through its task list, shows diffs, and asks before anything outside the rules. +

+ + + + + + + + +
+

🧠 Any model

+ Anthropic natively, plus everything that speaks the OpenAI API: Gemini, opencode Go, fal.ai, OpenRouter, Ollama, vLLM, llama.cpp. Mix them in one chat. +
+

🛠️ Real tools

+ Read, write, edit, glob, grep, shell, web fetch, task lists, subagents, skills – and every tool of your MCP servers. +
+

🙋 You decide

+ Permission requests with diffs, questions from agents and MCP servers, per-tool rules. On your phone, with notifications. +
+

📊 Every token counted

+ Tokens, cache hits, prefill and output speed, and cost per request – per chat and in a dashboard. +
+ +
+ +## How it works + +CodeCraft runs on your server, next to your projects. You talk to it from a browser or the +installed app; the agent keeps working when you close the tab. + +1. **Pick a folder and a mode.** Every user has their own folder; admins can share more. +2. **Say what you want.** The agent plans with a task list, reads the code and makes changes. +3. **Approve or answer.** Anything outside your rules shows up as a card: allow once, allow + for the session, or reject with feedback. Questions from agents and MCP servers too. +4. **Delegate.** The agent hands work to subagents – in parallel or in the background, on + other models and hosts – and continues while they run. + + + + + + +
+ CodeCraft on a phone: a session with a question card offering three options
+ Mobile: answer questions and approve changes from your phone. +
+ Settings: providers with models, context sizes and prices per million tokens
+ Settings: providers, models, prices, MCP, permissions and users – all in the UI. +
+ +## Modes + +| Mode | What it does | +|---|---| +| **build** | Full access: implement, edit, run. | +| **plan** | Read-only: investigate, ask, and propose a step-by-step plan. One click switches to build. | +| **craft** | Study a target, build your own version, check it visually, refine. Websites are captured at several screen widths in a headless browser and compared with the build (similarity per page and width). *Clean room* uses only what a visitor sees; *deep mode* may read the page source. | +| *custom* | Your own modes and subagents – in the UI, the config file or as Markdown in `.codecraft/agents` / `.claude/agents`. | + +Projects keep working the way they do in Claude Code and opencode: `AGENTS.md`, `CLAUDE.md`, +slash commands in `.claude/commands` and skills in `.claude/skills` are picked up automatically. + +## Models + +Every provider is an endpoint with a list of models. Each model can have a context size, +output limit, reasoning effort, extra request options and prices per million tokens. + +- **Several models per chat:** switch the model at any time, give modes a fixed model, and + run subagents on other models – e.g. a coding model on one GPU host and a small model that + writes documentation and translations in the background on another. +- **Concurrency limits:** `max_concurrency` per host or model queues requests for servers that + can only process one at a time; `max_parallel` limits instances of a subagent. Waiting time + is shown in the chat and never counted as model latency. +- **Background subagents:** the agent starts them with `background = true`, keeps working, + and receives their result as a message when they finish. + +## Users + +- Local accounts (Argon2 passwords) and single sign-on via OpenID Connect (Authentik, Authelia, + Keycloak, …) with optional admin and access groups. +- **Admins** manage providers, MCP, permissions, users and shared folders and see all statistics. + **Users** see their own sessions and statistics. +- Every user works in `/`; admins can share additional folders with + selected users. Sessions are private and can be shared read-only. + +> [!NOTE] +> File tools are confined to a user's folders. Shell commands run in the shared container and +> are **not** sandboxed per user – only give accounts to people you trust with that container. + +## Statistics + +

+ The statistics dashboard: cost and token tiles, cost per day stacked by model, tokens per day, and an activity heatmap by weekday and hour +

+ +Every model request is stored with input, output and cache tokens, time to first token, +prefill and output speed, and its cost at the price configured at that moment. The dashboard +shows totals, cost per day by model, tokens per day, a weekday × hour heatmap, and tables per +model, provider, mode, user and session – each chart also as a table. + +## Get started ```sh -cp .env.example .env # set CODECRAFT_TOKEN, WORKSPACE and provider keys +git clone codecraft && cd codecraft +cp .env.example .env # workspace path, provider keys, optional first admin docker compose up -d --build +docker compose logs codecraft # shows the one-time setup code for the first admin ``` -Open `http://:8787`, sign in with the token and pick a project directory. On first -start the commented default config is written to `data/config.toml`. Put the service behind -a TLS reverse proxy for remote access – the PWA install prompt requires HTTPS. +All state – configuration, database, the agent's home directory – lives in the Docker volume +`codecraft-data`; rebuilding or recreating the container keeps it. Your projects are mounted +from `WORKSPACE`. + +The port is bound to `127.0.0.1` by default. Put a TLS reverse proxy in front of it (enable +WebSocket support) and set `auth.public_url` – installing the PWA requires HTTPS. ## Configuration -See [`config.example.toml`](config.example.toml) – every option is documented there. -Highlights: +One commented TOML file, `/data/config.toml` in the volume, created from +[`config.example.toml`](config.example.toml) on first start. The settings page edits it in +place and keeps your comments. Secrets belong in the environment and are referenced as +`${NAME}`. -- `[providers.]` – `kind = "anthropic" | "openai"`, `base_url`, `api_key` - (`${ENV}` references recommended), optional `models` with context size, effort and - prices per 1M tokens. Without `models`, the list is fetched from the provider. -- `[mcp.]` – `type = "stdio"` (command/args/env) or `"http"` (url/headers). -- `[permissions]` – `allow` / `ask` / `deny` per tool (globs such as `mcp__*`), plus - `[permissions.bash_rules]` for shell command patterns. Compound commands are only - auto-allowed when every part is allowed. -- `[agents.]` – custom modes and subagents. -- `[craft]` – Chromium binary, screenshot widths, clean room vs. deep mode. +| Section | Purpose | +|---|---| +| `[server]` | workspace, default and small model, currency, auto-compaction, subagent limit | +| `[providers.]` | `kind = "anthropic" \| "openai"`, `base_url`, `api_key`, `max_concurrency`, `models` | +| `[mcp.]` | `type = "stdio"` (command, args, env) or `"http"` (url, headers) | +| `[permissions]` | `allow` / `ask` / `deny` per tool (globs like `mcp__*`); `bash_rules` for commands | +| `[agents.]` | modes and subagents: model, tools, `max_parallel`, prompt | +| `[auth]`, `[auth.oidc]` | sign-in, public URL, single sign-on | +| `[craft]` | headless browser, screen widths, clean room | + +Compound shell commands (`&&`, `;`, `|`) are only auto-allowed when every part is allowed. + +## Themes + +Colours, syntax highlighting and chart palettes come from CSS variables. Each theme is one +file in [`web/src/themes`](web/src/themes) (Gruvbox Dark and Light ship by default): + +1. Copy `gruvbox-dark.css` to `.css`, change the values on `:root[data-theme=""]`. +2. Import it in `themes/index.ts` and add it to `THEMES` in `web/src/theme.ts`. + +The variables cover surfaces (`--bg0`–`--bg3`), text (`--fg0`–`--fg3`, `--muted`), accents, +diffs, syntax (`--syntax-*`), charts (`--series-1`–`8` categorical, `--seq-0`–`6` sequential) +and the browser bar colour (`--theme-color`). The chart colours are validated for colour-vision +deficiencies against the background – re-check them when the background changes a lot. ## Development -Requirements: Rust (edition 2024), Node ≥ 20.19. +Requirements: Rust (edition 2024) and Node ≥ 20.19. ```sh -# backend (writes ./data/config.toml on first start) +# backend – writes ./data/config.toml on first start cd server && CODECRAFT_CONFIG=../data/config.toml cargo run # frontend with hot reload, proxies /api to 127.0.0.1:8787 cd web && npm install && npm run dev ``` -For local development set `bind = "127.0.0.1:8787"` and `workspace` to your projects -directory in `data/config.toml`. - -Tests: `cd server && cargo test`, type check: `cd web && npx tsc --noEmit`. - -## Architecture +Tests: `cd server && cargo test` · type check: `cd web && npx tsc --noEmit`. ``` -server/ (Rust, axum + tokio) - provider/ Anthropic + OpenAI-compatible streaming clients - runner.rs agent loop: streaming, tool execution, permissions, subagents, compaction - tools/ built-in tools (fs, bash, web, agent, craft) - mcp.rs MCP client (stdio, Streamable HTTP, elicitation) - prompts.rs agents/modes, system prompt, AGENTS.md, slash commands, skills - permission.rs, config.rs (toml + toml_edit), store.rs (SQLite), api.rs (REST + WebSocket) -web/ (React + Vite, PWA) - src/components chat, composer, permission/question cards, settings, statistics - src/i18n en (reference), de +server/ Rust (axum, tokio) + runner.rs agent loop: streaming, tools, permissions, subagents, compaction + provider/ Anthropic and OpenAI-compatible streaming clients, concurrency slots + tools/ built-in tools (files, shell, web, agent, craft) + mcp.rs MCP client (stdio, Streamable HTTP, elicitation) + auth.rs users, sessions, OpenID Connect + prompts.rs modes, system prompt, AGENTS.md, commands, skills +web/ React + Vite PWA + src/components chat, composer, cards, settings, statistics + src/themes CSS themes + src/i18n English (reference) and German ``` -The server streams all events over one WebSocket (`/api/ws`); the UI reconnects and -resyncs automatically, so long-running agents keep going when the browser is closed. +Translations: copy `web/src/i18n/en.ts`, translate the values and register the language in +`web/src/i18n/index.ts` – the type checker reports missing keys. -## Roadmap +## Status -- Background shell processes, file checkpoints/undo, `@file` mentions -- MCP OAuth, resources and prompts; remote MCP over SSE (legacy) -- Hooks (pre/post tool), output styles, per-project config overrides -- Image generation tools (fal.ai), OpenAI Responses API -- Multi-user accounts +Early alpha. Planned next: + +- Per-user sandboxes for shell commands +- File checkpoints and undo, background shells, `@file` mentions +- MCP OAuth, resources and prompts +- Hooks before/after tool calls +- Image generation tools (fal.ai) and the OpenAI Responses API diff --git a/config.example.toml b/config.example.toml index cb10f18..2c9a80b 100644 --- a/config.example.toml +++ b/config.example.toml @@ -24,10 +24,6 @@ workspace = "/workspace" # Where sessions (SQLite) and runtime state are stored. data_dir = "/data" -# Access token for the web UI and API. Leave empty to disable auth (only do -# this on localhost!). Recommended: auth_token = "${CODECRAFT_TOKEN}" -auth_token = "${CODECRAFT_TOKEN}" - # Default model for new sessions, as "/". default_model = "anthropic/claude-sonnet-5-5" @@ -37,6 +33,14 @@ small_model = "anthropic/claude-haiku-5-5" # Compact the conversation automatically when the context is this full (0.0–1.0). auto_compact_threshold = 0.85 +# Max. subagents running at the same time (all sessions). Additionally, each +# subagent can be limited with max_parallel, and each provider/model with +# max_concurrency (see below). +max_parallel_subagents = 4 + +# MCP servers with more tools than this are loaded on demand (see [mcp.*] load). +mcp_direct_limit = 15 + # Currency label for the cost statistics (prices below are in this currency). currency = "USD" @@ -57,6 +61,13 @@ currency = "USD" # Cache prices default to input_price when omitted. Costs are stored per request, # so changing a price only affects future requests. # +# Concurrency: max_concurrency limits simultaneous requests – on the provider +# (= the host) and/or per model. Requests beyond the limit wait in a queue, +# e.g. for self-hosted models that can only process one request at a time. +# +# options: extra request body fields sent as-is, e.g. +# options = { temperature = 0.5, reasoning_budget_tokens = 5000 } +# # Authentication: by default "Authorization: Bearer " (openai) or # "x-api-key: " (anthropic). Override with auth_header / auth_prefix. # Extra static headers: headers = { "X-Foo" = "bar" } @@ -112,6 +123,7 @@ enabled = false kind = "openai" base_url = "http://host.docker.internal:11434/v1" api_key = "" +max_concurrency = 1 # one request at a time on this host models = [] enabled = false @@ -122,6 +134,10 @@ enabled = false # type = "stdio" → command + args (+ env), started as a child process # type = "http" → Streamable HTTP endpoint (url + optional headers) # Tools appear to the model as mcp____. +# load = "auto" (default) | "always" | "on_demand": servers with many tools +# (more than server.mcp_direct_limit) are not sent with every request; the +# model finds them with mcp_search and calls them with mcp_call. This keeps +# requests small – important for self-hosted models. # MCP servers may ask you questions (elicitation); they show up in the UI. # ----------------------------------------------------------------------------- @@ -163,6 +179,7 @@ bash = "ask" web_fetch = "ask" browser_snapshot = "allow" image_compare = "allow" +generate_image = "ask" # costs money per image "mcp__*" = "ask" # Shell command patterns (glob, matched against the full command). First match wins. @@ -185,14 +202,85 @@ image_compare = "allow" # mode switcher, mode = "subagent" is only available through the task tool. # ----------------------------------------------------------------------------- -# [agents.reviewer] +# Subagents can run on a different model (and host) than the chat, and in the +# background: the main agent calls task(background = true), keeps working and +# receives the result as a message when the subagent is done. + +# [agents.writer] # mode = "subagent" -# description = "Reviews code changes for bugs and style issues." -# model = "anthropic/claude-opus-5-5" # optional, otherwise session model -# tools = ["read", "glob", "grep", "bash"] # optional, default: all tools -# prompt = """ -# You are a meticulous code reviewer. ... -# """ +# description = "Writes documentation, summaries, guides and translations." +# model = "local/qwen3-4b" # optional, otherwise the session model +# tools = ["read", "glob", "grep"] # optional, default: all tools +# max_parallel = 1 # optional, instances at the same time +# prompt = ''' +# You are a technical writer. Write clear, well-structured text. +# ''' + + +# ----------------------------------------------------------------------------- +# Image generation (fal.ai) +# ----------------------------------------------------------------------------- +# Agents get the tool generate_image; the "Images" page creates images manually. +# Images are saved in the project (output_dir) and listed in a gallery. +# Model ids: https://fal.ai/models – price is per image (for the statistics). +# size_param: "image_size" (FLUX-style presets, default) or "aspect_ratio" ("16:9"). +# ----------------------------------------------------------------------------- + +[images] +enabled = false +base_url = "https://fal.run" +api_key = "${FAL_KEY}" +default_model = "fal-ai/flux/schnell" +output_dir = "generated" +models = [ + { id = "fal-ai/flux/schnell", name = "FLUX.1 schnell – fast", price = 0.003 }, + { id = "fal-ai/flux-2-pro", name = "FLUX.2 pro" }, + { id = "fal-ai/ideogram/v3", name = "Ideogram 3 – text in images", size_param = "aspect_ratio" }, + { id = "fal-ai/recraft/v4/pro/text-to-image", name = "Recraft V4 pro – design" }, +] + + +# ----------------------------------------------------------------------------- +# Users & sign-in +# ----------------------------------------------------------------------------- +# Users are managed in the web UI (Settings → Users). On first start without +# users, the server prints a one-time setup code to its log; use it in the UI +# to create the first admin – or set CODECRAFT_ADMIN_USER and +# CODECRAFT_ADMIN_PASSWORD in the environment. +# +# Every user works in their own directory /; admins can +# use the whole workspace. Note: file tools are confined to that directory, +# but shell commands run in the shared container and are not sandboxed. +# ----------------------------------------------------------------------------- + +[auth] +# Lifetime of a login in days. +session_days = 30 +# External URL of this server – required for single sign-on (OIDC redirects). +public_url = "" +# Secure cookies: "auto" (when served via https), "true" or "false". +cookie_secure = "auto" + +# Single sign-on via OpenID Connect (Authentik, Authelia, Keycloak, ...). +# Redirect URI to register at the IdP: /api/auth/oidc/callback +[auth.oidc] +enabled = false +issuer = "https://auth.example.org/application/o/codecraft/" +client_id = "codecraft" +client_secret = "${OIDC_CLIENT_SECRET}" +scopes = ["openid", "profile", "email", "groups"] +username_claim = "preferred_username" +groups_claim = "groups" +# Members of this group are admins; empty = manage roles in CodeCraft. +admin_group = "" +# Only members of this group may sign in; empty = no restriction. +allowed_group = "" +# Create users automatically on their first sign-in. +auto_create = true +# Link a sign-in to an existing local user with the same username. +# Only enable this if you trust the usernames of your IdP. +link_by_username = false +button_label = "Single sign-on" # ----------------------------------------------------------------------------- diff --git a/docker-compose.yml b/docker-compose.yml index 8b628ad..34b783f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,10 @@ # CodeCraft – copy .env.example to .env and adjust, then: docker compose up -d --build +# +# Persistent state lives in the named volume "codecraft-data" (mounted at /data): +# /data/config.toml configuration (also editable in the web UI) +# /data/codecraft.db sessions, messages, statistics +# /data/home home directory of the agent (git config, caches, ...) +# Rebuilding or recreating the container keeps all of it. services: codecraft: build: . @@ -7,21 +13,24 @@ services: restart: unless-stopped # Same UID/GID as the owner of your project files. user: "${UID:-1000}:${GID:-1000}" + # Bound to localhost by default: expose it through a TLS reverse proxy. + # Set BIND_ADDR=0.0.0.0 to publish it on all interfaces. ports: - - "${PORT:-8787}:8787" - environment: - CODECRAFT_TOKEN: ${CODECRAFT_TOKEN:?set CODECRAFT_TOKEN in .env} - ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} - GEMINI_API_KEY: ${GEMINI_API_KEY:-} - OPENCODE_API_KEY: ${OPENCODE_API_KEY:-} - FAL_KEY: ${FAL_KEY:-} - OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-} + - "${BIND_ADDR:-127.0.0.1}:${PORT:-8787}:8787" + # All variables from .env are available to the config as ${NAME} + # (provider keys, MCP tokens, OIDC secret, initial admin). + env_file: + - path: .env + required: false volumes: - # Config (config.toml), SQLite database, agent home directory. - - ./data:/data + - codecraft-data:/data # The projects the agents may work on. - ${WORKSPACE:-./workspace}:/workspace # Reach self-hosted models (Ollama, vLLM, ...) on the host. extra_hosts: - "host.docker.internal:host-gateway" shm_size: "1gb" # Chromium (craft mode) + +volumes: + codecraft-data: + name: codecraft-data diff --git a/docs/images/chat.png b/docs/images/chat.png new file mode 100644 index 0000000000000000000000000000000000000000..62671dfe75ac55cca55b68c6df9e0514cc7c7583 GIT binary patch literal 101308 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

yv=SlRq63X>oyw9Za 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%0DQ5L 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

^m>{{;loBWTbgYt8j|?3MQGoa@8{4>toN~G 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 literal 0 HcmV?d00001 diff --git a/docs/images/settings.png b/docs/images/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..3ce1ded4a9af01f5c0cc4ba03fcd6476a0e51bb2 GIT binary patch literal 102317 zcmcG$WmH^C)GbOvfCv)7^#BP3cefDS-7R?I?lcWdj^WVfXnfhc!<#s_lo2oHp{@y^D{}C{V8)Yxl3YsFk#QC? zWJkzffN?A4?-I%<4$;X!MU*jgQPe+WPLgL>e~Oc~uQ~s#-0(kK`8t|ZktQMh!`n7_ zW=rBoF&t*8Ul5nJzd8PN`1p<(?}>ELw6jxDB^@1V9K+@duaH(%`UJK=6^CLlo`?1~*CB$O?xltuuz?}7s2c{mH?kI`+3Cn=L$;<81(MP}~E}d0~gNL4tvQB5w@j+3)XdNC-zw$^=2n%U;&nAPa!yaX zP~|5y!v*ok(|!uZy`r6;=sK&6h(!Z z+D^=#lbZkesFSC}c$}P>?BD3dH$lm7g~Q4`^QTts+JMJLoUdHO%qi$0Gkv4lh&dij zcBY*rrh$7c$@BdkDKZI(qVS=#p`o-{A!|<_G1RR4d#Yzx2(E@=?EFN^o?OqnYL1f^ zRs#dx<1zDxGB3vtdgBDxAPO32H3)%UYbE{u;c zcpv^`Vv1ghQF$lANY8A#-ZM5Up7R*`wZCemwWr(ILY|1%TARm34K-YxIOSWj$=G;r zE&$r&v63<#qj|{c(*l(6-+DLWu=aw2r<``>tgD4)CYb*=R-4^KnqW?jw%z08@g|IC ze;?}Bk(E`+-*ol*H;5U@lE>R@kCnnyW2 zPFEN|QJ(Wi$4Fmgh$Rg7cThMYUN0)6l$Dk>YOq&=5$ywPI_thf)x=iBJ3nr~)QwC0UwO!olMg2}K�_Xcc}SiM1>NPrtJp!AsmizpjS|puwDk^>E$n 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 H@cI7%TEv8_ literal 0 HcmV?d00001 diff --git a/docs/images/stats.png b/docs/images/stats.png new file mode 100644 index 0000000000000000000000000000000000000000..ccb86457f3c9418e5023b15a0aea507974958222 GIT binary patch literal 111081 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%0D-F66 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 z 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 -pub struct ApiError(StatusCode, String); +pub struct ApiError(pub StatusCode, pub String); impl IntoResponse for ApiError { fn into_response(self) -> Response { @@ -33,15 +36,33 @@ impl> From for ApiError { } } -type ApiResult> = Result; +pub type ApiResult> = Result; fn not_found(what: &str) -> ApiError { ApiError(StatusCode::NOT_FOUND, format!("{what} not found")) } +fn forbidden() -> ApiError { + ApiError(StatusCode::FORBIDDEN, "no access to this session".into()) +} + pub fn router(app: AppState) -> Router { let api = Router::new() - .route("/info", get(info)) + // public + .route("/info", get(auth::info)) + .route("/auth/login", post(auth::login)) + .route("/auth/logout", post(auth::logout)) + .route("/auth/setup", post(auth::setup)) + .route("/auth/oidc/start", get(auth::oidc_start)) + .route("/auth/oidc/callback", get(auth::oidc_callback)) + // signed in + .route("/auth/password", post(auth::change_password)) + .route("/users", get(auth::list_users).post(auth::create_user)) + .route("/users/directory", get(auth::directory)) + .route("/users/{id}", axum::routing::patch(auth::update_user).delete(auth::delete_user)) + .route("/folders", get(list_folders).post(create_folder)) + .route("/folders/{id}", delete(delete_folder)) + .route("/folders/{id}/access", put(set_folder_access)) .route("/config", get(get_config).patch(patch_config)) .route("/config/raw", get(get_config_raw).put(put_config_raw)) .route("/providers/reload", post(reload_providers)) @@ -55,15 +76,38 @@ pub fn router(app: AppState) -> Router { .route("/sessions/{id}/compact", post(compact)) .route("/sessions/{id}/rewind", post(rewind)) .route("/sessions/{id}/stats", get(session_stats)) + .route("/sessions/{id}/shares", get(get_shares).put(put_shares)) + .route("/sessions/{id}/queue", get(get_queue)) + .route("/sessions/{id}/queue/flush", post(flush_queue)) + .route("/sessions/{id}/queue/{item}", delete(delete_queue_item)) .route("/permissions/{id}", post(reply_permission)) .route("/questions/{id}", post(answer_question)) .route("/pending", get(pending)) .route("/mcp", get(mcp_status)) .route("/mcp/reload", post(mcp_reload)) + .route("/fs/roots", get(list_roots)) + .route("/files/list", get(crate::files::list)) + .route("/files/read", get(crate::files::read)) + .route("/files/raw", get(crate::files::raw)) + .route("/files/write", put(crate::files::write)) + .route("/files/create", post(crate::files::create)) + .route("/files/move", post(crate::files::move_or_copy)) + .route("/files/delete", delete(crate::files::remove)) + .route("/files/upload", put(crate::files::upload)) + .route("/files/search", get(crate::files::search)) + .route("/git", post(crate::git::handle)) .route("/fs/dirs", get(list_dirs)) .route("/stats", get(stats)) + .route("/budget", get(budget)) + .route("/providers/{name}/prices", get(provider_prices)) + .route("/images", get(list_images).post(create_images)) + .route("/images/config", get(images_config)) + .route("/images/{id}", delete(delete_image)) + .route("/images/{id}/file", get(image_file)) .route("/ws", get(ws)) - .layer(middleware::from_fn_with_state(app.clone(), auth)); + .layer(middleware::from_fn_with_state(app.clone(), auth::require_user)) + // Prompts may carry images (base64); the default limit is 2 MB. + .layer(axum::extract::DefaultBodyLimit::max(50 * 1024 * 1024)); let web_dir = std::env::var("CODECRAFT_WEB").map(PathBuf::from).unwrap_or_else(|_| { ["../web/dist", "./web/dist", "/app/web"] @@ -73,64 +117,42 @@ pub fn router(app: AppState) -> Router { .unwrap_or_else(|| PathBuf::from("./web/dist")) }); let index = web_dir.join("index.html"); - let static_files = tower_http::services::ServeDir::new(&web_dir) - .fallback(tower_http::services::ServeFile::new(index)); + let static_files = tower_http::services::ServeDir::new(&web_dir).fallback(tower_http::services::ServeFile::new(index)); Router::new().nest("/api", api).fallback_service(static_files).with_state(app) } -/// Bearer token, `token` query parameter (WebSocket) or `codecraft_token` cookie. -async fn auth(State(app): State, req: Request, next: Next) -> Response { - let expected = app.config().auth_token(); - if expected.is_empty() || req.uri().path().ends_with("/info") { - return next.run(req).await; +/// Load a session the user may at least read. +fn readable(app: &App, user: &User, id: &str) -> Result { + let s = app.store.get_session(id)?.ok_or_else(|| not_found("session"))?; + if app.access(user, id) == Access::None { + return Err(forbidden()); } - let header_token = req - .headers() - .get(header::AUTHORIZATION) - .and_then(|v| v.to_str().ok()) - .and_then(|v| v.strip_prefix("Bearer ")) - .map(String::from); - let query_token = req.uri().query().and_then(|q| { - q.split('&').find_map(|kv| kv.strip_prefix("token=")).map(String::from) - }); - let cookie_token = req - .headers() - .get(header::COOKIE) - .and_then(|v| v.to_str().ok()) - .and_then(|c| c.split(';').find_map(|kv| kv.trim().strip_prefix("codecraft_token=")).map(String::from)); - let ok = [header_token, query_token, cookie_token] - .into_iter() - .flatten() - .any(|t| constant_time_eq(t.as_bytes(), expected.as_bytes())); - if ok { - next.run(req).await - } else { - ApiError(StatusCode::UNAUTHORIZED, "unauthorized".into()).into_response() + Ok(s) +} + +/// Load a session the user may control (owner or admin). +fn writable(app: &App, user: &User, id: &str) -> Result { + let s = readable(app, user, id)?; + if app.access(user, id) != Access::Full { + return Err(ApiError(StatusCode::FORBIDDEN, "this session is shared with you read-only".into())); } + Ok(s) } -fn constant_time_eq(a: &[u8], b: &[u8]) -> bool { - a.len() == b.len() && a.iter().zip(b).fold(0u8, |acc, (x, y)| acc | (x ^ y)) == 0 -} +// --- config (admin) ----------------------------------------------------------- -// --- info & config ---------------------------------------------------------- - -async fn info(State(app): State) -> Json { - let cfg = app.config(); - Json(json!({ - "name": "CodeCraft", - "version": env!("CARGO_PKG_VERSION"), - "auth_required": !cfg.auth_token().is_empty(), - })) -} - -async fn get_config(State(app): State) -> ApiResult { +async fn get_config(Extension(user): Extension, State(app): State) -> ApiResult { let cfg = app.config(); + let default_model = if cfg.server.default_model.is_empty() { app.registry().first_model() } else { Some(cfg.server.default_model.clone()) }; + if !user.is_admin() { + // Users only need defaults, not the configuration. + return Ok(Json(json!({"default_model": default_model, "currency": cfg.server.currency}))); + } Ok(Json(json!({ "config": config::redacted_json(&cfg), "workspace": app.workspace(), - "default_model": if cfg.server.default_model.is_empty() { app.registry().first_model() } else { Some(cfg.server.default_model.clone()) }, + "default_model": default_model, "currency": cfg.server.currency, }))) } @@ -140,14 +162,16 @@ struct PatchBody { patches: Vec, } -async fn patch_config(State(app): State, Json(body): Json) -> ApiResult { +async fn patch_config(Extension(user): Extension, State(app): State, Json(body): Json) -> ApiResult { + require_admin(&user)?; // Masked secrets coming back from the UI must not overwrite real values. let patches: Vec = body.patches.into_iter().filter(|p| p.value.as_str() != Some("••••••••")).collect(); app.patch_config(&patches).await?; Ok(Json(json!({"ok": true}))) } -async fn get_config_raw(State(app): State) -> ApiResult { +async fn get_config_raw(Extension(user): Extension, State(app): State) -> ApiResult { + require_admin(&user)?; Ok(Json(json!({"text": app.config_text()?}))) } @@ -156,12 +180,14 @@ struct RawBody { text: String, } -async fn put_config_raw(State(app): State, Json(body): Json) -> ApiResult { +async fn put_config_raw(Extension(user): Extension, State(app): State, Json(body): Json) -> ApiResult { + require_admin(&user)?; app.save_config(&body.text).await?; Ok(Json(json!({"ok": true}))) } -async fn reload_providers(State(app): State) -> ApiResult { +async fn reload_providers(Extension(user): Extension, State(app): State) -> ApiResult { + require_admin(&user)?; app.reload_providers().await; Ok(Json(app.registry().models_json())) } @@ -175,33 +201,92 @@ struct CwdQuery { cwd: Option, } -fn cwd_or_workspace(app: &App, cwd: Option) -> PathBuf { - cwd.map(PathBuf::from).unwrap_or_else(|| app.workspace()) +/// Root and cwd for listing agents/commands; falls back to the user's first root. +fn scope(app: &App, user: &User, cwd: Option) -> (PathBuf, PathBuf) { + let owner = Some(user.id.as_str()); + let roots = app.user_roots(owner); + let cwd = cwd.map(PathBuf::from).filter(|c| roots.iter().any(|r| c.starts_with(r))).unwrap_or_else(|| roots[0].clone()); + (app.root_for(owner, &cwd), cwd) } -async fn agents(State(app): State, Query(q): Query) -> Json { - let cwd = cwd_or_workspace(&app, q.cwd); - Json(json!(prompts::agents(&app, &cwd))) +async fn agents(Extension(user): Extension, State(app): State, Query(q): Query) -> Json { + let (root, cwd) = scope(&app, &user, q.cwd); + Json(json!(prompts::agents(&app, &root, &cwd))) } -async fn commands(State(app): State, Query(q): Query) -> Json { - let cwd = cwd_or_workspace(&app, q.cwd); +async fn commands(Extension(user): Extension, State(app): State, Query(q): Query) -> Json { + let (root, cwd) = scope(&app, &user, q.cwd); Json(json!({ - "commands": prompts::commands(&app, &cwd), - "skills": prompts::skills(&app, &cwd), + "commands": prompts::commands(&app, &root, &cwd), + "skills": prompts::skills(&app, &root, &cwd), })) } +// --- shared folders (admin) --------------------------------------------------- + +async fn list_folders(Extension(user): Extension, State(app): State) -> ApiResult { + require_admin(&user)?; + Ok(Json(json!({"workspace": app.workspace(), "folders": app.store.folders()?}))) +} + +#[derive(Deserialize)] +struct FolderBody { + /// Path relative to the workspace (or absolute inside it). Created if missing. + path: String, + #[serde(default)] + users: Vec, +} + +async fn create_folder(Extension(user): Extension, State(app): State, Json(body): Json) -> ApiResult { + require_admin(&user)?; + let ws = app.workspace(); + let rel = body.path.trim().trim_start_matches(ws.to_str().unwrap_or("")).trim_matches('/'); + if rel.is_empty() || rel.split('/').any(|c| c.is_empty() || c == "." || c == ".." || c.starts_with('.')) { + return Err(ApiError(StatusCode::BAD_REQUEST, "invalid folder path".into())); + } + let path = ws.join(rel); + std::fs::create_dir_all(&path)?; + let path = std::fs::canonicalize(&path)?; + if !path.starts_with(&ws) || path == ws { + return Err(ApiError(StatusCode::BAD_REQUEST, "folder must be inside the workspace".into())); + } + let id = app.store.add_folder(&path.display().to_string())?; + app.store.set_folder_access(&id, &body.users)?; + Ok(Json(json!({"id": id, "path": path}))) +} + +async fn delete_folder(Extension(user): Extension, State(app): State, Path(id): Path) -> ApiResult { + require_admin(&user)?; + // Only the share is removed; the directory and its files stay. + app.store.delete_folder(&id)?; + Ok(Json(json!({"ok": true}))) +} + +#[derive(Deserialize)] +struct AccessBody { + users: Vec, +} + +async fn set_folder_access(Extension(user): Extension, State(app): State, Path(id): Path, Json(body): Json) -> ApiResult { + require_admin(&user)?; + app.store.set_folder_access(&id, &body.users)?; + Ok(Json(json!({"ok": true}))) +} + // --- sessions --------------------------------------------------------------- -async fn list_sessions(State(app): State) -> ApiResult { - let sessions = app.store.list_sessions()?; - let list: Vec = sessions +async fn list_sessions(Extension(user): Extension, State(app): State) -> ApiResult { + let filter = if user.is_admin() { None } else { Some(user.id.as_str()) }; + let list: Vec = app + .store + .list_sessions(filter)? .into_iter() .map(|s| { let busy = app.is_busy(&s.id); + let shared = s.owner_id.as_deref() != Some(user.id.as_str()); let mut v = json!(s); v["busy"] = json!(busy); + v["shared"] = json!(shared); v }) .collect(); @@ -216,29 +301,34 @@ struct CreateSession { permission_mode: Option, } -fn validate_cwd(app: &App, cwd: Option) -> Result { - let ws = app.workspace(); +/// Resolve a directory and check it lies inside one of the user's roots. +fn validate_cwd(app: &App, user: &User, cwd: Option) -> Result { + let roots = app.user_roots(Some(&user.id)); let p = match cwd.filter(|c| !c.trim().is_empty()) { Some(c) if std::path::Path::new(&c).is_absolute() => PathBuf::from(c), - Some(c) => ws.join(c), - None => ws.clone(), + Some(c) => roots[0].join(c), + None => roots[0].clone(), }; let p = std::fs::canonicalize(&p).map_err(|_| ApiError(StatusCode::BAD_REQUEST, format!("directory does not exist: {}", p.display())))?; - if !p.starts_with(&ws) || !p.is_dir() { - return Err(ApiError(StatusCode::BAD_REQUEST, "directory must be inside the workspace".into())); + if !p.is_dir() || !roots.iter().any(|r| p.starts_with(r)) { + return Err(ApiError(StatusCode::FORBIDDEN, "directory is outside your folders".into())); } Ok(p) } -async fn create_session(State(app): State, Json(body): Json) -> ApiResult { - let cwd = validate_cwd(&app, body.cwd)?; +async fn create_session(Extension(user): Extension, State(app): State, Json(body): Json) -> ApiResult { + let cwd = validate_cwd(&app, &user, body.cwd)?; let cfg = app.config(); let model = body .model .filter(|m| !m.is_empty()) .or_else(|| Some(cfg.server.default_model.clone()).filter(|m| !m.is_empty())) .or_else(|| app.registry().first_model()) - .ok_or_else(|| ApiError(StatusCode::BAD_REQUEST, "no model configured – add a provider in the settings".into()))?; + .ok_or_else(|| ApiError(StatusCode::BAD_REQUEST, "no model configured – ask an admin to add a provider".into()))?; + let mode = body.permission_mode.unwrap_or_else(|| "auto".into()); + if !crate::permission::MODES.contains(&mode.as_str()) { + return Err(ApiError(StatusCode::BAD_REQUEST, "invalid permission mode".into())); + } let now = now_ms(); let s = Session { id: new_id(), @@ -246,8 +336,9 @@ async fn create_session(State(app): State, Json(body): Json, Json(body): Json, Path(id): Path) -> ApiResult { - let s = app.store.get_session(&id)?.ok_or_else(|| not_found("session"))?; +async fn get_session(Extension(user): Extension, State(app): State, Path(id): Path) -> ApiResult { + let s = readable(&app, &user, &id)?; let messages = app.store.messages(&id)?; let busy = app.is_busy(&id); - Ok(Json(json!({"session": s, "messages": messages, "busy": busy}))) + let can_write = app.access(&user, &id) == Access::Full; + let queue = app.store.queue_list(&id)?; + Ok(Json(json!({"session": s, "messages": messages, "busy": busy, "can_write": can_write, "queue": queue}))) } #[derive(Deserialize)] @@ -272,8 +365,8 @@ struct UpdateSession { permission_mode: Option, } -async fn update_session(State(app): State, Path(id): Path, Json(body): Json) -> ApiResult { - let mut s = app.store.get_session(&id)?.ok_or_else(|| not_found("session"))?; +async fn update_session(Extension(user): Extension, State(app): State, Path(id): Path, Json(body): Json) -> ApiResult { + let mut s = writable(&app, &user, &id)?; if let Some(t) = body.title { s.title = t; } @@ -285,7 +378,7 @@ async fn update_session(State(app): State, Path(id): Path, Jso s.agent = a; } if let Some(p) = body.permission_mode { - if !["default", "accept_edits", "bypass"].contains(&p.as_str()) { + if !crate::permission::MODES.contains(&p.as_str()) { return Err(ApiError(StatusCode::BAD_REQUEST, "invalid permission mode".into())); } s.permission_mode = p; @@ -293,10 +386,12 @@ async fn update_session(State(app): State, Path(id): Path, Jso Ok(Json(json!(app.save_session(s)?))) } -async fn delete_session(State(app): State, Path(id): Path) -> ApiResult { +async fn delete_session(Extension(user): Extension, State(app): State, Path(id): Path) -> ApiResult { + writable(&app, &user, &id)?; + // Notify before deleting: visibility is checked against the stored session. + app.emit(Event::SessionDeleted { session_id: id.clone() }); runner::abort(&app, &id); app.store.delete_session(&id)?; - app.emit(Event::SessionDeleted { session_id: id }); Ok(Json(json!({"ok": true}))) } @@ -307,8 +402,8 @@ struct PromptBody { images: Vec, } -async fn prompt(State(app): State, Path(id): Path, Json(body): Json) -> ApiResult { - let session = app.store.get_session(&id)?.ok_or_else(|| not_found("session"))?; +async fn prompt(Extension(user): Extension, State(app): State, Path(id): Path, Json(body): Json) -> ApiResult { + let session = writable(&app, &user, &id)?; let text = body.text.trim().to_string(); // Built-in commands. if text == "/compact" { @@ -319,9 +414,10 @@ async fn prompt(State(app): State, Path(id): Path, Json(body): return Ok(Json(json!({"ok": true, "command": "compact"}))); } let cwd = PathBuf::from(&session.cwd); + let root = app.root_for(session.owner_id.as_deref(), &cwd); let mut prompt_text = text.clone(); if text.starts_with('/') { - if let Some((expanded, agent)) = prompts::expand_command(&app, &cwd, &text) { + if let Some((expanded, agent)) = prompts::expand_command(&app, &root, &cwd, &text) { prompt_text = expanded; if let Some(a) = agent { let mut s = session.clone(); @@ -334,10 +430,32 @@ async fn prompt(State(app): State, Path(id): Path, Json(body): for image in body.images { parts.push(Part::Image { image }); } - runner::start_prompt(app.clone(), id, parts)?; + match runner::submit(app.clone(), id, parts, "user")? { + runner::Submitted::Started => Ok(Json(json!({"ok": true}))), + runner::Submitted::Queued(item) => Ok(Json(json!({"ok": true, "queued": item}))), + } +} + +async fn get_queue(Extension(user): Extension, State(app): State, Path(id): Path) -> ApiResult { + readable(&app, &user, &id)?; + Ok(Json(json!(app.store.queue_list(&id)?))) +} + +async fn delete_queue_item(Extension(user): Extension, State(app): State, Path((id, item)): Path<(String, String)>) -> ApiResult { + writable(&app, &user, &id)?; + if !app.store.queue_remove(&id, &item)? { + return Err(ApiError(StatusCode::CONFLICT, "already delivered to the agent".into())); + } + let items = app.store.queue_list(&id)?; + app.emit(Event::QueueUpdated { session_id: id, items: json!(items) }); Ok(Json(json!({"ok": true}))) } +async fn flush_queue(Extension(user): Extension, State(app): State, Path(id): Path) -> ApiResult { + writable(&app, &user, &id)?; + Ok(Json(json!({"started": runner::flush_queue(app.clone(), &id)?}))) +} + async fn do_compact(app: AppState, id: String) { let cancel = tokio_util::sync::CancellationToken::new(); app.with_runtime(&id, |r| r.cancel = Some(cancel.clone())); @@ -349,12 +467,14 @@ async fn do_compact(app: AppState, id: String) { app.emit(Event::SessionStatus { session_id: id, busy: false }); } -async fn abort(State(app): State, Path(id): Path) -> Json { +async fn abort(Extension(user): Extension, State(app): State, Path(id): Path) -> ApiResult { + writable(&app, &user, &id)?; runner::abort(&app, &id); - Json(json!({"ok": true})) + Ok(Json(json!({"ok": true}))) } -async fn compact(State(app): State, Path(id): Path) -> ApiResult { +async fn compact(Extension(user): Extension, State(app): State, Path(id): Path) -> ApiResult { + writable(&app, &user, &id)?; if app.is_busy(&id) { return Err(ApiError(StatusCode::CONFLICT, "session is busy".into())); } @@ -368,33 +488,58 @@ struct RewindBody { } /// Remove a message and everything after it (edit & resend). Files are not restored. -async fn rewind(State(app): State, Path(id): Path, Json(body): Json) -> ApiResult { +async fn rewind(Extension(user): Extension, State(app): State, Path(id): Path, Json(body): Json) -> ApiResult { + let mut s = writable(&app, &user, &id)?; if app.is_busy(&id) { return Err(ApiError(StatusCode::CONFLICT, "session is busy".into())); } app.store.truncate_from(&id, &body.message_id)?; - let s = app.store.get_session(&id)?.ok_or_else(|| not_found("session"))?; - let mut s = s; s.last_usage = None; app.save_session(s)?; Ok(Json(json!({"ok": true}))) } -async fn session_stats(State(app): State, Path(id): Path) -> ApiResult { +async fn session_stats(Extension(user): Extension, State(app): State, Path(id): Path) -> ApiResult { + readable(&app, &user, &id)?; let mut v = app.store.session_stats(&id)?; v["currency"] = json!(app.config().server.currency); Ok(Json(v)) } +async fn get_shares(Extension(user): Extension, State(app): State, Path(id): Path) -> ApiResult { + writable(&app, &user, &id)?; + Ok(Json(json!(app.store.shares(&id)?))) +} + +async fn put_shares(Extension(user): Extension, State(app): State, Path(id): Path, Json(body): Json) -> ApiResult { + let s = writable(&app, &user, &id)?; + if s.parent_id.is_some() { + return Err(ApiError(StatusCode::BAD_REQUEST, "share the parent session instead".into())); + } + let users: Vec = body.users.into_iter().filter(|u| Some(u) != s.owner_id.as_ref()).collect(); + app.set_shares(&id, &users)?; + Ok(Json(json!({"ok": true}))) +} + // --- interaction ------------------------------------------------------------ +/// Only users who control the session may answer its permission requests and questions. +fn check_pending(app: &App, user: &User, id: &str) -> Result<(), ApiError> { + let sid = app.pending_session(id).ok_or_else(|| not_found("request"))?; + if app.access(user, &sid) != Access::Full { + return Err(forbidden()); + } + Ok(()) +} + #[derive(Deserialize)] struct PermissionBody { reply: PermissionReply, message: Option, } -async fn reply_permission(State(app): State, Path(id): Path, Json(body): Json) -> ApiResult { +async fn reply_permission(Extension(user): Extension, State(app): State, Path(id): Path, Json(body): Json) -> ApiResult { + check_pending(&app, &user, &id)?; app.reply_permission(&id, body.reply, body.message)?; Ok(Json(json!({"ok": true}))) } @@ -405,37 +550,55 @@ struct QuestionBody { answers: Option>, } -async fn answer_question(State(app): State, Path(id): Path, Json(body): Json) -> ApiResult { +async fn answer_question(Extension(user): Extension, State(app): State, Path(id): Path, Json(body): Json) -> ApiResult { + check_pending(&app, &user, &id)?; app.answer_question(&id, body.answers)?; Ok(Json(json!({"ok": true}))) } -async fn pending(State(app): State) -> Json { - Json(json!(app.pending_events())) +async fn pending(Extension(user): Extension, State(app): State) -> Json { + let list: Vec = app.pending_events().into_iter().filter(|e| app.event_visible(&user, e)).collect(); + Json(json!(list)) } -async fn mcp_status(State(app): State) -> Json { - Json(app.mcp.status_json()) +async fn mcp_status(Extension(user): Extension, State(app): State) -> ApiResult { + require_admin(&user)?; + Ok(Json(app.mcp.status_json())) } -async fn mcp_reload(State(app): State) -> Json { +async fn mcp_reload(Extension(user): Extension, State(app): State) -> ApiResult { + require_admin(&user)?; let mcp = app.mcp.clone(); let servers = app.config().mcp.clone(); let weak = Arc::downgrade(&app); mcp.start_all(weak, servers).await; - Json(app.mcp.status_json()) + Ok(Json(app.mcp.status_json())) } // --- files ------------------------------------------------------------------ +/// The directories the user may work in (own folder, shared folders). +async fn list_roots(Extension(user): Extension, State(app): State) -> Json { + let ws = app.workspace(); + let roots: Vec = app + .user_roots(Some(&user.id)) + .into_iter() + .map(|r| { + let rel = r.strip_prefix(&ws).map(|p| p.display().to_string()).unwrap_or_default(); + json!({"path": r, "name": if rel.is_empty() { "/".to_string() } else { rel }}) + }) + .collect(); + Json(json!(roots)) +} + #[derive(Deserialize)] struct DirQuery { path: Option, } -/// Subdirectories for the project picker (inside the workspace only). -async fn list_dirs(State(app): State, Query(q): Query) -> ApiResult { - let dir = validate_cwd(&app, q.path)?; +/// Subdirectories for the project picker (inside the user's roots only). +async fn list_dirs(Extension(user): Extension, State(app): State, Query(q): Query) -> ApiResult { + let dir = validate_cwd(&app, &user, q.path)?; let mut dirs = Vec::new(); for e in std::fs::read_dir(&dir)?.flatten() { let name = e.file_name().to_string_lossy().to_string(); @@ -445,15 +608,125 @@ async fn list_dirs(State(app): State, Query(q): Query) -> Ap dirs.push(json!({"name": name, "path": e.path(), "git": e.path().join(".git").exists()})); } dirs.sort_by(|a, b| a["name"].as_str().cmp(&b["name"].as_str())); - let ws = app.workspace(); + let root = app.root_for(Some(&user.id), &dir); Ok(Json(json!({ "path": dir, - "parent": if dir == ws { Value::Null } else { json!(dir.parent()) }, - "workspace": ws, + "parent": if dir == root { Value::Null } else { json!(dir.parent()) }, + "root": root, + "workspace": app.workspace(), "dirs": dirs, }))) } +// --- budget ------------------------------------------------------------------- + +/// Budgets concern the whole installation: admins only. +async fn budget(Extension(user): Extension, State(app): State) -> ApiResult { + require_admin(&user)?; + Ok(Json(crate::budget::overview(&app).await?)) +} + +async fn provider_prices(Extension(user): Extension, State(app): State, Path(name): Path) -> ApiResult { + require_admin(&user)?; + Ok(Json(crate::budget::discover_prices(&app, &name).await?)) +} + +// --- images ------------------------------------------------------------------- + +async fn images_config(State(app): State) -> Json { + let cfg = app.config(); + let ic = &cfg.images; + Json(json!({ + "enabled": ic.enabled && !crate::config::expand_env(&ic.api_key).is_empty(), + "default_model": ic.default_model, + "output_dir": ic.output_dir, + "sizes": crate::images::SIZES, + "models": ic.models.iter().map(|m| json!({"id": m.id, "name": m.name.clone().unwrap_or_else(|| m.id.clone()), "price": m.price})).collect::>(), + "currency": cfg.server.currency, + })) +} + +async fn list_images(Extension(user): Extension, State(app): State) -> ApiResult { + Ok(Json(json!(app.store.images(Some(&user.id), 200)?))) +} + +#[derive(Deserialize)] +struct ImageBody { + prompt: String, + model: Option, + size: Option, + #[serde(default = "one")] + count: u32, + /// Target directory (absolute inside the user's folders); default: first folder/output_dir. + directory: Option, +} + +fn one() -> u32 { + 1 +} + +async fn create_images(Extension(user): Extension, State(app): State, Json(body): Json) -> ApiResult { + if body.prompt.trim().is_empty() { + return Err(ApiError(StatusCode::BAD_REQUEST, "prompt is empty".into())); + } + let roots = app.user_roots(Some(&user.id)); + let dir = match body.directory.filter(|d| !d.trim().is_empty()) { + Some(d) => { + let p = crate::tools::normalize(&PathBuf::from(d)); + if !roots.iter().any(|r| p.starts_with(r)) { + return Err(ApiError(StatusCode::FORBIDDEN, "directory is outside your folders".into())); + } + p + } + None => roots[0].join(&app.config().images.output_dir), + }; + let records = crate::images::generate( + &app, + crate::images::GenerateRequest { + prompt: body.prompt.trim().to_string(), + model: body.model, + size: body.size, + count: body.count, + dir, + user_id: Some(user.id.clone()), + session_id: None, + }, + ) + .await?; + Ok(Json(json!(records))) +} + +fn own_image(app: &App, user: &User, id: &str) -> Result { + let img = app.store.image(id)?.ok_or_else(|| not_found("image"))?; + if !user.is_admin() && img.user_id.as_deref() != Some(user.id.as_str()) { + return Err(ApiError(StatusCode::FORBIDDEN, "not your image".into())); + } + Ok(img) +} + +async fn image_file(Extension(user): Extension, State(app): State, Path(id): Path) -> Result { + let img = own_image(&app, &user, &id)?; + let bytes = tokio::fs::read(&img.path).await.map_err(|_| not_found("image file"))?; + let ctype = match std::path::Path::new(&img.path).extension().and_then(|e| e.to_str()) { + Some("jpg") | Some("jpeg") => "image/jpeg", + Some("webp") => "image/webp", + _ => "image/png", + }; + Ok(([(axum::http::header::CONTENT_TYPE, ctype), (axum::http::header::CACHE_CONTROL, "private, max-age=86400")], bytes).into_response()) +} + +async fn delete_image(Extension(user): Extension, State(app): State, Path(id): Path) -> ApiResult { + let img = own_image(&app, &user, &id)?; + // Only delete files inside the owner's folders. + let roots = app.user_roots(img.user_id.as_deref()); + let p = PathBuf::from(&img.path); + if roots.iter().any(|r| p.starts_with(r)) { + let _ = tokio::fs::remove_file(&p).await; + } + app.store.delete_image(&id)?; + Ok(Json(json!({"ok": true}))) +} + // --- statistics ------------------------------------------------------------- #[derive(Deserialize)] @@ -462,12 +735,15 @@ struct StatsQuery { to: Option, /// Minutes east of UTC (JS: -new Date().getTimezoneOffset()). tz: Option, + /// Admins: user id to filter by (omitted = all users). + user: Option, } -async fn stats(State(app): State, Query(q): Query) -> ApiResult { +async fn stats(Extension(user): Extension, State(app): State, Query(q): Query) -> ApiResult { let to = q.to.unwrap_or_else(|| now_ms() + 1); let from = q.from.unwrap_or(to - 30 * 86_400_000); - let mut v = app.store.global_stats(from, to, q.tz.unwrap_or(0))?; + let filter = if user.is_admin() { q.user.filter(|u| !u.is_empty()) } else { Some(user.id.clone()) }; + let mut v = app.store.global_stats(from, to, q.tz.unwrap_or(0), filter.as_deref())?; v["currency"] = json!(app.config().server.currency); v["from"] = json!(from); v["to"] = json!(to); @@ -476,23 +752,25 @@ async fn stats(State(app): State, Query(q): Query) -> ApiR // --- websocket -------------------------------------------------------------- -async fn ws(State(app): State, upgrade: WebSocketUpgrade) -> Response { - upgrade.on_upgrade(move |socket| ws_loop(app, socket)) +async fn ws(Extension(user): Extension, State(app): State, upgrade: WebSocketUpgrade) -> Response { + upgrade.on_upgrade(move |socket| ws_loop(app, user, socket)) } -async fn ws_loop(app: AppState, mut socket: WebSocket) { +async fn ws_loop(app: AppState, user: User, mut socket: WebSocket) { let mut rx = app.subscribe(); for ev in app.pending_events() { - if send_event(&mut socket, &ev).await.is_err() { + if app.event_visible(&user, &ev) && send_event(&mut socket, &ev).await.is_err() { return; } } let mut ping = tokio::time::interval(std::time::Duration::from_secs(25)); + // Re-check the login regularly so disabled users lose their live connection. + let mut recheck = tokio::time::interval(std::time::Duration::from_secs(60)); loop { tokio::select! { ev = rx.recv() => match ev { Ok(ev) => { - if send_event(&mut socket, &ev).await.is_err() { + if app.event_visible(&user, &ev) && send_event(&mut socket, &ev).await.is_err() { break; } } @@ -514,6 +792,11 @@ async fn ws_loop(app: AppState, mut socket: WebSocket) { break; } } + _ = recheck.tick() => { + if !matches!(app.store.user(&user.id), Ok(Some(u)) if !u.disabled) { + break; + } + } } } } diff --git a/server/src/app.rs b/server/src/app.rs index 692d721..1f70a72 100644 --- a/server/src/app.rs +++ b/server/src/app.rs @@ -10,7 +10,7 @@ use serde_json::{Map, Value}; use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, RwLock}; -use tokio::sync::{broadcast, oneshot}; +use tokio::sync::{OwnedSemaphorePermit, Semaphore, broadcast, oneshot}; use tokio_util::sync::CancellationToken; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize)] @@ -39,8 +39,6 @@ pub struct Runtime { pub cancel: Option, pub read_files: HashSet, pub approved: HashSet, - /// Prompts sent while the agent was busy; injected at the next step. - pub queue: Vec, } pub struct App { @@ -53,6 +51,24 @@ pub struct App { runtime: Mutex>, permissions: Mutex>, questions: Mutex>, + /// Subagent concurrency limits: key → (size, semaphore). Recreated when the size changes. + subagent_limits: Mutex)>>, + /// One-time code for creating the first admin (None once users exist). + pub setup_code: Mutex>, + pub oidc_pending: Mutex>, + pub oidc_discovery: Mutex>, + /// session id → (root session id, owner) – both never change. + owners: Mutex)>>, + /// root session id → users it is shared with. + shares: Mutex>>, +} + +/// What a user may do with a session. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Access { + None, + Read, + Full, } impl App { @@ -72,7 +88,14 @@ impl App { runtime: Mutex::default(), permissions: Mutex::default(), questions: Mutex::default(), + subagent_limits: Mutex::default(), + setup_code: Mutex::default(), + oidc_pending: Mutex::default(), + oidc_discovery: Mutex::default(), + owners: Mutex::default(), + shares: Mutex::default(), }); + crate::auth::bootstrap(&app)?; app.clone().start_mcp(); Ok(app) } @@ -97,6 +120,81 @@ impl App { std::fs::canonicalize(&ws).unwrap_or(ws) } + /// Directories a session owner may work in: admins (and legacy sessions + /// without owner) the whole workspace, users their own folder plus shared folders. + pub fn user_roots(&self, owner_id: Option<&str>) -> Vec { + let ws = self.workspace(); + let Some(user) = owner_id.and_then(|id| self.store.user(id).ok().flatten()) else { + return vec![ws]; + }; + if user.is_admin() { + return vec![ws]; + } + let own = ws.join(&user.username); + let _ = std::fs::create_dir_all(&own); + let mut roots = vec![own]; + for p in self.store.folders_for_user(&user.id).unwrap_or_default() { + let p = PathBuf::from(p); + if p.starts_with(&ws) && p.is_dir() { + roots.push(p); + } + } + roots + } + + /// The root that contains `cwd` (most specific first). + pub fn root_for(&self, owner_id: Option<&str>, cwd: &Path) -> PathBuf { + let mut roots = self.user_roots(owner_id); + roots.sort_by_key(|r| std::cmp::Reverse(r.as_os_str().len())); + roots.iter().find(|r| cwd.starts_with(r)).cloned().unwrap_or_else(|| roots.pop().unwrap_or_else(|| self.workspace())) + } + + fn owner_of(&self, session_id: &str) -> Option<(String, Option)> { + if let Some(v) = self.owners.lock().unwrap().get(session_id) { + return Some(v.clone()); + } + let root = self.root_session_id(session_id); + let owner = self.store.get_session(&root).ok().flatten()?.owner_id; + let v = (root, owner); + self.owners.lock().unwrap().insert(session_id.to_string(), v.clone()); + Some(v) + } + + fn shared_with(&self, root: &str) -> Vec { + if let Some(v) = self.shares.lock().unwrap().get(root) { + return v.clone(); + } + let v = self.store.shares(root).unwrap_or_default(); + self.shares.lock().unwrap().insert(root.to_string(), v.clone()); + v + } + + pub fn set_shares(&self, root: &str, users: &[String]) -> Result<()> { + self.store.set_shares(root, users)?; + self.shares.lock().unwrap().insert(root.to_string(), users.to_vec()); + Ok(()) + } + + pub fn access(&self, user: &crate::store::User, session_id: &str) -> Access { + let Some((root, owner)) = self.owner_of(session_id) else { return Access::None }; + if user.is_admin() || owner.as_deref() == Some(user.id.as_str()) { + Access::Full + } else if self.shared_with(&root).contains(&user.id) { + Access::Read + } else { + Access::None + } + } + + /// Whether an event may be sent to this user's connections. + pub fn event_visible(&self, user: &crate::store::User, ev: &Event) -> bool { + match ev.session_id() { + None => user.is_admin(), + Some(_) if user.is_admin() => true, + Some(id) => self.access(user, id) != Access::None, + } + } + pub fn data_dir(&self) -> PathBuf { self.config().data_dir() } @@ -197,6 +295,54 @@ impl App { id } + fn limit(&self, key: &str, size: usize) -> Arc { + let mut m = self.subagent_limits.lock().unwrap(); + match m.get(key) { + Some((n, sem)) if *n == size => sem.clone(), + _ => { + let sem = Arc::new(Semaphore::new(size)); + m.insert(key.to_string(), (size, sem.clone())); + sem + } + } + } + + /// Wait until a subagent of this type may run (per-agent limit, then the global + /// limit – always in this order). Returns None if cancelled while waiting, and + /// whether the call had to wait. + pub async fn subagent_slot( + &self, + agent: &str, + max_parallel: Option, + session_id: &str, + cancel: &CancellationToken, + ) -> (Option>, bool) { + let mut sems = Vec::new(); + if let Some(n) = max_parallel.filter(|n| *n > 0) { + sems.push((format!("subagent:{agent}"), self.limit(&format!("agent:{agent}"), n))); + } + let global = self.config().server.max_parallel_subagents; + if global > 0 { + sems.push(("subagent:*".to_string(), self.limit("*", global))); + } + let mut waited = false; + let mut permits = Vec::new(); + for (reason, sem) in sems { + if sem.available_permits() == 0 { + waited = true; + self.emit(Event::Waiting { session_id: session_id.to_string(), reason: Some(reason) }); + } + tokio::select! { + p = sem.acquire_owned() => match p { + Ok(p) => permits.push(p), + Err(_) => return (None, waited), + }, + _ = cancel.cancelled() => return (None, waited), + } + } + (Some(permits), waited) + } + // --- user interaction ------------------------------------------------- /// Forward questions to the UI and wait. None = dismissed. @@ -227,6 +373,12 @@ impl App { Ok(res) } + /// Session of a pending permission request or question. + pub fn pending_session(&self, id: &str) -> Option { + let from_perm = self.permissions.lock().unwrap().get(id).and_then(|p| p.event.session_id().map(String::from)); + from_perm.or_else(|| self.questions.lock().unwrap().get(id).and_then(|q| q.event.session_id().map(String::from))) + } + pub fn answer_question(&self, id: &str, answers: Option>) -> Result<()> { let p = self.questions.lock().unwrap().remove(id).ok_or_else(|| anyhow!("question not pending"))?; let _ = p.tx.send(answers); diff --git a/server/src/auth.rs b/server/src/auth.rs new file mode 100644 index 0000000..2d620d7 --- /dev/null +++ b/server/src/auth.rs @@ -0,0 +1,612 @@ +//! Users, sign-in (local passwords + OpenID Connect) and access control. + +use crate::api::{ApiError, ApiResult}; +use crate::app::App; +use crate::config::expand_env; +use crate::store::User; +use anyhow::{Context, Result, anyhow, bail}; +use argon2::{Argon2, PasswordHasher, PasswordVerifier}; +use axum::extract::{Path, Query, Request, State}; +use axum::http::{HeaderMap, HeaderValue, StatusCode, header}; +use axum::middleware::Next; +use axum::response::{IntoResponse, Redirect, Response}; +use axum::{Extension, Json}; +use base64::Engine; +use serde::Deserialize; +use serde_json::{Value, json}; +use sha2::{Digest, Sha256}; +use std::sync::Arc; + +pub const COOKIE: &str = "codecraft_session"; +const MIN_PASSWORD: usize = 8; + +// --- primitives --------------------------------------------------------------- + +pub fn random_token(bytes: usize) -> String { + let mut buf = vec![0u8; bytes]; + rand::fill(&mut buf[..]); + base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(buf) +} + +pub fn sha256_hex(s: &str) -> String { + Sha256::digest(s.as_bytes()).iter().map(|b| format!("{b:02x}")).collect() +} + +pub fn hash_password(password: &str) -> Result { + let mut salt = [0u8; 16]; + rand::fill(&mut salt); + let hash = Argon2::default() + .hash_password_with_salt(password.as_bytes(), &salt) + .map_err(|e| anyhow!("hashing failed: {e}"))?; + Ok(hash.to_string()) +} + +pub fn verify_password(password: &str, hash: &str) -> bool { + Argon2::default().verify_password(password.as_bytes(), hash).is_ok() +} + +fn valid_username(name: &str) -> bool { + !name.is_empty() + && name.len() <= 64 + && name.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.')) + && !name.starts_with('.') +} + +/// Usernames become directory names; derive a safe one from an IdP claim. +fn sanitize_username(raw: &str) -> String { + let local = raw.split('@').next().unwrap_or(raw); + let s: String = local + .chars() + .map(|c| if c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.') { c.to_ascii_lowercase() } else { '_' }) + .collect(); + s.trim_start_matches('.').chars().take(64).collect() +} + +// --- cookies & middleware ------------------------------------------------------- + +fn cookie_secure(app: &App, headers: &HeaderMap) -> bool { + match app.config().auth.cookie_secure.as_str() { + "true" => true, + "false" => false, + _ => { + app.config().auth.public_url.starts_with("https://") + || headers.get("x-forwarded-proto").and_then(|v| v.to_str().ok()) == Some("https") + } + } +} + +fn session_cookie(app: &App, headers: &HeaderMap, token: &str, max_age_secs: i64) -> HeaderValue { + let secure = if cookie_secure(app, headers) { "; Secure" } else { "" }; + HeaderValue::from_str(&format!("{COOKIE}={token}; Path=/; HttpOnly; SameSite=Lax; Max-Age={max_age_secs}{secure}")) + .expect("valid cookie") +} + +pub fn token_from_headers(headers: &HeaderMap) -> Option { + if let Some(t) = headers + .get(header::AUTHORIZATION) + .and_then(|v| v.to_str().ok()) + .and_then(|v| v.strip_prefix("Bearer ")) + { + return Some(t.trim().to_string()); + } + headers + .get_all(header::COOKIE) + .iter() + .filter_map(|v| v.to_str().ok()) + .flat_map(|c| c.split(';')) + .find_map(|kv| kv.trim().strip_prefix(&format!("{COOKIE}=")).map(String::from)) +} + +pub fn current_user(app: &App, headers: &HeaderMap) -> Option { + let token = token_from_headers(headers)?; + app.store.user_by_token(&sha256_hex(&token)).ok().flatten() +} + +/// Paths reachable without a login. +fn is_public(path: &str) -> bool { + ["/info", "/auth/login", "/auth/setup", "/auth/logout", "/auth/oidc/start", "/auth/oidc/callback"] + .iter() + .any(|p| path.ends_with(p)) +} + +pub async fn require_user(State(app): State>, mut req: Request, next: Next) -> Response { + if is_public(req.uri().path()) { + return next.run(req).await; + } + match current_user(&app, req.headers()) { + Some(user) => { + req.extensions_mut().insert(user); + next.run(req).await + } + None => ApiError(StatusCode::UNAUTHORIZED, "unauthorized".into()).into_response(), + } +} + +pub fn require_admin(user: &User) -> Result<(), ApiError> { + if user.is_admin() { Ok(()) } else { Err(ApiError(StatusCode::FORBIDDEN, "admin only".into())) } +} + +fn issue_login(app: &App, headers: &HeaderMap, user: &User) -> Result { + let token = random_token(32); + let days = app.config().auth.session_days.max(1) as i64; + let ua = headers.get(header::USER_AGENT).and_then(|v| v.to_str().ok()).unwrap_or(""); + app.store.add_token(&sha256_hex(&token), &user.id, crate::types::now_ms() + days * 86_400_000, &ua.chars().take(200).collect::())?; + Ok(session_cookie(app, headers, &token, days * 86_400)) +} + +// --- bootstrap ------------------------------------------------------------------ + +/// Create the first admin from the environment, or generate a setup code. +pub fn bootstrap(app: &App) -> Result<()> { + if app.store.user_count()? > 0 { + return Ok(()); + } + if let (Ok(user), Ok(pass)) = (std::env::var("CODECRAFT_ADMIN_USER"), std::env::var("CODECRAFT_ADMIN_PASSWORD")) { + if valid_username(&user) && pass.len() >= MIN_PASSWORD { + let u = app.store.create_user(&user, &user, "admin", Some(&hash_password(&pass)?), None)?; + let n = app.store.adopt_orphans(&u.id)?; + tracing::info!("created admin '{user}' from the environment ({n} existing sessions assigned)"); + return Ok(()); + } + tracing::warn!("CODECRAFT_ADMIN_USER/PASSWORD ignored: invalid username or password shorter than {MIN_PASSWORD}"); + } + let code = random_token(9); + tracing::warn!("no users yet – open the web UI and create the first admin with setup code: {code}"); + *app.setup_code.lock().unwrap() = Some(code); + Ok(()) +} + +// --- endpoints: session ------------------------------------------------------- + +pub async fn info(State(app): State>, headers: HeaderMap) -> Json { + let cfg = app.config(); + let user = current_user(&app, &headers); + Json(json!({ + "name": "CodeCraft", + "version": env!("CARGO_PKG_VERSION"), + "setup_required": app.setup_code.lock().unwrap().is_some(), + "oidc": if cfg.auth.oidc.enabled { json!({"label": cfg.auth.oidc.button_label}) } else { Value::Null }, + "user": user, + })) +} + +#[derive(Deserialize)] +pub struct LoginBody { + username: String, + password: String, +} + +pub async fn login(State(app): State>, headers: HeaderMap, Json(body): Json) -> ApiResult { + let found = app.store.user_by_name(body.username.trim())?; + // Verify even for unknown users so timing does not reveal which names exist. + let hash = found.as_ref().and_then(|(_, h)| h.clone()); + let ok = verify_password(&body.password, hash.as_deref().unwrap_or("$argon2id$v=19$m=19456,t=2,p=1$c29tZXNhbHRzb21lc2FsdA$Zm9vYmFyZm9vYmFyZm9vYmFyZm9vYmFyZm9vYmFy")); + let Some((user, _)) = found.filter(|(u, _)| ok && hash.is_some() && !u.disabled) else { + tokio::time::sleep(std::time::Duration::from_millis(400)).await; + return Err(ApiError(StatusCode::UNAUTHORIZED, "invalid username or password".into())); + }; + let cookie = issue_login(&app, &headers, &user)?; + Ok(([(header::SET_COOKIE, cookie)], Json(json!(user))).into_response()) +} + +pub async fn logout(State(app): State>, headers: HeaderMap) -> Response { + if let Some(t) = token_from_headers(&headers) { + let _ = app.store.delete_token(&sha256_hex(&t)); + } + ([(header::SET_COOKIE, session_cookie(&app, &headers, "", 0))], Json(json!({"ok": true}))).into_response() +} + +#[derive(Deserialize)] +pub struct SetupBody { + code: String, + username: String, + password: String, +} + +pub async fn setup(State(app): State>, headers: HeaderMap, Json(body): Json) -> ApiResult { + let expected = app.setup_code.lock().unwrap().clone(); + let Some(expected) = expected else { + return Err(ApiError(StatusCode::CONFLICT, "setup already completed".into())); + }; + if body.code.trim() != expected { + tokio::time::sleep(std::time::Duration::from_millis(400)).await; + return Err(ApiError(StatusCode::UNAUTHORIZED, "invalid setup code".into())); + } + let name = body.username.trim(); + if !valid_username(name) { + return Err(ApiError(StatusCode::BAD_REQUEST, "invalid username".into())); + } + if body.password.len() < MIN_PASSWORD { + return Err(ApiError(StatusCode::BAD_REQUEST, format!("password must have at least {MIN_PASSWORD} characters"))); + } + let user = app.store.create_user(name, name, "admin", Some(&hash_password(&body.password)?), None)?; + app.store.adopt_orphans(&user.id)?; + *app.setup_code.lock().unwrap() = None; + let cookie = issue_login(&app, &headers, &user)?; + Ok(([(header::SET_COOKIE, cookie)], Json(json!(user))).into_response()) +} + +#[derive(Deserialize)] +pub struct PasswordBody { + current: Option, + new: String, +} + +/// Change the own password (OIDC-only users may set one without `current`). +pub async fn change_password(State(app): State>, Extension(user): Extension, headers: HeaderMap, Json(body): Json) -> ApiResult { + if body.new.len() < MIN_PASSWORD { + return Err(ApiError(StatusCode::BAD_REQUEST, format!("password must have at least {MIN_PASSWORD} characters"))); + } + if let Some((_, Some(hash))) = app.store.user_by_name(&user.username)? { + if !verify_password(body.current.as_deref().unwrap_or(""), &hash) { + return Err(ApiError(StatusCode::UNAUTHORIZED, "current password is wrong".into())); + } + } + app.store.update_user(&user.id, None, None, None, Some(&hash_password(&body.new)?), None)?; + // Sign out all other devices. + app.store.delete_user_tokens(&user.id)?; + let _ = issue_login(&app, &headers, &user)?; + Ok(Json(json!({"ok": true}))) +} + +// --- endpoints: user management (admin) ------------------------------------------ + +pub async fn list_users(Extension(me): Extension, State(app): State>) -> ApiResult { + require_admin(&me)?; + Ok(Json(json!(app.store.users()?))) +} + +/// Minimal user list for sharing dialogs (all signed-in users). +pub async fn directory(State(app): State>) -> ApiResult { + let list: Vec = app + .store + .users()? + .into_iter() + .filter(|u| !u.disabled) + .map(|u| json!({"id": u.id, "username": u.username, "display_name": u.display_name})) + .collect(); + Ok(Json(json!(list))) +} + +#[derive(Deserialize)] +pub struct CreateUserBody { + username: String, + #[serde(default)] + display_name: String, + password: Option, + #[serde(default = "default_role")] + role: String, +} + +fn default_role() -> String { + "user".into() +} + +fn check_role(role: &str) -> Result<(), ApiError> { + if role == "admin" || role == "user" { Ok(()) } else { Err(ApiError(StatusCode::BAD_REQUEST, "role must be admin or user".into())) } +} + +pub async fn create_user(Extension(me): Extension, State(app): State>, Json(body): Json) -> ApiResult { + require_admin(&me)?; + let name = body.username.trim(); + if !valid_username(name) { + return Err(ApiError(StatusCode::BAD_REQUEST, "invalid username (letters, digits, - _ .)".into())); + } + check_role(&body.role)?; + let hash = match body.password.as_deref().filter(|p| !p.is_empty()) { + Some(p) if p.len() < MIN_PASSWORD => { + return Err(ApiError(StatusCode::BAD_REQUEST, format!("password must have at least {MIN_PASSWORD} characters"))); + } + Some(p) => Some(hash_password(p)?), + None => None, + }; + if app.store.user_by_name(name)?.is_some() { + return Err(ApiError(StatusCode::CONFLICT, "username already exists".into())); + } + let display = if body.display_name.trim().is_empty() { name } else { body.display_name.trim() }; + let user = app.store.create_user(name, display, &body.role, hash.as_deref(), None)?; + Ok(Json(json!(user))) +} + +#[derive(Deserialize)] +pub struct UpdateUserBody { + display_name: Option, + role: Option, + disabled: Option, + password: Option, +} + +pub async fn update_user(Extension(me): Extension, State(app): State>, Path(id): Path, Json(body): Json) -> ApiResult { + require_admin(&me)?; + let target = app.store.user(&id)?.ok_or_else(|| ApiError(StatusCode::NOT_FOUND, "user not found".into()))?; + if let Some(r) = &body.role { + check_role(r)?; + } + let loses_admin = target.is_admin() && !target.disabled && (body.role.as_deref().is_some_and(|r| r != "admin") || body.disabled == Some(true)); + if loses_admin && app.store.admin_count()? <= 1 { + return Err(ApiError(StatusCode::CONFLICT, "the last admin cannot be demoted or disabled".into())); + } + let hash = match body.password.as_deref().filter(|p| !p.is_empty()) { + Some(p) if p.len() < MIN_PASSWORD => { + return Err(ApiError(StatusCode::BAD_REQUEST, format!("password must have at least {MIN_PASSWORD} characters"))); + } + Some(p) => Some(hash_password(p)?), + None => None, + }; + app.store.update_user(&id, body.display_name.as_deref(), body.role.as_deref(), body.disabled, hash.as_deref(), None)?; + if hash.is_some() { + app.store.delete_user_tokens(&id)?; + } + Ok(Json(json!(app.store.user(&id)?))) +} + +pub async fn delete_user(Extension(me): Extension, State(app): State>, Path(id): Path) -> ApiResult { + require_admin(&me)?; + let target = app.store.user(&id)?.ok_or_else(|| ApiError(StatusCode::NOT_FOUND, "user not found".into()))?; + if target.id == me.id { + return Err(ApiError(StatusCode::CONFLICT, "you cannot delete yourself".into())); + } + if target.is_admin() && app.store.admin_count()? <= 1 { + return Err(ApiError(StatusCode::CONFLICT, "the last admin cannot be deleted".into())); + } + // Sessions stay (statistics); they become visible to admins only. + app.store.delete_user(&id)?; + Ok(Json(json!({"ok": true}))) +} + +// --- OpenID Connect ----------------------------------------------------------------- + +pub struct OidcPending { + nonce: String, + verifier: String, + created: std::time::Instant, +} + +async fn discovery(app: &App) -> Result { + let cfg = app.config(); + let issuer = expand_env(&cfg.auth.oidc.issuer); + if let Some((iss, doc)) = app.oidc_discovery.lock().unwrap().clone() { + if iss == issuer { + return Ok(doc); + } + } + let url = format!("{}/.well-known/openid-configuration", issuer.trim_end_matches('/')); + let doc: Value = crate::provider::http_client().get(&url).send().await?.error_for_status()?.json().await?; + *app.oidc_discovery.lock().unwrap() = Some((issuer, doc.clone())); + Ok(doc) +} + +fn redirect_uri(app: &App) -> Result { + let base = app.config().auth.public_url.trim_end_matches('/').to_string(); + if base.is_empty() { + bail!("auth.public_url must be set for single sign-on"); + } + Ok(format!("{base}/api/auth/oidc/callback")) +} + +fn error_page(message: &str) -> Response { + let msg = message.replace('&', "&").replace('<', "<").replace('>', ">"); + ( + StatusCode::FORBIDDEN, + [(header::CONTENT_TYPE, "text/html; charset=utf-8")], + format!("CodeCraft

Sign-in failed

{msg}

Back

"), + ) + .into_response() +} + +pub async fn oidc_start(State(app): State>) -> Response { + match oidc_start_inner(&app).await { + Ok(url) => Redirect::to(&url).into_response(), + Err(e) => error_page(&format!("{e:#}")), + } +} + +async fn oidc_start_inner(app: &App) -> Result { + let cfg = app.config(); + if !cfg.auth.oidc.enabled { + bail!("single sign-on is not enabled"); + } + let doc = discovery(app).await?; + let endpoint = doc["authorization_endpoint"].as_str().context("no authorization_endpoint")?; + let state = random_token(24); + let nonce = random_token(24); + let verifier = random_token(48); + let challenge = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(Sha256::digest(verifier.as_bytes())); + { + let mut pending = app.oidc_pending.lock().unwrap(); + pending.retain(|_, p| p.created.elapsed().as_secs() < 600); + pending.insert(state.clone(), OidcPending { nonce: nonce.clone(), verifier, created: std::time::Instant::now() }); + } + let mut url = reqwest::Url::parse(endpoint)?; + url.query_pairs_mut() + .append_pair("response_type", "code") + .append_pair("client_id", &expand_env(&cfg.auth.oidc.client_id)) + .append_pair("redirect_uri", &redirect_uri(app)?) + .append_pair("scope", &cfg.auth.oidc.scopes.join(" ")) + .append_pair("state", &state) + .append_pair("nonce", &nonce) + .append_pair("code_challenge", &challenge) + .append_pair("code_challenge_method", "S256"); + Ok(url.to_string()) +} + +#[derive(Deserialize)] +pub struct CallbackQuery { + code: Option, + state: Option, + error: Option, + error_description: Option, +} + +pub async fn oidc_callback(State(app): State>, headers: HeaderMap, Query(q): Query) -> Response { + if let Some(e) = q.error { + return error_page(&format!("{e}: {}", q.error_description.unwrap_or_default())); + } + match oidc_callback_inner(&app, q.code.unwrap_or_default(), q.state.unwrap_or_default()).await { + Ok(user) => match issue_login(&app, &headers, &user) { + Ok(cookie) => ([(header::SET_COOKIE, cookie)], Redirect::to("/")).into_response(), + Err(e) => error_page(&format!("{e:#}")), + }, + Err(e) => { + tracing::warn!("oidc sign-in failed: {e:#}"); + error_page(&format!("{e:#}")) + } + } +} + +fn decode_jwt_claims(jwt: &str) -> Result { + let payload = jwt.split('.').nth(1).context("malformed id_token")?; + let bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(payload.trim_end_matches('=')) + .context("malformed id_token payload")?; + Ok(serde_json::from_slice(&bytes)?) +} + +fn claim_list(v: &Value) -> Vec { + match v { + Value::Array(a) => a.iter().filter_map(|x| x.as_str().map(String::from)).collect(), + Value::String(s) => s.split([',', ' ']).filter(|x| !x.is_empty()).map(String::from).collect(), + _ => vec![], + } +} + +async fn oidc_callback_inner(app: &App, code: String, state: String) -> Result { + let cfg = app.config(); + let oc = &cfg.auth.oidc; + let pending = app.oidc_pending.lock().unwrap().remove(&state).context("unknown or expired sign-in attempt – please try again")?; + let doc = discovery(app).await?; + let token_endpoint = doc["token_endpoint"].as_str().context("no token_endpoint")?; + let client_id = expand_env(&oc.client_id); + let resp = crate::provider::http_client() + .post(token_endpoint) + .form(&[ + ("grant_type", "authorization_code"), + ("code", code.as_str()), + ("redirect_uri", redirect_uri(app)?.as_str()), + ("client_id", client_id.as_str()), + ("client_secret", expand_env(&oc.client_secret).as_str()), + ("code_verifier", pending.verifier.as_str()), + ]) + .send() + .await?; + if !resp.status().is_success() { + bail!("token exchange failed: HTTP {} {}", resp.status(), resp.text().await.unwrap_or_default().chars().take(300).collect::()); + } + let tokens: Value = resp.json().await?; + // The id_token comes straight from the token endpoint over TLS, which + // authenticates the issuer (OIDC Core 3.1.3.7); we still check its claims. + let mut claims = decode_jwt_claims(tokens["id_token"].as_str().context("no id_token in response")?)?; + let issuer = expand_env(&oc.issuer); + if claims["iss"].as_str().map(|s| s.trim_end_matches('/')) != Some(issuer.trim_end_matches('/')) { + bail!("id_token issuer mismatch"); + } + let aud_ok = match &claims["aud"] { + Value::String(a) => *a == client_id, + Value::Array(a) => a.iter().any(|x| x.as_str() == Some(&client_id)), + _ => false, + }; + if !aud_ok { + bail!("id_token audience mismatch"); + } + if claims["nonce"].as_str() != Some(pending.nonce.as_str()) { + bail!("id_token nonce mismatch"); + } + if claims["exp"].as_i64().unwrap_or(0) < chrono::Utc::now().timestamp() { + bail!("id_token expired"); + } + // Groups and names are often only in the userinfo response. + if let (Some(ep), Some(at)) = (doc["userinfo_endpoint"].as_str(), tokens["access_token"].as_str()) { + if let Ok(r) = crate::provider::http_client().get(ep).bearer_auth(at).send().await { + if let Ok(info) = r.json::().await { + if info["sub"] == claims["sub"] { + for (k, v) in info.as_object().cloned().unwrap_or_default() { + if claims.get(&k).is_none() { + claims[&k] = v; + } + } + } + } + } + } + let sub = claims["sub"].as_str().context("no sub claim")?.to_string(); + let groups = claim_list(&claims[&oc.groups_claim]); + if !oc.allowed_group.is_empty() && !groups.contains(&oc.allowed_group) { + bail!("your account is not allowed to use CodeCraft"); + } + let is_admin = !oc.admin_group.is_empty() && groups.contains(&oc.admin_group); + let display = claims["name"].as_str().unwrap_or("").to_string(); + + let user = if let Some(u) = app.store.user_by_oidc(&sub)? { + u + } else { + let raw = claims[&oc.username_claim] + .as_str() + .or_else(|| claims["preferred_username"].as_str()) + .or_else(|| claims["email"].as_str()) + .unwrap_or(&sub); + let name = sanitize_username(raw); + if !valid_username(&name) { + bail!("cannot derive a valid username from the '{}' claim", oc.username_claim); + } + match app.store.user_by_name(&name)? { + Some((existing, _)) if oc.link_by_username && existing.oidc_sub_free() => { + app.store.update_user(&existing.id, None, None, None, None, Some(&sub))?; + existing + } + Some(_) => bail!("the username '{name}' is already taken by a local account"), + None if oc.auto_create => { + let first = app.store.user_count()? == 0; + let role = if is_admin || first { "admin" } else { "user" }; + let u = app.store.create_user(&name, if display.is_empty() { &name } else { &display }, role, None, Some(&sub))?; + if first { + app.store.adopt_orphans(&u.id)?; + *app.setup_code.lock().unwrap() = None; + } + u + } + None => bail!("no CodeCraft account exists for '{name}'"), + } + }; + if user.disabled { + bail!("this account is disabled"); + } + // Keep the role in sync with the IdP group (never removing the last admin). + if !oc.admin_group.is_empty() && user.is_admin() != is_admin && (is_admin || app.store.admin_count()? > 1) { + app.store.update_user(&user.id, None, Some(if is_admin { "admin" } else { "user" }), None, None, None)?; + } + Ok(app.store.user(&user.id)?.context("user vanished")?) +} + +impl User { + fn oidc_sub_free(&self) -> bool { + !self.oidc + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn password_roundtrip() { + let h = hash_password("correct horse").unwrap(); + assert!(verify_password("correct horse", &h)); + assert!(!verify_password("wrong", &h)); + } + + #[test] + fn usernames() { + assert!(valid_username("zink")); + assert!(!valid_username("../etc")); + assert!(!valid_username(".hidden")); + assert_eq!(sanitize_username("Richard.Zink@example.org"), "richard.zink"); + assert_eq!(sanitize_username("a/b c"), "a_b_c"); + } + + #[test] + fn jwt_claims() { + let payload = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(br#"{"sub":"1","aud":"x"}"#); + let v = decode_jwt_claims(&format!("h.{payload}.s")).unwrap(); + assert_eq!(v["sub"], "1"); + } +} diff --git a/server/src/budget.rs b/server/src/budget.rs new file mode 100644 index 0000000..0b4ea65 --- /dev/null +++ b/server/src/budget.rs @@ -0,0 +1,223 @@ +//! Budgets: monthly limits tracked from recorded costs, plus the real balance +//! for providers that expose one (OpenRouter, fal.ai), and price discovery. + +use crate::app::App; +use crate::config::{ProviderConfig, expand_env}; +use anyhow::{Context, Result, bail}; +use chrono::{Datelike, TimeZone}; +use serde_json::{Value, json}; +use std::time::Duration; + +/// Start of the current month in local time (ms). +pub fn month_start() -> i64 { + let now = chrono::Local::now(); + chrono::Local + .with_ymd_and_hms(now.year(), now.month(), 1, 0, 0, 0) + .single() + .map(|d| d.timestamp_millis()) + .unwrap_or(0) +} + +fn is_openrouter(p: &ProviderConfig) -> bool { + p.base_url.contains("openrouter.ai") +} + +/// Month-to-date cost from Anthropic's cost report (organisation-wide, USD). +async fn anthropic_cost(p: &ProviderConfig) -> Result { + let admin = expand_env(&p.admin_key); + let start = chrono::DateTime::from_timestamp_millis(month_start()).unwrap_or_default(); + let end = chrono::Utc::now() + chrono::Duration::days(1); + let base = if p.base_url.is_empty() { "https://api.anthropic.com/v1".to_string() } else { expand_env(&p.base_url).trim_end_matches('/').to_string() }; + let mut total_cents = 0.0; + let mut page: Option = None; + for _ in 0..10 { + let mut url = reqwest::Url::parse(&format!("{base}/organizations/cost_report"))?; + url.query_pairs_mut() + .append_pair("starting_at", &start.format("%Y-%m-%dT00:00:00Z").to_string()) + .append_pair("ending_at", &end.format("%Y-%m-%dT00:00:00Z").to_string()) + .append_pair("limit", "31"); + if let Some(pg) = &page { + url.query_pairs_mut().append_pair("page", pg); + } + let resp = crate::provider::http_client() + .get(url) + .header("x-api-key", &admin) + .header("anthropic-version", "2026-10-21") + .timeout(Duration::from_secs(20)) + .send() + .await?; + if !resp.status().is_success() { + return Err(crate::provider::http_error(resp).await); + } + let v: Value = resp.json().await?; + for bucket in v["data"].as_array().cloned().unwrap_or_default() { + for r in bucket["results"].as_array().cloned().unwrap_or_default() { + // Amounts are decimal strings in cents. + total_cents += r["amount"].as_str().and_then(|a| a.parse::().ok()).or_else(|| r["amount"].as_f64()).unwrap_or(0.0); + } + } + if v["has_more"].as_bool() != Some(true) { + break; + } + page = v["next_page"].as_str().map(String::from); + } + Ok(json!({"currency": "USD", "spent_month": total_cents / 100.0, "scope": "organization"})) +} + +/// Balance or cost as reported by the provider, if it has an API for it. +async fn remote_balance(p: &ProviderConfig) -> Option> { + if p.kind == crate::config::ProviderKind::Anthropic && !expand_env(&p.admin_key).is_empty() { + return Some(anthropic_cost(p).await); + } + if !is_openrouter(p) { + return None; + } + Some( + async { + let base = expand_env(&p.base_url).trim_end_matches('/').to_string(); + let resp = crate::provider::http_client() + .get(format!("{base}/key")) + .bearer_auth(expand_env(&p.api_key)) + .timeout(Duration::from_secs(15)) + .send() + .await? + .error_for_status()?; + let v: Value = resp.json().await?; + let d = &v["data"]; + Ok(json!({ + "currency": "USD", + "limit": d["limit"], + "used": d["usage"], + "remaining": d.get("limit_remaining").cloned().unwrap_or_else(|| match (d["limit"].as_f64(), d["usage"].as_f64()) { + (Some(l), Some(u)) => json!(l - u), + _ => Value::Null, + }), + })) + } + .await, + ) +} + +async fn fal_balance(admin_key: &str) -> Result { + let resp = crate::provider::http_client() + .get("https://api.fal.ai/v1/account/billing?expand=credits") + .header("Authorization", format!("Key {admin_key}")) + .timeout(Duration::from_secs(15)) + .send() + .await?; + if !resp.status().is_success() { + return Err(crate::provider::http_error(resp).await); + } + let v: Value = resp.json().await?; + Ok(json!({ + "currency": v["credits"]["currency"], + "remaining": v["credits"]["current_balance"], + })) +} + +fn entry(name: &str, budget: Option, spent: f64, warn: f64) -> Value { + let share = budget.filter(|b| *b > 0.0).map(|b| spent / b); + json!({ + "provider": name, + "budget": budget, + "spent": spent, + "remaining": budget.map(|b| b - spent), + "share": share, + "warning": share.is_some_and(|s| s >= warn), + "exceeded": share.is_some_and(|s| s >= 1.0), + }) +} + +/// Budget overview for the current month. +pub async fn overview(app: &App) -> Result { + let cfg = app.config(); + let spent = app.store.cost_by_provider(month_start())?; + let warn = cfg.server.budget_warning; + let mut rows = Vec::new(); + let mut tasks = Vec::new(); + for (name, p) in &cfg.providers { + if !p.enabled { + continue; + } + rows.push(entry(name, p.monthly_budget, spent.get(name).copied().unwrap_or(0.0), warn)); + let p = p.clone(); + tasks.push(tokio::spawn(async move { remote_balance(&p).await })); + } + for (row, task) in rows.iter_mut().zip(tasks) { + if let Ok(Some(res)) = task.await { + row["remote"] = match res { + Ok(v) => v, + Err(e) => json!({"error": format!("{e:#}")}), + }; + } + } + if cfg.images.enabled { + let mut row = entry("fal", cfg.images.monthly_budget, spent.get("fal").copied().unwrap_or(0.0), warn); + let admin = expand_env(&cfg.images.admin_key); + if !admin.is_empty() { + row["remote"] = match fal_balance(&admin).await { + Ok(v) => v, + Err(e) => json!({"error": format!("{e:#}")}), + }; + } + row["images"] = json!(true); + rows.push(row); + } + let total: f64 = spent.values().sum(); + let global = (cfg.server.monthly_budget > 0.0).then_some(cfg.server.monthly_budget); + Ok(json!({ + "month_start": month_start(), + "currency": cfg.server.currency, + "total": entry("total", global, total, warn), + "providers": rows, + })) +} + +/// Per-token prices from the provider's model list, converted to per 1M tokens. +pub async fn discover_prices(app: &App, provider: &str) -> Result { + let cfg = app.config(); + let p = cfg.providers.get(provider).context("unknown provider")?; + if !is_openrouter(p) { + bail!("this provider does not publish prices via its API – enter them manually"); + } + let base = expand_env(&p.base_url).trim_end_matches('/').to_string(); + let v: Value = crate::provider::http_client() + .get(format!("{base}/models")) + .timeout(Duration::from_secs(30)) + .send() + .await? + .error_for_status()? + .json() + .await?; + let per_m = |x: &Value| x.as_str().and_then(|s| s.parse::().ok()).or_else(|| x.as_f64()).map(|f| (f * 1_000_000.0 * 10_000.0).round() / 10_000.0); + let list: Vec = v["data"] + .as_array() + .cloned() + .unwrap_or_default() + .iter() + .map(|m| { + json!({ + "id": m["id"], + "context": m["context_length"], + "input_price": per_m(&m["pricing"]["prompt"]), + "output_price": per_m(&m["pricing"]["completion"]), + "cache_read_price": per_m(&m["pricing"]["input_cache_read"]), + "cache_write_price": per_m(&m["pricing"]["input_cache_write"]), + }) + }) + .collect(); + Ok(json!({"currency": "USD", "models": list})) +} + +#[cfg(test)] +mod tests { + #[test] + fn budget_entry_flags() { + let e = super::entry("x", Some(10.0), 8.5, 0.8); + assert_eq!(e["warning"], true); + assert_eq!(e["exceeded"], false); + assert_eq!(e["remaining"], 1.5); + let none = super::entry("y", None, 3.0, 0.8); + assert_eq!(none["warning"], false); + } +} diff --git a/server/src/config.rs b/server/src/config.rs index b5d95b0..822135e 100644 --- a/server/src/config.rs +++ b/server/src/config.rs @@ -19,6 +19,118 @@ pub struct Config { pub permissions: PermissionConfig, pub agents: BTreeMap, pub craft: CraftConfig, + pub auth: AuthConfig, + pub images: ImagesConfig, +} + +/// Image generation via fal.ai. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(default)] +pub struct ImagesConfig { + pub enabled: bool, + pub base_url: String, + pub api_key: String, + pub default_model: String, + /// Directory (relative to the session/project directory) for generated images. + pub output_dir: String, + pub models: Vec, + /// Monthly budget for image generation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub monthly_budget: Option, + /// Optional fal.ai Admin key to query the account balance. + #[serde(default, skip_serializing_if = "String::is_empty")] + pub admin_key: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize, Default)] +pub struct ImageModel { + pub id: String, + #[serde(default)] + pub name: Option, + /// Price per image (statistics). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub price: Option, + /// "image_size" (FLUX style enums) or "aspect_ratio" ("16:9" style). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub size_param: Option, + /// Extra request fields sent as-is. + #[serde(default, skip_serializing_if = "serde_json::Map::is_empty")] + pub options: serde_json::Map, +} + +impl Default for ImagesConfig { + fn default() -> Self { + Self { + enabled: false, + base_url: "https://fal.run".into(), + api_key: "${FAL_KEY}".into(), + default_model: "fal-ai/flux/schnell".into(), + output_dir: "generated".into(), + models: vec![ImageModel { id: "fal-ai/flux/schnell".into(), name: Some("FLUX.1 schnell".into()), price: Some(0.003), ..Default::default() }], + monthly_budget: None, + admin_key: String::new(), + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(default)] +pub struct AuthConfig { + /// Lifetime of a login in days. + pub session_days: u32, + /// External URL, e.g. https://codecraft.example.org (needed for OIDC). + pub public_url: String, + /// "auto" (secure when served via https), "true" or "false". + pub cookie_secure: String, + pub oidc: OidcConfig, +} + +impl Default for AuthConfig { + fn default() -> Self { + Self { session_days: 30, public_url: String::new(), cookie_secure: "auto".into(), oidc: OidcConfig::default() } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(default)] +pub struct OidcConfig { + pub enabled: bool, + pub issuer: String, + pub client_id: String, + pub client_secret: String, + pub scopes: Vec, + /// Claim used as username. + pub username_claim: String, + pub groups_claim: String, + /// Members of this group become admins (empty = roles are managed in CodeCraft). + pub admin_group: String, + /// Only members of this group may sign in (empty = everybody the IdP lets through). + pub allowed_group: String, + /// Create users on first login. + pub auto_create: bool, + /// Link an IdP login to an existing local user with the same username. + /// Only enable if you trust the IdP's usernames. + pub link_by_username: bool, + pub button_label: String, +} + +impl Default for OidcConfig { + fn default() -> Self { + Self { + enabled: false, + issuer: String::new(), + client_id: String::new(), + client_secret: String::new(), + scopes: vec!["openid".into(), "profile".into(), "email".into(), "groups".into()], + username_claim: "preferred_username".into(), + groups_claim: "groups".into(), + admin_group: String::new(), + allowed_group: String::new(), + auto_create: true, + link_by_username: false, + button_label: "Single sign-on".into(), + } + } } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -27,12 +139,19 @@ pub struct ServerConfig { pub bind: String, pub workspace: String, pub data_dir: String, - pub auth_token: String, pub default_model: String, pub small_model: String, pub auto_compact_threshold: f64, /// Currency label for cost statistics (display only). pub currency: String, + /// Max. subagents running at the same time (all sessions). + pub max_parallel_subagents: usize, + /// MCP servers with more tools than this are loaded on demand (load = "auto"). + pub mcp_direct_limit: usize, + /// Monthly budget for all providers together (0 = none). + pub monthly_budget: f64, + /// Warn when this share of a budget is used (0.0–1.0). + pub budget_warning: f64, } impl Default for ServerConfig { @@ -41,11 +160,14 @@ impl Default for ServerConfig { bind: "127.0.0.1:8787".into(), workspace: ".".into(), data_dir: "./data".into(), - auth_token: String::new(), default_model: String::new(), small_model: String::new(), auto_compact_threshold: 0.85, currency: "USD".into(), + max_parallel_subagents: 4, + mcp_direct_limit: 15, + monthly_budget: 0.0, + budget_warning: 0.8, } } } @@ -74,6 +196,16 @@ pub struct ProviderConfig { pub models: Vec, #[serde(default = "yes")] pub enabled: bool, + /// Max. simultaneous requests to this provider/host (None = unlimited). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub max_concurrency: Option, + /// Monthly budget in the configured currency (tracked from recorded costs). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub monthly_budget: Option, + /// Optional admin key to read the provider's own cost report + /// (Anthropic: sk-ant-admin01-…, organisation-wide). + #[serde(default, skip_serializing_if = "String::is_empty")] + pub admin_key: String, } fn yes() -> bool { @@ -112,6 +244,12 @@ pub struct ModelConfig { pub cache_read_price: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub cache_write_price: Option, + /// Max. simultaneous requests to this model (None = unlimited). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub max_concurrency: Option, + /// Extra request body fields sent as-is (e.g. temperature, reasoning_budget_tokens). + #[serde(default, skip_serializing_if = "serde_json::Map::is_empty")] + pub options: serde_json::Map, } impl ModelConfig { @@ -151,6 +289,9 @@ pub struct McpConfig { pub headers: BTreeMap, #[serde(default = "yes")] pub enabled: bool, + /// "auto" (default), "always" or "on_demand": how the server's tools reach the model. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub load: Option, } #[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)] @@ -180,6 +321,9 @@ pub struct AgentConfig { pub tools: Option>, #[serde(default)] pub prompt: Option, + /// Max. instances of this subagent running at the same time. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub max_parallel: Option, } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -224,10 +368,6 @@ impl Config { pub fn data_dir(&self) -> PathBuf { PathBuf::from(expand_env(&self.server.data_dir)) } - - pub fn auth_token(&self) -> String { - expand_env(&self.server.auth_token) - } } /// Locate the config file: $CODECRAFT_CONFIG, else ./data/config.toml (or /data in Docker). @@ -273,9 +413,12 @@ pub fn apply_patches(text: &str, patches: &[ConfigPatch]) -> Result { let (last, parents) = patch.path.split_last().unwrap(); let mut table: &mut toml_edit::Table = doc.as_table_mut(); for key in parents { - let entry = table - .entry(key) - .or_insert_with(|| toml_edit::Item::Table(toml_edit::Table::new())); + let entry = table.entry(key).or_insert_with(|| { + let mut t = toml_edit::Table::new(); + // No empty "[agents]" header when only "[agents.x]" is needed. + t.set_implicit(true); + toml_edit::Item::Table(t) + }); // Inline tables are converted so we can descend into them. if let Some(inline) = entry.as_inline_table().cloned() { *entry = toml_edit::Item::Table(inline.into_table()); @@ -327,6 +470,11 @@ fn json_to_toml(v: &serde_json::Value) -> Option { n.as_f64()?.into() } } + J::String(s) if s.contains('\n') && !s.contains("'''") && !s.contains('\r') => { + // Multi-line text (prompts) as a readable literal block. + let body = if s.ends_with('\n') { s.clone() } else { format!("{s}\n") }; + format!("'''\n{body}'''").parse::().unwrap_or_else(|_| s.as_str().into()) + } J::String(s) => s.as_str().into(), J::Array(a) => { let mut arr = toml_edit::Array::new(); @@ -369,12 +517,20 @@ pub fn redacted_json(cfg: &Config) -> serde_json::Value { }; if let Some(ps) = v.get_mut("providers").and_then(|p| p.as_object_mut()) { for p in ps.values_mut() { - if let Some(k) = p.get_mut("api_key") { - mask(k); + for field in ["api_key", "admin_key"] { + if let Some(k) = p.get_mut(field) { + mask(k); + } } } } - if let Some(t) = v.pointer_mut("/server/auth_token") { + if let Some(t) = v.pointer_mut("/auth/oidc/client_secret") { + mask(t); + } + if let Some(t) = v.pointer_mut("/images/api_key") { + mask(t); + } + if let Some(t) = v.pointer_mut("/images/admin_key") { mask(t); } v @@ -408,6 +564,23 @@ mod tests { assert!(cfg.providers["gemini"].enabled); } + #[test] + fn patch_multiline_prompt_roundtrips() { + let prompt = "You write docs.\nUse \"quotes\" and \\backslashes."; + let out = apply_patches( + EXAMPLE_CONFIG, + &[ConfigPatch { + path: vec!["agents".into(), "writer".into(), "prompt".into()], + value: serde_json::json!(prompt), + }], + ) + .unwrap(); + assert!(out.contains("prompt = '''\nYou write docs.")); + assert!(!out.contains("[agents]\n")); + let cfg = Config::parse(&out).unwrap(); + assert_eq!(cfg.agents["writer"].prompt.as_deref().unwrap().trim_end(), prompt); + } + #[test] fn patch_adds_provider_section() { let out = apply_patches( diff --git a/server/src/files.rs b/server/src/files.rs new file mode 100644 index 0000000..6495fc7 --- /dev/null +++ b/server/src/files.rs @@ -0,0 +1,300 @@ +//! File manager API: browse, edit, upload, move and delete inside the user's folders. + +use crate::api::{ApiError, ApiResult}; +use crate::app::App; +use crate::store::User; +use axum::body::Bytes; +use axum::extract::{Query, State}; +use axum::http::{StatusCode, header}; +use axum::response::{IntoResponse, Response}; +use axum::{Extension, Json}; +use serde::Deserialize; +use serde_json::{Value, json}; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +type AppState = Arc; + +const MAX_TEXT: u64 = 2 * 1024 * 1024; + +fn bad(msg: impl Into) -> ApiError { + ApiError(StatusCode::BAD_REQUEST, msg.into()) +} + +/// Resolve a path and make sure it (or, for new files, its parent) lies inside +/// one of the user's folders. Symlinks are resolved before the check. +pub fn checked_path(app: &App, user: &User, raw: &str, must_exist: bool) -> Result { + let roots = app.user_roots(Some(&user.id)); + let p = crate::tools::normalize(Path::new(raw.trim())); + if !p.is_absolute() { + return Err(bad("path must be absolute")); + } + let real = if p.exists() { + std::fs::canonicalize(&p).map_err(|e| bad(e.to_string()))? + } else { + if must_exist { + return Err(ApiError(StatusCode::NOT_FOUND, format!("{} does not exist", p.display()))); + } + let parent = p.parent().ok_or_else(|| bad("invalid path"))?; + let parent = std::fs::canonicalize(parent).map_err(|_| bad("parent directory does not exist"))?; + parent.join(p.file_name().ok_or_else(|| bad("invalid path"))?) + }; + if !roots.iter().any(|r| real.starts_with(r)) { + return Err(ApiError(StatusCode::FORBIDDEN, "path is outside your folders".into())); + } + Ok(real) +} + +fn is_root(app: &App, user: &User, p: &Path) -> bool { + app.user_roots(Some(&user.id)).iter().any(|r| r == p) +} + +#[derive(Deserialize)] +pub struct PathQuery { + path: String, + #[serde(default)] + hidden: bool, +} + +pub async fn list(Extension(user): Extension, State(app): State, Query(q): Query) -> ApiResult { + let dir = checked_path(&app, &user, &q.path, true)?; + if !dir.is_dir() { + return Err(bad("not a directory")); + } + let mut entries = Vec::new(); + for e in std::fs::read_dir(&dir)?.flatten() { + let name = e.file_name().to_string_lossy().to_string(); + if !q.hidden && name.starts_with('.') { + continue; + } + let meta = e.metadata().ok(); + let is_dir = e.file_type().map(|t| t.is_dir()).unwrap_or(false); + entries.push(json!({ + "name": name, + "path": e.path(), + "is_dir": is_dir, + "size": meta.as_ref().map(|m| m.len()).unwrap_or(0), + "mtime": meta.and_then(|m| m.modified().ok()).and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()).map(|d| d.as_millis() as u64), + })); + } + entries.sort_by(|a, b| { + b["is_dir"].as_bool().cmp(&a["is_dir"].as_bool()).then_with(|| a["name"].as_str().unwrap_or("").to_lowercase().cmp(&b["name"].as_str().unwrap_or("").to_lowercase())) + }); + let parent = if is_root(&app, &user, &dir) { Value::Null } else { json!(dir.parent()) }; + let repo = crate::git::repo_root(&dir); + Ok(Json(json!({"path": dir, "parent": parent, "entries": entries, "repo": repo}))) +} + +pub async fn read(Extension(user): Extension, State(app): State, Query(q): Query) -> ApiResult { + let p = checked_path(&app, &user, &q.path, true)?; + let meta = std::fs::metadata(&p)?; + if meta.is_dir() { + return Err(bad("is a directory")); + } + let mtime = meta.modified().ok().and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()).map(|d| d.as_millis() as u64); + if meta.len() > MAX_TEXT { + return Ok(Json(json!({"path": p, "binary": true, "too_large": true, "size": meta.len(), "mtime": mtime}))); + } + let bytes = tokio::fs::read(&p).await?; + let binary = bytes.iter().take(8000).any(|b| *b == 0) || std::str::from_utf8(&bytes).is_err(); + Ok(Json(json!({ + "path": p, + "binary": binary, + "size": meta.len(), + "mtime": mtime, + "content": if binary { Value::Null } else { json!(String::from_utf8_lossy(&bytes)) }, + }))) +} + +fn mime_for(p: &Path) -> &'static str { + match p.extension().and_then(|e| e.to_str()).map(|e| e.to_ascii_lowercase()).as_deref() { + Some("png") => "image/png", + Some("jpg") | Some("jpeg") => "image/jpeg", + Some("gif") => "image/gif", + Some("webp") => "image/webp", + Some("svg") => "image/svg+xml", + Some("pdf") => "application/pdf", + Some("txt") | Some("md") | Some("log") => "text/plain; charset=utf-8", + _ => "application/octet-stream", + } +} + +#[derive(Deserialize)] +pub struct RawQuery { + path: String, + #[serde(default)] + download: bool, +} + +/// File bytes for previews and downloads. SVGs are served as attachments only, +/// so scripts inside them never run in the app's origin. +pub async fn raw(Extension(user): Extension, State(app): State, Query(q): Query) -> Result { + let p = checked_path(&app, &user, &q.path, true)?; + if p.is_dir() { + return Err(bad("is a directory")); + } + let bytes = tokio::fs::read(&p).await?; + let mime = mime_for(&p); + let name = p.file_name().and_then(|n| n.to_str()).unwrap_or("file").replace('"', ""); + let disposition = if q.download || mime == "image/svg+xml" || mime == "application/octet-stream" { + format!("attachment; filename=\"{name}\"") + } else { + format!("inline; filename=\"{name}\"") + }; + Ok(( + [ + (header::CONTENT_TYPE, mime.to_string()), + (header::CONTENT_DISPOSITION, disposition), + (header::X_CONTENT_TYPE_OPTIONS, "nosniff".to_string()), + (header::CONTENT_SECURITY_POLICY, "default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'".to_string()), + ], + bytes, + ) + .into_response()) +} + +#[derive(Deserialize)] +pub struct WriteBody { + path: String, + content: String, + /// mtime the client loaded; a newer file on disk is a conflict. + expected_mtime: Option, + #[serde(default)] + force: bool, +} + +pub async fn write(Extension(user): Extension, State(app): State, Json(b): Json) -> ApiResult { + let p = checked_path(&app, &user, &b.path, false)?; + if p.is_dir() { + return Err(bad("is a directory")); + } + if let (Some(expected), false) = (b.expected_mtime, b.force) { + let current = std::fs::metadata(&p).ok().and_then(|m| m.modified().ok()).and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()).map(|d| d.as_millis() as u64); + if current.is_some_and(|c| c > expected) { + return Err(ApiError(StatusCode::CONFLICT, "the file was changed on disk since you opened it".into())); + } + } + tokio::fs::write(&p, b.content.as_bytes()).await?; + let mtime = std::fs::metadata(&p).ok().and_then(|m| m.modified().ok()).and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()).map(|d| d.as_millis() as u64); + Ok(Json(json!({"ok": true, "mtime": mtime}))) +} + +#[derive(Deserialize)] +pub struct CreateBody { + path: String, + #[serde(default)] + dir: bool, +} + +pub async fn create(Extension(user): Extension, State(app): State, Json(b): Json) -> ApiResult { + let p = checked_path(&app, &user, &b.path, false)?; + if p.exists() { + return Err(ApiError(StatusCode::CONFLICT, "already exists".into())); + } + if b.dir { + tokio::fs::create_dir(&p).await?; + } else { + tokio::fs::write(&p, b"").await?; + } + Ok(Json(json!({"ok": true, "path": p}))) +} + +#[derive(Deserialize)] +pub struct MoveBody { + from: String, + to: String, + #[serde(default)] + copy: bool, +} + +fn copy_recursive(from: &Path, to: &Path) -> std::io::Result<()> { + if from.is_dir() { + std::fs::create_dir_all(to)?; + for e in std::fs::read_dir(from)? { + let e = e?; + copy_recursive(&e.path(), &to.join(e.file_name()))?; + } + Ok(()) + } else { + std::fs::copy(from, to).map(|_| ()) + } +} + +pub async fn move_or_copy(Extension(user): Extension, State(app): State, Json(b): Json) -> ApiResult { + let from = checked_path(&app, &user, &b.from, true)?; + let to = checked_path(&app, &user, &b.to, false)?; + if is_root(&app, &user, &from) { + return Err(bad("a root folder cannot be moved")); + } + if to.exists() { + return Err(ApiError(StatusCode::CONFLICT, "target already exists".into())); + } + if to.starts_with(&from) { + return Err(bad("cannot move a folder into itself")); + } + let (f, t) = (from.clone(), to.clone()); + tokio::task::spawn_blocking(move || if b.copy { copy_recursive(&f, &t) } else { std::fs::rename(&f, &t) }).await??; + Ok(Json(json!({"ok": true, "path": to}))) +} + +pub async fn remove(Extension(user): Extension, State(app): State, Query(q): Query) -> ApiResult { + let p = checked_path(&app, &user, &q.path, true)?; + if is_root(&app, &user, &p) { + return Err(bad("a root folder cannot be deleted")); + } + if p.is_dir() { + tokio::fs::remove_dir_all(&p).await?; + } else { + tokio::fs::remove_file(&p).await?; + } + Ok(Json(json!({"ok": true}))) +} + +#[derive(Deserialize)] +pub struct UploadQuery { + /// Target file path (absolute). + path: String, +} + +/// Upload one file as the raw request body. +pub async fn upload(Extension(user): Extension, State(app): State, Query(q): Query, body: Bytes) -> ApiResult { + let p = checked_path(&app, &user, &q.path, false)?; + if p.is_dir() { + return Err(bad("is a directory")); + } + tokio::fs::write(&p, &body).await?; + Ok(Json(json!({"ok": true, "path": p, "size": body.len()}))) +} + +#[derive(Deserialize)] +pub struct SearchQuery { + path: String, + q: String, +} + +/// File name search below a folder (respects .gitignore). +pub async fn search(Extension(user): Extension, State(app): State, Query(q): Query) -> ApiResult { + let root = checked_path(&app, &user, &q.path, true)?; + let needle = q.q.to_lowercase(); + if needle.trim().is_empty() { + return Ok(Json(json!([]))); + } + let hits = tokio::task::spawn_blocking(move || { + let mut out = Vec::new(); + for e in ignore::WalkBuilder::new(&root).hidden(true).build().flatten() { + if e.depth() == 0 { + continue; + } + let name = e.file_name().to_string_lossy().to_lowercase(); + if name.contains(&needle) { + out.push(json!({"path": e.path(), "is_dir": e.file_type().is_some_and(|t| t.is_dir())})); + if out.len() >= 200 { + break; + } + } + } + out + }) + .await?; + Ok(Json(json!(hits))) +} diff --git a/server/src/git.rs b/server/src/git.rs new file mode 100644 index 0000000..d092bac --- /dev/null +++ b/server/src/git.rs @@ -0,0 +1,348 @@ +//! Git client for the file manager. Runs the git CLI without a shell; file +//! names always follow `--`, refs and URLs are validated. + +use crate::api::{ApiError, ApiResult}; +use crate::app::App; +use crate::files::checked_path; +use crate::store::User; +use axum::extract::State; +use axum::http::StatusCode; +use axum::{Extension, Json}; +use serde::Deserialize; +use serde_json::{Value, json}; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use std::time::Duration; +use tokio::process::Command; + +type AppState = Arc; + +/// Nearest directory containing `.git`. +pub fn repo_root(dir: &Path) -> Option { + let mut d = dir.to_path_buf(); + loop { + if d.join(".git").exists() { + return Some(d); + } + if !d.pop() { + return None; + } + } +} + +struct Out { + ok: bool, + stdout: String, + stderr: String, +} + +async fn git(dir: &Path, args: &[&str], timeout: u64) -> Result { + let fut = Command::new("git") + .args(args) + .current_dir(dir) + .env("GIT_TERMINAL_PROMPT", "0") + .env("GIT_PAGER", "cat") + .env("LC_ALL", "C") + .kill_on_drop(true) + .output(); + let out = tokio::time::timeout(Duration::from_secs(timeout), fut) + .await + .map_err(|_| ApiError(StatusCode::GATEWAY_TIMEOUT, "git timed out".into()))? + .map_err(|e| ApiError(StatusCode::INTERNAL_SERVER_ERROR, format!("cannot run git: {e}")))?; + Ok(Out { + ok: out.status.success(), + stdout: String::from_utf8_lossy(&out.stdout).into_owned(), + stderr: String::from_utf8_lossy(&out.stderr).into_owned(), + }) +} + +fn fail(o: Out) -> ApiError { + let msg = if o.stderr.trim().is_empty() { o.stdout } else { o.stderr }; + ApiError(StatusCode::BAD_REQUEST, msg.trim().to_string()) +} + +async fn git_ok(dir: &Path, args: &[&str], timeout: u64) -> Result { + let o = git(dir, args, timeout).await?; + if o.ok { Ok(o.stdout) } else { Err(fail(o)) } +} + +fn valid_ref(name: &str) -> bool { + !name.is_empty() + && !name.starts_with('-') + && !name.contains("..") + && name.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '/' | '-' | '_' | '.')) +} + +#[derive(Deserialize)] +pub struct GitBody { + /// Repository directory, or any path inside it. + path: String, + action: String, + #[serde(default)] + files: Vec, + #[serde(default)] + staged: bool, + message: Option, + #[serde(default)] + amend: bool, + branch: Option, + #[serde(default)] + create: bool, + rev: Option, + url: Option, + limit: Option, +} + +/// Repo-relative paths for the given absolute file paths (all checked). +fn rel_files(app: &App, user: &User, repo: &Path, files: &[String]) -> Result, ApiError> { + files + .iter() + .map(|f| { + let p = if Path::new(f).is_absolute() { PathBuf::from(f) } else { repo.join(f) }; + let p = checked_path(app, user, &p.to_string_lossy(), false)?; + p.strip_prefix(repo) + .map(|r| r.to_string_lossy().into_owned()) + .map_err(|_| ApiError(StatusCode::BAD_REQUEST, "file is outside the repository".into())) + }) + .collect() +} + +fn parse_status(raw: &str) -> Value { + let mut branch = Value::Null; + let mut upstream = Value::Null; + let (mut ahead, mut behind) = (0, 0); + let mut files = Vec::new(); + let mut entries = raw.split('\0').filter(|s| !s.is_empty()); + while let Some(e) = entries.next() { + if let Some(h) = e.strip_prefix("## ") { + // "main...origin/main [ahead 1, behind 2]" or "No commits yet on main" + let (names, info) = h.split_once(" [").map(|(a, b)| (a, b.trim_end_matches(']'))).unwrap_or((h, "")); + let names = names.trim_start_matches("No commits yet on "); + let (b, u) = names.split_once("...").map(|(a, b)| (a, Some(b))).unwrap_or((names, None)); + branch = json!(b); + upstream = json!(u); + for part in info.split(", ") { + if let Some(n) = part.strip_prefix("ahead ") { + ahead = n.parse().unwrap_or(0); + } + if let Some(n) = part.strip_prefix("behind ") { + behind = n.parse().unwrap_or(0); + } + } + continue; + } + if e.len() < 4 { + continue; + } + let (x, y) = (&e[0..1], &e[1..2]); + let path = &e[3..]; + let mut item = json!({"path": path, "index": x, "worktree": y}); + // Renames/copies carry the original path as the next entry. + if x == "R" || x == "C" { + item["from"] = json!(entries.next()); + } + files.push(item); + } + json!({"branch": branch, "upstream": upstream, "ahead": ahead, "behind": behind, "files": files}) +} + +pub async fn handle(Extension(user): Extension, State(app): State, Json(b): Json) -> ApiResult { + let target = checked_path(&app, &user, &b.path, b.action != "clone")?; + + // Actions that create a repository. + match b.action.as_str() { + "init" => { + if !target.is_dir() { + return Err(ApiError(StatusCode::BAD_REQUEST, "not a directory".into())); + } + let out = git_ok(&target, &["init", "-q"], 30).await?; + return Ok(Json(json!({"ok": true, "output": out}))); + } + "clone" => { + let url = b.url.as_deref().unwrap_or("").trim().to_string(); + let allowed = url.starts_with("https://") || url.starts_with("http://") || url.starts_with("ssh://") || url.starts_with("git@"); + if !allowed || url.contains(char::is_whitespace) { + return Err(ApiError(StatusCode::BAD_REQUEST, "unsupported repository URL".into())); + } + if target.exists() { + return Err(ApiError(StatusCode::CONFLICT, "target folder already exists".into())); + } + let parent = target.parent().unwrap_or(Path::new("/")).to_path_buf(); + let name = target.file_name().and_then(|n| n.to_str()).unwrap_or("repo").to_string(); + let o = git(&parent, &["clone", "--", &url, &name], 600).await?; + if !o.ok { + return Err(fail(o)); + } + return Ok(Json(json!({"ok": true, "path": target, "output": o.stderr}))); + } + _ => {} + } + + let start = if target.is_dir() { target.clone() } else { target.parent().unwrap_or(&target).to_path_buf() }; + let repo = repo_root(&start).ok_or_else(|| ApiError(StatusCode::BAD_REQUEST, "not a git repository".into()))?; + // The repository itself must be inside the user's folders. + checked_path(&app, &user, &repo.to_string_lossy(), true)?; + + let result = match b.action.as_str() { + "status" => { + let raw = git_ok(&repo, &["status", "--porcelain=v1", "-b", "-z", "--untracked-files=all"], 60).await?; + let mut v = parse_status(&raw); + v["repo"] = json!(repo); + v + } + "diff" => { + let files = rel_files(&app, &user, &repo, &b.files)?; + let mut args = vec!["diff", "--no-color", "--no-ext-diff"]; + if b.staged { + args.push("--cached"); + } + args.push("--"); + args.extend(files.iter().map(String::as_str)); + let mut out = git_ok(&repo, &args, 60).await?; + // Untracked files have no diff: show them as new. + if out.is_empty() && !b.staged && files.len() == 1 { + let o = git(&repo, &["diff", "--no-color", "--no-index", "--", "/dev/null", &files[0]], 60).await?; + out = o.stdout; + } + out.truncate(out.char_indices().nth(600_000).map(|(i, _)| i).unwrap_or(out.len())); + json!({"diff": out}) + } + "stage" | "unstage" | "discard" => { + let files = rel_files(&app, &user, &repo, &b.files)?; + if files.is_empty() { + return Err(ApiError(StatusCode::BAD_REQUEST, "no files".into())); + } + let mut args: Vec<&str> = match b.action.as_str() { + "stage" => vec!["add", "-A"], + "unstage" => vec!["restore", "--staged"], + _ => vec!["restore", "--worktree"], + }; + args.push("--"); + args.extend(files.iter().map(String::as_str)); + git_ok(&repo, &args, 60).await?; + json!({"ok": true}) + } + "commit" => { + let msg = b.message.as_deref().unwrap_or("").trim().to_string(); + if msg.is_empty() && !b.amend { + return Err(ApiError(StatusCode::BAD_REQUEST, "commit message is empty".into())); + } + // Use the repository's identity if configured, otherwise the CodeCraft user. + let has_identity = git(&repo, &["config", "user.email"], 10).await.map(|o| o.ok && !o.stdout.trim().is_empty()).unwrap_or(false); + let name = format!("user.name={}", if user.display_name.is_empty() { &user.username } else { &user.display_name }); + let email = format!("user.email={}@users.codecraft.local", user.username); + let mut args: Vec<&str> = Vec::new(); + if !has_identity { + args.extend(["-c", &name, "-c", &email]); + } + args.push("commit"); + if b.amend { + args.push("--amend"); + if msg.is_empty() { + args.push("--no-edit"); + } + } + if !msg.is_empty() { + args.extend(["-m", &msg]); + } + let out = git_ok(&repo, &args, 60).await?; + json!({"ok": true, "output": out}) + } + "log" => { + let n = b.limit.unwrap_or(50).min(500).to_string(); + let raw = git(&repo, &["log", "-n", &n, "--pretty=format:%H%x1f%an%x1f%at%x1f%s%x1f%D%x1e"], 60).await?; + let commits: Vec = raw + .stdout + .split('\u{1e}') + .filter(|s| !s.trim().is_empty()) + .map(|c| { + let f: Vec<&str> = c.trim_start_matches('\n').split('\u{1f}').collect(); + json!({ + "hash": f.first(), "author": f.get(1), + "time": f.get(2).and_then(|t| t.parse::().ok()).map(|t| t * 1000), + "subject": f.get(3), "refs": f.get(4), + }) + }) + .collect(); + json!({"commits": commits}) + } + "show" => { + let rev = b.rev.as_deref().unwrap_or(""); + if rev.len() < 4 || !rev.chars().all(|c| c.is_ascii_hexdigit()) { + return Err(ApiError(StatusCode::BAD_REQUEST, "invalid commit".into())); + } + let mut out = git_ok(&repo, &["show", "--no-color", "--stat", "--patch", rev], 60).await?; + out.truncate(out.char_indices().nth(600_000).map(|(i, _)| i).unwrap_or(out.len())); + json!({"diff": out}) + } + "branches" => { + let raw = git_ok(&repo, &["branch", "-a", "--format=%(refname:short)%1f%(HEAD)%1f%(upstream:short)"], 30).await?; + let list: Vec = raw + .lines() + .filter(|l| !l.trim().is_empty()) + .map(|l| { + let f: Vec<&str> = l.split('\u{1f}').collect(); + json!({"name": f.first(), "current": f.get(1) == Some(&"*"), "upstream": f.get(2).filter(|u| !u.is_empty())}) + }) + .collect(); + json!({"branches": list}) + } + "checkout" => { + let branch = b.branch.as_deref().unwrap_or(""); + if !valid_ref(branch) { + return Err(ApiError(StatusCode::BAD_REQUEST, "invalid branch name".into())); + } + let args: Vec<&str> = if b.create { vec!["switch", "-c", branch] } else { vec!["switch", branch] }; + let o = git(&repo, &args, 60).await?; + if !o.ok { + return Err(fail(o)); + } + json!({"ok": true, "output": o.stderr}) + } + "fetch" | "pull" | "push" => { + let args: Vec<&str> = match b.action.as_str() { + "fetch" => vec!["fetch", "--prune"], + "pull" => vec!["pull", "--ff-only"], + _ => { + let has_upstream = git(&repo, &["rev-parse", "--abbrev-ref", "@{u}"], 10).await.map(|o| o.ok).unwrap_or(false); + if has_upstream { vec!["push"] } else { vec!["push", "-u", "origin", "HEAD"] } + } + }; + let o = git(&repo, &args, 300).await?; + if !o.ok { + return Err(fail(o)); + } + json!({"ok": true, "output": format!("{}{}", o.stdout, o.stderr)}) + } + other => return Err(ApiError(StatusCode::BAD_REQUEST, format!("unknown action '{other}'"))), + }; + Ok(Json(result)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_porcelain_status() { + let raw = "## main...origin/main [ahead 2, behind 1]\0 M src/a.rs\0A new.txt\0R b.rs\0old.rs\0?? x y.txt\0"; + let v = parse_status(raw); + assert_eq!(v["branch"], "main"); + assert_eq!(v["upstream"], "origin/main"); + assert_eq!(v["ahead"], 2); + assert_eq!(v["behind"], 1); + let files = v["files"].as_array().unwrap(); + assert_eq!(files.len(), 4); + assert_eq!(files[2]["from"], "old.rs"); + assert_eq!(files[3]["path"], "x y.txt"); + assert_eq!(parse_status("## No commits yet on main\0")["branch"], "main"); + } + + #[test] + fn refs_are_validated() { + assert!(valid_ref("feature/login-2")); + assert!(!valid_ref("--upload-pack=x")); + assert!(!valid_ref("a..b")); + assert!(!valid_ref("a b")); + } +} diff --git a/server/src/images.rs b/server/src/images.rs new file mode 100644 index 0000000..31670a8 --- /dev/null +++ b/server/src/images.rs @@ -0,0 +1,237 @@ +//! Image generation via fal.ai – as an agent tool and for the manual image page. + +use crate::app::App; +use crate::config::{ImageModel, expand_env}; +use crate::store::{ImageRecord, UsageRecord}; +use crate::tools::{Tool, ToolCtx, ToolOutput, arg_str}; +use crate::types::{ImageData, new_id, now_ms}; +use anyhow::{Context, Result, anyhow, bail}; +use async_trait::async_trait; +use base64::Engine; +use serde_json::{Value, json}; +use std::path::{Path, PathBuf}; +use std::time::Duration; + +/// Size presets offered in the UI and to the model (FLUX-style names). +pub const SIZES: &[&str] = &["square_hd", "square", "landscape_4_3", "landscape_16_9", "portrait_4_3", "portrait_16_9"]; + +fn aspect_ratio(size: &str) -> &'static str { + match size { + "landscape_4_3" => "4:3", + "landscape_16_9" => "16:9", + "portrait_4_3" => "3:4", + "portrait_16_9" => "9:16", + _ => "1:1", + } +} + +pub struct GenerateRequest { + pub prompt: String, + pub model: Option, + pub size: Option, + pub count: u32, + /// Directory the files are written to. + pub dir: PathBuf, + pub user_id: Option, + pub session_id: Option, +} + +fn slug(s: &str) -> String { + let mut out: String = s + .chars() + .map(|c| if c.is_ascii_alphanumeric() { c.to_ascii_lowercase() } else { '-' }) + .collect(); + while out.contains("--") { + out = out.replace("--", "-"); + } + out.trim_matches('-').chars().take(40).collect::().trim_end_matches('-').to_string() +} + +fn model_config(app: &App, id: &str) -> ImageModel { + app.config() + .images + .models + .iter() + .find(|m| m.id == id) + .cloned() + .unwrap_or_else(|| ImageModel { id: id.to_string(), ..Default::default() }) +} + +async fn download(url: &str) -> Result<(Vec, String)> { + if let Some(rest) = url.strip_prefix("data:") { + let (meta, data) = rest.split_once(',').context("malformed data URI")?; + let ctype = meta.split(';').next().unwrap_or("image/png").to_string(); + return Ok((base64::engine::general_purpose::STANDARD.decode(data)?, ctype)); + } + let resp = crate::provider::http_client().get(url).timeout(Duration::from_secs(120)).send().await?.error_for_status()?; + let ctype = resp.headers().get(reqwest::header::CONTENT_TYPE).and_then(|v| v.to_str().ok()).unwrap_or("image/png").to_string(); + Ok((resp.bytes().await?.to_vec(), ctype)) +} + +/// Generate images, store them in `req.dir` and record them (history + cost). +pub async fn generate(app: &App, req: GenerateRequest) -> Result> { + let cfg = app.config(); + let ic = &cfg.images; + if !ic.enabled { + bail!("image generation is disabled – enable [images] in the settings"); + } + let key = expand_env(&ic.api_key); + if key.is_empty() { + bail!("no fal.ai API key configured (images.api_key, e.g. ${{FAL_KEY}})"); + } + let model_id = req.model.clone().filter(|m| !m.is_empty()).unwrap_or_else(|| ic.default_model.clone()); + let model = model_config(app, &model_id); + let size = req.size.clone().filter(|s| SIZES.contains(&s.as_str())).unwrap_or_else(|| "landscape_4_3".into()); + let count = req.count.clamp(1, 4); + + let mut body = json!({"prompt": req.prompt, "num_images": count, "output_format": "png"}); + match model.size_param.as_deref() { + Some("aspect_ratio") => body["aspect_ratio"] = json!(aspect_ratio(&size)), + Some("none") => {} + _ => body["image_size"] = json!(size), + } + for (k, v) in &model.options { + body[k] = v.clone(); + } + + let url = format!("{}/{}", expand_env(&ic.base_url).trim_end_matches('/'), model_id.trim_matches('/')); + let resp = crate::provider::http_client() + .post(&url) + .header("Authorization", format!("Key {key}")) + .timeout(Duration::from_secs(300)) + .json(&body) + .send() + .await + .context("fal.ai request failed")?; + if !resp.status().is_success() { + return Err(crate::provider::http_error(resp).await); + } + let out: Value = resp.json().await?; + let images = out["images"].as_array().cloned().or_else(|| out["image"].as_object().map(|o| vec![Value::Object(o.clone())])).unwrap_or_default(); + if images.is_empty() { + bail!("fal.ai returned no images: {}", out.to_string().chars().take(300).collect::()); + } + + tokio::fs::create_dir_all(&req.dir).await?; + let stamp = chrono::Local::now().format("%Y%m%d-%H%M%S"); + let base = slug(&req.prompt); + let mut records = Vec::new(); + for (i, img) in images.iter().enumerate() { + let src = img["url"].as_str().context("image without url")?; + let (bytes, ctype) = download(src).await?; + let ext = match ctype.as_str() { + "image/jpeg" => "jpg", + "image/webp" => "webp", + _ => "png", + }; + let name = if images.len() > 1 { format!("{stamp}-{base}-{}.{ext}", i + 1) } else { format!("{stamp}-{base}.{ext}") }; + let path = req.dir.join(name); + tokio::fs::write(&path, &bytes).await?; + let rec = ImageRecord { + id: new_id(), + user_id: req.user_id.clone(), + session_id: req.session_id.clone(), + prompt: req.prompt.clone(), + model: model_id.clone(), + path: path.display().to_string(), + width: img["width"].as_i64().unwrap_or(0), + height: img["height"].as_i64().unwrap_or(0), + created_at: now_ms(), + }; + app.store.add_image(&rec)?; + records.push(rec); + } + + // Costs go into the normal statistics (provider "fal"). + let cost = model.price.unwrap_or(0.0) * records.len() as f64; + let session = req.session_id.clone().unwrap_or_default(); + let _ = app.store.add_usage(&UsageRecord { + ts: now_ms(), + session_id: session.clone(), + root_session_id: if session.is_empty() { String::new() } else { app.root_session_id(&session) }, + provider: "fal".into(), + model: model_id, + agent: "image".into(), + input_tokens: 0, + output_tokens: 0, + cache_read_tokens: 0, + cache_write_tokens: 0, + ttft_ms: 0, + duration_ms: 0, + cost, + error: false, + }); + Ok(records) +} + +/// Downscaled copy for the model's context. +fn preview_for_model(path: &Path) -> Option { + let img = image::open(path).ok()?; + let img = img.resize(1024, 1024, image::imageops::FilterType::Triangle); + let mut buf = Vec::new(); + img.write_to(&mut std::io::Cursor::new(&mut buf), image::ImageFormat::Png).ok()?; + Some(ImageData { media_type: "image/png".into(), data: base64::engine::general_purpose::STANDARD.encode(buf) }) +} + +pub struct GenerateImage; + +#[async_trait] +impl Tool for GenerateImage { + fn name(&self) -> &str { + "generate_image" + } + fn description(&self) -> String { + "Generate images from a text prompt (fal.ai) and save them in the project. Use for \ + illustrations, icons, backgrounds or placeholder photos the user needs. Write a detailed \ + visual prompt in English (subject, style, composition, colours, lighting). Each image \ + costs money – generate only what is needed. Returns the file paths and a preview." + .into() + } + fn schema(&self) -> Value { + json!({ + "type": "object", + "properties": { + "prompt": {"type": "string"}, + "size": {"type": "string", "enum": SIZES}, + "count": {"type": "integer", "minimum": 1, "maximum": 4}, + "directory": {"type": "string", "description": "Target directory relative to the working directory (default: images.output_dir)"}, + "model": {"type": "string", "description": "fal.ai model id (optional, default from the settings)"} + }, + "required": ["prompt"] + }) + } + fn title(&self, input: &Value) -> String { + format!("Generate image: {}", input["prompt"].as_str().unwrap_or("").chars().take(80).collect::()) + } + async fn run(&self, input: Value, ctx: &ToolCtx) -> Result { + let prompt = arg_str(&input, "prompt")?.to_string(); + let dir_arg = input["directory"].as_str().map(String::from).unwrap_or_else(|| ctx.app.config().images.output_dir.clone()); + let dir = ctx.resolve(&dir_arg)?; + let owner = ctx.app.session(&ctx.session_id).ok().and_then(|s| s.owner_id); + let records = generate( + &ctx.app, + GenerateRequest { + prompt, + model: input["model"].as_str().map(String::from), + size: input["size"].as_str().map(String::from), + count: input["count"].as_u64().unwrap_or(1) as u32, + dir, + user_id: owner, + session_id: Some(ctx.session_id.clone()), + }, + ) + .await + .map_err(|e| anyhow!("{e:#}"))?; + let mut text = String::from("Generated:\n"); + let mut images = Vec::new(); + for r in &records { + let rel = Path::new(&r.path).strip_prefix(&ctx.cwd).map(|p| p.display().to_string()).unwrap_or_else(|_| r.path.clone()); + text.push_str(&format!("- {rel} ({}×{})\n", r.width, r.height)); + if let Some(p) = preview_for_model(Path::new(&r.path)) { + images.push(p); + } + } + let ids: Vec<&str> = records.iter().map(|r| r.id.as_str()).collect(); + Ok(ToolOutput { content: text, images, meta: Some(json!({"image_ids": ids})), ..Default::default() }) + } +} diff --git a/server/src/main.rs b/server/src/main.rs index 079386f..7d91bc3 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -2,7 +2,12 @@ mod api; mod app; +mod auth; +mod budget; mod config; +mod files; +mod git; +mod images; mod mcp; mod permission; mod prompts; @@ -30,9 +35,6 @@ async fn main() -> Result<()> { let (cfg, _) = config::load_or_init(&path)?; tracing::info!("config: {}", path.display()); tracing::info!("workspace: {}", cfg.workspace().display()); - if cfg.auth_token().is_empty() { - tracing::warn!("auth_token is empty – the API is unprotected. Only use this on localhost."); - } let bind = cfg.server.bind.clone(); let app = app::App::new(path, cfg).await?; let router = api::router(app); diff --git a/server/src/mcp.rs b/server/src/mcp.rs index 03c419c..a9b730e 100644 --- a/server/src/mcp.rs +++ b/server/src/mcp.rs @@ -30,6 +30,9 @@ pub struct McpToolInfo { pub name: String, pub description: String, pub input_schema: Value, + /// The server marks the tool as read-only (annotations.readOnlyHint). + #[serde(default)] + pub read_only: bool, } #[derive(Debug, Clone, Serialize)] @@ -183,6 +186,7 @@ impl McpServer { name: t["name"].as_str().unwrap_or_default().to_string(), description: t["description"].as_str().unwrap_or_default().to_string(), input_schema: t.get("inputSchema").cloned().unwrap_or(json!({"type": "object"})), + read_only: t["annotations"]["readOnlyHint"].as_bool().unwrap_or(false), }); } cursor = res["nextCursor"].as_str().map(String::from); @@ -498,20 +502,147 @@ impl McpManager { } } - pub fn tools(&self) -> Vec> { + /// All MCP tools, split into tools given to the model directly and tools + /// that are only reachable through mcp_search / mcp_call. + pub fn tools(&self, cfg: &crate::config::Config) -> (Vec>, Vec>) { let servers = self.servers.lock().unwrap(); - let mut out: Vec> = Vec::new(); + let mut direct: Vec> = Vec::new(); + let mut deferred = Vec::new(); for (sname, server) in servers.iter() { - for t in server.tools.lock().unwrap().iter() { - out.push(Arc::new(McpTool { - full_name: tool_name(sname, &t.name), - server: server.clone(), - info: t.clone(), - })); + let tools = server.tools.lock().unwrap(); + let load = cfg.mcp.get(sname).and_then(|m| m.load.clone()).unwrap_or_else(|| "auto".into()); + let on_demand = match load.as_str() { + "always" => false, + "on_demand" => true, + _ => tools.len() > cfg.server.mcp_direct_limit, + }; + for t in tools.iter() { + let tool = Arc::new(McpTool { full_name: tool_name(sname, &t.name), server: server.clone(), info: t.clone() }); + if on_demand { + deferred.push(tool); + } else { + direct.push(tool); + } } } - out + (direct, deferred) } + + /// Short overview of on-demand servers for the system prompt. + pub fn deferred_summary(&self, cfg: &crate::config::Config) -> Vec<(String, usize, Vec)> { + let (_, deferred) = self.tools(cfg); + let mut by_server: BTreeMap> = BTreeMap::new(); + for t in deferred { + by_server.entry(t.server.name.clone()).or_default().push(t.info.name.clone()); + } + by_server + .into_iter() + .map(|(s, names)| { + let n = names.len(); + (s, n, names.into_iter().take(12).collect()) + }) + .collect() + } +} + +/// Search the on-demand MCP tools by keywords. +pub struct McpSearch; + +#[async_trait] +impl Tool for McpSearch { + fn name(&self) -> &str { + "mcp_search" + } + fn description(&self) -> String { + "Find tools of the on-demand MCP servers listed in the system prompt. Returns matching tools \ + with their input schema; call them with mcp_call. Search with a few keywords (e.g. \ + \"container restart\", \"light living room\")." + .into() + } + fn schema(&self) -> Value { + json!({ + "type": "object", + "properties": { + "query": {"type": "string", "description": "Keywords"}, + "server": {"type": "string", "description": "Only search this server (optional)"} + }, + "required": ["query"] + }) + } + fn read_only(&self) -> bool { + true + } + fn title(&self, input: &Value) -> String { + format!("Search MCP tools: {}", input["query"].as_str().unwrap_or("")) + } + async fn run(&self, input: Value, ctx: &ToolCtx) -> Result { + let query = input["query"].as_str().unwrap_or("").to_lowercase(); + let server = input["server"].as_str(); + let words: Vec<&str> = query.split_whitespace().collect(); + let (_, deferred) = ctx.app.mcp.tools(&ctx.app.config()); + let mut scored: Vec<(usize, &Arc)> = deferred + .iter() + .filter(|t| server.is_none_or(|s| t.server.name == s)) + .map(|t| { + let name = t.info.name.to_lowercase(); + let desc = t.info.description.to_lowercase(); + let score: usize = words.iter().map(|w| if name.contains(w) { 3 } else if desc.contains(w) { 1 } else { 0 }).sum(); + (score, t) + }) + .filter(|(s, _)| *s > 0 || words.is_empty()) + .collect(); + scored.sort_by(|a, b| b.0.cmp(&a.0)); + if scored.is_empty() { + return Ok(ToolOutput::text("No matching tools. Try other keywords or another server.")); + } + let mut out = String::new(); + for (_, t) in scored.iter().take(8) { + let desc: String = t.info.description.chars().take(300).collect(); + out.push_str(&format!("## {}\n{}\nInput schema: {}\n\n", t.full_name, desc, t.info.input_schema)); + } + if scored.len() > 8 { + out.push_str(&format!("({} more matches – refine the query)\n", scored.len() - 8)); + } + Ok(ToolOutput::text(out)) + } +} + +/// Call an on-demand MCP tool found with mcp_search. Permissions are checked +/// against the target tool by the runner. +pub struct McpCall; + +#[async_trait] +impl Tool for McpCall { + fn name(&self) -> &str { + "mcp_call" + } + fn description(&self) -> String { + "Call an MCP tool found with mcp_search, by its full name (mcp____).".into() + } + fn schema(&self) -> Value { + json!({ + "type": "object", + "properties": { + "tool": {"type": "string", "description": "Full tool name, e.g. mcp__dockhand__list_containers"}, + "arguments": {"type": "object", "description": "Arguments matching the tool's input schema"} + }, + "required": ["tool"] + }) + } + async fn run(&self, input: Value, ctx: &ToolCtx) -> Result { + let target = resolve_call_target(ctx, &input)?; + target.run(input.get("arguments").cloned().unwrap_or(json!({})), ctx).await + } +} + +/// Find the MCP tool an mcp_call refers to. +pub fn resolve_call_target(ctx: &ToolCtx, input: &Value) -> Result> { + let name = input["tool"].as_str().unwrap_or(""); + let (_, deferred) = ctx.app.mcp.tools(&ctx.app.config()); + deferred + .into_iter() + .find(|t| t.full_name == name) + .ok_or_else(|| anyhow!("unknown MCP tool '{name}' – use mcp_search to find tool names")) } /// Tool names must match ^[a-zA-Z0-9_-]{1,64}$ for most providers. @@ -523,7 +654,7 @@ pub fn tool_name(server: &str, tool: &str) -> String { } pub struct McpTool { - full_name: String, + pub full_name: String, server: Arc, info: McpToolInfo, } @@ -546,6 +677,9 @@ impl Tool for McpTool { fn title(&self, _input: &Value) -> String { format!("{} → {}", self.server.name, self.info.name) } + fn read_only(&self) -> bool { + self.info.read_only + } async fn run(&self, input: Value, ctx: &ToolCtx) -> Result { self.server.call_tool(&ctx.session_id, &self.info.name, input, ctx.cancel.clone()).await } diff --git a/server/src/permission.rs b/server/src/permission.rs index 82172d6..238b00e 100644 --- a/server/src/permission.rs +++ b/server/src/permission.rs @@ -81,6 +81,70 @@ pub fn bash_prefix(cmd: &str) -> String { cmd.split_whitespace().take(2).collect::>().join(" ") } +/// Session permission modes. +pub const MODES: &[&str] = &["default", "auto", "accept_edits", "bypass"]; + +/// Commands that only read. Checked per simple command (after splitting). +const SAFE_COMMANDS: &[&str] = &[ + "ls", "ll", "cat", "head", "tail", "wc", "pwd", "echo", "printf", "which", "whereis", "type", "file", "stat", + "du", "df", "tree", "grep", "egrep", "fgrep", "rg", "ag", "sort", "uniq", "cut", "tr", "diff", "cmp", "basename", + "dirname", "realpath", "readlink", "date", "uname", "whoami", "id", "hostname", "nproc", "free", "uptime", "ps", + "jq", "yq", "md5sum", "sha1sum", "sha256sum", "column", "nl", "fold", "true", "false", "test", "[", "less", "more", +]; + +const SAFE_GIT: &[&str] = &[ + "status", "diff", "log", "show", "rev-parse", "ls-files", "blame", "describe", "grep", "shortlog", "reflog", "remote", +]; + +/// Strip harmless redirections so they don't count as file writes. +fn strip_redirections(cmd: &str) -> String { + cmd.replace("2>&1", "").replace("2>/dev/null", "").replace(">/dev/null", "").replace("> /dev/null", "") +} + +/// A simple command that only reads. +pub fn is_safe_command(part: &str) -> bool { + let cleaned = strip_redirections(part); + if cleaned.contains('>') { + return false; + } + let words: Vec = shell_words::split(&cleaned).unwrap_or_else(|_| cleaned.split_whitespace().map(String::from).collect()); + // Leading VAR=value assignments are not safe (could change behaviour); keep it strict. + let Some(cmd) = words.first() else { return true }; + let args: Vec<&str> = words[1..].iter().map(String::as_str).collect(); + if args.len() == 1 && matches!(args[0], "--version" | "-V" | "--help" | "-h") { + return true; + } + match cmd.as_str() { + c if SAFE_COMMANDS.contains(&c) => true, + "find" => !args.iter().any(|a| matches!(*a, "-exec" | "-execdir" | "-delete" | "-ok" | "-okdir" | "-fprint" | "-fprintf" | "-fls")), + "sed" => !args.iter().any(|a| a.starts_with("-i") || a.starts_with("--in-place")), + "git" => match args.first() { + Some(sub) if SAFE_GIT.contains(sub) => !(*sub == "remote" && args.len() > 1 && args[1] != "-v"), + Some(&"branch") => !args.iter().any(|a| matches!(*a, "-d" | "-D" | "-m" | "-M" | "--delete" | "--move")), + Some(&"stash") => args.get(1) == Some(&"list"), + Some(&"tag") => args.len() == 1 || args.get(1) == Some(&"-l") || args.get(1) == Some(&"--list"), + Some(&"config") => args.iter().any(|a| *a == "--get" || *a == "--list" || *a == "-l"), + _ => false, + }, + "cargo" => matches!(args.first(), Some(&"check") | Some(&"metadata") | Some(&"tree") | Some(&"--list")), + "npm" | "pnpm" | "yarn" => matches!(args.first(), Some(&"ls") | Some(&"list") | Some(&"outdated") | Some(&"view") | Some(&"why")), + "pip" | "pip3" => matches!(args.first(), Some(&"list") | Some(&"show") | Some(&"freeze")), + _ => false, + } +} + +/// Commands that always need confirmation unless everything is allowed. +pub fn is_dangerous(cmd: &str) -> bool { + let c = format!(" {} ", cmd.replace('\n', " ")); + const PATTERNS: &[&str] = &[ + " rm -r", " rm -f", " rm -R", " rm --recursive", " sudo ", " su ", " git push", " git reset --hard", " git clean", + " git checkout -- ", " git restore ", " git rebase", " git commit --amend", " chmod -R", " chown -R", " dd ", + " mkfs", " shutdown", " reboot", " poweroff", " kill ", " pkill ", " killall ", " docker ", " docker-compose ", + " systemctl ", " npm publish", " cargo publish", " | sh", " | bash", "|sh ", "|bash ", " > /dev/sd", " crontab ", + ]; + PATTERNS.iter().any(|p| c.contains(p)) +} + pub struct Evaluator<'a> { pub cfg: &'a PermissionConfig, pub mode: &'a str, @@ -102,7 +166,17 @@ impl Evaluator<'_> { if self.mode == "bypass" { return Decision::Allow; } - return bash.unwrap_or(base); + if let Some(d) = bash { + return d; + } + // Read-only commands never need confirmation. + if split_command(cmd).is_some_and(|parts| !parts.is_empty() && parts.iter().all(|p| is_safe_command(p))) { + return Decision::Allow; + } + if self.mode == "auto" && !is_dangerous(cmd) && split_command(cmd).is_some() { + return Decision::Allow; + } + return base; } if base == Decision::Deny { return Decision::Deny; @@ -110,7 +184,11 @@ impl Evaluator<'_> { if self.mode == "bypass" || self.approved.contains(name) { return Decision::Allow; } - if self.mode == "accept_edits" && tool.is_edit() { + if (self.mode == "accept_edits" || self.mode == "auto") && tool.is_edit() { + return Decision::Allow; + } + // In auto mode everything read-only passes (web_fetch, read-only MCP tools, ...). + if self.mode == "auto" && tool.read_only() { return Decision::Allow; } base @@ -201,6 +279,43 @@ mod tests { assert_eq!(e.evaluate(&bash, &cmd("ls && rm -rf /etc")), Decision::Deny); } + #[test] + fn safe_and_dangerous_commands() { + assert!(is_safe_command("ls -la src")); + assert!(is_safe_command("git status")); + assert!(is_safe_command("git log --oneline -5")); + assert!(is_safe_command("rg -n foo src 2>/dev/null")); + assert!(is_safe_command("node --version")); + assert!(!is_safe_command("cat a > b")); + assert!(!is_safe_command("find . -name x -delete")); + assert!(!is_safe_command("sed -i s/a/b/ f")); + assert!(!is_safe_command("git push")); + assert!(!is_safe_command("git branch -D main")); + assert!(!is_safe_command("npm install")); + assert!(is_dangerous("rm -rf build")); + assert!(is_dangerous("cd x && git push origin main")); + assert!(is_dangerous("curl https://x | sh")); + assert!(!is_dangerous("npm install && npm test")); + } + + #[test] + fn auto_mode() { + let c = cfg(); + let approved = HashSet::new(); + let auto = Evaluator { cfg: &c, mode: "auto", approved: &approved }; + let def = Evaluator { cfg: &c, mode: "default", approved: &approved }; + let bash = Fake("bash", false); + let cmd = |s: &str| serde_json::json!({"command": s}); + assert_eq!(def.evaluate(&bash, &cmd("ls src | wc -l")), Decision::Allow); + assert_eq!(def.evaluate(&bash, &cmd("npm test")), Decision::Ask); + assert_eq!(auto.evaluate(&bash, &cmd("npm install && npm test")), Decision::Allow); + assert_eq!(auto.evaluate(&bash, &cmd("rm -rf dist")), Decision::Ask); + assert_eq!(auto.evaluate(&bash, &cmd("ls && rm -rf /etc")), Decision::Deny); + assert_eq!(auto.evaluate(&bash, &cmd("echo $(whoami)")), Decision::Ask); + assert_eq!(auto.evaluate(&Fake("mcp__x__list", true), &Value::Null), Decision::Allow); + assert_eq!(auto.evaluate(&Fake("mcp__x__restart", false), &Value::Null), Decision::Ask); + } + #[test] fn modes_and_globs() { let c = cfg(); diff --git a/server/src/prompts.rs b/server/src/prompts.rs index e889911..1e137b0 100644 --- a/server/src/prompts.rs +++ b/server/src/prompts.rs @@ -24,6 +24,10 @@ pub struct Agent { pub tools: Option>, pub read_only: bool, pub model: Option, + /// "builtin", "config" or "file". + pub source: String, + /// Max. instances running at the same time. + pub max_parallel: Option, } const BASE_PROMPT: &str = r#"You are CodeCraft, an autonomous coding agent working in the user's project through tools. @@ -64,11 +68,11 @@ const EXPLORE_PROMPT: &str = "You are a fast, read-only exploration subagent. Se pub fn builtin_agents(clean_room: bool) -> Vec { let craft = CRAFT_PROMPT.replace("{CLEAN_ROOM}", if clean_room { CLEAN_ROOM_ON } else { CLEAN_ROOM_OFF }); vec![ - Agent { name: "build".into(), description: "Full access: implement, edit, run".into(), mode: "primary".into(), prompt: BUILD_PROMPT.into(), tools: None, read_only: false, model: None }, - Agent { name: "plan".into(), description: "Read-only: analyse and propose a plan".into(), mode: "primary".into(), prompt: PLAN_PROMPT.into(), tools: None, read_only: true, model: None }, - Agent { name: "craft".into(), description: "Study a target, build your own version, check and refine".into(), mode: "primary".into(), prompt: craft, tools: None, read_only: false, model: None }, - Agent { name: "general".into(), description: "General-purpose subagent for multi-step tasks".into(), mode: "subagent".into(), prompt: GENERAL_PROMPT.into(), tools: None, read_only: false, model: None }, - Agent { name: "explore".into(), description: "Read-only subagent for fast codebase searches".into(), mode: "subagent".into(), prompt: EXPLORE_PROMPT.into(), tools: None, read_only: true, model: None }, + Agent { name: "build".into(), description: "Full access: implement, edit, run".into(), mode: "primary".into(), prompt: BUILD_PROMPT.into(), tools: None, read_only: false, model: None, source: "builtin".into(), max_parallel: None }, + Agent { name: "plan".into(), description: "Read-only: analyse and propose a plan".into(), mode: "primary".into(), prompt: PLAN_PROMPT.into(), tools: None, read_only: true, model: None, source: "builtin".into(), max_parallel: None }, + Agent { name: "craft".into(), description: "Study a target, build your own version, check and refine".into(), mode: "primary".into(), prompt: craft, tools: None, read_only: false, model: None, source: "builtin".into(), max_parallel: None }, + Agent { name: "general".into(), description: "General-purpose subagent for multi-step tasks".into(), mode: "subagent".into(), prompt: GENERAL_PROMPT.into(), tools: None, read_only: false, model: None, source: "builtin".into(), max_parallel: None }, + Agent { name: "explore".into(), description: "Read-only subagent for fast codebase searches".into(), mode: "subagent".into(), prompt: EXPLORE_PROMPT.into(), tools: None, read_only: true, model: None, source: "builtin".into(), max_parallel: None }, ] } @@ -91,9 +95,9 @@ pub fn frontmatter(text: &str) -> (BTreeMap, String) { } /// Resource directories, lowest priority first: data dir, workspace, project. -fn resource_dirs(app: &App, cwd: &Path, kind: &str) -> Vec { +fn resource_dirs(app: &App, root: &Path, cwd: &Path, kind: &str) -> Vec { let mut dirs = vec![app.data_dir().join(kind)]; - let ws = app.workspace(); + let ws = root.to_path_buf(); let mut chain = vec![ws.clone()]; if cwd != ws { chain.push(cwd.to_path_buf()); @@ -105,12 +109,12 @@ fn resource_dirs(app: &App, cwd: &Path, kind: &str) -> Vec { dirs } -pub fn agents(app: &App, cwd: &Path) -> Vec { +pub fn agents(app: &App, root: &Path, cwd: &Path) -> Vec { let cfg = app.config(); let mut map: BTreeMap = builtin_agents(cfg.craft.clean_room).into_iter().map(|a| (a.name.clone(), a)).collect(); // Markdown agents (Claude Code format). - for dir in resource_dirs(app, cwd, "agents") { + for dir in resource_dirs(app, root, cwd, "agents") { let Ok(entries) = std::fs::read_dir(&dir) else { continue }; for e in entries.flatten() { let p = e.path(); @@ -129,6 +133,8 @@ pub fn agents(app: &App, cwd: &Path) -> Vec { tools, read_only: false, model: meta.get("model").filter(|m| m.contains('/')).cloned(), + source: "file".into(), + max_parallel: meta.get("max_parallel").and_then(|v| v.parse().ok()), }); } } @@ -142,23 +148,25 @@ pub fn agents(app: &App, cwd: &Path) -> Vec { prompt: a.prompt.clone().or(base.as_ref().map(|b| b.prompt.clone())).unwrap_or_default(), tools: a.tools.clone().or(base.as_ref().and_then(|b| b.tools.clone())), read_only: base.as_ref().is_some_and(|b| b.read_only), - model: a.model.clone().or(base.and_then(|b| b.model)), + model: a.model.clone().filter(|m| !m.is_empty()).or(base.as_ref().and_then(|b| b.model.clone())), + source: "config".into(), + max_parallel: a.max_parallel.or(base.as_ref().and_then(|b| b.max_parallel)), }); } map.into_values().collect() } -pub fn find_agent(app: &App, cwd: &Path, name: &str) -> Agent { - agents(app, cwd) +pub fn find_agent(app: &App, root: &Path, cwd: &Path, name: &str) -> Agent { + agents(app, root, cwd) .into_iter() .find(|a| a.name == name) .unwrap_or_else(|| builtin_agents(true).remove(0)) } /// AGENTS.md / CLAUDE.md files from the data dir, workspace root down to cwd. -fn instruction_files(app: &App, cwd: &Path) -> Vec<(PathBuf, String)> { +fn instruction_files(app: &App, root: &Path, cwd: &Path) -> Vec<(PathBuf, String)> { let mut dirs = vec![app.data_dir()]; - let ws = app.workspace(); + let ws = root.to_path_buf(); let mut d = cwd.to_path_buf(); let mut chain = Vec::new(); loop { @@ -183,28 +191,49 @@ fn instruction_files(app: &App, cwd: &Path) -> Vec<(PathBuf, String)> { out } -pub fn system_prompt(app: &App, agent: &Agent, cwd: &Path, model: &str) -> String { +pub fn system_prompt(app: &App, agent: &Agent, root: &Path, cwd: &Path, model: &str, permission_mode: &str) -> String { let mut s = String::from(BASE_PROMPT); s.push_str("\n\n"); s.push_str(&agent.prompt); + if permission_mode == "auto" && !agent.read_only { + s.push_str("\n\n# Working autonomously\nThe user does not want to approve every step. Before you start a larger change (several files, installing dependencies, migrations, anything hard to undo), confirm the plan ONCE with ask_user in a short summary. After the user agrees, carry out the whole plan without asking again – reading, editing, building and testing are pre-approved. Only stop to ask when something unexpected changes the plan. Small, obvious fixes need no confirmation. Destructive or external actions (deleting, git push, deployments, changing other systems via MCP) still trigger an approval prompt automatically."); + } let git = cwd.join(".git").exists(); s.push_str(&format!( - "\n\n# Environment\n- Working directory: {}\n- Workspace root: {}\n- Git repository: {}\n- Platform: {} ({})\n- Model: {model}\n- Date: {}\n", + "\n\n# Environment\n- Working directory: {}\n- Project root: {}\n- Git repository: {}\n- Platform: {} ({})\n- Model: {model}\n- Date: {}\n", cwd.display(), - app.workspace().display(), + root.display(), if git { "yes" } else { "no" }, std::env::consts::OS, std::env::consts::ARCH, chrono::Local::now().format("%Y-%m-%d"), )); - let skills = skills(app, cwd); + if !agent.read_only || agent.name == "plan" { + let subs: Vec = agents(app, root, cwd).into_iter().filter(|a| a.mode != "primary").collect(); + if !subs.is_empty() { + s.push_str("\n# Subagents\nAvailable for the task tool (subagent_type). Delegate work that fits a subagent's description – especially writing, summarizing and translating – and use background=true for tasks whose result you do not need right away.\n"); + for a in &subs { + let model = a.model.as_deref().map(|m| format!(" [model: {m}]")).unwrap_or_default(); + let ro = if a.read_only { " (read-only)" } else { "" }; + s.push_str(&format!("- {}{ro}{model}: {}\n", a.name, a.description)); + } + } + } + let deferred = app.mcp.deferred_summary(&app.config()); + if !deferred.is_empty() { + s.push_str("\n# On-demand MCP servers\nThese servers have many tools that are not listed directly. Find tools with mcp_search, then call them with mcp_call:\n"); + for (server, n, names) in &deferred { + s.push_str(&format!("- {server} ({n} tools, e.g. {})\n", names.join(", "))); + } + } + let skills = skills(app, root, cwd); if !skills.is_empty() { s.push_str("\n# Skills\nLoad a skill with the skill tool when the task matches its description:\n"); for sk in &skills { s.push_str(&format!("- {}: {}\n", sk.name, sk.description)); } } - let files = instruction_files(app, cwd); + let files = instruction_files(app, root, cwd); if !files.is_empty() { s.push_str("\n# Project instructions\nThese instructions come from the user's instruction files and OVERRIDE default behaviour.\n"); for (p, text) in files { @@ -226,9 +255,9 @@ pub struct Command { pub agent: Option, } -pub fn commands(app: &App, cwd: &Path) -> Vec { +pub fn commands(app: &App, root: &Path, cwd: &Path) -> Vec { let mut map = BTreeMap::new(); - for dir in resource_dirs(app, cwd, "commands") { + for dir in resource_dirs(app, root, cwd, "commands") { let Ok(entries) = std::fs::read_dir(&dir) else { continue }; for e in entries.flatten() { let p = e.path(); @@ -249,10 +278,10 @@ pub fn commands(app: &App, cwd: &Path) -> Vec { } /// Expand "/name args" into the command's prompt. Returns None if not a known command. -pub fn expand_command(app: &App, cwd: &Path, text: &str) -> Option<(String, Option)> { +pub fn expand_command(app: &App, root: &Path, cwd: &Path, text: &str) -> Option<(String, Option)> { let rest = text.strip_prefix('/')?; let (name, args) = rest.split_once(char::is_whitespace).unwrap_or((rest, "")); - let cmd = commands(app, cwd).into_iter().find(|c| c.name == name)?; + let cmd = commands(app, root, cwd).into_iter().find(|c| c.name == name)?; let args = args.trim(); let mut body = cmd.body.replace("$ARGUMENTS", args); for (i, a) in args.split_whitespace().enumerate() { @@ -274,9 +303,9 @@ pub struct Skill { pub path: PathBuf, } -pub fn skills(app: &App, cwd: &Path) -> Vec { +pub fn skills(app: &App, root: &Path, cwd: &Path) -> Vec { let mut map = BTreeMap::new(); - for dir in resource_dirs(app, cwd, "skills") { + for dir in resource_dirs(app, root, cwd, "skills") { let Ok(entries) = std::fs::read_dir(&dir) else { continue }; for e in entries.flatten() { let p = e.path().join("SKILL.md"); @@ -307,7 +336,7 @@ impl Tool for SkillTool { } async fn run(&self, input: Value, ctx: &ToolCtx) -> Result { let name = arg_str(&input, "name")?; - let Some(sk) = skills(&ctx.app, &ctx.cwd).into_iter().find(|s| s.name == name) else { + let Some(sk) = skills(&ctx.app, &ctx.root(), &ctx.cwd).into_iter().find(|s| s.name == name) else { return Ok(ToolOutput::error(format!("unknown skill '{name}'"))); }; let text = std::fs::read_to_string(&sk.path)?; diff --git a/server/src/provider/anthropic.rs b/server/src/provider/anthropic.rs index 2f396d3..3b6f297 100644 --- a/server/src/provider/anthropic.rs +++ b/server/src/provider/anthropic.rs @@ -165,6 +165,9 @@ impl Provider for Anthropic { body["output_config"] = json!({"effort": effort}); } + for (k, v) in &req.model.options { + body[k] = v.clone(); + } let resp = self.request(reqwest::Method::POST, "/messages").json(&body).send().await?; if !resp.status().is_success() { return Err(http_error(resp).await); diff --git a/server/src/provider/mod.rs b/server/src/provider/mod.rs index 1d31472..4964e5b 100644 --- a/server/src/provider/mod.rs +++ b/server/src/provider/mod.rs @@ -105,6 +105,13 @@ pub struct ProviderEntry { #[derive(Default)] pub struct Registry { pub providers: BTreeMap, + /// Concurrency limits keyed by "provider" and "provider/model". + limits: BTreeMap>, +} + +/// Held while a request runs; dropping it frees the slot(s). +pub struct Slot { + _permits: Vec, } pub fn http_client() -> reqwest::Client { @@ -121,6 +128,7 @@ impl Registry { pub async fn build(cfg: &Config) -> Self { let client = http_client(); let mut providers = BTreeMap::new(); + let mut limits = BTreeMap::new(); for (name, pc) in &cfg.providers { if !pc.enabled { continue; @@ -131,6 +139,14 @@ impl Registry { ProviderKind::Openai => Arc::new(openai::OpenAi::new(client.clone(), ep)), }; let mut models: Vec = pc.models.iter().map(|m| m.resolved()).collect(); + if let Some(n) = pc.max_concurrency.filter(|n| *n > 0) { + limits.insert(name.clone(), Arc::new(tokio::sync::Semaphore::new(n))); + } + for m in &models { + if let Some(n) = m.max_concurrency.filter(|n| *n > 0) { + limits.insert(format!("{name}/{}", m.id), Arc::new(tokio::sync::Semaphore::new(n))); + } + } if models.is_empty() { match tokio::time::timeout(std::time::Duration::from_secs(15), provider.list_models()).await { Ok(Ok(m)) => models = m, @@ -143,7 +159,28 @@ impl Registry { ProviderEntry { name: name.clone(), provider, models }, ); } - Self { providers } + Self { providers, limits } + } + + /// Wait for a free slot on the model and its provider. Always acquired in the + /// same order (model, then provider), so concurrent requests cannot deadlock. + pub async fn acquire(&self, spec: &str) -> Slot { + let provider = spec.split_once('/').map(|(p, _)| p).unwrap_or(spec); + let mut permits = Vec::new(); + for key in [spec, provider] { + if let Some(sem) = self.limits.get(key) { + if let Ok(p) = sem.clone().acquire_owned().await { + permits.push(p); + } + } + } + Slot { _permits: permits } + } + + /// True if a request to this model would have to wait. + pub fn is_saturated(&self, spec: &str) -> bool { + let provider = spec.split_once('/').map(|(p, _)| p).unwrap_or(spec); + [spec, provider].iter().any(|k| self.limits.get(*k).is_some_and(|s| s.available_permits() == 0)) } /// Resolve "provider/model". Unknown models of a known provider are allowed diff --git a/server/src/provider/openai.rs b/server/src/provider/openai.rs index 6be9fb4..364dbb1 100644 --- a/server/src/provider/openai.rs +++ b/server/src/provider/openai.rs @@ -107,6 +107,55 @@ pub fn to_wire_messages(system: &str, messages: &[Message]) -> Vec { out } +/// Splits streamed content into text and inline `…` reasoning. +/// Tags may be split across chunks, so a possible partial tag is held back. +#[derive(Default)] +pub struct ThinkSplitter { + in_think: bool, + pending: String, +} + +const OPEN: &str = ""; +const CLOSE: &str = ""; + +impl ThinkSplitter { + /// Returns (is_reasoning, text) pieces that are safe to emit. + pub fn push(&mut self, chunk: &str) -> Vec<(bool, String)> { + self.pending.push_str(chunk); + let mut out = Vec::new(); + loop { + let tag = if self.in_think { CLOSE } else { OPEN }; + if let Some(pos) = self.pending.find(tag) { + let before: String = self.pending[..pos].to_string(); + if !before.is_empty() { + out.push((self.in_think, before)); + } + self.pending.drain(..pos + tag.len()); + self.in_think = !self.in_think; + continue; + } + // Keep a trailing prefix of the tag (e.g. " 0 { + let text: String = self.pending.drain(..emit_len).collect(); + out.push((self.in_think, text)); + } + break; + } + // Leading whitespace after
is noise. + out.retain(|(_, t)| !t.is_empty()); + out + } + + pub fn finish(&mut self) -> Vec<(bool, String)> { + if self.pending.is_empty() { + return vec![]; + } + vec![(self.in_think, std::mem::take(&mut self.pending))] + } +} + #[derive(Default)] struct PendingCall { id: String, @@ -145,6 +194,9 @@ impl Provider for OpenAi { body["reasoning_effort"] = json!(effort); } + for (k, v) in &req.model.options { + body[k] = v.clone(); + } let rb = self.client.post(format!("{}/chat/completions", self.ep.base_url)).json(&body); let resp = self.ep.apply(rb).send().await?; if !resp.status().is_success() { @@ -153,6 +205,7 @@ impl Provider for OpenAi { let mut events = resp.bytes_stream().eventsource(); let mut calls: BTreeMap = BTreeMap::new(); + let mut think = ThinkSplitter::default(); let mut usage = Usage::default(); let mut stop_reason = String::from("stop"); @@ -182,7 +235,11 @@ impl Provider for OpenAi { let Some(choice) = data["choices"].get(0) else { continue }; let d = &choice["delta"]; if let Some(t) = d["content"].as_str().filter(|t| !t.is_empty()) { - tx.send(StreamEvent::TextDelta(t.into())).await.ok(); + // Models served by llama.cpp & co. may inline their reasoning as …. + for (is_reasoning, chunk) in think.push(t) { + let ev = if is_reasoning { StreamEvent::ReasoningDelta(chunk) } else { StreamEvent::TextDelta(chunk) }; + tx.send(ev).await.ok(); + } } let r = d["reasoning_content"].as_str().or_else(|| d["reasoning"].as_str()); if let Some(r) = r.filter(|r| !r.is_empty()) { @@ -211,6 +268,10 @@ impl Provider for OpenAi { } } + for (is_reasoning, chunk) in think.finish() { + let ev = if is_reasoning { StreamEvent::ReasoningDelta(chunk) } else { StreamEvent::TextDelta(chunk) }; + tx.send(ev).await.ok(); + } for (i, pc) in calls { let input = if pc.args.trim().is_empty() { json!({}) @@ -249,3 +310,34 @@ impl Provider for OpenAi { Ok(models) } } + +#[cfg(test)] +mod tests { + use super::ThinkSplitter; + + fn run(chunks: &[&str]) -> (String, String) { + let mut s = ThinkSplitter::default(); + let mut text = String::new(); + let mut reasoning = String::new(); + let mut parts: Vec<(bool, String)> = chunks.iter().flat_map(|c| s.push(c)).collect(); + parts.extend(s.finish()); + for (r, t) in parts { + if r { reasoning.push_str(&t) } else { text.push_str(&t) } + } + (text, reasoning) + } + + #[test] + fn splits_think_tags_across_chunks() { + let (text, reasoning) = run(&["plan ", "the fixHere is ", "the answer."]); + assert_eq!(reasoning, "plan the fix"); + assert_eq!(text, "Here is the answer."); + } + + #[test] + fn plain_text_and_lookalikes_pass_through() { + let (text, reasoning) = run(&["a < b and stays"]); + assert_eq!(text, "a < b and stays"); + assert!(reasoning.is_empty()); + } +} diff --git a/server/src/runner.rs b/server/src/runner.rs index aac31ef..12bc65b 100644 --- a/server/src/runner.rs +++ b/server/src/runner.rs @@ -20,30 +20,55 @@ use tokio_util::sync::CancellationToken; const MAX_STEPS: usize = 250; const MAX_SUBAGENT_DEPTH: usize = 3; -/// Start processing a user prompt. If the session is busy the prompt is queued -/// and injected at the next step. +/// What happened to a prompt. +pub enum Submitted { + Started, + Queued(crate::store::QueueItem), +} + +fn emit_queue(app: &App, session_id: &str) { + let items = app.store.queue_list(session_id).unwrap_or_default(); + app.emit(Event::QueueUpdated { session_id: session_id.into(), items: serde_json::json!(items) }); +} + +/// Take all queued messages (one user message's worth of parts). +fn take_queue(app: &App, session_id: &str) -> Vec { + let items = app.store.queue_take(session_id).unwrap_or_default(); + if !items.is_empty() { + emit_queue(app, session_id); + } + items.into_iter().flat_map(|i| i.parts).collect() +} + +/// Start processing a user prompt. If the session is busy, the prompt (with +/// attachments) is queued and delivered at the agent's next step. pub fn start_prompt(app: Arc, session_id: String, parts: Vec) -> Result<()> { + submit(app, session_id, parts, "user").map(|_| ()) +} + +pub fn submit(app: Arc, session_id: String, parts: Vec, source: &str) -> Result { + // Queueing and the end-of-run check run under the same runtime lock, so a + // message is never lost or delivered twice. + let cancel = CancellationToken::new(); + let queued = app.with_runtime(&session_id, |r| -> Result> { + if r.cancel.is_some() { + let item = crate::store::QueueItem { id: new_id(), parts: parts.clone(), source: source.into(), created_at: now_ms() }; + app.store.queue_add(&session_id, &item)?; + Ok(Some(item)) + } else { + r.cancel = Some(cancel.clone()); + Ok(None) + } + })?; + if let Some(item) = queued { + emit_queue(&app, &session_id); + return Ok(Submitted::Queued(item)); + } let text_only: String = parts .iter() .filter_map(|p| if let Part::Text { text } = p { Some(text.as_str()) } else { None }) .collect::>() .join("\n"); - let queued = app.with_runtime(&session_id, |r| { - if r.cancel.is_some() && parts.iter().all(|p| matches!(p, Part::Text { .. })) { - r.queue.push(text_only.clone()); - true - } else { - false - } - }); - if queued { - return Ok(()); - } - if app.is_busy(&session_id) { - bail!("session is busy"); - } - let cancel = CancellationToken::new(); - app.with_runtime(&session_id, |r| r.cancel = Some(cancel.clone())); app.emit(Event::SessionStatus { session_id: session_id.clone(), busy: true }); tokio::spawn(async move { @@ -59,17 +84,31 @@ pub fn start_prompt(app: Arc, session_id: String, parts: Vec) -> Resu tracing::warn!("session {session_id}: {e:#}"); app.emit(Event::Error { session_id: session_id.clone(), message: format!("{e:#}") }); } - // Prompts queued after the last step start a new turn. + // Messages queued after the last step start a new turn – unless the user + // stopped the agent: then they stay queued until sent explicitly. let leftover = app.with_runtime(&session_id, |r| { r.cancel = None; - std::mem::take(&mut r.queue) + if cancel.is_cancelled() { Vec::new() } else { take_queue(&app, &session_id) } }); app.emit(Event::SessionStatus { session_id: session_id.clone(), busy: false }); - if !leftover.is_empty() && !cancel.is_cancelled() { - let _ = start_prompt(app, session_id, vec![Part::Text { text: leftover.join("\n\n") }]); + if !leftover.is_empty() { + let _ = start_prompt(app, session_id, leftover); } }); - Ok(()) + Ok(Submitted::Started) +} + +/// Send queued messages now (after a stop or a server restart). +pub fn flush_queue(app: Arc, session_id: &str) -> Result { + if app.is_busy(session_id) { + return Ok(false); + } + let parts = take_queue(&app, session_id); + if parts.is_empty() { + return Ok(false); + } + start_prompt(app, session_id.to_string(), parts)?; + Ok(true) } pub fn abort(app: &App, session_id: &str) { @@ -77,17 +116,72 @@ pub fn abort(app: &App, session_id: &str) { if let Some(c) = &r.cancel { c.cancel(); } - r.queue.clear(); }); } -/// Messages since the last compaction summary. +/// Messages since the last compaction summary. Tool calls left without a +/// result (run interrupted, e.g. by a server restart) get a synthetic result: +/// providers reject histories with unanswered tool calls. fn active_history(all: Vec) -> Vec { let start = all .iter() .rposition(|m| m.parts.iter().any(|p| matches!(p, Part::Summary { .. }))) .unwrap_or(0); - all.into_iter().skip(start).collect() + let msgs: Vec = all.into_iter().skip(start).collect(); + let mut out: Vec = Vec::with_capacity(msgs.len()); + for (i, m) in msgs.iter().enumerate() { + out.push(m.clone()); + if m.role != Role::Assistant { + continue; + } + let answered: std::collections::HashSet<&str> = msgs + .get(i + 1) + .filter(|n| n.role == Role::User) + .map(|n| { + n.parts + .iter() + .filter_map(|p| if let Part::ToolResult { call_id, .. } = p { Some(call_id.as_str()) } else { None }) + .collect() + }) + .unwrap_or_default(); + let missing: Vec = m + .parts + .iter() + .filter_map(|p| match p { + Part::ToolCall { id, name, .. } if !answered.contains(id.as_str()) => { + Some(tool_result(id, name, "Not executed: the run was interrupted (e.g. server restart).".into(), true, None)) + } + _ => None, + }) + .collect(); + if missing.is_empty() { + continue; + } + // Results must directly follow the call: merge into the next user message or insert one. + match msgs.get(i + 1) { + Some(n) if n.role == Role::User => { + // The next message is pushed in the next iteration; prepend there. + let mut next = n.clone(); + let mut parts = missing; + parts.extend(next.parts); + next.parts = parts; + // Replace by pushing now and skipping the original. + out.push(next); + } + _ => out.push(Message::new(Role::User, missing)), + } + } + // Remove duplicates created by the merge above (same id pushed twice: keep the merged one). + let mut dedup: Vec = Vec::with_capacity(out.len()); + for m in out { + if let Some(last) = dedup.last() { + if last.id == m.id { + continue; + } + } + dedup.push(m); + } + dedup } fn normalize_tool_name(s: &str) -> String { @@ -95,8 +189,17 @@ fn normalize_tool_name(s: &str) -> String { } pub fn available_tools(app: &App, agent: &Agent) -> Vec> { + let cfg = app.config(); let mut all = tools::builtin_tools(); - all.extend(app.mcp.tools()); + if !cfg.images.enabled { + all.retain(|t| t.name() != "generate_image"); + } + let (direct, deferred) = app.mcp.tools(&cfg); + all.extend(direct); + if !deferred.is_empty() { + all.push(Arc::new(crate::mcp::McpSearch)); + all.push(Arc::new(crate::mcp::McpCall)); + } all.into_iter() .filter(|t| !agent.read_only || t.read_only()) // Craft tools only where they are useful. @@ -107,7 +210,7 @@ pub fn available_tools(app: &App, agent: &Agent) -> Vec> { let n = normalize_tool_name(t.name()); list.iter().any(|a| { let a = normalize_tool_name(a); - a == n || (t.name().starts_with("mcp__") && a == "mcp") + a == n || (t.name().starts_with("mcp") && a == "mcp") }) } }) @@ -119,6 +222,17 @@ struct Step { stop_reason: String, } +/// Boxed with an explicit `Send` bound: subagents recurse into the agent loop, +/// and the declared type breaks the cycle for the compiler. +fn run_session_boxed( + app: Arc, + session_id: String, + cancel: CancellationToken, + depth: usize, +) -> futures::future::BoxFuture<'static, Result> { + Box::pin(async move { run_session(app, &session_id, cancel, depth).await }) +} + /// Run the loop for a session until the model stops. Returns the final text. pub async fn run_session(app: Arc, session_id: &str, cancel: CancellationToken, depth: usize) -> Result { let mut final_text = String::new(); @@ -128,7 +242,9 @@ pub async fn run_session(app: Arc, session_id: &str, cancel: CancellationTo } let session = app.session(session_id)?; let cwd = PathBuf::from(&session.cwd); - let agent = prompts::find_agent(&app, &cwd, &session.agent); + let roots = app.user_roots(session.owner_id.as_deref()); + let root = app.root_for(session.owner_id.as_deref(), &cwd); + let agent = prompts::find_agent(&app, &root, &cwd, &session.agent); let model_spec = agent.model.clone().unwrap_or_else(|| session.model.clone()); let (provider, model) = app.registry().resolve(&model_spec)?; @@ -144,7 +260,7 @@ pub async fn run_session(app: Arc, session_id: &str, cancel: CancellationTo let tools = available_tools(&app, &agent); let history = active_history(app.store.messages(session_id)?); let req = ChatRequest { - system: prompts::system_prompt(&app, &agent, &cwd, &model_spec), + system: prompts::system_prompt(&app, &agent, &root, &cwd, &model_spec, &session.permission_mode), messages: history, tools: tools.iter().map(|t| t.def()).collect(), max_tokens: model.max_output.unwrap_or(16_000), @@ -203,6 +319,7 @@ pub async fn run_session(app: Arc, session_id: &str, cancel: CancellationTo cwd: cwd.clone(), cancel: cancel.clone(), agent: agent.name.clone(), + roots: roots.clone(), }; let by_name = |n: &str| tools.iter().find(|t| t.name() == n).cloned(); let all_read_only = calls.iter().all(|(_, n, _)| by_name(n).is_some_and(|t| t.read_only())); @@ -217,11 +334,8 @@ pub async fn run_session(app: Arc, session_id: &str, cancel: CancellationTo results.push(execute_call(&app, &session, &base, by_name(name), id, name, input.clone(), depth).await); } } - // Inject prompts the user sent while we were working. - let queued = app.with_runtime(session_id, |r| std::mem::take(&mut r.queue)); - for q in queued { - results.push(Part::Text { text: q }); - } + // Deliver messages the user queued while we were working (with attachments). + results.extend(take_queue(&app, session_id)); save_user_parts(&app, session_id, results)?; } Ok(final_text) @@ -265,12 +379,6 @@ async fn execute_call( return tool_result(call_id, name, "Maximum subagent depth reached.".into(), true, None); } - // Permission check. Approvals are tracked on the top-level session. - let root = app.root_session_id(&session.id); - let cfg = app.config(); - let decision = app.with_runtime(&root, |r| { - Evaluator { cfg: &cfg.permissions, mode: &session.permission_mode, approved: &r.approved }.evaluate(tool.as_ref(), &input) - }); let ctx = ToolCtx { app: app.clone(), session_id: base.session_id.clone(), @@ -278,10 +386,33 @@ async fn execute_call( cwd: base.cwd.clone(), cancel: base.cancel.clone(), agent: base.agent.clone(), + roots: base.roots.clone(), }; + // mcp_call is judged (and approved) as the MCP tool it calls. + let call_name = name; + let (tool, input, name): (Arc, Value, String) = if name == "mcp_call" { + match crate::mcp::resolve_call_target(&ctx, &input) { + Ok(target) => { + let args = input.get("arguments").cloned().unwrap_or(serde_json::json!({})); + let n = target.full_name.clone(); + (target, args, n) + } + Err(e) => return tool_result(call_id, call_name, format!("Error: {e:#}"), true, None), + } + } else { + (tool, input, name.to_string()) + }; + let name = name.as_str(); + + // Permission check. Approvals are tracked on the top-level session. + let root = app.root_session_id(&session.id); + let cfg = app.config(); + let decision = app.with_runtime(&root, |r| { + Evaluator { cfg: &cfg.permissions, mode: &session.permission_mode, approved: &r.approved }.evaluate(tool.as_ref(), &input) + }); match decision { Decision::Deny => { - return tool_result(call_id, name, "Denied by the permission rules in the configuration.".into(), true, None); + return tool_result(call_id, call_name, "Denied by the permission rules in the configuration.".into(), true, None); } Decision::Ask => { let diff = tool.preview(&input, &ctx).await; @@ -294,7 +425,7 @@ async fn execute_call( if let Some(m) = message.filter(|m| !m.trim().is_empty()) { text.push_str(&format!(" User feedback: {m}")); } - return tool_result(call_id, name, text, true, None); + return tool_result(call_id, call_name, text, true, None); } PermissionReply::Always => app.with_runtime(&root, |r| { if name == "bash" { @@ -321,40 +452,76 @@ async fn execute_call( match res { Ok(out) => Part::ToolResult { call_id: call_id.into(), - name: name.into(), + name: call_name.into(), content: out.content, images: out.images, is_error: out.is_error, meta: out.meta, }, - Err(e) => tool_result(call_id, name, format!("Error: {e:#}"), true, None), + Err(e) => tool_result(call_id, call_name, format!("Error: {e:#}"), true, None), } } async fn run_task(app: &Arc, ctx: &ToolCtx, input: Value, depth: usize) -> Result { let prompt = tools::arg_str(&input, "prompt")?.to_string(); let description = input["description"].as_str().unwrap_or("subagent").to_string(); - let agent = input["subagent_type"].as_str().unwrap_or("general").to_string(); - let (child, text) = spawn_subagent(app.clone(), ctx, &agent, &description, prompt, depth + 1).await?; - Ok(tools::ToolOutput::text(text).with_meta(serde_json::json!({"session_id": child, "agent": agent}))) + let agent_name = input["subagent_type"].as_str().unwrap_or("general").to_string(); + let background = input["background"].as_bool().unwrap_or(false); + let (child, agent) = create_child(app, ctx, &agent_name, &description, prompt)?; + let meta = serde_json::json!({ + "session_id": child.id, "agent": agent.name, "model": child.model, "background": background + }); + + if background { + let app = app.clone(); + let parent_id = ctx.session_id.clone(); + let child_id = child.id.clone(); + let agent_name = agent.name.clone(); + let desc = description.clone(); + // Background tasks outlive the current turn; they are stopped via their own session. + let cancel = CancellationToken::new(); + tokio::spawn(async move { + let res = run_child(app.clone(), &child, &agent, cancel, depth + 1).await; + let body = match res { + Ok(text) => text, + Err(e) => format!("The background task failed: {e:#}"), + }; + let text = format!( + "\n{body}\n" + ); + // Injected into the running turn, or starts a new turn when the parent is idle. + if app.store.get_session(&parent_id).ok().flatten().is_some() { + if let Err(e) = submit(app.clone(), parent_id.clone(), vec![Part::Text { text }], "background") { + tracing::warn!("could not deliver background result: {e:#}"); + } + } + }); + return Ok(tools::ToolOutput::text(format!( + "Started background task '{description}' with subagent '{}' (model {}). Its result will arrive \ + automatically as a message when it finishes – continue with other work and do not poll.", + meta["agent"].as_str().unwrap_or(""), + meta["model"].as_str().unwrap_or("") + )) + .with_meta(meta)); + } + + let text = run_child(app.clone(), &child, &agent, ctx.cancel.child_token(), depth + 1).await?; + Ok(tools::ToolOutput::text(text).with_meta(meta)) } /// Entry point used by the task tool when called outside the runner. pub async fn run_subagent(app: Arc, ctx: &ToolCtx, agent: &str, description: &str, prompt: String) -> Result<(String, String)> { - spawn_subagent(app, ctx, agent, description, prompt, 1).await + let (child, agent) = create_child(&app, ctx, agent, description, prompt)?; + let text = run_child(app, &child, &agent, ctx.cancel.child_token(), 1).await?; + Ok((child.id, text)) } -async fn spawn_subagent( - app: Arc, - ctx: &ToolCtx, - agent_name: &str, - description: &str, - prompt: String, - depth: usize, -) -> Result<(String, String)> { +/// Create the subagent session with its first message. +fn create_child(app: &Arc, ctx: &ToolCtx, agent_name: &str, description: &str, prompt: String) -> Result<(Session, Agent)> { let parent = app.session(&ctx.session_id)?; - let parent_agent = prompts::find_agent(&app, &ctx.cwd, &parent.agent); - let agents = prompts::agents(&app, &ctx.cwd); + let root = ctx.root(); + let parent_agent = prompts::find_agent(app, &root, &ctx.cwd, &parent.agent); + let agents = prompts::agents(app, &root, &ctx.cwd); let mut agent = agents .iter() .find(|a| a.name == agent_name && a.mode != "primary") @@ -367,6 +534,9 @@ async fn spawn_subagent( if parent_agent.read_only && !agent.read_only { agent = agents.into_iter().find(|a| a.name == "explore").unwrap_or(agent); } + if let Some(m) = &agent.model { + app.registry().resolve(m).map_err(|e| anyhow!("subagent '{}' has an unusable model: {e:#}", agent.name))?; + } let now = now_ms(); let child = Session { id: new_id(), @@ -376,6 +546,7 @@ async fn spawn_subagent( agent: agent.name.clone(), permission_mode: parent.permission_mode.clone(), parent_id: Some(parent.id.clone()), + owner_id: parent.owner_id.clone(), created_at: now, updated_at: now, todos: Value::Null, @@ -385,11 +556,34 @@ async fn spawn_subagent( let msg = Message::new(Role::User, vec![Part::Text { text: prompt }]); app.store.add_message(&child.id, &msg)?; app.emit(Event::MessageAdded { session_id: child.id.clone(), message: msg }); + Ok((child, agent)) +} + +/// Run a subagent session to completion, honouring the subagent limits. +async fn run_child(app: Arc, child: &Session, agent: &Agent, cancel: CancellationToken, depth: usize) -> Result { + // Registered like a normal run so the user can stop it from its own session. + app.with_runtime(&child.id, |r| r.cancel = Some(cancel.clone())); app.emit(Event::SessionStatus { session_id: child.id.clone(), busy: true }); - let res = Box::pin(run_session(app.clone(), &child.id, ctx.cancel.child_token(), depth)).await; + // Only first-level subagents count against the limits: nested subagents would + // otherwise deadlock waiting for a slot their own parent holds. + let _slot = if depth <= 1 { + let (slot, waited) = app.subagent_slot(&agent.name, agent.max_parallel, &child.id, &cancel).await; + if waited { + app.emit(Event::Waiting { session_id: child.id.clone(), reason: None }); + } + slot + } else { + None + }; + let res = if cancel.is_cancelled() { + Err(anyhow!("aborted")) + } else { + run_session_boxed(app.clone(), child.id.clone(), cancel, depth).await + }; + app.with_runtime(&child.id, |r| r.cancel = None); app.emit(Event::SessionStatus { session_id: child.id.clone(), busy: false }); let text = res?; - Ok((child.id, if text.trim().is_empty() { "(subagent returned no text)".into() } else { text })) + Ok(if text.trim().is_empty() { "(subagent returned no text)".into() } else { text }) } fn is_retryable(e: &anyhow::Error) -> bool { @@ -415,7 +609,20 @@ async fn stream_step( let p = provider.clone(); let r = req.clone(); let c = cancel.clone(); - let handle = tokio::spawn(async move { p.stream(r, tx, c).await }); + // Wait for a free slot on the model/host; not counted as time to first token. + let registry = app.registry(); + if registry.is_saturated(model_spec) { + app.emit(Event::Waiting { session_id: session.id.clone(), reason: Some(format!("model:{model_spec}")) }); + } + let slot = tokio::select! { + s = registry.acquire(model_spec) => s, + _ = cancel.cancelled() => bail!("aborted"), + }; + app.emit(Event::Waiting { session_id: session.id.clone(), reason: None }); + let handle = tokio::spawn(async move { + let _slot = slot; + p.stream(r, tx, c).await + }); let started = Instant::now(); let mut ttft: Option = None; @@ -516,9 +723,13 @@ async fn stream_step( } /// Collect a complete, tool-free answer (titles, summaries). -async fn complete_text(provider: Arc, req: ChatRequest, cancel: CancellationToken) -> Result<(String, Usage)> { +async fn complete_text(app: &App, spec: &str, provider: Arc, req: ChatRequest, cancel: CancellationToken) -> Result<(String, Usage)> { + let slot = app.registry().acquire(spec).await; let (tx, mut rx) = mpsc::channel(256); - let handle = tokio::spawn(async move { provider.stream(req, tx, cancel).await }); + let handle = tokio::spawn(async move { + let _slot = slot; + provider.stream(req, tx, cancel).await + }); let mut text = String::new(); let mut usage = Usage::default(); while let Some(ev) = rx.recv().await { @@ -579,7 +790,7 @@ fn maybe_generate_title(app: Arc, session_id: &str, text: &str) { reasoning: false, model: model.clone(), }; - match complete_text(provider, req, CancellationToken::new()).await { + match complete_text(&app, &spec, provider, req, CancellationToken::new()).await { Ok((title, usage)) => { record_side_usage(&app, &session, &spec, &model, &usage, "title"); let title = title.lines().find(|l| !l.trim().is_empty()).unwrap_or("").trim().trim_matches('"').to_string(); @@ -625,7 +836,7 @@ pub async fn compact(app: Arc, session_id: &str, cancel: CancellationToken) reasoning: false, model: model.clone(), }; - let (summary, usage) = complete_text(provider, req, cancel).await?; + let (summary, usage) = complete_text(&app, &spec, provider, req, cancel).await?; record_side_usage(&app, &session, &spec, &model, &usage, "compaction"); if summary.trim().is_empty() { bail!("compaction produced an empty summary"); @@ -638,3 +849,25 @@ pub async fn compact(app: Arc, session_id: &str, cancel: CancellationToken) app.save_session(s)?; Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + fn call(id: &str) -> Part { + Part::ToolCall { id: id.into(), name: "bash".into(), input: serde_json::json!({}), extra: None } + } + + #[test] + fn dangling_tool_calls_get_results() { + let a = Message::new(Role::Assistant, vec![call("1"), call("2")]); + let r = Message::new(Role::User, vec![tool_result("1", "bash", "ok".into(), false, None)]); + let a2 = Message::new(Role::Assistant, vec![call("3")]); + let h = active_history(vec![Message::new(Role::User, vec![Part::Text { text: "hi".into() }]), a, r, a2]); + // user, assistant(1,2), user(result 2 + result 1), assistant(3), user(result 3) + assert_eq!(h.len(), 5); + let ids = |m: &Message| m.parts.iter().filter_map(|p| if let Part::ToolResult { call_id, .. } = p { Some(call_id.clone()) } else { None }).collect::>(); + assert_eq!(ids(&h[2]), vec!["2", "1"]); + assert_eq!(ids(&h[4]), vec!["3"]); + } +} diff --git a/server/src/store.rs b/server/src/store.rs index dc1ab67..f78b5e0 100644 --- a/server/src/store.rs +++ b/server/src/store.rs @@ -50,7 +50,74 @@ impl Store { error INTEGER NOT NULL DEFAULT 0 ); CREATE INDEX IF NOT EXISTS usage_ts ON usage(ts); - CREATE INDEX IF NOT EXISTS usage_root ON usage(root_session_id);", + CREATE INDEX IF NOT EXISTS usage_root ON usage(root_session_id); + CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + username TEXT NOT NULL UNIQUE COLLATE NOCASE, + display_name TEXT NOT NULL DEFAULT '', + password_hash TEXT, + role TEXT NOT NULL DEFAULT 'user', + oidc_sub TEXT UNIQUE, + disabled INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL + ); + CREATE TABLE IF NOT EXISTS auth_tokens ( + token_hash TEXT PRIMARY KEY, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + created_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL, + user_agent TEXT NOT NULL DEFAULT '' + ); + CREATE TABLE IF NOT EXISTS folders ( + id TEXT PRIMARY KEY, + path TEXT NOT NULL UNIQUE, + created_at INTEGER NOT NULL + ); + CREATE TABLE IF NOT EXISTS folder_access ( + folder_id TEXT NOT NULL REFERENCES folders(id) ON DELETE CASCADE, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY (folder_id, user_id) + ); + CREATE TABLE IF NOT EXISTS images ( + id TEXT PRIMARY KEY, + user_id TEXT, + session_id TEXT, + prompt TEXT NOT NULL, + model TEXT NOT NULL, + path TEXT NOT NULL, + width INTEGER NOT NULL DEFAULT 0, + height INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL + ); + CREATE INDEX IF NOT EXISTS images_user ON images(user_id, created_at); + CREATE TABLE IF NOT EXISTS queue ( + id TEXT PRIMARY KEY, + session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE, + data TEXT NOT NULL, + created_at INTEGER NOT NULL + ); + CREATE INDEX IF NOT EXISTS queue_session ON queue(session_id, created_at); + CREATE TABLE IF NOT EXISTS session_shares ( + session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY (session_id, user_id) + );", + )?; + // Columns added after the first release. + for (table, column, ddl) in [ + ("sessions", "owner_id", "ALTER TABLE sessions ADD COLUMN owner_id TEXT"), + ("usage", "user_id", "ALTER TABLE usage ADD COLUMN user_id TEXT"), + ] { + let exists: bool = conn + .prepare(&format!("SELECT 1 FROM pragma_table_info('{table}') WHERE name = ?1"))? + .exists([column])?; + if !exists { + conn.execute(ddl, [])?; + } + } + conn.execute_batch( + "CREATE INDEX IF NOT EXISTS sessions_owner ON sessions(owner_id); + CREATE INDEX IF NOT EXISTS usage_user ON usage(user_id);", )?; Ok(Self { conn: Mutex::new(conn) }) } @@ -58,9 +125,10 @@ impl Store { pub fn save_session(&self, s: &Session) -> Result<()> { let conn = self.conn.lock().unwrap(); conn.execute( - "INSERT INTO sessions (id, data, parent_id, updated_at) VALUES (?1, ?2, ?3, ?4) - ON CONFLICT(id) DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at", - params![s.id, serde_json::to_string(s)?, s.parent_id, s.updated_at], + "INSERT INTO sessions (id, data, parent_id, updated_at, owner_id) VALUES (?1, ?2, ?3, ?4, ?5) + ON CONFLICT(id) DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at, + owner_id = excluded.owner_id", + params![s.id, serde_json::to_string(s)?, s.parent_id, s.updated_at, s.owner_id], )?; Ok(()) } @@ -74,12 +142,15 @@ impl Store { } /// Top-level sessions (subagent sessions are reachable through their parent). - pub fn list_sessions(&self) -> Result> { + /// `user = None` lists all sessions (admins); otherwise own and shared ones. + pub fn list_sessions(&self, user: Option<&str>) -> Result> { let conn = self.conn.lock().unwrap(); let mut stmt = conn.prepare( - "SELECT data FROM sessions WHERE parent_id IS NULL ORDER BY updated_at DESC LIMIT 500", + "SELECT data FROM sessions WHERE parent_id IS NULL AND (?1 IS NULL OR owner_id = ?1 + OR id IN (SELECT session_id FROM session_shares WHERE user_id = ?1)) + ORDER BY updated_at DESC LIMIT 500", )?; - let rows = stmt.query_map([], |r| r.get::<_, String>(0))?; + let rows = stmt.query_map([user], |r| r.get::<_, String>(0))?; let mut out = Vec::new(); for row in rows { out.push(serde_json::from_str(&row?)?); @@ -197,8 +268,9 @@ impl Store { conn.execute( "INSERT INTO usage (ts, session_id, root_session_id, provider, model, agent, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, - ttft_ms, duration_ms, cost, error) - VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14)", + ttft_ms, duration_ms, cost, error, user_id) + VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14, + (SELECT owner_id FROM sessions WHERE id = ?3))", params![ u.ts, u.session_id, u.root_session_id, u.provider, u.model, u.agent, u.input_tokens as i64, u.output_tokens as i64, u.cache_read_tokens as i64, @@ -232,21 +304,22 @@ impl Store { /// Global statistics in [from, to) (ms). `tz_offset_min` shifts day/hour buckets /// into the viewer's local time. - pub fn global_stats(&self, from: i64, to: i64, tz_offset_min: i64) -> Result { + pub fn global_stats(&self, from: i64, to: i64, tz_offset_min: i64, user: Option<&str>) -> Result { let conn = self.conn.lock().unwrap(); let shift = tz_offset_min * 60_000; let total = conn.query_row( - &format!("SELECT {AGG} FROM usage WHERE ts >= ?1 AND ts < ?2"), - params![from, to], + &format!("SELECT {AGG} FROM usage WHERE ts >= ?1 AND ts < ?2 AND (?3 IS NULL OR user_id = ?3)"), + params![from, to, user], |r| agg_row(r, 0), )?; let grouped = |key_sql: &str, key_name: &str| -> Result> { let mut stmt = conn.prepare(&format!( - "SELECT {key_sql}, {AGG} FROM usage WHERE ts >= ?1 AND ts < ?2 AND ?3 = ?3 GROUP BY 1 ORDER BY 1" + "SELECT {key_sql}, {AGG} FROM usage WHERE ts >= ?1 AND ts < ?2 AND ?3 = ?3 + AND (?4 IS NULL OR user_id = ?4) GROUP BY 1 ORDER BY 1" ))?; let rows = stmt - .query_map(params![from, to, shift], |r| { + .query_map(params![from, to, shift, user], |r| { let mut v = agg_row(r, 1)?; v[key_name] = match r.get_ref(0)? { rusqlite::types::ValueRef::Integer(i) => json!(i), @@ -264,6 +337,10 @@ impl Store { let by_model = grouped("provider || '/' || model", "model")?; let by_provider = grouped("provider", "provider")?; let by_agent = grouped("agent", "agent")?; + let by_user = grouped( + "COALESCE((SELECT username FROM users WHERE users.id = usage.user_id), '(none)')", + "user", + )?; // Heatmap: weekday (0 = Sunday) x hour of day. let heatmap = grouped( "CAST(strftime('%w', (ts + ?3) / 1000, 'unixepoch') AS INTEGER) * 24 + \ @@ -274,9 +351,10 @@ impl Store { let mut stmt = conn.prepare(&format!( "SELECT {day_sql}, provider || '/' || model, SUM(cost), SUM(input_tokens + cache_read_tokens + cache_write_tokens), SUM(output_tokens) - FROM usage WHERE ts >= ?1 AND ts < ?2 GROUP BY 1, 2 ORDER BY 1" + FROM usage WHERE ts >= ?1 AND ts < ?2 AND (?4 IS NULL OR user_id = ?4) + GROUP BY 1, 2 ORDER BY 1" ))?; - stmt.query_map(params![from, to, shift], |r| { + stmt.query_map(params![from, to, shift, user], |r| { Ok(json!({ "day": r.get::<_, String>(0)?, "model": r.get::<_, String>(1)?, @@ -292,10 +370,10 @@ impl Store { let mut stmt = conn.prepare(&format!( "SELECT u.root_session_id, COALESCE(json_extract(s.data, '$.title'), '(deleted)'), {AGG} FROM usage u LEFT JOIN sessions s ON s.id = u.root_session_id - WHERE u.ts >= ?1 AND u.ts < ?2 + WHERE u.ts >= ?1 AND u.ts < ?2 AND (?3 IS NULL OR u.user_id = ?3) GROUP BY 1 ORDER BY SUM(u.cost) DESC, COUNT(*) DESC LIMIT 15" ))?; - stmt.query_map(params![from, to], |r| { + stmt.query_map(params![from, to, user], |r| { let mut v = agg_row(r, 2)?; v["session_id"] = json!(r.get::<_, String>(0)?); v["title"] = json!(r.get::<_, String>(1)?); @@ -310,8 +388,385 @@ impl Store { "by_model": by_model, "by_provider": by_provider, "by_agent": by_agent, + "by_user": by_user, "heatmap": heatmap, "top_sessions": top_sessions, })) } } + +// --- users & auth ------------------------------------------------------------- + +#[derive(Debug, Clone, Serialize)] +pub struct User { + pub id: String, + pub username: String, + pub display_name: String, + pub role: String, + pub has_password: bool, + pub oidc: bool, + pub disabled: bool, + pub created_at: i64, +} + +impl User { + pub fn is_admin(&self) -> bool { + self.role == "admin" + } +} + +const USER_COLS: &str = + "id, username, display_name, role, password_hash IS NOT NULL, oidc_sub IS NOT NULL, disabled, created_at"; + +fn user_row(r: &rusqlite::Row) -> rusqlite::Result { + Ok(User { + id: r.get(0)?, + username: r.get(1)?, + display_name: r.get(2)?, + role: r.get(3)?, + has_password: r.get(4)?, + oidc: r.get(5)?, + disabled: r.get::<_, i64>(6)? != 0, + created_at: r.get(7)?, + }) +} + +impl Store { + pub fn user_count(&self) -> Result { + let conn = self.conn.lock().unwrap(); + Ok(conn.query_row("SELECT COUNT(*) FROM users", [], |r| r.get(0))?) + } + + pub fn create_user(&self, username: &str, display_name: &str, role: &str, password_hash: Option<&str>, oidc_sub: Option<&str>) -> Result { + let id = crate::types::new_id(); + { + let conn = self.conn.lock().unwrap(); + conn.execute( + "INSERT INTO users (id, username, display_name, password_hash, role, oidc_sub, created_at) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", + params![id, username, display_name, password_hash, role, oidc_sub, now_ms()], + )?; + } + self.user(&id)?.ok_or_else(|| anyhow::anyhow!("user vanished")) + } + + pub fn user(&self, id: &str) -> Result> { + let conn = self.conn.lock().unwrap(); + Ok(conn.query_row(&format!("SELECT {USER_COLS} FROM users WHERE id = ?1"), [id], user_row).optional()?) + } + + pub fn user_by_name(&self, username: &str) -> Result)>> { + let conn = self.conn.lock().unwrap(); + Ok(conn + .query_row( + &format!("SELECT {USER_COLS}, password_hash FROM users WHERE username = ?1"), + [username], + |r| Ok((user_row(r)?, r.get::<_, Option>(8)?)), + ) + .optional()?) + } + + pub fn user_by_oidc(&self, sub: &str) -> Result> { + let conn = self.conn.lock().unwrap(); + Ok(conn.query_row(&format!("SELECT {USER_COLS} FROM users WHERE oidc_sub = ?1"), [sub], user_row).optional()?) + } + + pub fn users(&self) -> Result> { + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!("SELECT {USER_COLS} FROM users ORDER BY username"))?; + let rows = stmt.query_map([], user_row)?.collect::>>()?; + Ok(rows) + } + + /// Update selected fields; None leaves a field unchanged. + pub fn update_user(&self, id: &str, display_name: Option<&str>, role: Option<&str>, disabled: Option, password_hash: Option<&str>, oidc_sub: Option<&str>) -> Result<()> { + let conn = self.conn.lock().unwrap(); + conn.execute( + "UPDATE users SET display_name = COALESCE(?2, display_name), role = COALESCE(?3, role), + disabled = COALESCE(?4, disabled), password_hash = COALESCE(?5, password_hash), + oidc_sub = COALESCE(?6, oidc_sub) WHERE id = ?1", + params![id, display_name, role, disabled.map(|d| d as i64), password_hash, oidc_sub], + )?; + if disabled == Some(true) { + conn.execute("DELETE FROM auth_tokens WHERE user_id = ?1", [id])?; + } + Ok(()) + } + + pub fn delete_user(&self, id: &str) -> Result<()> { + let conn = self.conn.lock().unwrap(); + conn.execute("DELETE FROM users WHERE id = ?1", [id])?; + Ok(()) + } + + pub fn admin_count(&self) -> Result { + let conn = self.conn.lock().unwrap(); + Ok(conn.query_row("SELECT COUNT(*) FROM users WHERE role = 'admin' AND disabled = 0", [], |r| r.get(0))?) + } + + pub fn add_token(&self, token_hash: &str, user_id: &str, expires_at: i64, user_agent: &str) -> Result<()> { + let conn = self.conn.lock().unwrap(); + conn.execute("DELETE FROM auth_tokens WHERE expires_at < ?1", [now_ms()])?; + conn.execute( + "INSERT INTO auth_tokens (token_hash, user_id, created_at, expires_at, user_agent) VALUES (?1, ?2, ?3, ?4, ?5)", + params![token_hash, user_id, now_ms(), expires_at, user_agent], + )?; + Ok(()) + } + + /// The (enabled) user behind a valid login token. + pub fn user_by_token(&self, token_hash: &str) -> Result> { + let conn = self.conn.lock().unwrap(); + Ok(conn + .query_row( + &format!( + "SELECT {} FROM auth_tokens t JOIN users u ON u.id = t.user_id + WHERE t.token_hash = ?1 AND t.expires_at > ?2 AND u.disabled = 0", + USER_COLS.split(", ").map(|c| format!("u.{c}")).collect::>().join(", ") + ), + params![token_hash, now_ms()], + user_row, + ) + .optional()?) + } + + pub fn delete_token(&self, token_hash: &str) -> Result<()> { + let conn = self.conn.lock().unwrap(); + conn.execute("DELETE FROM auth_tokens WHERE token_hash = ?1", [token_hash])?; + Ok(()) + } + + pub fn delete_user_tokens(&self, user_id: &str) -> Result<()> { + let conn = self.conn.lock().unwrap(); + conn.execute("DELETE FROM auth_tokens WHERE user_id = ?1", [user_id])?; + Ok(()) + } + + pub fn shares(&self, session_id: &str) -> Result> { + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare("SELECT user_id FROM session_shares WHERE session_id = ?1")?; + let rows = stmt.query_map([session_id], |r| r.get(0))?.collect::>>()?; + Ok(rows) + } + + pub fn set_shares(&self, session_id: &str, users: &[String]) -> Result<()> { + let conn = self.conn.lock().unwrap(); + conn.execute("DELETE FROM session_shares WHERE session_id = ?1", [session_id])?; + for u in users { + conn.execute("INSERT OR IGNORE INTO session_shares (session_id, user_id) VALUES (?1, ?2)", params![session_id, u])?; + } + Ok(()) + } + + /// Assign sessions without owner (created before user management) to a user. + pub fn adopt_orphans(&self, user_id: &str) -> Result { + let conn = self.conn.lock().unwrap(); + let ids: Vec<(String, String)> = { + let mut stmt = conn.prepare("SELECT id, data FROM sessions WHERE owner_id IS NULL")?; + stmt.query_map([], |r| Ok((r.get(0)?, r.get(1)?)))?.collect::>>()? + }; + for (id, data) in &ids { + let mut v: Value = serde_json::from_str(data)?; + v["owner_id"] = json!(user_id); + conn.execute("UPDATE sessions SET owner_id = ?2, data = ?3 WHERE id = ?1", params![id, user_id, v.to_string()])?; + } + conn.execute( + "UPDATE usage SET user_id = ?1 WHERE user_id IS NULL", + [user_id], + )?; + Ok(ids.len()) + } +} + +// --- shared folders ----------------------------------------------------------- + +#[derive(Debug, Clone, Serialize)] +pub struct Folder { + pub id: String, + /// Absolute path inside the workspace. + pub path: String, + pub users: Vec, + pub created_at: i64, +} + +impl Store { + pub fn folders(&self) -> Result> { + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare( + "SELECT f.id, f.path, f.created_at, COALESCE(group_concat(a.user_id), '') + FROM folders f LEFT JOIN folder_access a ON a.folder_id = f.id GROUP BY f.id ORDER BY f.path", + )?; + let rows = stmt + .query_map([], |r| { + let users: String = r.get(3)?; + Ok(Folder { + id: r.get(0)?, + path: r.get(1)?, + created_at: r.get(2)?, + users: users.split(',').filter(|u| !u.is_empty()).map(String::from).collect(), + }) + })? + .collect::>>()?; + Ok(rows) + } + + pub fn add_folder(&self, path: &str) -> Result { + let conn = self.conn.lock().unwrap(); + let id = crate::types::new_id(); + conn.execute("INSERT INTO folders (id, path, created_at) VALUES (?1, ?2, ?3)", params![id, path, now_ms()])?; + Ok(id) + } + + pub fn delete_folder(&self, id: &str) -> Result<()> { + let conn = self.conn.lock().unwrap(); + conn.execute("DELETE FROM folders WHERE id = ?1", [id])?; + Ok(()) + } + + pub fn set_folder_access(&self, id: &str, users: &[String]) -> Result<()> { + let conn = self.conn.lock().unwrap(); + conn.execute("DELETE FROM folder_access WHERE folder_id = ?1", [id])?; + for u in users { + conn.execute("INSERT OR IGNORE INTO folder_access (folder_id, user_id) VALUES (?1, ?2)", params![id, u])?; + } + Ok(()) + } + + /// Paths of folders shared with a user. + pub fn folders_for_user(&self, user_id: &str) -> Result> { + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare( + "SELECT f.path FROM folders f JOIN folder_access a ON a.folder_id = f.id WHERE a.user_id = ?1 ORDER BY f.path", + )?; + let rows = stmt.query_map([user_id], |r| r.get(0))?.collect::>>()?; + Ok(rows) + } +} + +// --- generated images --------------------------------------------------------- + +#[derive(Debug, Clone, Serialize)] +pub struct ImageRecord { + pub id: String, + pub user_id: Option, + pub session_id: Option, + pub prompt: String, + pub model: String, + pub path: String, + pub width: i64, + pub height: i64, + pub created_at: i64, +} + +fn image_row(r: &rusqlite::Row) -> rusqlite::Result { + Ok(ImageRecord { + id: r.get(0)?, + user_id: r.get(1)?, + session_id: r.get(2)?, + prompt: r.get(3)?, + model: r.get(4)?, + path: r.get(5)?, + width: r.get(6)?, + height: r.get(7)?, + created_at: r.get(8)?, + }) +} + +const IMAGE_COLS: &str = "id, user_id, session_id, prompt, model, path, width, height, created_at"; + +impl Store { + pub fn add_image(&self, img: &ImageRecord) -> Result<()> { + let conn = self.conn.lock().unwrap(); + conn.execute( + &format!("INSERT INTO images ({IMAGE_COLS}) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9)"), + params![img.id, img.user_id, img.session_id, img.prompt, img.model, img.path, img.width, img.height, img.created_at], + )?; + Ok(()) + } + + /// Latest images; `user = None` lists everybody's (admins). + pub fn images(&self, user: Option<&str>, limit: i64) -> Result> { + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!( + "SELECT {IMAGE_COLS} FROM images WHERE ?1 IS NULL OR user_id = ?1 ORDER BY created_at DESC LIMIT ?2" + ))?; + let rows = stmt.query_map(params![user, limit], image_row)?.collect::>>()?; + Ok(rows) + } + + pub fn image(&self, id: &str) -> Result> { + let conn = self.conn.lock().unwrap(); + Ok(conn.query_row(&format!("SELECT {IMAGE_COLS} FROM images WHERE id = ?1"), [id], image_row).optional()?) + } + + pub fn delete_image(&self, id: &str) -> Result<()> { + let conn = self.conn.lock().unwrap(); + conn.execute("DELETE FROM images WHERE id = ?1", [id])?; + Ok(()) + } +} + +impl Store { + /// Cost per provider since `from` (ms). + pub fn cost_by_provider(&self, from: i64) -> Result> { + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare("SELECT provider, COALESCE(SUM(cost), 0) FROM usage WHERE ts >= ?1 GROUP BY provider")?; + let rows = stmt.query_map([from], |r| Ok((r.get::<_, String>(0)?, r.get::<_, f64>(1)?)))?; + let mut out = std::collections::BTreeMap::new(); + for r in rows { + let (p, c) = r?; + out.insert(p, c); + } + Ok(out) + } +} + +// --- message queue ------------------------------------------------------------- + +/// A message waiting to be delivered to a busy session. +#[derive(Debug, Clone, Serialize, serde::Deserialize)] +pub struct QueueItem { + pub id: String, + pub parts: Vec, + /// "user" or "background" (results of background subagents). + pub source: String, + pub created_at: i64, +} + +impl Store { + pub fn queue_add(&self, session_id: &str, item: &QueueItem) -> Result<()> { + let conn = self.conn.lock().unwrap(); + conn.execute( + "INSERT INTO queue (id, session_id, data, created_at) VALUES (?1, ?2, ?3, ?4)", + params![item.id, session_id, serde_json::to_string(item)?, item.created_at], + )?; + Ok(()) + } + + pub fn queue_list(&self, session_id: &str) -> Result> { + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare("SELECT data FROM queue WHERE session_id = ?1 ORDER BY created_at, rowid")?; + let rows = stmt.query_map([session_id], |r| r.get::<_, String>(0))?; + let mut out = Vec::new(); + for r in rows { + out.push(serde_json::from_str(&r?)?); + } + Ok(out) + } + + /// Remove one item; false if it was already delivered. + pub fn queue_remove(&self, session_id: &str, id: &str) -> Result { + let conn = self.conn.lock().unwrap(); + Ok(conn.execute("DELETE FROM queue WHERE session_id = ?1 AND id = ?2", params![session_id, id])? > 0) + } + + /// Take all items for delivery. + pub fn queue_take(&self, session_id: &str) -> Result> { + let items = self.queue_list(session_id)?; + let conn = self.conn.lock().unwrap(); + for it in &items { + conn.execute("DELETE FROM queue WHERE id = ?1", [&it.id])?; + } + Ok(items) + } +} diff --git a/server/src/tools/agent.rs b/server/src/tools/agent.rs index 9ee4304..48f5873 100644 --- a/server/src/tools/agent.rs +++ b/server/src/tools/agent.rs @@ -143,7 +143,8 @@ impl Tool for Task { "Launch a subagent to handle an independent, multi-step task in its own context (e.g. a \ broad codebase search, a focused review). The subagent only knows what you put in the \ prompt – write a complete, self-contained brief. Its final message is returned to you. \ - Several task calls in one response run in parallel." + Several task calls in one response run in parallel (subject to the configured limits). \ + With background=true you continue working and receive the result later." .into() } fn schema(&self) -> Value { @@ -152,7 +153,8 @@ impl Tool for Task { "properties": { "description": {"type": "string", "description": "Short (3-5 words) task description"}, "prompt": {"type": "string", "description": "The full brief for the subagent"}, - "subagent_type": {"type": "string", "description": "Subagent to use (default: general)"} + "subagent_type": {"type": "string", "description": "Subagent to use (default: general)"}, + "background": {"type": "boolean", "description": "Run in the background: returns immediately, the result arrives later as a message. Use for work you don't need right away (e.g. writing docs, summaries, translations)."} }, "required": ["description", "prompt"] }) diff --git a/server/src/tools/mod.rs b/server/src/tools/mod.rs index 27a34ae..44e0098 100644 --- a/server/src/tools/mod.rs +++ b/server/src/tools/mod.rs @@ -38,6 +38,7 @@ impl ToolOutput { } } +#[derive(Clone)] pub struct ToolCtx { pub app: Arc, pub session_id: String, @@ -46,6 +47,8 @@ pub struct ToolCtx { pub cancel: CancellationToken, /// Agent mode running this tool (subagents inherit restrictions). pub agent: String, + /// Directories this session may access (the owner's roots). + pub roots: Vec, } impl ToolCtx { @@ -55,11 +58,24 @@ impl ToolCtx { let p = p.trim(); let raw = if Path::new(p).is_absolute() { PathBuf::from(p) } else { self.cwd.join(p) }; let norm = normalize(&raw); - let ws = normalize(&self.app.workspace()); - if !norm.starts_with(&ws) && !norm.starts_with("/tmp") { - bail!("path {} is outside the workspace {}", norm.display(), ws.display()); + if norm.starts_with("/tmp") || self.roots.iter().any(|r| norm.starts_with(normalize(r))) { + // Symlinks must not lead out of the allowed directories either. + if let Ok(real) = std::fs::canonicalize(&norm) { + if !real.starts_with("/tmp") && !self.roots.iter().any(|r| real.starts_with(r)) { + bail!("path {} resolves outside your directories", norm.display()); + } + } + return Ok(norm); } - Ok(norm) + let allowed: Vec = self.roots.iter().map(|r| r.display().to_string()).collect(); + bail!("path {} is outside your directories ({})", norm.display(), allowed.join(", ")) + } + + /// The root directory containing the working directory. + pub fn root(&self) -> PathBuf { + let mut roots = self.roots.clone(); + roots.sort_by_key(|r| std::cmp::Reverse(r.as_os_str().len())); + roots.iter().find(|r| self.cwd.starts_with(r)).cloned().unwrap_or_else(|| self.cwd.clone()) } pub fn progress(&self, output: impl Into) { @@ -131,6 +147,7 @@ pub fn builtin_tools() -> Vec> { Arc::new(craft::BrowserSnapshot), Arc::new(craft::ImageCompare), Arc::new(crate::prompts::SkillTool), + Arc::new(crate::images::GenerateImage), ] } diff --git a/server/src/types.rs b/server/src/types.rs index 13d7b56..03746d9 100644 --- a/server/src/types.rs +++ b/server/src/types.rs @@ -115,6 +115,9 @@ pub struct Session { pub permission_mode: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub parent_id: Option, + /// User who owns the session (subagent sessions inherit it). + #[serde(default)] + pub owner_id: Option, pub created_at: i64, pub updated_at: i64, #[serde(default)] @@ -160,7 +163,36 @@ pub enum Event { QuestionRequest { id: String, session_id: String, root_session_id: String, source: String, questions: Vec }, QuestionResolved { id: String, session_id: String }, Error { session_id: String, message: String }, + /// The session waits for a free model or subagent slot (reason None = no longer waiting). + /// reason: "model:" or "subagent:". + Waiting { session_id: String, reason: Option }, McpStatus { servers: Value }, + /// Messages waiting for a busy session (full list). + QueueUpdated { session_id: String, items: Value }, +} + +impl Event { + pub fn session_id(&self) -> Option<&str> { + use Event::*; + match self { + SessionUpdated { session } => Some(&session.id), + SessionDeleted { session_id } + | SessionStatus { session_id, .. } + | MessageAdded { session_id, .. } + | TextDelta { session_id, .. } + | ReasoningDelta { session_id, .. } + | ToolStarted { session_id, .. } + | ToolProgress { session_id, .. } + | PermissionRequest { session_id, .. } + | PermissionResolved { session_id, .. } + | QuestionRequest { session_id, .. } + | QuestionResolved { session_id, .. } + | Error { session_id, .. } + | Waiting { session_id, .. } + | QueueUpdated { session_id, .. } => Some(session_id), + McpStatus { .. } => None, + } + } } pub fn new_id() -> String { diff --git a/web/package-lock.json b/web/package-lock.json index ec70a5e..7e8db87 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,6 +8,14 @@ "name": "codecraft-web", "version": "0.1.0", "dependencies": { + "@codemirror/commands": "^6.11.1", + "@codemirror/language": "^6.13.1", + "@codemirror/language-data": "^6.5.2", + "@codemirror/search": "^6.7.2", + "@codemirror/state": "^6.7.6", + "@codemirror/view": "^6.43.15", + "@lezer/highlight": "^1.2.5", + "codemirror": "^6.0.2", "react": "^19.3.1", "react-dom": "^19.3.1", "react-markdown": "^10.1.0", @@ -1582,6 +1590,414 @@ "node": ">=6.9.0" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.4", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.4.tgz", + "integrity": "sha512-yF8RhOf2SXlg00+qtupzLhrglcas61boqaONiNcbPIebHCpJocpqTBA6dyrtUrQO47hqyTdeob/3XnYilT7TaQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.11.1.tgz", + "integrity": "sha512-O/4hG3SC1YwcmQ0d2UVNDs+AsaNWd1iHVxbTeEBuqH+6bExAiPK3iS/BvpY6rZGURALv4ZD3sIgcCmRvw3ehBg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.7.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-angular": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-angular/-/lang-angular-0.1.4.tgz", + "integrity": "sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.3" + } + }, + "node_modules/@codemirror/lang-cpp": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-cpp/-/lang-cpp-6.0.3.tgz", + "integrity": "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/cpp": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-go": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-go/-/lang-go-6.0.1.tgz", + "integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/go": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.12", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.12.tgz", + "integrity": "sha512-pw2ReWKUqSkbvh76RAT4NYxiogRu+PWkR2ukAwO9uOgrm8uipkzjtKKtNpyeAQwHOqxEeSvAXZ6vr3AfyB9y/w==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-java": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.2.tgz", + "integrity": "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/java": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-jinja": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-jinja/-/lang-jinja-6.0.1.tgz", + "integrity": "sha512-P5kyHLObzjtbGj16h+hyvZTxJhSjBEeSx4wMjbnAf3b0uwTy2+F0zGjMZL4PQOm/mh2eGZ5xUDVZXgwP783Nsw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.4.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-less": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-less/-/lang-less-6.0.2.tgz", + "integrity": "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-css": "^6.2.0", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-liquid": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-liquid/-/lang-liquid-6.3.3.tgz", + "integrity": "sha512-U9EnyzWuAx1TX3MZSKDXydKH734n1Aogk/ijKdW+jdIwmbf9zSPQYOIKggC/b2PTRjQb7ddvMktmxnfTLuv/SQ==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.1" + } + }, + "node_modules/@codemirror/lang-markdown": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.2.tgz", + "integrity": "sha512-AwBOdkWYuA//WcM0xO5PfHPUcmz/O2i5o0Nsg1U69SII/loCJlFI1Romd9xp2HYb1kYJRGZotyqRghuHH5n8Kw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.7.1", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.3.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/markdown": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-php": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.2.tgz", + "integrity": "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/php": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-python": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz", + "integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.3.2", + "@codemirror/language": "^6.8.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/python": "^1.1.4" + } + }, + "node_modules/@codemirror/lang-rust": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-rust/-/lang-rust-6.0.2.tgz", + "integrity": "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/rust": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-sass": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-sass/-/lang-sass-6.0.2.tgz", + "integrity": "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-css": "^6.2.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/sass": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-sql": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.10.0.tgz", + "integrity": "sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-vue": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-vue/-/lang-vue-0.1.3.tgz", + "integrity": "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.1" + } + }, + "node_modules/@codemirror/lang-wast": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-wast/-/lang-wast-6.0.2.tgz", + "integrity": "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-xml": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", + "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-yaml": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.3.tgz", + "integrity": "sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.0.0", + "@lezer/yaml": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.13.1.tgz", + "integrity": "sha512-zfvU2r3TyylQLMj/te+5CnQ0rSqZi8B1oTCBbQkagcLW4Xc6imab6qN+pEJhsuNOLaJqiRyBXp9UhWA1DhVvDw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/streamparser": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/language-data": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/language-data/-/language-data-6.5.2.tgz", + "integrity": "sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-angular": "^0.1.0", + "@codemirror/lang-cpp": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-go": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-java": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/lang-jinja": "^6.0.0", + "@codemirror/lang-json": "^6.0.0", + "@codemirror/lang-less": "^6.0.0", + "@codemirror/lang-liquid": "^6.0.0", + "@codemirror/lang-markdown": "^6.0.0", + "@codemirror/lang-php": "^6.0.0", + "@codemirror/lang-python": "^6.0.0", + "@codemirror/lang-rust": "^6.0.0", + "@codemirror/lang-sass": "^6.0.0", + "@codemirror/lang-sql": "^6.0.0", + "@codemirror/lang-vue": "^0.1.1", + "@codemirror/lang-wast": "^6.0.0", + "@codemirror/lang-xml": "^6.0.0", + "@codemirror/lang-yaml": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/legacy-modes": "^6.4.0" + } + }, + "node_modules/@codemirror/legacy-modes": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.5.tgz", + "integrity": "sha512-9ltT214oMb3Y1w5eAZMSigzrjzZnMI2Wa3Feq73rU/i4C56MkwrpfswfQYOr28C5pXr6dWwspNTMyeHXlzWMxg==", + "license": "MIT", + "dependencies": { + "@codemirror/streamparser": "^6.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.7.tgz", + "integrity": "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.42.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.2.tgz", + "integrity": "sha512-gUYkYhT2+n/+VGZ+8EzE5WFkYZUZYm1VOKDudIsNqh42uRVQJ0a6Yss9sdKT3MeOYfuL1N6AZA57oza0Oyr0LA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.7.6", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.6.tgz", + "integrity": "sha512-kAz+AncRtKuIknedxT1bq4XwXv4UowhbkHU1myPrtVb/jZtImWuV5BXzv5vK6i3kYACsdiZiQKFQQ5Mq7elW8w==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/streamparser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/streamparser/-/streamparser-6.0.0.tgz", + "integrity": "sha512-JTFPF6xrfjt0mnfGPgzcLiMwYmvIyNaoizp8BYzv8FE8KrT6GBGDZ190rtt+O7Wke3gG/MFHyiY9YGNeimof4w==", + "license": "MIT" + }, + "node_modules/@codemirror/view": { + "version": "6.43.15", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.15.tgz", + "integrity": "sha512-HHsfpqFeHyXLs983pSRe38Xajs0vFWZQ2BuX0m7Ki8w3jZ5iD8cuR3lBz2/5/X53Dhf32SBHKh6rtSTB6tKeGw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.7.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@isaacs/cliui": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", @@ -1653,6 +2069,189 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lezer/common": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.5.tgz", + "integrity": "sha512-Pw7PVCndruF1pb3ebCxpSVpkMHSDjwRCLcTRw+RizUKco5QbWylwq2g4lnG/RYN7yTTQDXE9F77MwtkAyphrjg==", + "license": "MIT" + }, + "node_modules/@lezer/cpp": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@lezer/cpp/-/cpp-1.1.6.tgz", + "integrity": "sha512-vh9gWWJOXFVY8HBHK3Twzq8MgwG2iN4GSyzBP9sCGTe37P15x2R14VaBQk0VA0ezTRN1KHYBBsHhvpGZ2Xy/pA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/css": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.9.tgz", + "integrity": "sha512-ZmzgvTOBdJWGIsY/ItAfN7UW8DV802oZG+jeG6E508SwEzz5YhuAajptpWPICvIyqYAD2+VjMdOS40GgGkyEOQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/go": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@lezer/go/-/go-1.0.1.tgz", + "integrity": "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.5.tgz", + "integrity": "sha512-O1GMVKgtf5YspFaRzpmqkVgMtIk2HG9uMAbH6wdAIdNh3TVzCsch/+cRi+6l2//UGDKe6NB9BW3cvuR5ezMQ0w==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/java": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@lezer/java/-/java-1.1.5.tgz", + "integrity": "sha512-JppTd1iKHpeNlLYw/Zl8JX7zSY5HqAzK4zZ05lpn8l8Sk7Fo5KAKiDiMiezKoT5lfeC2xZf8HeKp1tSctj4G6Q==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.6.tgz", + "integrity": "sha512-sEvOqpl4vJQca0NugSQK02vQPbc5KrvoojnYGoqqFgFO0MtcYhwGiHKVEB+LuXYJnfyXo4QPA8ztbrEa4Fw5NA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.12.tgz", + "integrity": "sha512-On9HU4oMMiHPMkQDgoTnGahjgG5n/MkN38CgG77sMraKLL+3JBbeQNi1FcCXV9QRqwF9/BUXP3caHyJC1fBOTQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/markdown": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.8.1.tgz", + "integrity": "sha512-M5YmEcKWO4mJ45Y1hECwqyu8cLv25EiXOK554PVOvDOWOP0olwelHHQUWr9sjF+d1bENVhy0nfJ8E57XZUTiXg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@lezer/php": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.6.tgz", + "integrity": "sha512-QJ2xNXPmsm/Z3IpThq8fnJrEIVpxhsIoj6GZBW0JYEd/l9zxpJZW216X4fzqQY4vgpdGIumypvI4uQjd4tnYtw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.1.0" + } + }, + "node_modules/@lezer/python": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.19.tgz", + "integrity": "sha512-MhQIURHRytsNzP/YXnqpYKW6la6voAH3kyplTOOiCdjyFY6cWWGFVmYVdHIPrElqSDf4iCDktQCockB9FxuhzQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/rust": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/rust/-/rust-1.0.3.tgz", + "integrity": "sha512-XxErOjZzQ7yJt1agUT4fu9qQvESZ3acgoxpPaPTPOiUx+duCjaVAtZGFIgphkHxlN05djdVAIOy/wItShMEjqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/sass": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lezer/sass/-/sass-1.1.0.tgz", + "integrity": "sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/xml": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", + "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/yaml": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz", + "integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.4.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.4.tgz", + "integrity": "sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==", + "license": "MIT" + }, "node_modules/@oxc-project/types": { "version": "0.156.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.156.0.tgz", @@ -3250,6 +3849,21 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -3301,6 +3915,12 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/crelt": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -7139,6 +7759,12 @@ "node": ">=10" } }, + "node_modules/style-mod": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.4.tgz", + "integrity": "sha512-XXWIQt633/EpAFx8aZDOTjBzrCaGmhvEQlQo6MVPfa2OzO2cWo+4hV9h+6UkHYlXGfy+ODXKUdP7Pthmcu5ATw==", + "license": "MIT" + }, "node_modules/style-to-object": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-2.0.2.tgz", @@ -7723,6 +8349,12 @@ } } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/web/package.json b/web/package.json index 1b6a000..b8198d9 100644 --- a/web/package.json +++ b/web/package.json @@ -10,6 +10,14 @@ "preview": "vite preview" }, "dependencies": { + "@codemirror/commands": "^6.11.1", + "@codemirror/language": "^6.13.1", + "@codemirror/language-data": "^6.5.2", + "@codemirror/search": "^6.7.2", + "@codemirror/state": "^6.7.6", + "@codemirror/view": "^6.43.15", + "@lezer/highlight": "^1.2.5", + "codemirror": "^6.0.2", "react": "^19.3.1", "react-dom": "^19.3.1", "react-markdown": "^10.1.0", diff --git a/web/src/App.tsx b/web/src/App.tsx index d3f36f8..ca5f608 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,39 +1,48 @@ -import { useEffect, useState } from "react"; -import { ApiError, connectEvents, get, getToken } from "./api"; +import { lazy, Suspense, useEffect, useState } from "react"; +import { connectEvents, get, setUnauthorizedHandler } from "./api"; import { useT } from "./i18n"; import { navigate, useRoute } from "./router"; import { applyEvent, loadSessions, resync, setState } from "./store"; +import type { Info } from "./types"; import { Sidebar } from "./components/Sidebar"; import { Login } from "./components/Login"; import { ChatView } from "./components/ChatView"; import { NewSession } from "./components/NewSession"; import { Settings } from "./components/Settings"; import { Stats } from "./components/Stats"; +import { Images } from "./components/Images"; +import { Spinner, useTitleActivity } from "./components/Activity"; -type AuthState = "checking" | "login" | "ok"; +// The file manager (editor, git) is loaded on demand to keep the chat small. +const Files = lazy(() => import("./components/Files").then((m) => ({ default: m.Files }))); export function App() { const t = useT(); const route = useRoute(); - const [auth, setAuth] = useState("checking"); + const [info, setInfo] = useState(); + const [error, setError] = useState(); const [menuOpen, setMenuOpen] = useState(false); + useTitleActivity(); + + const refresh = async () => { + try { + const i = await get("/info"); + setState(() => ({ me: i.user })); + if (i.user) await loadSessions(); + setInfo(i); + } catch (e: any) { + setError(e.message); + } + }; - // Check whether a token is needed and whether ours works. useEffect(() => { - (async () => { - try { - const info = await get<{ auth_required: boolean }>("/info"); - if (info.auth_required && !getToken()) return setAuth("login"); - await loadSessions(); - setAuth("ok"); - } catch (e) { - setAuth(e instanceof ApiError && e.status === 401 ? "login" : "ok"); - } - })(); + setUnauthorizedHandler(() => setInfo((i) => (i ? { ...i, user: null } : i))); + void refresh(); }, []); + const signedIn = !!info?.user; useEffect(() => { - if (auth !== "ok") return; + if (!signedIn) return; return connectEvents({ onEvent: applyEvent, onOpen: () => { @@ -42,19 +51,27 @@ export function App() { }, onClose: () => setState(() => ({ connected: false })), }); - }, [auth]); + }, [signedIn]); // Close the mobile menu on navigation. useEffect(() => setMenuOpen(false), [route.join("/")]); - if (auth === "checking") return
{t("common.loading")}
; - if (auth === "login") return setAuth("ok")} />; + if (error) return
{error}
; + if (!info) return
{t("common.loading")}
; + if (!info.user) return ; const [section, arg] = route; let content; if (section === "s" && arg) content = setMenuOpen(true)} />; else if (section === "settings") content = setMenuOpen(true)} />; else if (section === "stats") content = setMenuOpen(true)} />; + else if (section === "images") content = setMenuOpen(true)} />; + else if (section === "files") + content = ( + }> + setMenuOpen(true)} /> + + ); else content = setMenuOpen(true)} onCreated={(id) => navigate(`/s/${id}`)} />; return ( diff --git a/web/src/api.ts b/web/src/api.ts index b48caa5..4a886a5 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -1,14 +1,6 @@ import type { ServerEvent } from "./types"; -const TOKEN_KEY = "codecraft.token"; - -export function getToken(): string { - return localStorage.getItem(TOKEN_KEY) ?? ""; -} - -export function setToken(token: string) { - localStorage.setItem(TOKEN_KEY, token); -} +// Authentication uses an HttpOnly session cookie set by /api/auth/login. export class ApiError extends Error { constructor( @@ -21,20 +13,32 @@ export class ApiError extends Error { export async function api(path: string, init: RequestInit & { json?: unknown } = {}): Promise { const headers: Record = { ...(init.headers as Record) }; - const token = getToken(); - if (token) headers.Authorization = `Bearer ${token}`; let body = init.body; if (init.json !== undefined) { headers["Content-Type"] = "application/json"; body = JSON.stringify(init.json); } - const res = await fetch(`/api${path}`, { ...init, headers, body }); + const res = await fetch(`/api${path}`, { ...init, headers, body, credentials: "same-origin" }); + if (res.status === 401 && !path.startsWith("/auth/") && path !== "/info") onUnauthorized?.(); const text = await res.text(); - const data = text ? JSON.parse(text) : null; + let data: any = null; + try { + data = text ? JSON.parse(text) : null; + } catch { + // Proxies answer with HTML error pages (e.g. 413 from nginx). + const hint = res.status === 413 ? "request too large – the reverse proxy rejected the upload (client_max_body_size)" : res.statusText || "unexpected response"; + throw new ApiError(res.status, `HTTP ${res.status}: ${hint}`); + } if (!res.ok) throw new ApiError(res.status, data?.error ?? res.statusText); return data as T; } +/** Called when the session expired (set by the app to show the login). */ +let onUnauthorized: (() => void) | undefined; +export function setUnauthorizedHandler(fn: () => void) { + onUnauthorized = fn; +} + export const get = (path: string) => api(path); export const post = (path: string, json?: unknown) => api(path, { method: "POST", json: json ?? {} }); export const patch = (path: string, json: unknown) => api(path, { method: "PATCH", json }); @@ -55,8 +59,7 @@ export function connectEvents(handlers: { const open = () => { const proto = location.protocol === "https:" ? "wss" : "ws"; - const token = encodeURIComponent(getToken()); - ws = new WebSocket(`${proto}://${location.host}/api/ws?token=${token}`); + ws = new WebSocket(`${proto}://${location.host}/api/ws`); ws.onopen = () => { retry = 0; handlers.onOpen(); diff --git a/web/src/components/Activity.tsx b/web/src/components/Activity.tsx new file mode 100644 index 0000000..0c84690 --- /dev/null +++ b/web/src/components/Activity.tsx @@ -0,0 +1,80 @@ +import { useEffect } from "react"; +import { useT } from "../i18n"; +import { useStore, type Draft } from "../store"; +import type { Part } from "../types"; + +type ToolResult = Extract; + +/** Animated three-dot pulse. */ +export function Dots() { + return ( + + ); +} + +export function Spinner({ small }: { small?: boolean }) { + return