@goodandready/dsh-messenger-gateway 0.3.22 → 0.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/lib/stream.js CHANGED
@@ -1,116 +1,116 @@
1
- /** Helpers for Telegram streaming edits + progress. */
2
-
3
- export function extractTextDelta(chunk) {
4
- if (!chunk || typeof chunk !== 'object') return ''
5
- if (chunk.type === 'text-delta' && typeof chunk.text === 'string') return chunk.text
6
- return ''
7
- }
8
-
9
- export function extractToolName(eventData) {
10
- const name = eventData?.tool?.name || eventData?.toolName || eventData?.name
11
- return name ? String(name) : ''
12
- }
13
-
14
- export function formatProgressLine(toolName, locale = 'en') {
15
- if (!toolName) {
16
- return String(locale || '').toLowerCase().startsWith('zh') ? '⏳ 思考中…' : '⏳ Thinking…'
17
- }
18
- return `🔧 ${toolName}…`
19
- }
20
-
21
- export function buildStreamPreview(streamText, toolName, maxLen = 3500) {
22
- const body = String(streamText || '').trimEnd()
23
- const head = toolName ? `${formatProgressLine(toolName)}\n\n` : ''
24
- const combined = head + (body || '…')
25
- if (combined.length <= maxLen) return combined
26
- return combined.slice(0, maxLen - 1) + '…'
27
- }
28
-
29
- export function parseRetryAfter(err) {
30
- if (!err) return 0
31
- const msg = err.message || String(err)
32
- const match = /retry after (\d+)/i.exec(msg)
33
- if (match) return parseInt(match[1], 10) * 1000
34
- return 0
35
- }
36
-
37
- export function createEditScheduler(editFn, intervalMs = 1200, firstChunkDelayMs = 250) {
38
- let pending = null
39
- let timer = null
40
- let lastSent = ''
41
- let inFlight = Promise.resolve()
42
- let retryDelayMs = 0
43
-
44
- const flush = () => {
45
- timer = null
46
- if (pending === null || pending === lastSent) return
47
- const text = pending
48
- inFlight = inFlight.then(async () => {
49
- try {
50
- if (retryDelayMs > 0) {
51
- const delay = retryDelayMs
52
- retryDelayMs = 0
53
- await new Promise((r) => setTimeout(r, delay))
54
- }
55
- await editFn(text)
56
- lastSent = text
57
- } catch (err) {
58
- const retry = parseRetryAfter(err)
59
- if (retry > 0) {
60
- retryDelayMs = retry
61
- // Re-schedule flush with retry delay
62
- if (!timer) {
63
- timer = setTimeout(flush, retry + 100)
64
- timer.unref?.()
65
- }
66
- }
67
- }
68
- })
69
- }
70
-
71
- return {
72
- push(text) {
73
- pending = text
74
- if (timer) return
75
- // Fast first chunk: when lastSent is empty, dispatch quickly so user sees immediate response.
76
- // Subsequent edits throttle to intervalMs to respect Telegram rate limits.
77
- const delay = !lastSent ? Math.min(firstChunkDelayMs, intervalMs) : Math.max(200, intervalMs)
78
- timer = setTimeout(flush, delay)
79
- timer.unref?.()
80
- },
81
- async flush() {
82
- if (timer) { clearTimeout(timer); timer = null }
83
- flush()
84
- await inFlight
85
- // If there's still a pending difference (e.g. rate limit delay happened), wait and retry
86
- if (pending !== null && pending !== lastSent) {
87
- if (retryDelayMs > 0) {
88
- await new Promise((r) => setTimeout(r, retryDelayMs + 50))
89
- retryDelayMs = 0
90
- }
91
- try {
92
- await editFn(pending)
93
- lastSent = pending
94
- } catch {}
95
- }
96
- },
97
- }
98
- }
99
-
100
- export function startTypingHeartbeat(typingFn, intervalMs = 4000) {
101
- if (typeof typingFn !== 'function') return () => {}
102
- let active = true
103
- const safeInvoke = () => {
104
- if (!active) return
105
- try {
106
- typingFn()?.catch?.(() => {})
107
- } catch {}
108
- }
109
- safeInvoke()
110
- const timer = setInterval(safeInvoke, intervalMs)
111
- timer.unref?.()
112
- return () => {
113
- active = false
114
- clearInterval(timer)
115
- }
1
+ /** Helpers for Telegram streaming edits + progress. */
2
+
3
+ export function extractTextDelta(chunk) {
4
+ if (!chunk || typeof chunk !== 'object') return ''
5
+ if (chunk.type === 'text-delta' && typeof chunk.text === 'string') return chunk.text
6
+ return ''
7
+ }
8
+
9
+ export function extractToolName(eventData) {
10
+ const name = eventData?.tool?.name || eventData?.toolName || eventData?.name
11
+ return name ? String(name) : ''
12
+ }
13
+
14
+ export function formatProgressLine(toolName, locale = 'en') {
15
+ if (!toolName) {
16
+ return String(locale || '').toLowerCase().startsWith('zh') ? '⏳ 思考中…' : '⏳ Thinking…'
17
+ }
18
+ return `🔧 ${toolName}…`
19
+ }
20
+
21
+ export function buildStreamPreview(streamText, toolName, maxLen = 3500) {
22
+ const body = String(streamText || '').trimEnd()
23
+ const head = toolName ? `${formatProgressLine(toolName)}\n\n` : ''
24
+ const combined = head + (body || '…')
25
+ if (combined.length <= maxLen) return combined
26
+ return combined.slice(0, maxLen - 1) + '…'
27
+ }
28
+
29
+ export function parseRetryAfter(err) {
30
+ if (!err) return 0
31
+ const msg = err.message || String(err)
32
+ const match = /retry after (\d+)/i.exec(msg)
33
+ if (match) return parseInt(match[1], 10) * 1000
34
+ return 0
35
+ }
36
+
37
+ export function createEditScheduler(editFn, intervalMs = 1200, firstChunkDelayMs = 250) {
38
+ let pending = null
39
+ let timer = null
40
+ let lastSent = ''
41
+ let inFlight = Promise.resolve()
42
+ let retryDelayMs = 0
43
+
44
+ const flush = () => {
45
+ timer = null
46
+ if (pending === null || pending === lastSent) return
47
+ const text = pending
48
+ inFlight = inFlight.then(async () => {
49
+ try {
50
+ if (retryDelayMs > 0) {
51
+ const delay = retryDelayMs
52
+ retryDelayMs = 0
53
+ await new Promise((r) => setTimeout(r, delay))
54
+ }
55
+ await editFn(text)
56
+ lastSent = text
57
+ } catch (err) {
58
+ const retry = parseRetryAfter(err)
59
+ if (retry > 0) {
60
+ retryDelayMs = retry
61
+ // Re-schedule flush with retry delay
62
+ if (!timer) {
63
+ timer = setTimeout(flush, retry + 100)
64
+ timer.unref?.()
65
+ }
66
+ }
67
+ }
68
+ })
69
+ }
70
+
71
+ return {
72
+ push(text) {
73
+ pending = text
74
+ if (timer) return
75
+ // Fast first chunk: when lastSent is empty, dispatch quickly so user sees immediate response.
76
+ // Subsequent edits throttle to intervalMs to respect Telegram rate limits.
77
+ const delay = !lastSent ? Math.min(firstChunkDelayMs, intervalMs) : Math.max(200, intervalMs)
78
+ timer = setTimeout(flush, delay)
79
+ timer.unref?.()
80
+ },
81
+ async flush() {
82
+ if (timer) { clearTimeout(timer); timer = null }
83
+ flush()
84
+ await inFlight
85
+ // If there's still a pending difference (e.g. rate limit delay happened), wait and retry
86
+ if (pending !== null && pending !== lastSent) {
87
+ if (retryDelayMs > 0) {
88
+ await new Promise((r) => setTimeout(r, retryDelayMs + 50))
89
+ retryDelayMs = 0
90
+ }
91
+ try {
92
+ await editFn(pending)
93
+ lastSent = pending
94
+ } catch { /* safe best-effort final stream update delivery */ }
95
+ }
96
+ },
97
+ }
98
+ }
99
+
100
+ export function startTypingHeartbeat(typingFn, intervalMs = 4000) {
101
+ if (typeof typingFn !== 'function') return () => {}
102
+ let active = true
103
+ const safeInvoke = () => {
104
+ if (!active) return
105
+ try {
106
+ typingFn()?.catch?.(() => { /* safe best-effort typing indicator */ })
107
+ } catch { /* safe best-effort typing indicator */ }
108
+ }
109
+ safeInvoke()
110
+ const timer = setInterval(safeInvoke, intervalMs)
111
+ timer.unref?.()
112
+ return () => {
113
+ active = false
114
+ clearInterval(timer)
115
+ }
116
116
  }
File without changes
package/lib/topics.js CHANGED
@@ -25,20 +25,6 @@ export function sessionKey({ platform, chatId, threadId = 0, userId, chatType, s
25
25
  return base
26
26
  }
27
27
 
28
- export function parseChatKey(key) {
29
- const parts = String(key || "").split(":")
30
- if (parts.length < 2) return null
31
- const platform = parts[0]
32
- const chatId = parts[1]
33
- const threadId = parts.length >= 3 ? normalizeThreadId(parts[2]) : 0
34
- const out = { platform, chatId, threadId }
35
- if (parts.length >= 5 && parts[3] === "u") {
36
- const userId = Number(parts[4])
37
- if (Number.isFinite(userId)) out.userId = userId
38
- }
39
- return out
40
- }
41
-
42
28
  /** Extra Telegram API fields for forum topic replies. */
43
29
  export function telegramThreadParams(threadId) {
44
30
  const tid = normalizeThreadId(threadId)
package/lib/tts.js CHANGED
@@ -57,16 +57,6 @@ export function isOggOpusMime(mime) {
57
57
  return m.includes('ogg') || m.includes('opus')
58
58
  }
59
59
 
60
- export function voiceReplyFile(spoken) {
61
- const mime = spoken?.mime || 'audio/mpeg'
62
- return {
63
- bytes: spoken.audio,
64
- mime,
65
- kind: 'voice',
66
- name: voiceFileNameForMime(mime),
67
- }
68
- }
69
-
70
60
  function runFfmpeg(args) {
71
61
  return new Promise((resolve, reject) => {
72
62
  const child = spawn('ffmpeg', args, { stdio: ['ignore', 'ignore', 'pipe'] })
@@ -10,25 +10,29 @@ function writeJsonAtomicSync(filePath, data) {
10
10
  writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf8")
11
11
  renameSync(tmpPath, filePath)
12
12
  } catch (err) {
13
- try { unlinkSync(tmpPath) } catch {}
13
+ try { unlinkSync(tmpPath) } catch { /* safe best-effort temp file cleanup */ }
14
14
  throw err
15
15
  }
16
16
  }
17
17
 
18
- export function createVoicePrefs(filePath) {
18
+ export function createVoicePrefs(filePath, { logger } = {}) {
19
19
  /** @type {Record<string, boolean>} */
20
20
  let state = {}
21
21
  if (filePath && existsSync(filePath)) {
22
22
  try {
23
23
  const raw = JSON.parse(readFileSync(filePath, "utf8"))
24
24
  if (raw && typeof raw === "object") state = raw
25
- } catch {}
25
+ } catch (err) {
26
+ logger?.warn?.('[dsh-messenger-gateway] voice prefs read error:', err?.message || err)
27
+ }
26
28
  }
27
29
  const persist = () => {
28
30
  if (!filePath) return
29
31
  try {
30
32
  writeJsonAtomicSync(filePath, state)
31
- } catch {}
33
+ } catch (err) {
34
+ logger?.warn?.('[dsh-messenger-gateway] voice prefs persist error:', err?.message || err)
35
+ }
32
36
  }
33
37
  const key = (userId) => String(Number(userId) || userId || "")
34
38
  return {
package/package.json CHANGED
@@ -1,65 +1,69 @@
1
- {
2
- "name": "@goodandready/dsh-messenger-gateway",
3
- "version": "0.3.22",
4
- "description": "Telegram messenger bridge for DeepSeek Harness: sessions, steer, homes, inline asks, notify bridge, and optional TTS voice notes.",
5
- "license": "MIT",
6
- "type": "module",
7
- "main": "./lib/index.js",
8
- "exports": {
9
- ".": "./lib/index.js",
10
- "./client": "./lib/client.js",
11
- "./package.json": "./package.json",
12
- "./cordis.patch.yml": "./cordis.patch.yml"
13
- },
14
- "files": [
15
- "lib/",
16
- "cordis.patch.yml",
17
- "README.md",
18
- "LICENSE"
19
- ],
20
- "keywords": [
21
- "dsh",
22
- "dsh-plugin",
23
- "telegram",
24
- "messenger",
25
- "deepseek-harness",
26
- "bot"
27
- ],
28
- "repository": {
29
- "type": "git",
30
- "url": "git+https://github.com/GooDAnDReaDY/dsh-messenger-gateway.git"
31
- },
32
- "homepage": "https://github.com/GooDAnDReaDY/dsh-messenger-gateway#readme",
33
- "scripts": {
34
- "test": "node --test test/*.test.mjs",
35
- "test:load": "node scripts/verify-dsh-load.mjs",
36
- "test:all": "npm test && npm run test:load",
37
- "smoke:http": "node scripts/smoke-http.mjs"
38
- },
39
- "dsh": {
40
- "bundle": {
41
- "patch": "./cordis.patch.yml"
42
- },
43
- "client": {
44
- "platform": "web",
45
- "inject": []
46
- }
47
- },
48
- "peerDependencies": {
49
- "@deepseek-ai/cordis": "^4.0.2",
50
- "@deepseek-ai/dsh-agent": "^0.1.2-alpha.2",
51
- "@deepseek-ai/dsh-agent-loop": "^0.1.2-alpha.2",
52
- "@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.2",
53
- "@deepseek-ai/dsh-llm": "^0.1.2-alpha.2",
54
- "@deepseek-ai/dsh-session": "^0.1.2-alpha.2",
55
- "@deepseek-ai/dsh-settings": "^0.1.2-alpha.2",
56
- "@deepseek-ai/schemastery": "^3.18.2",
57
- "@deepseek-ai/dsh-tools": "^0.1.2-alpha.2"
58
- },
59
- "publishConfig": {
60
- "access": "public"
61
- },
62
- "bugs": {
63
- "url": "https://github.com/GooDAnDReaDY/dsh-messenger-gateway/issues"
64
- }
65
- }
1
+ {
2
+ "name": "@goodandready/dsh-messenger-gateway",
3
+ "version": "0.4.0",
4
+ "description": "Telegram messenger bridge for DeepSeek Harness: sessions, steer, homes, inline asks, notify bridge, and optional TTS voice notes.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./lib/index.js",
8
+ "exports": {
9
+ ".": "./lib/index.js",
10
+ "./client": "./lib/client.js",
11
+ "./package.json": "./package.json",
12
+ "./cordis.patch.yml": "./cordis.patch.yml"
13
+ },
14
+ "files": [
15
+ "lib/",
16
+ "cordis.patch.yml",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "keywords": [
21
+ "dsh",
22
+ "dsh-plugin",
23
+ "telegram",
24
+ "messenger",
25
+ "deepseek-harness",
26
+ "bot"
27
+ ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/GooDAnDReaDY/dsh-messenger-gateway.git"
31
+ },
32
+ "homepage": "https://github.com/GooDAnDReaDY/dsh-messenger-gateway#readme",
33
+ "scripts": {
34
+ "test": "node --test test/*.test.mjs",
35
+ "test:load": "node scripts/verify-dsh-load.mjs",
36
+ "test:all": "npm test && npm run test:load",
37
+ "smoke:http": "node scripts/smoke-http.mjs"
38
+ },
39
+ "dsh": {
40
+ "bundle": {
41
+ "patch": "./cordis.patch.yml"
42
+ },
43
+ "client": {
44
+ "platform": "web",
45
+ "inject": [
46
+ "@deepseek-ai/dsh-client-locale",
47
+ "@deepseek-ai/dsh-client-ui-slots",
48
+ "@deepseek-ai/dsh-client-ui-settings"
49
+ ]
50
+ }
51
+ },
52
+ "peerDependencies": {
53
+ "@deepseek-ai/cordis": "^4.0.2",
54
+ "@deepseek-ai/dsh-agent": "^0.1.2-alpha.2",
55
+ "@deepseek-ai/dsh-agent-loop": "^0.1.2-alpha.2",
56
+ "@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.2",
57
+ "@deepseek-ai/dsh-llm": "^0.1.2-alpha.2",
58
+ "@deepseek-ai/dsh-session": "^0.1.2-alpha.2",
59
+ "@deepseek-ai/dsh-settings": "^0.1.2-alpha.2",
60
+ "@deepseek-ai/schemastery": "^3.18.2",
61
+ "@deepseek-ai/dsh-tools": "^0.1.2-alpha.2"
62
+ },
63
+ "publishConfig": {
64
+ "access": "public"
65
+ },
66
+ "bugs": {
67
+ "url": "https://github.com/GooDAnDReaDY/dsh-messenger-gateway/issues"
68
+ }
69
+ }