dsh-telegram-multiagent 1.2.3 → 1.4.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.
package/README.md CHANGED
@@ -1,121 +1,166 @@
1
1
  # dsh-telegram-multiagent
2
2
 
3
- A Telegram channel for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness): message
4
- your agent from a phone, get answers back, keep one conversation per chat.
3
+ Канал Telegram для платформы DeepSeek Harness. Один модуль на машину обслуживает
4
+ несколько агентов: у каждого свой бот, свой файл токена и свой список тех, кому он
5
+ отвечает. Агенты не видят переписку друг друга.
5
6
 
6
- The harness ships no messenger channel — there is no channel abstraction in the product at all. This
7
- plugin builds one out of the two ends the harness does give you:
7
+ ## Установка
8
8
 
9
- ```
10
- in: ctx.agents.create/resume(...) → agent.send(message)
11
- out: ctx.on('session/event', ...) → send to Telegram
12
- ```
9
+ npm install dsh-telegram-multiagent
13
10
 
14
- One chat = one session = one agent. Sessions live independently, the way the core intends.
11
+ Строка подключения кладётся в пресет агента (см. «Куда класть строку» ниже):
15
12
 
16
- ## Install
13
+ # внутри каталога профиля ($DSH_HOME/profiles/<имя>)
17
14
 
18
- ```bash
19
- # inside your profile directory ($DSH_HOME/profiles/<name>)
20
- pnpm add dsh-telegram-multiagent
21
- ```
15
+ ## Настройки
22
16
 
