dsh-tg-bridge 1.2.0 → 1.2.2
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 +1 -0
- package/bot-menu.mjs +4 -6
- package/bridge.mjs +17 -2
- package/control.mjs +22 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -157,6 +157,7 @@ Telegram → bridge.mjs → dsh --profile headless --json → ответ
|
|
|
157
157
|
- [docs/ROADMAP.md](docs/ROADMAP.md) — что дальше
|
|
158
158
|
- [docs/UX-ANALYSIS.md](docs/UX-ANALYSIS.md) — разбор интерактива
|
|
159
159
|
- [docs/ACCEPTANCE.md](docs/ACCEPTANCE.md) — критерии приёмки
|
|
160
|
+
- [docs/COST.md](docs/COST.md) — сколько стоит один ход и как это уменьшить
|
|
160
161
|
- [docs/CHANGELOG.md](docs/CHANGELOG.md) — история изменений
|
|
161
162
|
- [docs/RELEASE-CRITERIA.md](docs/RELEASE-CRITERIA.md) — критерии готовности релиза
|
|
162
163
|
|
package/bot-menu.mjs
CHANGED
|
@@ -19,6 +19,7 @@ const token = (() => {
|
|
|
19
19
|
try {
|
|
20
20
|
const home = process.env.DSH_HOME || join(homedir(), '.dsh');
|
|
21
21
|
const yaml = readFileSync(join(home, '.credentials.yaml'), 'utf8');
|
|
22
|
+
// `dsh-tg init` writes the plain name; the DSH app adds a random suffix.
|
|
22
23
|
const m = /^\s*(DSH_TELEGRAM_BOT_TOKEN[A-Z0-9_]*):\s*["']?([^"'\s]+)/m.exec(yaml);
|
|
23
24
|
return m ? m[2] : '';
|
|
24
25
|
} catch {
|
|
@@ -41,8 +42,7 @@ const COMMANDS = [
|
|
|
41
42
|
{ command: 'model', description: 'Сменить модель: /model <id>' },
|
|
42
43
|
{ command: 'skills', description: 'Скиллы: список или текст одного' },
|
|
43
44
|
{ command: 'soul', description: 'Персона: показать или задать' },
|
|
44
|
-
{ command: 'plugins', description: '
|
|
45
|
-
{ command: 'plugin', description: 'add/remove/disable/enable плагина' },
|
|
45
|
+
{ command: 'plugins', description: 'Список и управление: add/remove/disable/enable' },
|
|
46
46
|
{ command: 'app', description: 'Перезапуск приложения: /app restart confirm' },
|
|
47
47
|
{ command: 'restart', description: 'Перезапуск моста: /restart confirm' },
|
|
48
48
|
{ command: 'doctor', description: 'Диагностика: мост, сеть, диск, каталог, сессия' },
|
|
@@ -66,8 +66,7 @@ const COMMANDS_EN = [
|
|
|
66
66
|
{ command: 'model', description: 'Show or switch the model' },
|
|
67
67
|
{ command: 'skills', description: 'Installed skills' },
|
|
68
68
|
{ command: 'soul', description: 'Agent persona' },
|
|
69
|
-
{ command: 'plugins', description: '
|
|
70
|
-
{ command: 'plugin', description: 'Enable or disable a plugin' },
|
|
69
|
+
{ command: 'plugins', description: 'List and manage: add/remove/disable/enable' },
|
|
71
70
|
{ command: 'app', description: 'Desktop app control' },
|
|
72
71
|
{ command: 'restart', description: 'Restart the bridge: /restart confirm' },
|
|
73
72
|
{ command: 'doctor', description: 'Diagnostics: bridge, network, disk, directory, session' },
|
|
@@ -91,8 +90,7 @@ const COMMANDS_ZH = [
|
|
|
91
90
|
{ command: 'model', description: '查看或切换模型' },
|
|
92
91
|
{ command: 'skills', description: '已安装的技能' },
|
|
93
92
|
{ command: 'soul', description: '代理人格设定' },
|
|
94
|
-
{ command: 'plugins', description: '
|
|
95
|
-
{ command: 'plugin', description: '启用或停用插件' },
|
|
93
|
+
{ command: 'plugins', description: '列表与管理:add/remove/disable/enable' },
|
|
96
94
|
{ command: 'app', description: '桌面应用控制' },
|
|
97
95
|
{ command: 'restart', description: '重启桥接:/restart confirm' },
|
|
98
96
|
{ command: 'doctor', description: '诊断:桥接、网络、磁盘、目录、会话' },
|
package/bridge.mjs
CHANGED
|
@@ -73,6 +73,8 @@ const config = {
|
|
|
73
73
|
};
|
|
74
74
|
/** Turns currently running, keyed like sessions — `/stop` kills one. */
|
|
75
75
|
const activeTurns = new Map();
|
|
76
|
+
/** Wall-clock start of the running turn per session key, for the queue notice. */
|
|
77
|
+
const turnStartedAt = new Map();
|
|
76
78
|
try {
|
|
77
79
|
Object.assign(config, JSON.parse(readFileSync(join(DIR, 'config.json'), 'utf8')));
|
|
78
80
|
} catch {}
|
|
@@ -1368,9 +1370,15 @@ function enqueue(key, task, onBusy) {
|
|
|
1368
1370
|
const queued = queues.has(key);
|
|
1369
1371
|
const run = prev
|
|
1370
1372
|
.then(() => acquire())
|
|
1371
|
-
.then(() =>
|
|
1373
|
+
.then(() => {
|
|
1374
|
+
// Remember when the turn actually started: the queue notice has to say
|
|
1375
|
+
// "идёт уже 24 мин", not just "занято" — silence is what reads as a hang.
|
|
1376
|
+
turnStartedAt.set(key, Date.now());
|
|
1377
|
+
return task();
|
|
1378
|
+
})
|
|
1372
1379
|
.catch((error) => log(`turn queue error ${key}: ${error.message}`))
|
|
1373
1380
|
.finally(() => {
|
|
1381
|
+
turnStartedAt.delete(key);
|
|
1374
1382
|
release();
|
|
1375
1383
|
if (queues.get(key) === run) queues.delete(key);
|
|
1376
1384
|
// A deferred restart waits for exactly this moment: the answer is out and
|
|
@@ -1669,7 +1677,14 @@ async function main() {
|
|
|
1669
1677
|
? async () => {
|
|
1670
1678
|
await new Promise((resolve) => setTimeout(resolve, Number(config.busyNoticeMs ?? 45000)));
|
|
1671
1679
|
if (!queues.has(queueKey)) return; // already finished
|
|
1672
|
-
|
|
1680
|
+
const started = turnStartedAt.get(queueKey);
|
|
1681
|
+
const mins = started ? Math.max(1, Math.round((Date.now() - started) / 60000)) : 0;
|
|
1682
|
+
const howLong = mins > 0 ? `идёт уже ${mins} мин` : 'ещё идёт';
|
|
1683
|
+
await send(
|
|
1684
|
+
msg.chat.id,
|
|
1685
|
+
`⏳ Предыдущий ход ${howLong} — твоё сообщение в очереди.\nЕсли это затянулось, /stop прервёт ход, и я сразу возьмусь за твоим.`,
|
|
1686
|
+
thread,
|
|
1687
|
+
);
|
|
1673
1688
|
}
|
|
1674
1689
|
: undefined,
|
|
1675
1690
|
);
|
package/control.mjs
CHANGED
|
@@ -354,7 +354,8 @@ export const CONTROL_COMMAND_HINT =
|
|
|
354
354
|
'Работа: /cd · /new · /stop · /send · /seed\n' +
|
|
355
355
|
'Сессии: /status · /sessions · /resume\n' +
|
|
356
356
|
'Модели: /models · /model · /prices\n' +
|
|
357
|
-
'Расширения: /skills · /
|
|
357
|
+
'Расширения: /skills · /soul\n' +
|
|
358
|
+
'Плагины: /plugins [headless] — список; /plugins add|remove|disable|enable <что> — действия\n' +
|
|
358
359
|
'Диагностика: /doctor · /logs\n' +
|
|
359
360
|
'Язык: /lang en|ru|zh\n' +
|
|
360
361
|
'Служебное: /app · /restart · /help';
|
|
@@ -363,7 +364,21 @@ export async function handleControl(ctx) {
|
|
|
363
364
|
const { command, arg, send, reply, log, profile, state, key, config } = ctx;
|
|
364
365
|
const say = (text) => reply(text);
|
|
365
366
|
|
|
366
|
-
|
|
367
|
+
// One command for one job: `/plugins` is both the list and the way to change
|
|
368
|
+
// things. Two near-identical names (`/plugin` / `/plugins`) invited the exact
|
|
369
|
+
// mistake of typing the plural with an action. The singular form stays as an
|
|
370
|
+
// alias so old instructions and muscle memory keep working.
|
|
371
|
+
let effective = command;
|
|
372
|
+
if (effective === '/plugins') {
|
|
373
|
+
const [maybeAction] = String(arg || '').trim().split(/\s+/);
|
|
374
|
+
// `/plugins add|remove|disable|enable …` is the same work as `/plugin …`;
|
|
375
|
+
// the case bodies read `arg`, which already holds the action and its value.
|
|
376
|
+
if (['add', 'remove', 'disable', 'enable'].includes((maybeAction || '').toLowerCase())) {
|
|
377
|
+
effective = '/plugin';
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
switch (effective) {
|
|
367
382
|
case '/status': {
|
|
368
383
|
const head = modelOf('headless');
|
|
369
384
|
const desk = modelOf('desktop');
|
|
@@ -452,6 +467,10 @@ export async function handleControl(ctx) {
|
|
|
452
467
|
...lines,
|
|
453
468
|
orphan.length ? `\nУстановлены, но не в bundles:\n${fmt(orphan)}` : '',
|
|
454
469
|
`\nОтключены строки: ${disabled.length ? disabled.join(', ') : 'нет'}`,
|
|
470
|
+
// The actions live behind the same command, so the list must say how
|
|
471
|
+
// to reach them — otherwise people keep hunting for `/plugin`.
|
|
472
|
+
`\nИзменить: /plugins add <пакет> · remove <имя> · disable <id> · enable <id>`,
|
|
473
|
+
'Профиль: добавь headless или desktop в конце.',
|
|
455
474
|
]
|
|
456
475
|
.filter(Boolean)
|
|
457
476
|
.join('\n'),
|
|
@@ -464,7 +483,7 @@ export async function handleControl(ctx) {
|
|
|
464
483
|
const target = (rest[0] === 'headless' || rest[0] === 'desktop') && rest.length > 1 ? rest.shift() : 'desktop';
|
|
465
484
|
const value = rest.join(' ').trim();
|
|
466
485
|
if (!action || !value) {
|
|
467
|
-
say('Использование:\n/
|
|
486
|
+
say('Использование:\n/plugins add <npm|github|file:>\n/plugins remove <name>\n/plugins disable <id>\n/plugins enable <id>\nПрофиль: допиши headless или desktop.\n\n/plugin — то же самое, старая форма.');
|
|
468
487
|
return true;
|
|
469
488
|
}
|
|
470
489
|
if (action === 'disable' || action === 'enable') {
|
package/package.json
CHANGED