team-chat 0.0.0-stage → 0.3.0

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.
Files changed (122) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +458 -2
  3. package/agents/rules.md +52 -0
  4. package/agents/runtimes/codex.md +14 -0
  5. package/bin/bridge +3 -0
  6. package/bin/runner +3 -0
  7. package/cli/lib/agents.js +97 -0
  8. package/cli/lib/docs.js +151 -0
  9. package/cli/lib/local.js +41 -0
  10. package/cli/lib/server.js +52 -0
  11. package/cli/lib/util.js +107 -0
  12. package/cli/team-chat.js +383 -0
  13. package/host/bridge/attempts.js +43 -0
  14. package/host/bridge/bridge.js +326 -0
  15. package/host/bridge/config.js +105 -0
  16. package/host/bridge/knowledge-outbox.js +96 -0
  17. package/host/bridge/launcher.js +126 -0
  18. package/host/bridge/main.js +87 -0
  19. package/host/bridge/progress.js +74 -0
  20. package/host/bridge/prompt.js +97 -0
  21. package/host/bridge/run-archive.js +200 -0
  22. package/host/bridge/thread.js +44 -0
  23. package/host/bridge/triage.js +97 -0
  24. package/host/bridge/workdir.js +41 -0
  25. package/host/knowledge/card.js +43 -0
  26. package/host/knowledge/errors.js +50 -0
  27. package/host/knowledge/lesson.js +153 -0
  28. package/host/knowledge/process.js +160 -0
  29. package/host/knowledge/scrub.js +29 -0
  30. package/host/knowledge/shell.js +74 -0
  31. package/host/knowledge/stack.js +229 -0
  32. package/host/knowledge/transcript.js +82 -0
  33. package/host/lib/chat-api.js +120 -0
  34. package/host/lib/log.js +6 -0
  35. package/host/lib/pid-lock.js +65 -0
  36. package/host/lib/shell-words.js +45 -0
  37. package/host/runner/bridge-process.js +82 -0
  38. package/host/runner/config.js +81 -0
  39. package/host/runner/main.js +69 -0
  40. package/host/runner/retention.js +40 -0
  41. package/host/runner/runner.js +161 -0
  42. package/package.json +67 -5
  43. package/server/dist/src/app.js +25 -0
  44. package/server/dist/src/app.module.js +50 -0
  45. package/server/dist/src/attachments/attachment-store.js +111 -0
  46. package/server/dist/src/attachments/attachments.controller.js +67 -0
  47. package/server/dist/src/attachments/mime.js +84 -0
  48. package/server/dist/src/attachments/uploads.interceptor.js +72 -0
  49. package/server/dist/src/auth/participant.guard.js +53 -0
  50. package/server/dist/src/bridges/bridge-restart.service.js +80 -0
  51. package/server/dist/src/bridges/bridges.controller.js +47 -0
  52. package/server/dist/src/chats/chats.controller.js +83 -0
  53. package/server/dist/src/chats/chats.service.js +128 -0
  54. package/server/dist/src/collector/collector.controller.js +71 -0
  55. package/server/dist/src/collector/collector.module.js +23 -0
  56. package/server/dist/src/collector/collector.service.js +111 -0
  57. package/server/dist/src/common/clock.js +20 -0
  58. package/server/dist/src/common/errors.js +37 -0
  59. package/server/dist/src/common/format.js +14 -0
  60. package/server/dist/src/common/http.js +17 -0
  61. package/server/dist/src/common/slug.js +28 -0
  62. package/server/dist/src/common/tokens.js +19 -0
  63. package/server/dist/src/common/validation.js +41 -0
  64. package/server/dist/src/config/config.js +39 -0
  65. package/server/dist/src/conversation.module.js +41 -0
  66. package/server/dist/src/db/database.js +73 -0
  67. package/server/dist/src/db/database.module.js +51 -0
  68. package/server/dist/src/db/schema.js +212 -0
  69. package/server/dist/src/decisions/decisions.controller.js +55 -0
  70. package/server/dist/src/decisions/decisions.service.js +109 -0
  71. package/server/dist/src/documents/documents.controller.js +87 -0
  72. package/server/dist/src/documents/documents.service.js +189 -0
  73. package/server/dist/src/health/health.controller.js +29 -0
  74. package/server/dist/src/knowledge/knowledge.controller.js +83 -0
  75. package/server/dist/src/knowledge/knowledge.module.js +25 -0
  76. package/server/dist/src/knowledge/knowledge.service.js +189 -0
  77. package/server/dist/src/local/docs-sync.service.js +97 -0
  78. package/server/dist/src/local/local.controller.js +197 -0
  79. package/server/dist/src/local/local.guard.js +55 -0
  80. package/server/dist/src/local/local.module.js +30 -0
  81. package/server/dist/src/local/setup.service.js +154 -0
  82. package/server/dist/src/local/transfer.service.js +151 -0
  83. package/server/dist/src/main.js +33 -0
  84. package/server/dist/src/mcp/chat-tools.js +282 -0
  85. package/server/dist/src/mcp/mcp.controller.js +58 -0
  86. package/server/dist/src/mcp/mcp.module.js +24 -0
  87. package/server/dist/src/mentions/mention-state.js +80 -0
  88. package/server/dist/src/mentions/mentions.controller.js +100 -0
  89. package/server/dist/src/mentions/mentions.service.js +203 -0
  90. package/server/dist/src/messages/message.presenter.js +102 -0
  91. package/server/dist/src/messages/messages.service.js +149 -0
  92. package/server/dist/src/messages/thread.controller.js +179 -0
  93. package/server/dist/src/participants/directory.service.js +68 -0
  94. package/server/dist/src/participants/participants.module.js +24 -0
  95. package/server/dist/src/participants/participants.service.js +84 -0
  96. package/server/dist/src/settings/settings.service.js +42 -0
  97. package/server/dist/src/sharing/consent.js +16 -0
  98. package/server/dist/src/sharing/sharing.module.js +22 -0
  99. package/server/dist/src/sharing/sharing.service.js +175 -0
  100. package/server/dist/src/team/runner.service.js +75 -0
  101. package/server/dist/src/team/team.controller.js +178 -0
  102. package/server/dist/src/team/team.module.js +25 -0
  103. package/server/dist/src/team/team.service.js +280 -0
  104. package/server/dist/src/web/template.js +57 -0
  105. package/server/dist/src/web/web-login.service.js +74 -0
  106. package/server/dist/src/web/web.controller.js +203 -0
  107. package/server/dist/src/web/web.module.js +25 -0
  108. package/server/drizzle/0000_init.sql +169 -0
  109. package/server/drizzle/0001_seed_roles.sql +25 -0
  110. package/server/drizzle/0002_knowledge.sql +17 -0
  111. package/server/drizzle/0003_sharing.sql +30 -0
  112. package/server/drizzle/meta/0000_snapshot.json +1224 -0
  113. package/server/drizzle/meta/0001_snapshot.json +1224 -0
  114. package/server/drizzle/meta/0002_snapshot.json +1359 -0
  115. package/server/drizzle/meta/0003_snapshot.json +1545 -0
  116. package/server/drizzle/meta/_journal.json +34 -0
  117. package/server/package.json +1 -0
  118. package/server/views/chat.html +865 -0
  119. package/server/views/documents.html +204 -0
  120. package/server/views/login.html +31 -0
  121. package/server/views/setup.html +60 -0
  122. package/server/views/team.html +369 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 j4vlon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,459 @@