23
- Then add one row to your **agent preset** (`agent.cordis.yml`), not to the profile patch layer —
24
- see "Where to put the row" below:
17
+ | Поле | Обязательно | Что делает |
18
+ |---|---|---|
19
+ | `tokenFile` | одно из трёх | Путь к файлу с токеном бота. Предпочтительно: секрет принадлежит машине, настройка несёт только путь. |
20
+ | `token` / `tokenEnv` | одно из трёх | Сам токен либо имя переменной окружения. Для отладки. |
21
+ | `appDir` | да | Каталог, где установлена платформа этого агента. Пакеты платформы разрешаются **отсюда**, а не от места самого плагина — см. «Почему `appDir`». |
22
+ | `agentName` | нет | Подпись в строках журнала. Полезно, когда на машине несколько ботов. |
23
+ | `allowedUsers` | нет | Числовые идентификаторы тех, кому можно говорить с агентом. **Пусто означает «всем»** — а у агента обычно есть настоящий доступ к машине, так что задайте. |
24
+ | `workspace` | нет | Рабочий каталог, передаваемый сессии агента. |
25
+ | `preset` | нет | Пресет агента для сессий, созданных этим каналом. |
26
+ | `provider` / `model` | нет | Запасной вариант, если в установке нет службы модели по умолчанию. |
27
+ | `a2aDir` | нет | Каталог файлового канала между агентами (`in/`, `out/`). Не задан — канала нет. |
28
+ | `a2aSession` | нет | Идентификатор сессии для этого канала. По умолчанию `a2a`. |
29
+ | `spoolDir` | нет | Системный почтовый ящик агента. Не задан — ящик не читается. |
30
+ | `transcribeCommand` | нет | Внешняя команда для голосовых: `<команда> <файл> auto` → расшифровка в stdout. Не задана — голос вежливо отклоняется. |
31
+ | `goalUsers` | нет | Кому можно ставить цель из Telegram. **Пусто — команда отвергается для всех.** Намеренно не наследуется от `allowedUsers`: говорить с агентом и запускать ему платный автономный цикл — разные права. |
32
+ | `goalA2ASenders` | нет | Кому можно ставить цель из канала между агентами. Отправитель называет себя первой строкой файла `From: <имя>`. Это учёт, а не удостоверение личности. |
33
+
34
+
35
+ 🔴 **Предел: 3 постановки цели за скользящий час.** Считается по отметкам состоявшихся
36
+ постановок, отдельно на каждый канал. Исчерпан — команда отвергается со словами «зовите
37
+ человека». СНЯТИЕ цели при исчерпанном пределе разрешено: предел бережёт от разгона, а не
38
+ запирает возможность остановиться.
39
+
40
+ Предел не защищает от намеренного действия агента с правами администратора: такой агент
41
+ снимет и счётчик, и сам предел. Защита здесь от разгона и случайности, а не от намерения.
42
+ ## Пять вещей, стоивших нам дня
43
+
44
+ Каждая из них отказывает **выглядя успехом**. В исходниках они помечены комментариями
45
+ рядом с местом; здесь короткая версия.
46
+
47
+ **1. Опрос принадлежит боту, а не монтажу.** Платформа монтирует композицию больше
48
+ одного раза на процесс и снимает лишний монтаж. С одним общим флагом «идёт» вы
49
+ получите два опроса, дерущихся за одного бота, и Telegram оборвёт оба с ошибкой
50
+ `Conflict`. С вежливым «новый монтаж просит старый посторониться» вы не получите
51
+ опроса вовсе — потому что снимут именно новый. Лечение — счёт ссылок: первый монтаж
52
+ запускает опрос, последний снятый его останавливает.
53
+
54
+ **2. Входящее письмо удаляется только после того, как дошло до агента.** Прочитать
55
+ файл и сразу удалить — значит терять сообщение всякий раз, когда передача не удалась.
56
+ А она не удастся, см. (3). Снаружи это неотличимо от «бот меня проигнорировал».
57
+
58
+ **3. Фабрика агентов появляется позже канала.** Первое сообщение может прийти раньше,
59
+ чем платформа её зарегистрировала, и вы получите `no agent factory registered`. Ждите
60
+ платформу, а не предполагайте, что она готова.
61
+
62
+ **4. Сессию, уже лежащую на диске, надо продолжать, а не создавать.** Вызов `create()`
63
+ с существующим идентификатором даёт новую пустую сессию, и переписка начинается с
64
+ чистого листа — при том, что старая цела и лежит рядом.
65
+
66
+ **5. Отказ незнакомцу должен быть громким.** Молчаливый отказ прячет событие
67
+ безопасности: владелец не узнает, что кто-то нашёл его бота.
68
+
69
+ ## Куда класть строку
70
+
71
+ В профиле `web` инструменты общего плана **выключены намеренно**; набор приходит из
72
+ пресета агента. Строка плагина, положенная в слой патча профиля, соберётся без единой
73
+ ошибки, будет числиться смонтированной — и никогда не дойдёт до агента. Кладите строку
74
+ в пресет агента.
75
+
76
+ Пресет читается ещё и **при создании сессии**: правка файла не действует на уже идущую.
77
+ Перезапустите платформу либо смените пресет по умолчанию в настройках (перечитывается на
78
+ ходу, действует для следующей созданной сессии).
79
+
80
+ ## Почему `appDir`
81
+
82
+ Плагин разрешает `@deepseek-ai/dsh-llm` и `@deepseek-ai/dsh-agent` из каталога, который
83
+ вы передали в `appDir`, а не от собственного расположения. Это сделано намеренно: модуль
84
+ должен лежать на машине один раз и обслуживать нескольких агентов, у каждого своя
85
+ установка платформы. Привяжи его жёстко к `node_modules` одного агента — и удаление
86
+ ЭТОГО агента сломает канал всем остальным.
87
+
88
+ ## Заметки о безопасности
89
+
90
+ - Пустой `allowedUsers` означает, что любой нашедший бота говорит с агентом, у которого
91
+ обычно есть доступ к оболочке машины. Задайте его.
92
+ - Отвергнутый незнакомец попадает в журнал и в сообщение владельцу. Молчаливый отказ
93
+ прячет событие безопасности.
94
+ - Токен читается из файла при запуске; настройка несёт путь, а не секрет.
95
+ - Изоляция агентов держится на правах файлов токенов, а не на самом модуле: модуль
96
+ общий, разделяет их файловая система.
97
+
98
+ ## Состояние
99
+
100
+ Написан для собственного парка агентов и работает в бою на нескольких. Платформа молодая
101
+ (`0.1.0-rc`), её интерфейс плагинов меняется — рассчитывайте подстраиваться. Комментарии
102
+ в исходниках русские: они несут причину каждой неочевидной строки.
25
103
 
