---
name: agent-os-scripts
description: Писать инструменты и хуки Agent OS скриптами (script_tool / script_hook) на JavaScript (.js) или Rhai (.rhai) — помощники HTTP/JSON/LLM/state, run(args) и функции жизненного цикла. Используй, когда просят добавить кастомный инструмент или хук с логикой на JS или Rhai.
---

# Agent OS — скрипты (JavaScript и Rhai)

Инструменты и хуки можно писать скриптами, подгружаемыми из YAML без пересборки.
Движок выбирается по расширению `script:`: `.js` (или `.mjs`/`.cjs`) — JavaScript
на [QuickJS](https://bellard.org/quickjs/); любое другое — [Rhai](https://rhai.rs).
Оба движка дают одинаковый контракт и помощники. **Рекомендуемый выбор — JS**
(полный язык + stdlib). Полный справочник: `docs/07-scripts.md`.

## Раскладка

```
config/scripts/tools/currency.yaml + currency.rhai    (kind: script_tool)
config/scripts/tools/wordcount.yaml + wordcount.js    (kind: script_tool, JavaScript)
config/scripts/hooks/audit.yaml   + audit.rhai        (kind: script_hook)
```

## Скриптовый инструмент

```yaml
# currency.yaml
kind: script_tool
name: currency
description: Конвертация валют.
script: currency.js             # относительно этого YAML
parameters:
  type: object
  properties:
    base: { type: string }
    target: { type: string }
```

```javascript
// currency.js
function run(args) {
    const base = args.base ?? "USD";
    const r = http_get(`https://open.er-api.com/v6/latest/${base}`);
    if (r.status !== 200) {
        return { content: `error: ${r.error}`, success: false };
    }
    const data = JSON.parse(r.body);
    const rate = data.rates[args.target ?? "RUB"];
    return { content: `1 ${base} = ${rate} ${args.target}`, success: true };
}
```

`run(args)` возвращает `{ content, success, error }` или простую строку.

## Скриптовый хук

```yaml
# moderation.yaml
kind: script_hook
name: moderation
script: moderation.js
events: [before_user_message]
```

```javascript
// moderation.js
function before_user_message(ctx, msg) {
    const verdict = llm("Ты — модератор. Ответь OK или FLAG.",
                        `Сообщение: ${msg.content}`);
    if (verdict.includes("FLAG")) {
        return { suppress: true, notice: "🚫 заблокировано" };
    }
    return { notice: "✅ ok" };
}
```

Функции хука: `before_user_message(ctx, msg)`, `after_turn(ctx, q, a)`,
`after_loop(ctx, reason)`. `ctx` = `{ pid, agent, template, session_id, user_id,
config }`.

## Rhai-вариант (альтернатива)

Контракт идентичен; отличается синтаксис (`#{ … }` вместо `{ … }`, `.len` вместо
`.length`, `parse_json`/`to_json` вместо `JSON.*`):

```javascript
// currency.rhai
fn run(args) {
    let base = args.base ?? "USD";
    let r = http_get(`https://open.er-api.com/v6/latest/${base}`);
    if r.status != 200 {
        return #{ content: `error: ${r.error}`, success: false };
    }
    let data = parse_json(r.body);
    let rate = data.rates[args.target ?? "RUB"];
    #{ content: `1 ${base} = ${rate} ${args.target}`, success: true }
}
```

## Встроенные помощники

| Функция | Описание |
|---|---|
| `http_get(url [, timeout_secs])` | → `{ status, body, error }` (не бросает) |
| `http_post(url, body [, content_type [, timeout]])` | → та же карта |
| `http_put` / `http_patch` / `http_delete` | → та же карта |
| `http(method, url [, body [, content_type [, timeout]]])` | универсальный вызов |
| `env_var(name)` | переменная окружения (`""` если нет; пишет предупреждение) |
| `llm(prompt)` / `llm(system, user)` / `llm({user, system, max_tokens, temperature})` | один инференс, списывается на бюджет вызывающего |
| `now()` | текущее UTC-время (RFC 3339) |
| `print(...)`, `debug(...)` | лог в сервер |
| `urlencode`/`url_encode`, `urldecode`/`url_decode` | URL encode/decode |
| `extract_script_json(html, contains)` | JSON-блоб `<script>` (напр. `__NEXT_DATA__`) → объект или `null` |
| `json_get(data, pointer)` | JSON-указатель (`/a/b/0`) → значение или `null` |
| `schedule_cron` / `cancel_cron` / `list_cron` | динамические cron-задания (персистятся) |
| `state_get(scope,key)` / `state_set` / `state_push` / `state_sadd` / … | scoped state store (`docs/05-tools-state.md`, state_tool) |

JSON: в JS — нативные `JSON.parse`/`JSON.stringify`, в Rhai — `parse_json`/`to_json`.

## Лимиты

- Нет доступа к ФС, shell, БД или ядру (`spawn`, `inject`, …) — только HTTP/LLM/state
  и возвращаемые значения.
- `max_operations` (по умолчанию 10 000 000) обрывает runaway-циклы.
- Состояние между вызовами не сохраняется; используй `state_*`, HTTP или хуки
  (`faq_cache`, `memory`).
- Скрипты синхронны; избегай многих последовательных `llm(...)`.

## Отладка

```bash
hook_tool config/scripts/hooks/moderation.yaml user "Привет" --live
```
