@goodandready/dsh-messenger-gateway 0.3.0 → 0.3.1

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/lib/commands.js CHANGED
@@ -10,6 +10,9 @@ export const DEFAULT_TELEGRAM_COMMANDS = [
10
10
  { command: 'model', description: 'Показать или сменить модель' },
11
11
  { command: 'status', description: 'Статус шлюза' },
12
12
  { command: 'voice', description: 'Голосовые ответы: /voice on|off|status' },
13
+ { command: 'tts', description: 'Озвучка в этом чате: /tts on|off|status' },
14
+ { command: 'mute', description: 'Не присылать уведомления в этот чат' },
15
+ { command: 'unmute', description: 'Вернуть уведомления в этот чат' },
13
16
  ]
14
17
 
15
18
  export function normalizeTelegramCommands(commands) {
package/lib/gateway.js CHANGED
@@ -62,8 +62,14 @@ export class Gateway {
62
62
  const home = process.env.DSH_HOME || join(homedir(), '.dsh')
63
63
  this.pairing = createPairingStore(join(home, 'messenger-gateway', 'pairing.json'))
64
64
  this.voicePrefs = createVoicePrefs(join(home, 'messenger-gateway', 'voice-prefs.json'))
65
+ this.chatTts = createVoicePrefs(join(home, 'messenger-gateway', 'chat-tts.json'))
66
+ this.muted = createVoicePrefs(join(home, 'messenger-gateway', 'muted.json'))
67
+ this.stats = { sent: 0, errors: 0, startedAt: Date.now() }
65
68
  }
66
69
 
70
+ isMuted(chatId) { return this.muted.get(chatId) === true }
71
+ setMuted(chatId, on) { return this.muted.set(chatId, on) }
72
+
67
73
  baseUrl() {
68
74
  const raw = String(this.config.internalBaseURL || '').trim()
69
75
  return raw || 'http://127.0.0.1:3080'
@@ -130,6 +136,7 @@ export class Gateway {
130
136
  await adapter.start()
131
137
  this.adapterList.push(adapter)
132
138
  this.adapters.set(adapter.name, adapter)
139
+ if (adapter.name === 'telegram') this.tgAdapter = adapter
133
140
  this.ctx.logger?.info?.(`dsh-messenger-gateway: ${adapter.name} started`)
134
141
  } catch (err) {
135
142
  this.ctx.logger?.warn?.(`dsh-messenger-gateway: ${adapter.name}: ${err.message}`)
@@ -173,7 +180,9 @@ export class Gateway {
173
180
  if (!adapter?.sendTo) throw new Error(`adapter ${resolved.platform} unavailable`)
174
181
  try {
175
182
  await adapter.sendTo(resolved.chatId, payload, { threadId: resolved.threadId })
183
+ this.stats.sent++
176
184
  } catch (err) {
185
+ this.stats.errors++
177
186
  if (isTopicGoneError(err)) {
178
187
  this.logger?.warn?.(`messengerSend: topic gone for ${resolved.platform}:${resolved.chatId}:${resolved.threadId} — the chat/topic was deleted; skipping delivery`)
179
188
  return
@@ -362,7 +371,12 @@ export class Gateway {
362
371
  }
363
372
  return reply('Активной сессии нет.')
364
373
  }
365
- if (cmd === '/whoami') return reply(`Ваш id: ${userId}`)
374
+ if (cmd === '/whoami') {
375
+ const lines = [`Ваш id: ${userId}`]
376
+ if (chatId) lines.push(`chatId: ${chatId}`)
377
+ if (threadId) lines.push(`threadId: ${threadId}`)
378
+ return reply(lines.join('\n'))
379
+ }
366
380
  if (cmd === '/stop') {
367
381
  const chat = this.chats.get(key)
368
382
  if (chat?.turnActive || chat?.abort) {
@@ -386,6 +400,10 @@ export class Gateway {
386
400
  ? `home: chat ${home.chatId}${home.threadId ? ` topic ${home.threadId}` : ''}`
387
401
  : 'home: не задан'
388
402
  const pending = this.pairing.listPending().length
403
+ const up = Math.max(0, Math.round((Date.now() - this.stats.startedAt) / 1000))
404
+ const hh = String(Math.floor(up / 3600)).padStart(2, '0')
405
+ const mm = String(Math.floor((up % 3600) / 60)).padStart(2, '0')
406
+ const ss = String(up % 60).padStart(2, '0')
389
407
  return reply([
390
408
  'Messenger gateway',
391
409
  `адаптеры: ${[...this.adapters.keys()].join(', ') || '(нет)'}`,
@@ -395,6 +413,10 @@ export class Gateway {
395
413
  `pairing pending: ${pending}`,
396
414
  `transport: ${this.tg().transport || 'poll'}`,
397
415
  `sessionScope: ${this.config.agent?.sessionScope || 'user'}`,
416
+ `доставлено: ${this.stats.sent}`,
417
+ `ошибок: ${this.stats.errors}`,
418
+ `polling conflict: ${this.tgAdapter?.pollingConflict ? 'да' : 'нет'}`,
419
+ `uptime: ${hh}:${mm}:${ss}`,
398
420
  ].join('\n'))
399
421
  }
400
422
  if (cmd === '/model') {
@@ -466,6 +488,24 @@ export class Gateway {
466
488
  const prefLine = pref === null ? 'не задан (/voice on|off)' : (pref ? 'on' : 'off')
467
489
  return reply(`voiceMode=${mode}\nваш /voice: ${prefLine}\nglobal tts: ${this.config.tts?.enabled ? 'on' : 'off'}`)
468
490
  }
491
+ if (cmd === '/tts') {
492
+ const sub = String(parts[1] || 'status').toLowerCase()
493
+ if (sub === 'on' || sub === 'off') {
494
+ this.chatTts.set(chatId, sub === 'on')
495
+ return reply(sub === 'on' ? 'Озвучка в этом чате: on' : 'Озвучка в этом чате: off')
496
+ }
497
+ const cur = this.chatTts.get(chatId)
498
+ const line = cur === null ? 'не задан (/tts on|off)' : (cur ? 'on' : 'off')
499
+ return reply(`Озвучка в этом чате: ${line}\nglobal tts: ${this.config.tts?.enabled ? 'on' : 'off'}`)
500
+ }
501
+ if (cmd === '/mute') {
502
+ this.setMuted(chatId, true)
503
+ return reply('Уведомления в этот чат: выключены (/unmute)')
504
+ }
505
+ if (cmd === '/unmute') {
506
+ this.setMuted(chatId, false)
507
+ return reply('Уведомления в этот чат: включены')
508
+ }
469
509
  return reply(`Неизвестная команда ${cmd}. /help`)
470
510
  }
471
511
 
@@ -674,11 +714,13 @@ export class Gateway {
674
714
  await reply({ text: chunks[i] || undefined, files: i === 0 ? files : [] })
675
715
  }
676
716
  }
717
+ const chatTtsPref = this.chatTts.get(chat.target?.chatId)
677
718
  const speak = shouldSpeakReply({
678
719
  globalTts: Boolean(this.config.tts?.enabled),
679
720
  voiceMode: this.tg().voiceMode || 'mirror',
680
721
  inboundWasVoice: Boolean(inboundWasVoice),
681
722
  userPref: this.voicePrefs.get(userId),
723
+ chatPref: chatTtsPref,
682
724
  })
683
725
  if (speak && !signal.aborted) {
684
726
  const ttsText = prepareTtsText(answer, this.config.tts?.maxChars)
package/lib/index.js CHANGED
@@ -244,6 +244,8 @@ export function apply(ctx, config) {
244
244
  lines.push(`error: ${reason.error?.message || reason.error?.code || 'unknown'}`)
245
245
  }
246
246
  const homeName = cfg.home || 'default'
247
+ const home = gw.messenger.home?.() || null
248
+ if (home && gw.isMuted?.(home.chatId)) return
247
249
  gw.messengerSend({ platform: 'telegram', home: homeName }, { text: lines.join('\n') })
248
250
  .catch((e) => ctx.logger?.warn?.(`notify bridge: ${e.message}`))
249
251
  }), 'dsh-messenger-gateway: notify bridge')
@@ -33,7 +33,9 @@ export function createVoicePrefs(filePath) {
33
33
  }
34
34
 
35
35
  /** Decide whether to speak the reply. */
36
- export function shouldSpeakReply({ globalTts, voiceMode, inboundWasVoice, userPref }) {
36
+ export function shouldSpeakReply({ globalTts, voiceMode, inboundWasVoice, userPref, chatPref }) {
37
+ if (chatPref === false) return false
38
+ if (chatPref === true) return true
37
39
  if (globalTts) return true
38
40
  if (userPref === true) return true
39
41
  if (userPref === false) return false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-messenger-gateway",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Telegram messenger bridge for DeepSeek Harness: sessions, steer, homes, inline asks, notify bridge, and optional TTS voice notes.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -52,14 +52,14 @@
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@deepseek-ai/cordis": "^4.0.1",
55
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
56
- "@deepseek-ai/dsh-agent-loop": "^0.1.0-rc.6",
57
- "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
58
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
59
- "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
60
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
55
+ "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
56
+ "@deepseek-ai/dsh-agent-loop": "^0.1.1-rc.2",
57
+ "@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.2",
58
+ "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
59
+ "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
60
+ "@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
61
61
  "@deepseek-ai/schemastery": "^3.18.1",
62
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6"
62
+ "@deepseek-ai/dsh-tools": "^0.1.1-rc.2"
63
63
  },
64
64
  "publishConfig": {
65
65
  "access": "public"