26
- ```yaml
27
- - insert:
28
- - name: dsh-telegram-multiagent
29
- config:
30
- agentName: my-agent # log label only
31
- tokenFile: /etc/dsh/bot.token # preferred: a file readable by this agent
32
- appDir: /opt/my-agent/app # where THIS agent's harness is installed
33
- workspace: /opt/my-agent/work
34
- allowedUsers: [123456789] # empty = everyone; you do not want that
35
- ```
104
+ MIT.
36
105
 
37
- ## Configuration
106
+ ## 1.4.0 — причина сетевого отказа и чтение почтового ящика
38
107
 
39
- | Field | Required | What it does |
40
- |---|---|---|
41
- | `tokenFile` | one of these | Path to a file holding the bot token. Preferred: the secret belongs to the machine, the config only carries a path. |
42
- | `token` / `tokenEnv` | one of these | Literal token, or the name of an env var. For debugging. |
43
- | `appDir` | yes | Directory where this agent's harness is installed. Platform packages are resolved from **here**, not from the plugin's own location — see "Why appDir" below. |
44
- | `agentName` | no | Label in log lines. Useful when several bots run on one machine. |
45
- | `allowedUsers` | no | Numeric user ids allowed to talk to the agent. **Empty means everyone** — an agent usually has real access to the machine, so set it. |
46
- | `workspace` | no | Working directory handed to the agent session. |
47
- | `preset` | no | Agent preset to mount for sessions created by this channel. |
48
- | `provider` / `model` | no | Fallback if the deployment has no default model service. |
49
- | `a2aDir` | no | Directory for a file-based agent-to-agent channel (`in/`, `out/`). Omit and no such channel exists. |
50
- | `a2aSession` | no | Session id used for that channel. Default `a2a`. |
51
- | `transcribeCommand` | no | External command for voice messages: `<cmd> <audio-file> auto` → transcript on stdout. Omit and voice is politely refused. |
52
-
53
- ## Five things that cost us a day
54
-
55
- Each of these fails **while looking like success**. They are commented inline in the source; this is
56
- the short version.
57
-
58
- **1. Polling belongs to the bot, not to the mount.** The harness mounts a composition more than once
59
- per process and unmounts the extra one. With a single shared `running` flag you get two pollers
60
- fighting over one bot and Telegram cuts both with `Conflict`. With a polite "new mount asks the old
61
- one to step aside" you get *no* poller at all — because the new mount is the one that gets unmounted.
62
- The fix is reference counting: the first mount starts polling, the last unmount stops it.
63
-
64
- **2. Delete the incoming message only after it reached the agent.** Reading a file and unlinking it
65
- immediately loses the message whenever the handoff fails — and the handoff *will* fail, see (3).
66
- From the outside that is indistinguishable from "the bot ignored me".
67
-
68
- **3. The agent factory appears later than the channel.** The first message can arrive before the
69
- harness has registered it, and you get `no agent factory registered`. Wait for the platform instead
70
- of assuming it is ready.
71
-
72
- **4. A session already on disk must be resumed, not created.** Calling `create()` with an existing
73
- session id makes the persistence layer abort *every* turn with an id-collision error. Externally:
74
- "accepted the message and went quiet" — the turn honestly starts and dies in milliseconds. It works
75
- until the first restart, which is what makes it nasty. Use `resume()` when the session exists.
76
-
77
- **5. `ctx.get()` returns nothing *quietly* while a service is still starting.** cordis hands back
78
- `undefined` without throwing until the provider's fiber is active, so "this service is not in the
79
- build" and "this service is thirty milliseconds away" arrive as the same value. Code written as
80
- `const x = ctx.get('x'); if (x) {...}` then takes the "feature absent" branch and says nothing — the
81
- agent is assembled, answers, and never mentions what it lost. This bit us three times in three days:
82
- the agent factory (3), session persistence (4, the whole resume block was skipped), and the agent
83
- preset — where a neighbouring agent's toolset dropped from 33 tools to 3 with no error and no log
84
- line. Wait for the service with a stated deadline, and when the deadline passes, say so *and name
85
- the consequence*. One helper for all of them: a fix applied to the instance instead of the class
86
- guarantees a relapse, and the relapse looks like a new illness.
87
-
88
- ## Where to put the row
89
-
90
- In the `web` profile the common-plane tools are **disabled on purpose**; the toolset comes from the
91
- agent preset. A plugin row placed in the profile patch layer composes without a single error, is
92
- listed as mounted — and never reaches the agent. Put the row in the agent preset.
93
-
94
- The preset is also picked up when a **session is created**: editing the file does not affect a
95
- running session. Restart the platform, or change the default preset in settings (hot-reloaded, takes
96
- effect for the next created session).
97
-
98
- ## Why `appDir`
99
-
100
- The plugin resolves `@deepseek-ai/dsh-llm` and `@deepseek-ai/dsh-agent` from the directory you pass
101
- in `appDir`, not from its own location. That is deliberate: the module is meant to live once on a
102
- machine and serve several agents, each with its own harness installation. Hard-linking it to one
103
- agent's `node_modules` would mean that removing *that* agent breaks the channel for everybody else.
104
-
105
- ## Security notes
106
-
107
- - `allowedUsers` empty means anyone who finds the bot talks to an agent that usually has shell access
108
- to the machine. Set it.
109
- - A rejected stranger is logged and reported to the owner. A silent refusal hides a security event.
110
- - The token is read from a file at startup; the config carries a path, not a secret.
111
-
112
- ## Status
113
-
114
- Written for our own fleet and running in production on several agents. The harness is young
115
- (`0.1.0-rc`) and its plugin API moves; expect to adapt. Inline comments are currently in Russian —
116
- they carry the reasoning behind each non-obvious line, and a translation is welcome.
108
+ **Ломающих изменений НЕТ.** Проверено сравнением состава: из 1.3.0 не пропало ни одного
109
+ файла, добавилось два стенда. Пути внутри пакета прежние.
117
110
 
