dsh-tg-bridge 1.2.6 → 1.2.7
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 +16 -0
- package/bridge.mjs +17 -11
- package/control.mjs +2 -13
- package/i18n.mjs +27 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,6 +74,21 @@ irm https://raw.githubusercontent.com/aleksvega/dsh-tg-bridge/main/install.ps1 |
|
|
|
74
74
|
Id берётся у [@userinfobot](https://t.me/userinfobot), токен — у
|
|
75
75
|
[@BotFather](https://t.me/BotFather).
|
|
76
76
|
|
|
77
|
+
### Настройки бота в Telegram
|
|
78
|
+
|
|
79
|
+
Три вещи, без которых бот будет молчать в группе или работать наполовину:
|
|
80
|
+
|
|
81
|
+
1. **Privacy mode → Disable** (`/setprivacy` у BotFather). Иначе бот видит
|
|
82
|
+
только команды, а обычный текст в теме — нет.
|
|
83
|
+
2. **Темы включены** в настройках группы (Edit → Topics). Без них вся работа
|
|
84
|
+
идёт в один чат, отдельные сессии на тему не создаются.
|
|
85
|
+
3. **Право Pin Messages** для бота. Нужно только для закрепления первого
|
|
86
|
+
сообщения темы; без него мост продолжит работать, но пин не поставит.
|
|
87
|
+
|
|
88
|
+
Меню команд настраивать руками не нужно: мост ставит его сам при старте, сразу
|
|
89
|
+
на русском, английском и китайском. Полная таблица настроек, права, проверка и
|
|
90
|
+
типичные ошибки — [docs/TELEGRAM-SETUP.md](docs/TELEGRAM-SETUP.md).
|
|
91
|
+
|
|
77
92
|
**Автозапуск** установщик ставит сам: `launchd` на macOS, `systemd --user` на
|
|
78
93
|
Linux. На Windows `setup` регистрирует задачу `DSH-TG-Bridge` и минутный сторож
|
|
79
94
|
`DSH-Bridge-Kick`, который поднимает мост после падения.
|
|
@@ -153,6 +168,7 @@ Telegram → bridge.mjs → dsh --profile headless --json → ответ
|
|
|
153
168
|
|
|
154
169
|
## Документация
|
|
155
170
|
|
|
171
|
+
- [docs/TELEGRAM-SETUP.md](docs/TELEGRAM-SETUP.md) — настройки BotFather, группы и права бота
|
|
156
172
|
- [docs/STATE.md](docs/STATE.md) — что работает, известные проблемы
|
|
157
173
|
- [docs/ROADMAP.md](docs/ROADMAP.md) — что дальше
|
|
158
174
|
- [docs/UX-ANALYSIS.md](docs/UX-ANALYSIS.md) — разбор интерактива
|
package/bridge.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import dns from 'node:dns';
|
|
|
22
22
|
import net from 'node:net';
|
|
23
23
|
dns.setDefaultResultOrder('ipv4first');
|
|
24
24
|
net.setDefaultAutoSelectFamily(false);
|
|
25
|
-
import { handleControl, CONTROL_COMMANDS
|
|
25
|
+
import { handleControl, CONTROL_COMMANDS } from './control.mjs';
|
|
26
26
|
import { renderPrices } from './prices.mjs';
|
|
27
27
|
import { t, resolveLang } from './i18n.mjs';
|
|
28
28
|
|
|
@@ -676,14 +676,14 @@ async function ensureTopicSeed(chatId, threadId, text) {
|
|
|
676
676
|
* reads as a log of what was started instead of a wall of prompts. The full
|
|
677
677
|
* text lives in the topic itself; this is only the pointer.
|
|
678
678
|
*/
|
|
679
|
-
async function logTopicToGeneral(chatId, threadId, text) {
|
|
679
|
+
async function logTopicToGeneral(chatId, threadId, text, lang = 'ru') {
|
|
680
680
|
if (config.topicLog === false) return;
|
|
681
681
|
const head = String(text ?? '').replace(/\s+/g, ' ').trim();
|
|
682
682
|
const preview = head.length > 90 ? `${head.slice(0, 90)}…` : head;
|
|
683
683
|
try {
|
|
684
684
|
await tg('sendMessage', {
|
|
685
685
|
chat_id: chatId,
|
|
686
|
-
text:
|
|
686
|
+
text: t(lang, 'msg.topicLog', { preview }),
|
|
687
687
|
disable_web_page_preview: true,
|
|
688
688
|
});
|
|
689
689
|
log(`topic ${threadId} logged to General`);
|
|
@@ -845,10 +845,13 @@ async function startProgress(chatId, threadId, lang = 'ru') {
|
|
|
845
845
|
// Three recent steps is enough to see the shape of the work; six lines of
|
|
846
846
|
// shell commands read as noise on a phone.
|
|
847
847
|
const tail = steps.slice(-3).map((line) => `• ${line}`).join('\n');
|
|
848
|
-
const more = steps.length > 3 ? `\n
|
|
848
|
+
const more = steps.length > 3 ? `\n${t(lang, 'status.more', { n: steps.length - 3 })}` : '';
|
|
849
849
|
// The cache line belongs to the outcome, not to every intermediate edit.
|
|
850
850
|
const cache = final && usage.cacheReadTokens
|
|
851
|
-
? `\n
|
|
851
|
+
? `\n${t(lang, 'status.cacheLine', {
|
|
852
|
+
pct: Math.round((usage.cacheReadTokens / Math.max(1, usage.cacheReadTokens + (usage.inputTokens ?? 0))) * 100),
|
|
853
|
+
k: Math.round(usage.cacheReadTokens / 1000),
|
|
854
|
+
})}`
|
|
852
855
|
: '';
|
|
853
856
|
return (tail ? `${head}\n${tail}${more}${cache}` : `${head}${cache}`);
|
|
854
857
|
};
|
|
@@ -1009,7 +1012,7 @@ async function handleMessage(msg) {
|
|
|
1009
1012
|
try {
|
|
1010
1013
|
const res = await tg('sendMessage', {
|
|
1011
1014
|
chat_id: chatId,
|
|
1012
|
-
text:
|
|
1015
|
+
text: t(lang, 'msg.transcribing'),
|
|
1013
1016
|
disable_web_page_preview: true,
|
|
1014
1017
|
...(threadId > 0 ? { message_thread_id: threadId } : {}),
|
|
1015
1018
|
...(msg.message_id ? { reply_to_message_id: msg.message_id } : {}),
|
|
@@ -1109,7 +1112,7 @@ async function handleMessage(msg) {
|
|
|
1109
1112
|
const seed = await ensureTopicSeed(chatId, threadId, text);
|
|
1110
1113
|
if (seed) {
|
|
1111
1114
|
log(`topic ${threadId} seeded with message=${seed}`);
|
|
1112
|
-
await logTopicToGeneral(chatId, threadId, text);
|
|
1115
|
+
await logTopicToGeneral(chatId, threadId, text, lang);
|
|
1113
1116
|
}
|
|
1114
1117
|
}
|
|
1115
1118
|
|
|
@@ -1150,7 +1153,7 @@ async function handleMessage(msg) {
|
|
|
1150
1153
|
chatId,
|
|
1151
1154
|
`${t(lang, 'msg.unknownCommand', { cmd: head })}
|
|
1152
1155
|
|
|
1153
|
-
${
|
|
1156
|
+
${t(lang, 'help.body')}`,
|
|
1154
1157
|
threadId,
|
|
1155
1158
|
msg.message_id
|
|
1156
1159
|
);
|
|
@@ -1667,9 +1670,10 @@ async function main() {
|
|
|
1667
1670
|
const notice = JSON.parse(readFileSync(RESTART_NOTICE, 'utf8'));
|
|
1668
1671
|
unlinkSync(RESTART_NOTICE);
|
|
1669
1672
|
if (Date.now() - Number(notice.at || 0) < 10 * 60 * 1000) {
|
|
1673
|
+
const lang = resolveLang(state, sessionKey(notice.chatId, notice.threadId || 0), null, config);
|
|
1670
1674
|
await send(
|
|
1671
1675
|
notice.chatId,
|
|
1672
|
-
`✅
|
|
1676
|
+
`✅ ${t(lang, 'msg.restarted', { pid: process.pid })}`,
|
|
1673
1677
|
notice.threadId || 0,
|
|
1674
1678
|
);
|
|
1675
1679
|
}
|
|
@@ -1726,10 +1730,12 @@ async function main() {
|
|
|
1726
1730
|
if (!queues.has(queueKey)) return; // already finished
|
|
1727
1731
|
const started = turnStartedAt.get(queueKey);
|
|
1728
1732
|
const mins = started ? Math.max(1, Math.round((Date.now() - started) / 60000)) : 0;
|
|
1729
|
-
const
|
|
1733
|
+
const notice = mins > 0
|
|
1734
|
+
? t(lang, 'msg.busy.since', { min: mins })
|
|
1735
|
+
: t(lang, 'msg.busy.now');
|
|
1730
1736
|
await send(
|
|
1731
1737
|
msg.chat.id,
|
|
1732
|
-
`⏳
|
|
1738
|
+
`⏳ ${notice}`,
|
|
1733
1739
|
thread,
|
|
1734
1740
|
);
|
|
1735
1741
|
}
|
package/control.mjs
CHANGED
|
@@ -349,17 +349,6 @@ export const CONTROL_COMMANDS = [
|
|
|
349
349
|
'prices', 'help', 'doctor', 'logs', 'lang',
|
|
350
350
|
];
|
|
351
351
|
|
|
352
|
-
/** One-line menu used when an unknown command arrives. */
|
|
353
|
-
export const CONTROL_COMMAND_HINT =
|
|
354
|
-
'Работа: /cd · /new · /stop · /send · /seed\n' +
|
|
355
|
-
'Сессии: /status · /sessions · /resume\n' +
|
|
356
|
-
'Модели: /models · /model · /prices\n' +
|
|
357
|
-
'Расширения: /skills · /soul\n' +
|
|
358
|
-
'Плагины: /plugins [headless] — список; /plugins add|remove|disable|enable <что> — действия\n' +
|
|
359
|
-
'Диагностика: /doctor · /logs\n' +
|
|
360
|
-
'Язык: /lang en|ru|zh\n' +
|
|
361
|
-
'Служебное: /app · /restart · /help';
|
|
362
|
-
|
|
363
352
|
export async function handleControl(ctx) {
|
|
364
353
|
const { command, arg, send, reply, log, profile, state, key, config } = ctx;
|
|
365
354
|
const say = (text) => reply(text);
|
|
@@ -549,10 +538,10 @@ export async function handleControl(ctx) {
|
|
|
549
538
|
|
|
550
539
|
case '/restart': {
|
|
551
540
|
if (arg !== 'confirm') {
|
|
552
|
-
say(
|
|
541
|
+
say(t(ctx.lang || 'ru', 'cmd.restart.prompt'));
|
|
553
542
|
return true;
|
|
554
543
|
}
|
|
555
|
-
say('
|
|
544
|
+
say(t(ctx.lang || 'ru', 'cmd.restart.started'));
|
|
556
545
|
const bridge = fileURLToPath(new URL('./bridge.mjs', import.meta.url));
|
|
557
546
|
// Tell the replacement process where to confirm, so the chat does not go
|
|
558
547
|
// silent after "Перезапускаю мост…" (it did, twice on 2026-10-24).
|
package/i18n.mjs
CHANGED
|
@@ -25,11 +25,20 @@ const DICT = {
|
|
|
25
25
|
'btn.retry': 'Retry',
|
|
26
26
|
'btn.doctor': 'Doctor',
|
|
27
27
|
'status.cache': 'cache',
|
|
28
|
+
'status.more': '…and {n} more',
|
|
29
|
+
'status.cacheLine': '🧠 cache: {pct}% ({k}k from cache)',
|
|
30
|
+
'msg.topicLog': '📌 New topic: {preview}\n→ work happens in the topic, this is the log.',
|
|
31
|
+
'msg.transcribing': '🎤 Transcribing…',
|
|
28
32
|
'status.steps': 'steps',
|
|
29
33
|
'status.thinking': 'waiting for the model',
|
|
30
34
|
'err.timeout': 'no activity for {min} min — turn interrupted',
|
|
31
35
|
'err.generic': 'something went wrong',
|
|
36
|
+
'msg.busy.since': '⏳ The previous turn has been running for {min} min — your message is queued.\nIf it stalls, /stop interrupts it and I take yours right away.',
|
|
37
|
+
'msg.busy.now': '⏳ The previous turn is still running — your message is queued.\nIf it stalls, /stop interrupts it and I take yours right away.',
|
|
32
38
|
'msg.started': 'Telegram bridge for DSH is running (headless). Send a task; /help for commands.',
|
|
39
|
+
'msg.restarted': 'Bridge restarted — back online (pid {pid}).\nThe topic and session are the same; keep going.',
|
|
40
|
+
'cmd.restart.prompt': 'To restart the bridge: /restart confirm. For the DSH app: /app restart confirm.',
|
|
41
|
+
'cmd.restart.started': 'Restarting the bridge…',
|
|
33
42
|
'msg.unknownCommand': 'Unknown command: {cmd}. Open /help — it lists everything this bot can do.',
|
|
34
43
|
'msg.langSet': 'Language set to English.',
|
|
35
44
|
'cmd.help.title': 'Commands',
|
|
@@ -72,11 +81,20 @@ const DICT = {
|
|
|
72
81
|
'btn.retry': 'Повторить',
|
|
73
82
|
'btn.doctor': 'Диагноз',
|
|
74
83
|
'status.cache': 'кэш',
|
|
84
|
+
'status.more': '…и ещё {n}',
|
|
85
|
+
'status.cacheLine': '🧠 кэш: {pct}% ({k}k из кэша)',
|
|
86
|
+
'msg.topicLog': '📌 Новая тема: {preview}\n→ работа идёт в теме, здесь — лог.',
|
|
87
|
+
'msg.transcribing': '🎤 Расшифровываю…',
|
|
75
88
|
'status.steps': 'шагов',
|
|
76
89
|
'status.thinking': 'жду модель',
|
|
77
90
|
'err.timeout': 'нет активности {min} мин — ход прерван',
|
|
78
91
|
'err.generic': 'что-то пошло не так',
|
|
92
|
+
'msg.busy.since': '⏳ Предыдущий ход идёт уже {min} мин — твоё сообщение в очереди.\nЕсли это затянулось, /stop прервёт ход, и я сразу возьмусь за твоим.',
|
|
93
|
+
'msg.busy.now': '⏳ Предыдущий ход ещё идёт — твоё сообщение в очереди.\nЕсли это затянулось, /stop прервёт ход, и я сразу возьмусь за твоим.',
|
|
79
94
|
'msg.started': 'Telegram-мост DSH запущен (headless). Пиши задачу; /help — команды.',
|
|
95
|
+
'msg.restarted': 'Мост перезапущен — на связи (pid {pid}).\nТема и сессия прежние, можно продолжать.',
|
|
96
|
+
'cmd.restart.prompt': 'Перезапуск моста: /restart confirm. Приложение DSH: /app restart confirm.',
|
|
97
|
+
'cmd.restart.started': 'Перезапускаю мост…',
|
|
80
98
|
'msg.unknownCommand': 'Неизвестная команда: {cmd}. Открой /help — там всё, что умеет бот.',
|
|
81
99
|
'msg.langSet': 'Язык переключён на русский.',
|
|
82
100
|
'cmd.help.title': 'Команды',
|
|
@@ -119,11 +137,20 @@ const DICT = {
|
|
|
119
137
|
'btn.retry': '重试',
|
|
120
138
|
'btn.doctor': '诊断',
|
|
121
139
|
'status.cache': '缓存',
|
|
140
|
+
'status.more': '…还有 {n} 条',
|
|
141
|
+
'status.cacheLine': '🧠 缓存:{pct}%({k}k 来自缓存)',
|
|
142
|
+
'msg.topicLog': '📌 新主题:{preview}\n→ 工作在主题里进行,这里是日志。',
|
|
143
|
+
'msg.transcribing': '🎤 正在转写…',
|
|
122
144
|
'status.steps': '步',
|
|
123
145
|
'status.thinking': '等待模型',
|
|
124
146
|
'err.timeout': '{min} 分钟无活动 — 已中断',
|
|
125
147
|
'err.generic': '出现错误',
|
|
148
|
+
'msg.busy.since': '⏳ 上一轮已运行 {min} 分钟 —— 你的消息在队列中。\n如果太久,/stop 会中断它,我马上处理你的消息。',
|
|
149
|
+
'msg.busy.now': '⏳ 上一轮还在运行 —— 你的消息在队列中。\n如果太久,/stop 会中断它,我马上处理你的消息。',
|
|
126
150
|
'msg.started': 'DSH 的 Telegram 桥已启动(headless)。直接发送任务;/help 查看命令。',
|
|
151
|
+
'msg.restarted': '桥已重启,连接正常(pid {pid})。\n主题和会话不变,可以继续。',
|
|
152
|
+
'cmd.restart.prompt': '重启桥:/restart confirm。重启 DSH 应用:/app restart confirm。',
|
|
153
|
+
'cmd.restart.started': '正在重启桥…',
|
|
127
154
|
'msg.unknownCommand': '未知命令:{cmd}。打开 /help 查看机器人支持的全部命令。',
|
|
128
155
|
'msg.langSet': '界面语言已切换为中文。',
|
|
129
156
|
'cmd.help.title': '命令',
|
package/package.json
CHANGED