---
name: agent-os-routes
description: Добавлять в Agent OS собственные HTTP-эндпоинты и статику без пересборки — http_route (обработчик на JS/Rhai) и static_route (файлы из папки), глобально (config/routes/) или от агента (route_files:). Используй, когда просят добавить REST-эндпоинт, веб-хук наружу, страницу сайта или статику, которую обслуживает агент.
---

# Agent OS — HTTP-маршруты (http_route / static_route)

Сервер обслуживает пользовательские HTTP-эндпоинты, добавленные из YAML без
пересборки — так же, как `script_tool`/`script_hook`. Полный справочник:
`docs/21-http-routes.md`.

## Раскладка

```
config/routes/weather.yaml + weather.rhai        (глобальный http_route)
config/routes/assets.yaml + assets/              (глобальный static_route)
config/agents/felix/routes/card.yaml + card.rhai (агентский, из route_files:)
```

Глобальные маршруты читаются из `config/routes/*.yaml` сами. Агентские
подключаются через `route_files:` в `agent.yaml`:

```yaml
# config/agents/felix/agent.yaml
route_files: [routes/card.yaml]
```

Агентские пути по соглашению начинаются с `/ext/<agent>/`.

## http_route — скриптовый REST-эндпоинт

```yaml
# weather.yaml
kind: http_route
name: weather
method: GET
path: /ext/weather/:city      # :param и *rest
script: weather.rhai          # .js или .rhai, относительно YAML
auth: public                  # public | bearer | token_env | user
```

```javascript
// weather.rhai
fn handler(request) {
    // request: { method, path, params, query, headers, body, json, user_id, user, config }
    if request.user_id == () {
        return #{ status: 401, body: #{ error: "unauthorized" } };
    }
    let r = http_get(`https://api.weather.example/${request.params.city}`);
    if r.status != 200 { return #{ status: 502, body: #{ error: r.error } }; }
    #{ status: 200, body: parse_json(r.body) }
}
```

`handler(request)` возвращает `#{ status, headers, body }` (всё опционально):
карта `body` → JSON (`application/json`), строка → текст (`text/plain`),
исключение → `500`.

**Помощники** — те же, что у `script_tool` (§7): `http_get/post/put/patch/delete`,
`http(method, …)`, `env_var`, `now`, `db_query`/`db_schema`/`db_execute`,
`parse_json`/`to_json`, `json_get`, `extract_script_json`, `print`/`debug`.
`llm(...)` **недоступен** (нет процесса, на который списать бюджет).

## static_route — статика из папки

```yaml
# site.yaml
kind: static_route
name: felix_site
path: /ext/felix/*rest        # обязательно заканчивается на *rest
dir: static/                  # относительно YAML
index: index.html
auth: public
cache_ttl_secs: 300
```

`*rest` резолвится в файл под `dir` (traversal и dotfiles/`.env` блокируются),
content-type — по расширению. Только GET/HEAD.

## Auth

| Режим | Поведение |
|---|---|
| `public` | открыто |
| `bearer` | `Authorization: Bearer`, сверка с `auth.admin_tokens` |
| `token_env` | bearer = значение env из `token_env` (пусто = fail-closed) |
| `user` | `X-User-Token` (HMAC) или `X-User-Id` (dev); даёт `request.user_id` |

## Ограничения

- Встроенные маршруты (`/`, `/health`, `/v1/*`, `/login`, `/proxy*`, `/widget*`, …)
  всегда выигрывают; конфликт `path`+`method` отклоняется при загрузке.
- Кастомные маршруты живут под `/ext/...` (и `/ext/<agent>/...` у агентов).
- Live reload (`kernel.live_reload`) перерегистрирует маршруты при изменении.
- Имя маршрута и скоуп пишутся в access-лог вместо пути.

## Отладка

```bash
route_tool config/routes/weather.yaml '{"path":"/ext/weather/moscow"}' --user alice
route_tool config/routes/site.yaml     # static_route: список резолвящихся файлов
```