118
- MIT.
111
+ пропало: ничего
112
+ добавилось: test/stend-imena.mjs, test/stend-yashchika-shiny.mjs
113
+ изменилось: src/index.js, test/test-goal-control.mjs, cordis.patch.yml, README.md
114
+
115
+ **Причина сетевого отказа печатается рядом с сообщением.** Раньше `fetch failed` скрывал
116
+ причину: обрыв соединения, истёкшее ожидание и ненайденное имя выглядели одинаково, и по
117
+ журналу нельзя было понять, что чинить. Теперь рядом печатается `e.cause`. Если причины
118
+ нет — пишется «не указана», а не пустое место: пустое читается как «причины нет» и
119
+ возвращает ту же слепоту.
120
+
121
+ 🔴 **У двух каналов РАЗНЫЕ способы назвать отправителя, и путать их дорого:**
122
+
123
+ канал a2a (каталог a2aDir) — отправитель из заголовка первой строки: From: <имя>
124
+ почтовый ящик (spoolDir) — отправитель из ИМЕНИ ФАЙЛА: ГГГГММДДTЧЧММССZ-<имя>-<хвост>
125
+
126
+ Положив в ящик письмо с заголовком `From:`, вы получите отправителем то, что стоит
127
+ в имени файла, а сам заголовок останется в тексте письма. Ошибки не будет, отказа
128
+ тоже — просто отправитель окажется не тем, кого вы назвали. Проверено пробой.
129
+
130
+ Причина разницы: в ящик кладёт посторонний процесс правами каталога, и имя файла —
131
+ единственное, что он не может подделать незаметно для владельца ящика. В канале a2a
132
+ файл пишет сам отправитель, там заголовок и есть его подпись. В обоих случаях это
133
+ учёт, а не удостоверение личности.
134
+
135
+ **Чтение писем из системного почтового ящика** (`spoolDir`). Письмо старше 12 часов
136
+ подаётся с явной пометкой возраста — иначе двухсуточное письмо толкает отвечать на
137
+ устаревшее. Больше пяти писем подаются одной пачкой, а не по одному. Ящик не задан —
138
+ не читается, молчаливого умолчания нет.
139
+
140
+ **Блок управления целями появился в `cordis.patch.yml`.** Раньше `goalUsers` и
141
+ `goalA2ASenders` были описаны в README, но отсутствовали в патче монтажа — то есть
142
+ настройка была недоступна тому, кто ставил пакет по инструкции.
143
+
144
+ ### Проверка, что работает
145
+
146
+ Стенды едут вместе с кодом:
147
+
148
+ node test/test-goal-control.mjs управление целями из канала
149
+ node test/stend-imena.mjs метки отправителя и обезличенность
150
+ node test/stend-yashchika-shiny.mjs чтение почтового ящика
151
+
152
+ Коды: 0 — сошлось, 1 — расхождение, 2 — проверить нечем (слепота).
153
+ 🔴 Код 2 не означает «всё хорошо»: он означает, что часть проверок не состоялась. Чаще
154
+ всего это отсутствие необязательных зависимостей или файлов настроек, которых у только
155
+ что установленного модуля ещё нет.
156
+
157
+ ### Чего 1.4.0 НЕ делает
158
+
159
+ - не хранит переписку — она живёт в журнале сессии платформы;
160
+ - не гарантирует доставку при недоступной сети: повторяет опрос и пишет причину отказа в
161
+ журнал, но письмо в этот момент не уходит;
162
+ - не проверяет, что пишущий в канал между агентами — тот, за кого себя выдаёт: метка
163
+ отправителя берётся из имени файла, а право положить файл раздаётся правами каталога.
119
164
 
