@agfpd/iapeer 0.4.61 → 0.4.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,142 @@
1
+ # 15 — Fleet API
2
+
3
+ The daemon's HTTP surface for **operator clients**: dashboards, menu-bar apps, scripts, future web gateways. It serves three things: a **snapshot** of the fleet state, a **live event stream**, and **commands** over the existing management verbs. Everything an `iapeer list`-style client needs — without parsing CLI output.
4
+
5
+ This is a **normative contract**: fleet clients (SwiftBar plugins, native apps, web pages) are written against this document, not against the daemon source. The MUST/MUST NOT rules in «Client obligations» below are load-bearing — a client that skips them will break on a future daemon update.
6
+
7
+ ## Surface and transport
8
+
9
+ - Base path: **`/fleet/v1`**, plain HTTP + JSON (+ SSE for events). This is *not* MCP: MCP (`/mcp`) is the agent surface and deliberately carries a single tool; fleet clients are GUIs and scripts that consume HTTP natively.
10
+ - Served on **both** daemon listeners:
11
+ - the unix socket `~/.iapeer/state/iapeer/router.sock` — `curl --unix-socket ~/.iapeer/state/iapeer/router.sock http://iapeer/fleet/v1/snapshot`
12
+ - TCP loopback — `http://127.0.0.1:8765/fleet/v1/snapshot`
13
+ - **Discovery:** read `~/.iapeer/state/iapeer/router.json`. A daemon that serves this API advertises `"fleet": 1` there, next to the active `sock`/`tcp` addresses and the daemon `version`. Absent `fleet` key ⇒ pre-fleet daemon ⇒ the client must degrade (hide, or fall back to CLI).
14
+ - **Auth:** when the daemon is configured with a bearer token (`IAPEER_BEARER_TOKEN`), every fleet request must carry `Authorization: Bearer <token>` — the same gate as MCP, checked before dispatch. Without a configured token, the surface is open to the local user (same trust class as the CLI: loopback + same-uid socket).
15
+ - **No CORS headers, deliberately.** A browser page must not be able to drive loopback fleet commands cross-origin. A web gateway is a separate future decision with mandatory auth.
16
+
17
+ ## Client obligations (normative)
18
+
19
+ 1. **Unknown event kinds MUST be ignored.** The `ev` vocabulary grows without a version bump (e.g. future `approval-request` / `approval-resolved`). A client that throws on an unknown `ev` is broken by design.
20
+ 2. **Unknown JSON fields MUST be ignored.** The snapshot and every response evolve **additively** within `/fleet/v1`: new fields appear, existing fields keep their meaning and type. Removals or type changes would be a `/fleet/v2`.
21
+ 3. **Events are at-least-once.** After an SSE reconnect (and around log rotation) a client may see an event twice. The SSE `id:` is the event's epoch-ms timestamp — use it (plus field equality) for display-level dedup. There is no exactly-once replay cursor; on reconnect, re-fetch the snapshot and resume from live events.
22
+ 4. **The snapshot is the state; events are change hints.** The intended loop: fetch `/snapshot` once, subscribe to `/events`, re-fetch `/snapshot` when an event arrives (cheap, coalesce bursts). Do not reconstruct fleet state from events alone.
23
+ 5. **Ages are client-rendered.** `last_active_ms` is an epoch timestamp; render the ticking «4m ago» yourself instead of polling the snapshot per second.
24
+
25
+ ## GET /fleet/v1/snapshot
26
+
27
+ The full fleet state. The peer rows are produced by the **same in-process function that renders `iapeer list`** — agreement with the CLI is by construction.
28
+
29
+ ```json
30
+ {
31
+ "api": 1,
32
+ "version": "0.4.63",
33
+ "ts": "2026-07-05T18:00:00.000Z",
34
+ "host": {
35
+ "version": "0.4.63",
36
+ "pid": 12345,
37
+ "startedAt": "2026-07-05T09:00:00.000Z",
38
+ "uptimeSecs": 32400,
39
+ "memory": { "provider": "iapeer-memory", "version": "1.2.3", "heartbeatAgeSecs": 12 },
40
+ "voice": { "provider": "voice-connect", "version": "0.3.1", "heartbeatAgeSecs": null },
41
+ "fda": true
42
+ },
43
+ "peers": [
44
+ {
45
+ "personality": "boris",
46
+ "description": "Напарник Артура по мультиагентной системе",
47
+ "intelligence": "artificial",
48
+ "default_runtime": "claude",
49
+ "cwd": "/Users/me/.iapeer/peers/boris",
50
+ "runtimes": [
51
+ { "runtime": "claude", "status": "live", "attached": true },
52
+ { "runtime": "codex", "status": "asleep" }
53
+ ],
54
+ "last_active_runtime": "claude",
55
+ "last_active_ms": 1751731100000,
56
+ "attached": true,
57
+ "launchd_managed": false,
58
+ "wake_policy": "warm"
59
+ }
60
+ ]
61
+ }
62
+ ```
63
+
64
+ Field notes:
65
+
66
+ - `runtimes[].status` — `live` (session up) · `asleep` (wakeable on demand) · `stopped` (operator stop flag; the daemon won't wake it). Same glyph semantics as the CLI: `●` / `○` / `✕`.
67
+ - `runtimes[].attached` — present (`true`) only when a **human operator** is attached to that live hosted session.
68
+ - `attached` — any runtime attached (peer-level convenience).
69
+ - `launchd_managed` — launchd owns this peer's lifecycle (H4): the daemon never wakes/reaps it, and wake/stop/start commands are guarded accordingly.
70
+ - `wake_policy` — `warm` (default: wake-on-message, idle-reap, resume) or `ephemeral` (stateless worker: serial task queue, die-after-reply).
71
+ - `queue_depth` — ephemeral peers only: pending tasks across the peer's serial queues.
72
+ - `host.memory` / `host.voice` — the provider slots (`null` = empty slot); `heartbeatAgeSecs: null` with a non-null slot = provider declared but its daemon isn't heartbeating.
73
+ - `host.fda` — Full Disk Access of the binary: `true` / `false` / `null` (undeterminable).
74
+ - The daemon answering **is** the health signal: there is no `healthy` boolean. A dashboard that cannot reach any advertised address should render the daemon as down (this is also why a purely API-fed dashboard is insufficient when the daemon itself is broken — the built-in TUI keeps its direct in-process read path for exactly that case).
75
+ - The peer's **LLM model is deliberately absent**: the effective model of a live session is not an observable registry fact (static launch pins exist, but reporting a pin as «the model» would lie under runtime auto-switching). If it ever becomes observable, it will appear as an additive field.
76
+
77
+ ## GET /fleet/v1/peers/&lt;personality&gt;
78
+
79
+ One peer's card: the same object as the snapshot row, plus its recent history.
80
+
81
+ ```json
82
+ { "api": 1, "version": "0.4.63", "ts": "…", "peer": { …snapshot row… },
83
+ "events": [ { "src": "lifecycle", "ev": "wake", "ts": "…", "personality": "boris", "mode": "resume" } ] }
84
+ ```
85
+
86
+ `events` — the newest ≤50 events concerning this peer (matched on personality / `<runtime>-<personality>` identity fields), merged across the logs by timestamp, oldest first. `404` for an unregistered peer.
87
+
88
+ ## GET /fleet/v1/events — SSE stream
89
+
90
+ `text/event-stream` of fleet events, sourced by tail-following the daemon's **durable logs** (`lifecycle.log`, `delivery.log`, `exits.log`). Those files are written by *every* participating process — the daemon, CLI verbs (attach/stop/wake), the pty supervisors — so the stream covers events an in-daemon bus would miss.
91
+
92
+ ```
93
+ GET /fleet/v1/events?replay=50
94
+ ```
95
+
96
+ - `replay=N` (0…500, default 0) — emit the newest N historical events before going live.
97
+ - Each event:
98
+
99
+ ```
100
+ event: wake
101
+ id: 1751731100123
102
+ data: {"src":"lifecycle","ev":"wake","ts":"2026-07-05T18:00:00.123Z","personality":"boris","mode":"resume"}
103
+ ```
104
+
105
+ `data` is the full parsed log line: `src` (`lifecycle` | `delivery` | `exits`) + `ev` + every logfmt field with quoted values decoded. `id` = epoch-ms of the event.
106
+ - A comment `: connected` marks the transition from replay to live; `: hb` comments every ~15 s keep the connection alive.
107
+ - Latency: live events surface within ~0.5 s (log-tail poll).
108
+
109
+ Current `ev` vocabulary (grows over time — see obligation 1): `wake`, `supervise` (with `action`: `reaped-idle` = parked, `reaped-ephemeral`, `dead`→fresh/resume classification, `eager-orphan-fresh`, `skipped-error`…), `delivery`, `topic-note`, `ephemeral-drain`, `attach` / `attach-end`, `hosted-deliver`, `pty-host-failed`, `supervise-error`, `composer-queue-failed-notify`, `memory-provision`, `session-exit` (from `exits.log`, with `dead_status`/`dead_signal`).
110
+
111
+ What the headline states look like on the stream: a peer **waking** is `ev=wake`; a peer **parked to sleep** is `ev=supervise action=reaped-idle`; a **death** is `ev=session-exit` (immediately, from the supervisor) followed by the daemon's classification on its next pass.
112
+
113
+ ## Commands
114
+
115
+ `POST /fleet/v1/peers/<personality>/<command>` with an optional JSON body `{ "runtime": "claude", "topic": "…" }` (both optional; `topic` is used by `wake` only). The commands are the **existing management verbs called in-process** — every guard (H4 launchd read-only, the foreign-plist fleet guard, the crash-loop guard, wake serialization) lives inside those verbs and cannot be bypassed via the API.
116
+
117
+ | Command | Verb semantics | Success |
118
+ |---|---|---|
119
+ | `wake` | bring the peer up (no message; fresh-vs-resume decided by the daemon) | `200` + wake outcome, `502` + `status:"FAILED"` |
120
+ | `stop` | set the stop flag / bootout an always-on peer | `200` + `outcomes[]`, `502` if a bootout failed or a foreign plist was refused |
121
+ | `start` | clear the stop flag (warm peers become wakeable; no session is launched) / bootstrap always-on | `200` / `502` mirror of `stop` |
122
+ | `new` | **unconditional** fresh restart (the emergency lever) | `200` + `action:"fresh"` |
123
+ | `refresh` | lazy soft-reload: fresh on next natural wake, no kill | `200` + `outcomes[]` |
124
+ | `interrupt` | interrupt a stuck/raving turn (Escape into the live session) | `200` / `502` |
125
+ | `compact` | compact the resumable dialogue (honest failure when none) | `200` + `action:"compacted"` / `502` |
126
+
127
+ `POST /fleet/v1/send` — deliver a message through the same routing path as the `send_to_peer` tool (wake-on-miss, queueing, delivery log — identical semantics):
128
+
129
+ ```json
130
+ { "personality": "boris", "message": "…", "runtime": "claude", "topic": "…", "from": "nova" }
131
+ ```
132
+
133
+ `from` is optional: a full `<runtime>-<personality>`, or a bare personality (its default runtime). Default: the **single natural-intelligence peer** of the registry — a GUI-originated message is the human's. With zero or several natural peers the request is refused (`400`) with an instruction to pass `from`.
134
+
135
+ Not part of the API, deliberately: **attach** (a terminal handoff — a client opens the system terminal with `iapeer attach <peer>`), registry/host **admin verbs** (`create`/`remove`/`update`/`uninstall` — will be added when a client actually needs them), **approve** (no approval broker exists yet; it will arrive as new endpoints + `ev` kinds under this same contract).
136
+
137
+ Errors are always `{"error": "…"}` with an honest status: `400` malformed request, `401` bearer required, `404` unknown peer/endpoint, `405` wrong method, `502` the verb itself failed, `500` internal.
138
+
139
+ ## Compatibility
140
+
141
+ - `/fleet/v1` evolves **additively**; `api: 1` in every snapshot. Breaking changes mean `/fleet/v2` served alongside.
142
+ - The capability marker in `router.json` (`fleet: 1`) is the feature-detect: clients must treat its absence as «no fleet API» rather than probing endpoints.
package/docs/README.md CHANGED
@@ -76,6 +76,7 @@ The sections are layered: from "install and use" to "understand the architecture
76
76
  - [12 — CLI](12-cli-reference.md) — all commands with flags
77
77
  - [13 — Architecture and contract](13-architecture-and-contract.md) — for plugin and runtime-package developers
78
78
  - [14 — Configuration and logs](14-configuration-and-logs.md) — environment variables, logs, diagnostics
79
+ - [15 — Fleet API](15-fleet-api.md) — the daemon's HTTP surface for operator clients (dashboards, tray apps, scripts): snapshot, event stream, commands
79
80
 
80
81
  ## Status
81
82
 
@@ -0,0 +1,142 @@
1
+ # 15 — Fleet API
2
+
3
+ HTTP-поверхность демона для **операторских клиентов**: дашбордов, menu-bar-приложений, скриптов, будущих веб-гейтвеев. Отдаёт три вещи: **снапшот** состояния флота, **живой поток событий** и **команды** поверх существующих управляющих verb'ов. Всё, что нужно клиенту класса `iapeer list`, — без парсинга CLI-вывода.
4
+
5
+ Это **нормативный контракт**: клиенты fleet API (SwiftBar-плагины, нативные приложения, веб-страницы) пишутся по этому документу, а не по исходникам демона. Правила «Обязанности клиента» ниже — несущие: клиент, который их пропустит, сломается на будущем обновлении демона.
6
+
7
+ ## Поверхность и транспорт
8
+
9
+ - Базовый путь: **`/fleet/v1`**, обычный HTTP + JSON (+ SSE для событий). Это *не* MCP: MCP (`/mcp`) — агентская поверхность и намеренно несёт единственный инструмент; fleet-клиенты — GUI и скрипты, потребляющие HTTP нативно.
10
+ - Обслуживается на **обоих** слушателях демона:
11
+ - unix-сокет `~/.iapeer/state/iapeer/router.sock` — `curl --unix-socket ~/.iapeer/state/iapeer/router.sock http://iapeer/fleet/v1/snapshot`
12
+ - TCP loopback — `http://127.0.0.1:8765/fleet/v1/snapshot`
13
+ - **Обнаружение:** читайте `~/.iapeer/state/iapeer/router.json`. Демон, отдающий этот API, объявляет там `"fleet": 1` — рядом с активными адресами `sock`/`tcp` и версией демона. Нет ключа `fleet` ⇒ демон без fleet API ⇒ клиент обязан деградировать (скрыться или уйти на CLI).
14
+ - **Аутентификация:** если демон настроен с bearer-токеном (`IAPEER_BEARER_TOKEN`), каждый fleet-запрос обязан нести `Authorization: Bearer <токен>` — тот же гейт, что у MCP, проверяется до диспатча. Без настроенного токена поверхность открыта локальному пользователю (тот же класс доверия, что CLI: loopback + same-uid сокет).
15
+ - **Без CORS-заголовков, намеренно.** Страница в браузере не должна уметь дёргать loopback-команды флота кросс-доменно. Веб-гейтвей — отдельное будущее решение с обязательной аутентификацией.
16
+
17
+ ## Обязанности клиента (нормативно)
18
+
19
+ 1. **Неизвестные виды событий ОБЯЗАНЫ игнорироваться.** Словарь `ev` растёт без смены версии (например, будущие `approval-request` / `approval-resolved`). Клиент, падающий на неизвестном `ev`, сломан по построению.
20
+ 2. **Неизвестные JSON-поля ОБЯЗАНЫ игнорироваться.** Снапшот и каждый ответ развиваются **аддитивно** внутри `/fleet/v1`: новые поля появляются, существующие сохраняют смысл и тип. Удаление или смена типа означали бы `/fleet/v2`.
21
+ 3. **События — at-least-once.** После SSE-переподключения (и вокруг ротации логов) клиент может увидеть событие дважды. SSE `id:` — epoch-ms события; используйте его (плюс равенство полей) для дедупликации на уровне отображения. Точного replay-курсора нет; после reconnect перечитайте снапшот и продолжайте с живых событий.
22
+ 4. **Снапшот — состояние; события — сигналы изменений.** Целевой цикл: получить `/snapshot` один раз, подписаться на `/events`, перечитывать `/snapshot` по приходу события (дёшево; всплески коалесцируйте). Не восстанавливайте состояние флота из одних событий.
23
+ 5. **Возрасты рендерит клиент.** `last_active_ms` — epoch-метка; тикающее «4m ago» рисуйте сами, а не поллингом снапшота каждую секунду.
24
+
25
+ ## GET /fleet/v1/snapshot
26
+
27
+ Полное состояние флота. Строки пиров производит **та же in-process функция, что рендерит `iapeer list`** — совпадение с CLI обеспечено по построению.
28
+
29
+ ```json
30
+ {
31
+ "api": 1,
32
+ "version": "0.4.63",
33
+ "ts": "2026-07-05T18:00:00.000Z",
34
+ "host": {
35
+ "version": "0.4.63",
36
+ "pid": 12345,
37
+ "startedAt": "2026-07-05T09:00:00.000Z",
38
+ "uptimeSecs": 32400,
39
+ "memory": { "provider": "iapeer-memory", "version": "1.2.3", "heartbeatAgeSecs": 12 },
40
+ "voice": { "provider": "voice-connect", "version": "0.3.1", "heartbeatAgeSecs": null },
41
+ "fda": true
42
+ },
43
+ "peers": [
44
+ {
45
+ "personality": "boris",
46
+ "description": "Напарник Артура по мультиагентной системе",
47
+ "intelligence": "artificial",
48
+ "default_runtime": "claude",
49
+ "cwd": "/Users/me/.iapeer/peers/boris",
50
+ "runtimes": [
51
+ { "runtime": "claude", "status": "live", "attached": true },
52
+ { "runtime": "codex", "status": "asleep" }
53
+ ],
54
+ "last_active_runtime": "claude",
55
+ "last_active_ms": 1751731100000,
56
+ "attached": true,
57
+ "launchd_managed": false,
58
+ "wake_policy": "warm"
59
+ }
60
+ ]
61
+ }
62
+ ```
63
+
64
+ Заметки по полям:
65
+
66
+ - `runtimes[].status` — `live` (сессия поднята) · `asleep` (разбудится по требованию) · `stopped` (операторский стоп-флаг; демон будить не станет). Та же семантика глифов, что в CLI: `●` / `○` / `✕`.
67
+ - `runtimes[].attached` — присутствует (`true`) только когда к этой живой hosted-сессии подключён **человек-оператор**.
68
+ - `attached` — любой рантайм с подключённым человеком (удобство уровня пира).
69
+ - `launchd_managed` — жизненным циклом пира владеет launchd (H4): демон его не будит/не усыпляет, а команды wake/stop/start соответствующе защищены.
70
+ - `wake_policy` — `warm` (по умолчанию: пробуждение по сообщению, idle-reap, resume) или `ephemeral` (stateless-воркер: серийная очередь задач, die-after-reply).
71
+ - `queue_depth` — только у ephemeral-пиров: ожидающие задачи в серийных очередях пира.
72
+ - `host.memory` / `host.voice` — провайдерские слоты (`null` = слот пуст); `heartbeatAgeSecs: null` при непустом слоте = провайдер объявлен, но его демон не шлёт heartbeat.
73
+ - `host.fda` — Full Disk Access бинаря: `true` / `false` / `null` (неопределимо).
74
+ - Сам факт ответа демона **и есть** сигнал здоровья: булева `healthy` нет. Дашборд, не достучавшийся ни до одного объявленного адреса, обязан рисовать демона лежачим (поэтому же дашборд, питающийся только API, недостаточен, когда сломан сам демон — встроенный TUI сохраняет прямой in-process путь чтения ровно на этот случай).
75
+ - **LLM-модель пира отсутствует намеренно**: эффективная модель живой сессии — не наблюдаемый факт реестра (статические пины запуска существуют, но выдавать пин за «модель» — врать при рантаймном автопереключении). Станет наблюдаемой — появится аддитивным полем.
76
+
77
+ ## GET /fleet/v1/peers/&lt;personality&gt;
78
+
79
+ Карточка одного пира: тот же объект, что строка снапшота, плюс его недавняя история.
80
+
81
+ ```json
82
+ { "api": 1, "version": "0.4.63", "ts": "…", "peer": { …строка снапшота… },
83
+ "events": [ { "src": "lifecycle", "ev": "wake", "ts": "…", "personality": "boris", "mode": "resume" } ] }
84
+ ```
85
+
86
+ `events` — новейшие ≤50 событий, касающихся пира (матч по personality / полям идентичности `<runtime>-<personality>`), слитые по времени из всех логов, старые первыми. `404` для незарегистрированного пира.
87
+
88
+ ## GET /fleet/v1/events — SSE-поток
89
+
90
+ `text/event-stream` событий флота, источник — tail-follow **долговременных логов** демона (`lifecycle.log`, `delivery.log`, `exits.log`). Эти файлы пишут *все* участвующие процессы — демон, CLI-verb'ы (attach/stop/wake), pty-супервизоры, — поэтому поток покрывает события, которые внутридемонная шина бы пропустила.
91
+
92
+ ```
93
+ GET /fleet/v1/events?replay=50
94
+ ```
95
+
96
+ - `replay=N` (0…500, по умолчанию 0) — выдать новейшие N исторических событий перед переходом в live.
97
+ - Каждое событие:
98
+
99
+ ```
100
+ event: wake
101
+ id: 1751731100123
102
+ data: {"src":"lifecycle","ev":"wake","ts":"2026-07-05T18:00:00.123Z","personality":"boris","mode":"resume"}
103
+ ```
104
+
105
+ `data` — полностью разобранная строка лога: `src` (`lifecycle` | `delivery` | `exits`) + `ev` + все logfmt-поля с раскодированными кавычками. `id` = epoch-ms события.
106
+ - Комментарий `: connected` отмечает переход от replay к live; комментарии `: hb` каждые ~15 с держат соединение.
107
+ - Латентность: живые события всплывают в пределах ~0.5 с (поллинг хвоста лога).
108
+
109
+ Текущий словарь `ev` (растёт со временем — см. обязанность 1): `wake`, `supervise` (с `action`: `reaped-idle` = парковка, `reaped-ephemeral`, классификация `dead`→fresh/resume, `eager-orphan-fresh`, `skipped-error`…), `delivery`, `topic-note`, `ephemeral-drain`, `attach` / `attach-end`, `hosted-deliver`, `pty-host-failed`, `supervise-error`, `composer-queue-failed-notify`, `memory-provision`, `session-exit` (из `exits.log`, с `dead_status`/`dead_signal`).
110
+
111
+ Как выглядят главные состояния на потоке: **пробуждение** пира — `ev=wake`; **парковка в сон** — `ev=supervise action=reaped-idle`; **смерть** — `ev=session-exit` (немедленно, от супервизора), затем классификация демона на его следующем проходе.
112
+
113
+ ## Команды
114
+
115
+ `POST /fleet/v1/peers/<personality>/<команда>` с необязательным JSON-телом `{ "runtime": "claude", "topic": "…" }` (оба опциональны; `topic` использует только `wake`). Команды — это **существующие управляющие verb'ы, вызванные in-process**: каждый гвард (H4 read-only для launchd-пиров, fleet-guard чужих плистов, защита от crash-loop, сериализация пробуждений) живёт внутри самих verb'ов и через API не обходится.
116
+
117
+ | Команда | Семантика verb'а | Успех |
118
+ |---|---|---|
119
+ | `wake` | поднять пира (без сообщения; fresh-vs-resume решает демон) | `200` + исход, `502` + `status:"FAILED"` |
120
+ | `stop` | поставить стоп-флаг / bootout always-on пира | `200` + `outcomes[]`, `502` при неудачном bootout или отказе на чужом плисте |
121
+ | `start` | снять стоп-флаг (warm-пиры становятся пробуждаемыми; сессия не запускается) / bootstrap always-on | `200` / `502`, зеркально `stop` |
122
+ | `new` | **безусловный** свежий рестарт (аварийный рычаг) | `200` + `action:"fresh"` |
123
+ | `refresh` | ленивая мягкая перезагрузка: fresh на следующем естественном пробуждении, без kill | `200` + `outcomes[]` |
124
+ | `interrupt` | прервать зависший/зациклившийся turn (Escape в живую сессию) | `200` / `502` |
125
+ | `compact` | компактифицировать резюмируемый диалог (честный отказ, когда его нет) | `200` + `action:"compacted"` / `502` |
126
+
127
+ `POST /fleet/v1/send` — доставить сообщение тем же маршрутом, что инструмент `send_to_peer` (wake-on-miss, очереди, delivery-лог — семантика идентична):
128
+
129
+ ```json
130
+ { "personality": "boris", "message": "…", "runtime": "claude", "topic": "…", "from": "nova" }
131
+ ```
132
+
133
+ `from` опционален: полный `<runtime>-<personality>` или голая personality (её default-рантайм). По умолчанию — **единственный natural-intelligence пир** реестра: сообщение из GUI принадлежит человеку. При нуле или нескольких natural-пирах запрос отклоняется (`400`) с указанием передать `from`.
134
+
135
+ Не входит в API, намеренно: **attach** (передача терминала — клиент открывает системный терминал с `iapeer attach <peer>`), реестровые/хостовые **admin-verb'ы** (`create`/`remove`/`update`/`uninstall` — добавятся, когда появится клиент с реальной потребностью), **approve** (брокера подтверждений ещё не существует; он придёт новыми эндпоинтами + видами `ev` в рамках этого же контракта).
136
+
137
+ Ошибки — всегда `{"error": "…"}` с честным статусом: `400` некорректный запрос, `401` требуется bearer, `404` неизвестный пир/эндпоинт, `405` не тот метод, `502` сам verb упал, `500` внутренняя.
138
+
139
+ ## Совместимость
140
+
141
+ - `/fleet/v1` развивается **аддитивно**; `api: 1` в каждом снапшоте. Ломающие изменения означают `/fleet/v2`, поднятый рядом.
142
+ - Маркер capability в `router.json` (`fleet: 1`) — способ feature-detect: его отсутствие клиент обязан трактовать как «fleet API нет», а не прощупывать эндпоинты.
package/docs/ru/README.md CHANGED
@@ -76,6 +76,7 @@ iapeer send assistant --message "привет, ты на связи?"
76
76
  - [12 — CLI](12-cli-reference.md) — все команды с флагами
77
77
  - [13 — Архитектура и контракт](13-архитектура-и-контракт.md) — для разработчиков плагинов и рантайм-пакетов
78
78
  - [14 — Конфигурация и журналы](14-конфигурация-и-журналы.md) — переменные окружения, журналы, диагностика
79
+ - [15 — Fleet API](15-fleet-api.md) — HTTP-поверхность демона для операторских клиентов (дашборды, tray-приложения, скрипты): снапшот, поток событий, команды
79
80
 
80
81
  ## Статус
81
82
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agfpd/iapeer",
3
- "version": "0.4.61",
3
+ "version": "0.4.63",
4
4
  "description": "Run Claude Code and Codex CLI as local background agents that message each other, wake on demand, and share one memory.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "agfpd",