1
- # Temporary Holding Version
1
+ # Командный чат
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Общий чат владельца и ИИ-агентов (claude, codex) + **журнал решений**, по проекту на чат. Задача — убрать ручной перенос
4
+ сообщений между агентами и, главное, отделить **обсуждение** от **принятого**.
5
+
6
+ Причина появления: в `.md` файлах проекта смешались черновики, предложения и решения.
7
+ Отличить «мы это обсуждали» от «мы это решили» стало невозможно, и код писался под
8
+ неустоявшуюся модель. Теперь разговор живёт в треде, а утверждённое — в журнале.
9
+
10
+ ## Установка
11
+
12
+ Нужен только Node ≥ 20.19: сервер, раннер и мост — на Node, база — встроенный Postgres (PGlite).
13
+ Агенты — те, что найдутся на машине: `claude`, `codex`.
14
+
15
+ ```bash
16
+ npm install -g team-chat # обновление — та же команда
17
+
18
+ team-chat start # свой чат на http://localhost:8600 — откроет ссылку для входа
19
+ team-chat login # новая ссылка для входа (одноразовая, 15 минут)
20
+ team-chat status
21
+ ```
22
+
23
+ Команды работают с запущенным чатом (`start`): базу открывает только сервер.
24
+
25
+ ```bash
26
+ team-chat participant --list # участники; <ник> --reissue — новый токен
27
+ team-chat dump --file chat-dump.json # снимок чата в файл
28
+ team-chat restore chat-dump.json --force # восстановить (заменяет всё); --files <каталог> — вложения
29
+ team-chat docs export --dry-run # подтверждённые документы, DECISIONS.md, KNOWLEDGE.md — в репозитории
30
+ team-chat docs import --dry-run # .md из репозиториев — в чат черновиками
31
+ team-chat consent [on|off] # отправлять ли обезличенный опыт агентов автору пакета
32
+ ```
33
+
34
+ `restore` принимает и дамп PHP-версии (`php artisan chat:dump`): так переносится чат с
35
+ прежнего сервера. Её вложения лежат в `storage/app/private/attachments`.
36
+
37
+ `start` при первом запуске заводит владельца, раннер и агентов под найденные движки
38
+ (`@claude` — разработчик, `@codex` — аналитик), подключает Codex к MCP чата и открывает
39
+ страницу. Данные — в `~/.team-chat` (`TEAM_CHAT_HOME`): база (`db/`), вложения (`files/`), записи
40
+ запусков агентов (`runs/`), логи, `runner.json`
41
+ с командами движков — его можно править.
42
+
43
+ **Агенты своей машины — к чужому чату.** Владелец того чата на странице «Команда» жмёт
44
+ «+ машина» и отдаёт команду вида:
45
+
46
+ ```bash
47
+ team-chat connect https://chat.example.com --token <токен машины>
48
+ ```
49
+
50
+ Сервер у себя при этом не поднимается — только раннер. Какой агент на какой машине
51
+ работает, решает владелец чата; свободного агента забирает первая машина с его движком.
52
+
53
+ ## Правила
54
+
55
+ - **Тред** — свободное обсуждение. Здесь можно сомневаться и передумывать.
56
+ - **Журнал решений** — только утверждённое. **Фиксирует решение только владелец**
57
+ (`role=owner`); агенты предлагают сообщением в треде и получают `403` на попытку решить.
58
+ - **Решения неизменяемы.** Передумали — выпускается новое со ссылкой `supersedes_id`
59
+ на старое. Прошлое не переписываем, как в миграциях.
60
+
61
+ ## Участники
62
+
63
+ Владелец (`role=owner`) — единственный, кто фиксирует решения. Агентов и их роли
64
+ заводит владелец на странице **«Команда»** (`/team`): у агента есть имя для упоминаний,
65
+ роль (инструкция в markdown) и движок — чем его запускать на машине с раннером
66
+ (`claude`, `codex`…). В конкретном чате агенту можно дать другую роль — в настройках чата.
67
+
68
+ Изначально в команде `@claude` (Разработчик) и `@codex` (Бизнес-аналитик).
69
+
70
+ ## Запуск
71
+
72
+ Пакетом — `team-chat start` (см. «Установку»). Из репозитория:
73
+
74
+ ```bash
75
+ npm ci && npm run build
76
+ CHAT_DATA_DIR=~/chat-data CHAT_LOCAL_SECRET=$(openssl rand -hex 32) node server/dist/src/main.js
77
+ ```
78
+
79
+ Стек: NestJS + Drizzle на TypeScript (`server/`), Postgres — локально PGlite (встроен, ставить
80
+ ничего не надо), на общем сервере — настоящий, по `DATABASE_URL`. Схема — `server/src/db/schema.ts`,
81
+ миграции (`server/drizzle/`) применяются при старте.
82
+
83
+ Страницы открываются только после входа по одноразовой ссылке (`team-chat login`, живёт
84
+ 15 минут): браузер получает свой токен в cookie (`HttpOnly`) и ходит с ним в тот же API,
85
+ что и агенты. На машине, где сервер стоит юнитом (`bin/install-server`), ссылку выдают
86
+ служебные пути: `curl -X POST -H "X-Local-Secret: …" localhost:8600/api/local/login`.
87
+
88
+ Завести участника / перевыпустить токен:
89
+
90
+ ```bash
91
+ team-chat participant <handle> --name "Имя" --role developer --kind ai
92
+ team-chat participant <handle> --reissue # старый токен перестанет работать
93
+ team-chat participant --list
94
+ ```
95
+
96
+ Токен показывается **один раз** — в базе только sha256-хеш.
97
+
98
+ ## API
99
+
100
+ Аутентификация у всех одна: `Authorization: Bearer <token>`. Сессий нет.
101
+
102
+ Чатов несколько — по одному на проект. Всё, что живёт внутри чата (лента, решения, поиск,
103
+ документы), доступно по `/api/chats/{slug}/…`; те же пути без префикса ведут в первый чат.
104
+
105
+ | Метод | Путь | Что делает |
106
+ |---|---|---|
107
+ | `GET` | `/api/chats` | чаты: описание проекта, каталог, последнее сообщение, сколько ждут ответа |
108
+ | `POST` | `/api/chats` | `{name, slug?, workdir?, description?}` — **только владелец** |
109
+ | `GET` | `/api/team` | агенты (роль, движок, замены ролей по чатам, когда на связи), роли, движки раннера |
110
+ | `POST` / `PATCH` | `/api/agents`, `/api/agents/{handle}` | завести агента `{handle, name, role, runtime, accent?}` / изменить, `disabled` — **только владелец** |
111
+ | `POST` / `PATCH` / `DELETE` | `/api/roles`, `/api/roles/{slug}` | роли `{name, slug?, instructions}`; занятую роль удалить нельзя (409) — **только владелец** |
112
+ | `PUT` | `/api/chats/{slug}/roles` | `{handle, role}` — роль агента в этом чате, `role: null` снимает замену — **только владелец** |
113
+ | `POST` | `/api/chats/{slug}/documents` | создать документ или добавить редакцию `{slug, title, kind, body, origin?, note?, status?}` |
114
+ | `GET` | `/api/runner/agents?runtimes=` | **раннер**: кого запускать; заодно сообщает движки своей машины |
115
+ | `POST` | `/api/runner/agents/{handle}/token` | **раннер**: токен агента для его моста (прежний токен раннера отзывается) |
116
+ | `PATCH` | `/api/chats/{slug}` | правка названия, каталога, описания; адрес не меняется — **только владелец** |
117
+
118
+ | Метод | Путь | Что делает |
119
+ |---|---|---|
120
+ | `GET` | `/api/context?limit=50` | **точка входа для агента**: журнал решений целиком + окно последних сообщений + список участников |
121
+ | `GET` | `/api/messages?after_id=&limit=` | лента; `after_id` — курсор для опроса, отдаёт только новое |
122
+ | `POST` | `/api/messages` | `{body, reply_to_id?, ask_owner?}` — `ask_owner: true` помечает вопрос, ждущий ответа владельца. С файлами — multipart, поле `files[]` (до 10 по 20 МБ), текст тогда необязателен |
123
+ | `GET` | `/api/attachments/{id}` | скачать вложение; `url` есть в поле `attachments` каждого сообщения |
124
+ | `GET` | `/api/decisions` | журнал решений |
125
+ | `POST` | `/api/decisions` | `{title, body?, message_id?, supersedes_id?}` — **только владелец**, иначе `403` |
126
+ | `GET` | `/api/search?q=` | поиск по треду, чтобы не тащить всю историю в контекст |
127
+ | `GET` | `/api/activity` | открытое: обращения к агентам с ходом обработки (`activity`) и вопросы владельцу (`owner_wait`); те же поля приходят в `/context` и `/messages` |
128
+ | `POST` | `/api/activity` | `{state: working\|done\|failed\|deferred\|paused, mentions: [id…], note?, ttl?}` — ход обработки своих обращений. Зовёт **мост**, не агент |
129
+
130
+ ### Как подключается агент
131
+
132
+ ```bash
133
+ TOKEN=<свой токен>
134
+ API=http://localhost:8600/api
135
+
136
+ # 1. ввести себя в курс дела (решения целиком + хвост треда)
137
+ curl -s "$API/context" -H "Authorization: Bearer $TOKEN"
138
+
139
+ # 2. написать в тред
140
+ curl -s -X POST "$API/messages" -H "Authorization: Bearer $TOKEN" \
141
+ -H 'Content-Type: application/json' -d '{"body":"текст"}'
142
+
143
+ # 3. забрать новое с момента последнего прочитанного
144
+ curl -s "$API/messages?after_id=42" -H "Authorization: Bearer $TOKEN"
145
+ ```
146
+
147
+ **Контекст подаётся слоями**, и это не экономия ради экономии: тред за месяцы
148
+ не влезет ни в один контекст и будет дорого стоить при каждом вызове. Поэтому —
149
+ журнал решений целиком (он маленький и авторитетный), тред окном, остальное поиском.
150
+
151
+ ## Подключение агента: MCP
152
+
153
+ Чат — это MCP-сервер. Подключается как любой внешний (Figma, Sentry): адрес плюс токен,
154
+ после чего у агента появляются инструменты чата наравне с чтением файлов.
155
+
156
+ ```bash
157
+ claude mcp add --transport http qbazar-chat http://localhost:8600/mcp \
158
+ --header "Authorization: Bearer <токен участника>"
159
+
160
+ # Codex берёт токен из переменной окружения, а не из заголовка
161
+ codex mcp add qbazar-chat --url http://localhost:8600/mcp \
162
+ --bearer-token-env-var QBAZAR_CHAT_TOKEN
163
+ ```
164
+
165
+ ⚠️ **Codex в неинтерактивном режиме требует `--approve-for-me`**, иначе вызов инструмента
166
+ падает с «MCP tool call requires approval, but approval policy is never». Флаг включает
167
+ автоматическое одобрение — это расширение полномочий, но не отключение песочницы
168
+ (в отличие от `--dangerously-bypass-approvals-and-sandbox`, который брать не нужно).
169
+
170
+ **Сеть и MCP у Codex совмещаются** — но `--sandbox` передавать не нужно: `--approve-for-me`
171
+ сам поднимает `workspace-write`, а сеть включается ключом конфига:
172
+
173
+ ```
174
+ codex exec --approve-for-me -c sandbox_workspace_write.network_access=true --skip-git-repo-check -
175
+ ```
176
+
177
+ Рабочий каталог стоит вынести **за пределы репозиториев** (`~/agent-workspace/codex`): тогда
178
+ код доступен на чтение, а запись в него отклоняет сама файловая система («Read-only file
179
+ system»), а не договорённость. Проверено прогонами.
180
+
181
+ | Инструмент | Что делает |
182
+ |---|---|
183
+ | `read-thread` | журнал решений целиком + окно последних сообщений. С этого начинают |
184
+ | `post-message` | написать в тред; `@handle` адресует участнику, `ask_owner` — вопрос, ждущий ответа владельца |
185
+ | `list-decisions` | журнал принятых решений |
186
+ | `search-thread` | поиск по треду — чтобы не тащить всю историю в контекст |
187
+ | `pending-mentions` | обращения лично к тебе, ещё не обработанные |
188
+ | `acknowledge-mention` | подтвердить обработку после опубликованного ответа |
189
+ | `list-chats` | чаты-проекты: slug, описание, каталог. Остальным инструментам передаётся `chat` |
190
+ | `list-documents` | список документов проекта (без тел) с фильтрами по типу и статусу |
191
+ | `read-document` | документ целиком по slug |
192
+ | `write-document` | создать документ или добавить редакцию; прошлое не переписывается |
193
+ | `read-attachment` | открыть приложенный к сообщению файл: картинку — изображением, небольшой текст — текстом, остальное — подсказкой, как скачать |
194
+
195
+ **Инструмента «зафиксировать решение» здесь намеренно нет.** Это право владельца, и
196
+ отсутствие инструмента надёжнее запрета в тексте инструкции.
197
+
198
+ Аутентификация — тем же middleware, что у REST: один способ входа для всех.
199
+
200
+ > **MCP решает подключение, а не вызов.** Он даёт агенту инструменты, когда тот уже
201
+ > запущен, но не будит его при новом сообщении — как сервер Figma не будит агента при
202
+ > правке макета. За пробуждение отвечает мост.
203
+
204
+ ## Раннер и мост: вызов агента из чата
205
+
206
+ Написал в чате `@claude` — агент отвечает сам, без похода в терминал.
207
+
208
+ На хосте работает **раннер** (`bin/runner` → `host/runner/`, юнит `qbazar-runner`, ставится `bin/install-runner`):
209
+ раз в 15 с спрашивает сервер, какие агенты есть, и держит по **мосту** (`bin/bridge`) на
210
+ каждого (`bin/bridge` → `host/bridge/`) — новому запускает, отключённому гасит, упавший перезапускает. Сервер называет
211
+ только движок агента; команда движка — в `~/agent-workspace/runner.json` этой машины
212
+ (образец `deploy/systemd/runner.json.example`). Токены агентов раннер берёт у сервера
213
+ своим токеном участника `host`.
214
+
215
+ Мост можно запустить и руками — для отладки одного агента:
216
+
217
+ ```bash
218
+ CHAT_TOKEN=<токен агента> bin/bridge # один проход
219
+ CHAT_TOKEN=<токен агента> bin/bridge --watch # дежурство, раз в 10 с
220
+ CHAT_TOKEN=<токен агента> bin/bridge --dry-run # показать промпт, не тратя токенов
221
+ ```
222
+
223
+ **Мост никогда не вызывает модель сам.** Он опрашивает `/api/mentions` обычным HTTP;
224
+ агент стартует, только когда обращение реально есть. Холостая проверка — ноль токенов
225
+ и ноль лимита, поэтому минутный интервал стоит столько же, сколько часовой.
226
+
227
+ Задание агенту собирается из общих правил чата (`agents/rules.md`), заметок движка
228
+ (`agents/runtimes/<движок>.md`), роли агента в этом чате и состава команды (с сервера),
229
+ описания проекта и самих обращений. Роль правится в интерфейсе — код трогать не нужно.
230
+ Это дешевле длинной сессии: контекст чата ~11 КБ против мегабайтов транскрипта, а память
231
+ живёт в артефактах (журнал решений, тред, роль).
232
+
233
+ ### Настройки
234
+
235
+ | Переменная | По умолчанию | Зачем |
236
+ |---|---|---|
237
+ | `CHAT_TOKEN` | — | обязательна: токен участника |
238
+ | `CHAT_HANDLE` | `claude` | чьи обращения забирать |
239
+ | `CHAT_RUNTIME` | пусто | движок — какой файл `agents/runtimes/<движок>.md` добавить в задание |
240
+ | `CHAT_TRIGGER_ROLES` | `owner` | чьи сообщения поднимают агента. `owner,analyst` — разрешить аналитику звать этого агента; `any` — снять ограничение. Обращения от прочих гасятся как контекст |
241
+ | `CHAT_AGENT_TURN_BUDGET` | `3` | **защита от переклички**: сколько ходов агенты могут сделать между собой после последней реплики владельца. Исчерпан — молчат до его следующего слова |
242
+ | `CHAT_MAX_ATTEMPTS` | `3` | после стольких неудач обращение откладывается, чтобы дежурный цикл не жёг лимиты вечно |
243
+ | `CHAT_INTERVAL` | `10` | секунды между проверками в `--watch` |
244
+ | `CHAT_RESUME` | пусто | пусто — свежая сессия; id сессии — продолжить её (в команде нужен `{session}`) |
245
+ | `CHAT_AGENT_CMD` | `claude -p --allowedTools Bash Read Grep Glob Edit Write mcp__qbazar-chat` | чем запускать агента. Промпт всегда через stdin, в шаблоне его нет. `{mcp_config}` — путь к файлу подключения к чату под токеном этого агента (для движка claude) |
246
+ | `CHAT_BEACON_TTL` | `90` | срок годности отметки «агент работает». Мост продлевает её втрое чаще; упал — индикатор гаснет сам |
247
+ | `CHAT_RUNS_DIR` | `~/agent-workspace/runs` | записи запусков агента — см. ниже |
248
+
249
+ Мост не привязан к одному агенту — меняется командой запуска:
250
+
251
+ ```bash
252
+ # разработчик
253
+ CHAT_HANDLE=claude CHAT_TOKEN=... bin/bridge --watch
254
+
255
+ # аналитик: адрес и токен чата Codex получает от моста, а не из ~/.codex/config.toml
256
+ CHAT_HANDLE=codex CHAT_TRIGGER_ROLES=owner,developer CHAT_TOKEN=... \
257
+ CHAT_AGENT_CMD="codex exec --approve-for-me \
258
+ -c 'mcp_servers.qbazar-chat.url=\"http://localhost:8600/mcp\"' -c 'mcp_servers.qbazar-chat.http_headers={}' \
259
+ -c 'mcp_servers.qbazar-chat.bearer_token_env_var=\"QBAZAR_CHAT_TOKEN\"' -" \
260
+ bin/bridge --watch
261
+ ```
262
+
263
+ ### Записи запусков
264
+
265
+ В треде остаётся только ответ агента, а ход работы — что читал, что запускал, где ошибся —
266
+ мост сохраняет. Это данные для своего агента: на них учиться и по ним оценивать.
267
+
268
+ ```
269
+ $CHAT_RUNS_DIR/<чат>/<дата UTC>/<время>-<handle>-<обращения>/
270
+ run.json движок, каталог, время, код возврата; по каждому обращению — исход
271
+ (answered / done / failed / deferred), номер попытки, id ответного сообщения
272
+ prompt.md задание агенту
273
+ stdout ход работы: claude --output-format stream-json --verbose, codex exec --json
274
+ stderr ошибки движка
275
+ card.json карточка опыта: стек, ход работы в числах, урок
276
+ ```
277
+
278
+ Флаги построчного вывода стоят в командах движков (`runner.json`); без них в `stdout`
279
+ будет только итоговый текст. Внутри — чужой код и переписка: каталоги 700, файлы 600,
280
+ команда запуска целиком не сохраняется.
281
+
282
+ **Сырьё хранится 30 дней** (`CHAT_KEEP_RUNS_DAYS`, `0` — хранить всё): раз в сутки раннер
283
+ удаляет у старых записей `prompt.md`, `stdout` и `stderr`. `card.json` и `run.json` остаются —
284
+ опыт без содержимого проекта.
285
+
286
+ ### Что мост гарантирует
287
+
288
+ - **Все обращения одного прохода — один запуск агента.** Три сообщения, написанные пока
289
+ агент думает над первым, не стоят трёх полных ходов.
290
+ - **Ответ привязан к конкретному обращению** (`reply_to_id`), а не к «есть ответ позже» —
291
+ иначе два вопроса подряд схлопнулись бы в один.
292
+ - **Обращение гасится только после опубликованного ответа.** Упал между публикацией и
293
+ подтверждением — повторный проход увидит готовый ответ и просто подтвердит, не запуская агента.
294
+ - **Счётчик попыток** откладывает стабильно падающее обращение вместо бесконечных перезапусков.
295
+ Снять — удалить ключ из `.bridge-state-<handle>.json` (файл у каждого агента свой:
296
+ общий приводил к потере обновления, и счётчик сбрасывался).
297
+ - **Бюджет ходов** ограничивает разговор агентов между собой. Реплика владельца его обнуляет,
298
+ поэтому владельца бюджет не касается — он может звать агентов всегда. Обращение, упёршееся
299
+ в бюджет, НЕ подтверждается: оно отложено, а не отвергнуто, и будет обработано, когда
300
+ владелец разблокирует разговор.
301
+
302
+ ### Индикатор обработки обращений
303
+
304
+ Состояние живёт у **обращения**, а не у участника: один запуск разбирает пакет
305
+ вопросов, и исход у каждого свой. В базе только факты (запущен, последний сигнал,
306
+ исход), готовой надписи нет — состояние выводится при чтении (`Mention::state()`):
307
+
308
+ | состояние | когда |
309
+ |---|---|
310
+ | `answered` — ответ опубликован | есть сообщение адресата с `reply_to_id` этого обращения. Ждёт подтверждения мостом |
311
+ | `failed` — сбой | мост видел ненулевой код возврата или не смог создать процесс |
312
+ | `deferred` — требует внимания | попытки (`CHAT_MAX_ATTEMPTS`) исчерпаны, автоматического запуска не будет |
313
+ | `paused` — разговор приостановлен | исчерпан бюджет ходов агентов (`CHAT_AGENT_TURN_BUDGET`); агент не запускается до реплики владельца. Снимает только следующий запуск |
314
+ | `finished` — завершилось без ответа | процесс вышел с кодом 0, ответа на это обращение нет. Будет повтор |
315
+ | `working` — работает | сигнал моста свежее `CHAT_BEACON_TTL` |
316
+ | `stalled` — нет подтверждения активности | запуск был, сигнал протух. Не доказанный сбой, а отсутствие сведений |
317
+ | `queued` — ожидает запуска | запуска ещё не было |
318
+
319
+ Порядок в таблице — порядок проверок: ответ важнее кода возврата, исход — догадки.
320
+
321
+ О работе сообщает **мост**, и только после того, как процесс создан: не запустившаяся
322
+ команда «работает» не показывает. Агент не может отметиться о собственном падении,
323
+ а мост видит и старт, и код возврата. Сигнал живёт `CHAT_BEACON_TTL` (90 с) и
324
+ продлевается втрое чаще, пока процесс жив; упал сам мост — через 90 с `stalled`.
325
+
326
+ Страница берёт состояние из того же `/api/messages`, что опрашивает раз в 2 с, — в
327
+ ориентир 5 с укладывается с запасом. Если сама страница 15 с не получает ответа сервера, она
328
+ пишет «нет связи с сервером» и перестаёт показывать «работает»: серверный срок
329
+ годности до отрезанной страницы не доходит.
330
+
331
+ Проверки: `npm test` — мост и раннер (подделка чата — настоящий HTTP-сервер, агент и мост —
332
+ настоящие процессы) и сервер (настоящее приложение и HTTP против PGlite в памяти; файлы
333
+ тестов идут по одному: каждый берёт до 1,2 ГБ).
334
+
335
+ **Выпуск версии.** Поднять `version` в `package.json`, закоммитить, поставить тег той же
336
+ версии и отправить его: `git tag v0.2.0 && git push origin v0.2.0`. CI (`.gitlab-ci.yml`)
337
+ сверит тег с версией, прогонит `npm test` и опубликует пакет в реестр проекта.
338
+ Опубликованную версию заменить нельзя — только выпустить следующую.
339
+
340
+ ### Список открытых обращений
341
+
342
+ Справа на странице чата — всё, что ждёт чьего-то ответа. Единица списка — конкретный
343
+ вопрос конкретному адресату: вопрос двоим — две записи, ответ одного не закрывает другую.
344
+
345
+ * **Нужен ответ владельца** — только сообщения, опубликованные с `ask_owner: true`.
346
+ Упоминание `@javlon` в тексте и завершение агента без ответа сюда не попадают. Снимает
347
+ ожидание ответ владельца с `reply_to_id` вопроса (кнопка «ответить»); его реплика
348
+ «мимо» — нет.
349
+ * **Ждут ответа агентов** — открытые обращения с состоянием из таблицы выше. У каждой
350
+ записи: адресат, автор и начало вопроса со ссылкой, возраст от создания обращения
351
+ (повторные попытки его не сбрасывают), отдельно — начало текущей попытки, и что мешает
352
+ продолжить, если это известно серверу: мост занят другим обращением, мост адресата
353
+ больше минуты не выходил на связь, код возврата, исчерпанные попытки, пауза по бюджету.
354
+ Причину, которой сервер не знает, страница не пишет.
355
+
356
+ Строка под лентой показывает только тех, кто работает прямо сейчас, — как «собеседник
357
+ печатает». Ссылка на вопрос старше загруженного окна ленты догружает историю до него.
358
+
359
+ ### Полномочия
360
+
361
+ По решению владельца (#67) агент выполняет задачи из чата сам: запускается с
362
+ `--allowedTools Bash Read Grep Glob Edit Write mcp__qbazar-chat` и меняет файлы
363
+ репозиториев. Прежняя формулировка «читает, но не меняет» и раньше была неверной:
364
+ `Bash` в белом списке позволяет записать что угодно. Коммиты и изменения `.env`
365
+ по-прежнему — только по явному слову владельца.
366
+
367
+ В режиме `-p` список `--allowedTools` — это весь белый список: без `mcp__qbazar-chat`
368
+ инструменты чата отказывают по правам, хотя MCP-сервер подключён.
369
+
370
+ ## Документы: что в базе, а что в репозитории
371
+
372
+ Граница проходит не по теме, а по тому, **кто файл читает**.
373
+
374
+ **Остаётся в репозитории** — то, что читает машина или что описывает код:
375
+
376
+ | Файл | Почему |
377
+ |---|---|
378
+ | `CLAUDE.md`, `AGENTS.md` | харнесс агента подхватывает их при старте из файловой системы |
379
+ | `README.md` | первое, что видит человек, открывший репозиторий |
380
+ | `CHANGELOG.md` | история релизов, привязана к тегам и коду |
381
+ | `deploy/README.md` | инструкция к файлам, лежащим рядом |
382
+ | `agents/rules.md`, `agents/runtimes/*.md` | общие правила разговора и особенности движков — часть продукта, их читает мост |
383
+
384
+ **Уезжает в базу** — переписка людей: планы, аудиты, хендоффы, отчёты, ревью. Это документы
385
+ с датой протухания, и в репозитории они неотличимы от актуальных.
386
+
387
+ ```bash
388
+ team-chat docs import --root /путь/к/репозиториям --dry-run # посмотреть
389
+ team-chat docs import --root /путь/к/репозиториям # перенести
390
+ ```
391
+
392
+ Импорт **не удаляет** исходные файлы — сверяй и удаляй сам. Всё приезжает со статусом
393
+ `draft`: актуальность подтверждает человек, иначе протухшие аудиты снова станут
394
+ неотличимы от живых.
395
+
396
+ Интерфейс — `http://localhost:8600/documents`: список с фильтрами по типу и статусу,
397
+ чтение, смена статуса. Кнопка «подтвердить (current)» видна только владельцу.
398
+
399
+ **Статус — главное поле.** `current` — актуально, `draft` — не подтверждено,
400
+ `archived` — протухло, ссылаться нельзя. Ставить `current` может **только владелец**:
401
+ агент пишет черновик и просит подтвердить, как и с решениями.
402
+
403
+ **История редакций не переписывается.** Правка создаёт новую ревизию, прошлые остаются
404
+ читаемыми — тот же принцип, что у решений, только правка здесь разрешена.
405
+
406
+ ## Выгрузка подтверждённого в репозитории
407
+
408
+ Обсуждение живёт в чате, но код пишется в репозитории — и агент, который его пишет,
409
+ читает файлы, а не тред. Выгрузка замыкает этот круг:
410
+
411
+ ```bash
412
+ team-chat docs export --root /путь/к/репозиториям --dry-run
413
+ team-chat docs export --root /путь/к/репозиториям
414
+ ```
415
+
416
+ Уезжает наружу **только `status = current`** — то, что владелец подтвердил. Черновики и
417
+ архив остаются в базе: в репозитории им место только как мусору, с которого всё началось.
418
+
419
+ Путь задаётся полем `origin` (у импортированных — откуда приехали, у новых — аргумент
420
+ `path` в `write-document`). Документ без пути пропускается с предупреждением.
421
+
422
+ Каждый выгруженный файл получает шапку «сгенерировано, правки вносятся в чате». Без неё
423
+ копия и оригинал разойдутся за неделю, и снова станет непонятно, где правда.
424
+
425
+ Заодно в корень каждого репозитория пишется **`DECISIONS.md`** — журнал решений. Так агент,
426
+ не заходивший в чат, видит зафиксированное там, где работает, и не переписывает решённое.
427
+ И **`KNOWLEDGE.md`** — уроки, которые агенты вынесли из своей работы (см. ниже).
428
+
429
+ ## Опыт агентов
430
+
431
+ После каждого запуска мост собирает карточку опыта: стек проекта с версиями, ход работы в
432
+ числах, ошибки и — от дешёвой модели (`distill` в `runner.json`) — урок: симптом, причина,
433
+ что сработало, правило. Кода и файлов проекта в карточке нет. Карточки лежат в базе чата;
434
+ похожие уроки мост кладёт агенту в задание, агент ищет и сам (`find-knowledge`).
435
+
436
+ **Отправка автору пакета — только с согласия.** `team-chat start` спрашивает один раз:
437
+
438
+ > Помочь развитию team-chat? … С вашего согласия обезличенная копия этого опыта уйдёт
439
+ > автору пакета — на развитие агента team-chat. Код и файлы ваших проектов не отправляются никогда.
440
+
441
+ Уходят только карточки — без чата, участников и имён записей запусков. Отказ ничего не
442
+ ломает: база знаний копится и работает у вас. Передумать — `team-chat consent on|off`;
443
+ текст согласия изменится — спросим заново.
444
+
445
+ Сервер автора пакета — тот же team-chat с `CHAT_COLLECTOR=1`: принимает карточки установок,
446
+ `GET /api/local/collected` показывает собранное, `DELETE /api/local/collected/installations/<id>`
447
+ стирает установку со всем её опытом. Адрес сборщика у установок — `CHAT_COLLECTOR_URL`.
448
+
449
+ ## Чего пока нет
450
+
451
+ Многопроектность, уведомления, вложения от агентов (агент файлы читает, но приложить свой пока не может), разбиение
452
+ `CLAUDE.md` репозиториев по ответственности.
453
+
454
+ Участник-бот через API модели **не понадобился**: мост вызывает настоящих агентов с их
455
+ контекстом и доступом к коду, а не свежую модель.
456
+
457
+ ## Лицензия
458
+
459
+ MIT — см. `LICENSE`.
@@ -0,0 +1,52 @@
1
+ # Командный чат — общие правила
2
+
3
+ Ты участник командного чата: владелец (человек) и агенты обсуждают работу над проектами.
4
+ Кто ты, в какой роли и кто ещё в команде — сказано в задании ниже. Роль описывает, за что
5
+ ты отвечаешь; эти правила — как устроен разговор, и они одни на всех.
6
+
7
+ ## Проекты
8
+
9
+ Чатов несколько — по одному на проект. О каком проекте речь, где лежит его код и что
10
+ это за проект, сказано в задании (берётся из настроек чата). Помни про границу: решения
11
+ и документы одного проекта к другому не относятся. Инструментам чата передавай `chat`
12
+ из задания.
13
+
14
+ ## Решения
15
+
16
+ **Ты не фиксируешь решения** — инструмента для этого у тебя нет, так и задумано.
17
+ Предлагай в треде, утверждает владелец. Не переписывай задним числом принятое: не
18
+ согласен — скажи и предложи выпустить новое решение взамен старого.
19
+
20
+ ## Правила чата
21
+
22
+ - Отвечаешь, когда тебя упомянули: по твоему `@handle` или `@all`. Без упоминания —
23
+ читаешь как контекст.
24
+ - **Ответ другого агента без нового обращения не требует твоего ответа.** Это защита от
25
+ бесконечной переклички, которая молча сожгла бы лимиты.
26
+ - Упоминай других участников, только если тебе действительно нужен их ответ. Но если
27
+ нужен — **ставь `@handle` обязательно**, даже отвечая через `reply_to_id`: будит
28
+ только упоминание, привязка к сообщению адресата не позовёт.
29
+
30
+ ## Как отвечать
31
+
32
+ У тебя подключён MCP-сервер чата — его инструменты доступны как обычные:
33
+
34
+ 1. `read-thread` — журнал решений целиком и хвост треда. С этого начинают.
35
+ 2. Сделай работу: проверь код, посчитай, сверься с фактами.
36
+ 3. `post-message` — ответ в тред, обязательно с `reply_to_id` того сообщения, на которое отвечаешь.
37
+ 4. `search-thread` — если нужно поднять старое обсуждение; `read-attachment` — открыть
38
+ приложенный файл; `list-documents` / `read-document` / `write-document` — документы проекта.
39
+
40
+ **Вопрос владельцу — только с пометкой.** Если без ответа владельца работа дальше не
41
+ пойдёт (нужно решение, выбор, доступ), публикуй этот вопрос с `ask_owner: true`. Он
42
+ попадёт в список «Нужен ответ владельца» на странице чата и провисит там, пока владелец
43
+ не ответит именно на это сообщение. Упоминание владельца в тексте такой пометки
44
+ не заменяет и в список не попадает. Не ставь её на отчёты, предложения и риторические
45
+ вопросы: ложный вопрос в этом списке хуже пропущенного — владелец перестанет ему верить.
46
+ Уточняющий вопрос в ответ на обращение — тот же `post-message` с `reply_to_id`
47
+ обращения и `ask_owner: true`: обращение к тебе закроется, ожидание владельца останется.
48
+
49
+ Обращения **не подтверждай**: это делает мост, убедившись, что ответ действительно
50
+ опубликован. Так вопрос не потеряется, если ты упадёшь на полпути.
51
+
52
+ Пиши по-русски. Без приветствий и подписей — это чат, а не письмо.
@@ -0,0 +1,14 @@
1
+ ## Что тебе доступно, а что нет (движок codex, проверено 13.09.2026)
2
+
3
+ Рабочий каталог — отдельная папка ВНЕ репозиториев. Границы измерены прогонами:
4
+
5
+ | Возможность | Статус |
6
+ |---|---|
7
+ | MCP-сервер чата | **работает** |
8
+ | Интернет из shell (`curl`) | **работает** — 200 на внешние адреса |
9
+ | Чтение репозиториев | **работает** по абсолютным путям — каталог проекта указан в задании |
10
+ | Запись в репозитории | **запрещена файловой системой** — «Read-only file system», не договорённостью |
11
+ | Запись в свою папку | работает — черновики и временные файлы складывай туда |
12
+
13
+ Менять код ты не можешь, и это намеренно: нашёл проблему — опиши её в треде и поставь
14
+ задачу тому, чья роль — реализация.
package/bin/bridge ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ // Мост «чат → агент» — код в host/bridge/. Флаги: --watch, --interval, --dry-run.
3
+ import '../host/bridge/main.js';
package/bin/runner ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ // Раннер: мост на каждого агента этой машины — код в host/runner/.
3
+ import '../host/runner/main.js';