120
165
  ## 1.1.0 — кто спросил, кому отвечено, и кто это видит
121
166
 
@@ -171,3 +216,96 @@ MIT.
171
216
 
172
217
  **Урок общий:** канал доставки без повтора — это тихая потеря. Отказ, о котором никто не узнал,
173
218
  дороже отказа громкого: его невозможно даже сосчитать.
219
+
220
+ ## 1.3.0 — постановка цели прямо из канала
221
+
222
+ 🔴 **Зачем это вообще нужно.** Если цикл агента ведёт внешний движок — например, агент работает
223
+ через подписку, а не через родной цикл платформы, — родные инструменты платформы до модели **не
224
+ доходят вовсе**. Доходит только то, что имеет форму сообщения. Значит поставить агенту цель изнутри
225
+ разговора нельзя: руками в веб-интерфейсе можно, а из чата нет. Здесь мы даём постановку из любого
226
+ канала: сервису целей не нужен инструмент модели, он доступен коду плагина напрямую.
227
+
228
+ Команды одинаковы в обоих каналах:
229
+
230
+ ```
231
+ /goal показать состояние цели
232
+ /goal <замысел> поставить цель
233
+ /goal stop снять цель
234
+ ```
235
+
236
+ **Кто вправе — двумя отдельными списками, оба по умолчанию ПУСТЫЕ, то есть команда отказывает
237
+ всем.** Право не наследуется от `allowedUsers` сознательно: «с кем я разговариваю» и «кто вправе
238
+ завести дорогой автономный цикл» — разные вопросы, а в `allowedUsers` завтра добавят гостя.
239
+
240
+ ```yaml
241
+ goalUsers: [123456789] # из Telegram — по числовому id
242
+ goalA2ASenders: ["coordinator"] # из служебного канала — по имени в заголовке
243
+ ```
244
+
245
+ 🔴 **Чем за это платят, сказать обязано само описание.** Проверки платформы
246
+ (`currentInitiator`, `hasDirectHumanInput`, права на корневого агента) живут в её инструменте целей
247
+ и на этом пути **не участвуют**. Плагин не обходит политику — он пишет её заново, и две строки выше
248
+ есть вся политика целиком. Ставя `goalUsers`, вы соглашаетесь именно на это.
249
+
250
+ **Счётчик постановок: не более трёх за скользящий час на канал.** Предел раундов у платформы стоит
251
+ на ЦЕЛЬ, а не на агента: новая цель — новый счётчик, и поток целей ничем не ограничен. А каталог
252
+ входящих принадлежит самому агенту, то есть в автономном раунде он способен положить себе файл
253
+ `/goal …` и продлить цикл собственными руками.
254
+
255
+ 🔴 **Где счётчик НЕ применяется — назвать важнее, чем сам счётчик.** Он защищает от РАЗГОНА:
256
+ от петли, в которую механизм вошёл сам, от ошибки в коде, от лавины повторов. Он **не** защищает от
257
+ намеренного действия агента с правами администратора: такой агент снимет и счётчик, и предел
258
+ раундов одной командой. Изоляция здесь **поведенческая**, технической границы против самого агента
259
+ не существует. Настоящий сторож живёт снаружи машины. Снятию цели (`/goal stop`) счётчик не мешает
260
+ никогда: механизм, включаемый снаружи и невыключаемый, хуже отсутствия механизма.
261
+
262
+ **Заголовок отправителя — учёт, а не защита.** Служебный канал файловый, и вид источника получает
263
+ любое сообщение в нём; правом «всем, кто пишет в канал» вы раздали бы дорогие циклы кому попало.
264
+ Поэтому отправитель называет себя первой строкой файла:
265
+
266
+ ```
267
+ From: coordinator
268
+ /goal разобрать вчерашние отказы и доложить
269
+ ```
270
+
271
+ Имя подделает любой, кто может положить файл в каталог входящих. Настоящая граница здесь — права
272
+ ОС на этот каталог. Нужна проверка сильнее самоназвания — это общий секрет в файле с правами, и
273
+ заводить его надо отдельным решением, а не походя.
274
+
275
+ **Об исходе цели плагин сообщает сам.** Доводчик платформы при упоре в предел раундов переводит
276
+ цель в `blocked` — и делает это молча: снаружи автономный цикл просто перестаёт просыпаться, что
277
+ неотличимо от поломки. Плагин говорит об исходе ровно один раз и в тот канал, из которого цель
278
+ ставили.
279
+
280
+ **Служебный маркер цели вырезается из всего, что уходит наружу.** Его дело — остановить цикл, а не
281
+ попасть человеку в чат. Вырезается маркер последней строки; маркер в середине текста оставлен
282
+ видимым нарочно — он и так отвергнут, и пусть будет заметен читателю.
283
+
284
+ ### Ожидание сервисов: где оно есть и где его нет — решение по каждому месту
285
+
286
+ `ctx.get()` возвращает `undefined` **молча**, пока волокно поставщика не активно. «Сервиса нет в
287
+ сборке» и «сервис ещё поднимается» приходят одним и тем же значением — и код, написанный как
288
+ `const x = ctx.get('x'); if (x)`, тихо уходит по ветке «этой возможности просто нет».
289
+
290
+ | место | ожидание | почему |
291
+ |---|---|---|
292
+ | заведение агента (пресет, модель, персистенция) | **есть**, до 30 с | идёт вплотную к подъёму платформы, отказ молчалив и разрушителен |
293
+ | `/goal` → сервисы целей, маркера, моста | **есть**, коротко | ответ «сервис не подключён» был бы диагнозом, которого код поставить не может; но на том конце живой собеседник, и полминуты молчания в чате читаются как «бот умер» |
294
+ | вырезание маркера из исходящего | **нет** | место синхронное, а отказ не молчалив: маркер уедет в чат видимым текстом |
295
+ | сообщение об исходе цели | **нет** | сюда попадают только сессии, где цель уже поставлена, — значит пустота означает не «ещё не поднялся», а «сервис пропал», и это надо сказать, а не переждать |
296
+
297
+ Замечено на живом: порядок готовности волокон **меняется от подъёма к подъёму**. В одном подъёме
298
+ ждала фабрика агентов, в другом — набор пресета, причём гонка была в обоих. Значит судить о гонке
299
+ по соседнему сервису нельзя, и ожидание ставится по устройству места, а не по тому, где однажды
300
+ видели примету.
301
+
302
+ ### Чего эта версия НЕ делает
303
+
304
+ - **не даёт модели инструмент постановки цели** — команда приходит от человека или от координатора
305
+ через канал, модель её не вызывает и в заголовке запроса плагин невидим;
306
+ - **не заменяет права платформы** — она их здесь не применяет вовсе (см. выше);
307
+ - **не защищает от агента с правами администратора** — вся защита поведенческая;
308
+ - **не проверяет подлинность отправителя** в служебном канале — только самоназвание;
309
+ - **не переносит цель через перезапуск процесса**: счётчик постановок обнуляется, а связь
310
+ «сессия → куда сообщить об исходе» живёт в памяти процесса. Сама цель хранится платформой и
311
+ переживает перезапуск, а вот сообщение об её исходе после перезапуска не придёт.
package/cordis.patch.yml CHANGED
@@ -17,3 +17,14 @@
17
17
  # Empty means everyone can talk to an agent that usually has shell
18
18
  # access. Set it.
19
19
  allowedUsers: []
20
+ # 🔴 УПРАВЛЕНИЕ ЦЕЛЯМИ ИЗ КАНАЛА. Оба списка пусты по умолчанию — это
21
+ # значит, что команда постановки цели отвергается для ВСЕХ. Так задумано:
22
+ # запуск автономного цикла стоит денег, поэтому право даётся поимённо и
23
+ # НИКОГДА не наследуется от allowedUsers. Говорить с агентом и запускать
24
+ # ему платный цикл — разные права.
25
+ # Кому можно ставить цель из Telegram (числовые идентификаторы):
26
+ goalUsers: []
27
+ # Кому можно ставить цель из служебного канала между агентами.
28
+ # Отправитель называет себя сам, первой строкой файла: "From: <имя>".
29
+ # Это учёт, а не удостоверение личности — см. README.
30
+ goalA2ASenders: []
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "dsh-telegram-multiagent",
3
- "version": "1.2.3",
4
- "description": "Telegram channel for DeepSeek Harness one shared module serving several agents, with unforgeable sender marks, merged owner+coordinator memory, and a delivery mode you switch in a file outside the code.",
3
+ "version": "1.4.0",
4
+ "description": "Канал Telegram для DeepSeek Harness: один модуль на машину обслуживает несколько агентов, у каждого свой бот, свой файл токена и свой список разрешённых. Метка отправителя неподделываема, память владельца и координатора слита в одну, режим доставки переключается файлом вне кода, цели управляются из канала.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "files": [
8
- "src",
8
+ "src/index.js",
9
+ "src/client.js",
10
+ "test/extract.mjs",
11
+ "test/stend-imena.mjs",
12
+ "test/stend-yashchika-shiny.mjs",
13
+ "test/test-goal-control.mjs",
9
14
  "cordis.patch.yml",
10
15
  "README.md"
11
16
  ],