@goodandready/dsh-messenger-gateway 0.3.13 → 0.3.18
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 +6 -0
- package/lib/adapters/telegram.js +46 -3
- package/lib/client.js +1307 -629
- package/lib/content-guard.js +14 -0
- package/lib/gateway.js +81 -45
- package/lib/index.js +49 -15
- package/lib/models.js +4 -3
- package/lib/outbound.js +2 -1
- package/lib/pairing.js +19 -5
- package/lib/photos.js +2 -1
- package/lib/scheduler.js +5 -5
- package/lib/session-ops.js +14 -4
- package/lib/storage-atomic.js +21 -0
- package/lib/stream.js +39 -2
- package/lib/telegram-errors.js +51 -41
- package/lib/voice-prefs.js +19 -4
- package/package.json +2 -5
package/lib/telegram-errors.js
CHANGED
|
@@ -1,41 +1,51 @@
|
|
|
1
|
-
// Network error classification for Telegram sends (mirrors Hermes adapter policy).
|
|
2
|
-
//
|
|
3
|
-
// The key distinction: some network failures mean the request NEVER left the
|
|
4
|
-
// process (connect/pool timeout, ECONNRESET before send) → resending is safe.
|
|
5
|
-
// Others (a generic timeout after the request may have reached Telegram) could
|
|
6
|
-
// duplicate a message if we resend → do NOT retry.
|
|
7
|
-
|
|
8
|
-
function rootCause(err) {
|
|
9
|
-
let e = err
|
|
10
|
-
let depth = 0
|
|
11
|
-
while (e && e.cause && e.cause !== e && depth < 10) {
|
|
12
|
-
e = e.cause
|
|
13
|
-
depth++
|
|
14
|
-
}
|
|
15
|
-
return e || err
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// True when a resend cannot duplicate a message: the request did not reach Telegram.
|
|
19
|
-
export function isResendSafeNetworkError(err) {
|
|
20
|
-
const c = rootCause(err)
|
|
21
|
-
const msg = String(c?.message || err?.message || '').toLowerCase()
|
|
22
|
-
if (/not sent to telegram|connect timeout|und_err_connect|econnreset|enotfound|econnrefused|ECONNRESET|ENOTFOUND|ECONNREFUSED/i.test(msg)) {
|
|
23
|
-
return true
|
|
24
|
-
}
|
|
25
|
-
// undici PoolTimeout message: "Request was *not* sent to Telegram."
|
|
26
|
-
if (/pool timeout|request was \*?not\*? sent/i.test(msg)) return true
|
|
27
|
-
return false
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// True for a 409 from getUpdates: a second bot instance polls the same token.
|
|
31
|
-
export function isPollingConflict(err) {
|
|
32
|
-
const msg = String(err?.message || '').toLowerCase()
|
|
33
|
-
return /terminated by other getupdates request|another bot instance is running|getupdates.*conflict|conflict.*getupdates/i.test(msg)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// True when the target chat/topic no longer exists (deleted/closed/upgraded).
|
|
37
|
-
// Such sends should not be retried and any pending ask binding must be pruned.
|
|
38
|
-
export function isTopicGoneError(err) {
|
|
39
|
-
const msg = String(err?.message || '').toLowerCase()
|
|
40
|
-
return /thread not found|message thread not found|topic[_ ]?(not found|closed|deleted)|chat (not found|was (upgraded|deleted))|group chat was (upgraded|deleted)/i.test(msg)
|
|
41
|
-
}
|
|
1
|
+
// Network error classification for Telegram sends (mirrors Hermes adapter policy).
|
|
2
|
+
//
|
|
3
|
+
// The key distinction: some network failures mean the request NEVER left the
|
|
4
|
+
// process (connect/pool timeout, ECONNRESET before send) → resending is safe.
|
|
5
|
+
// Others (a generic timeout after the request may have reached Telegram) could
|
|
6
|
+
// duplicate a message if we resend → do NOT retry.
|
|
7
|
+
|
|
8
|
+
function rootCause(err) {
|
|
9
|
+
let e = err
|
|
10
|
+
let depth = 0
|
|
11
|
+
while (e && e.cause && e.cause !== e && depth < 10) {
|
|
12
|
+
e = e.cause
|
|
13
|
+
depth++
|
|
14
|
+
}
|
|
15
|
+
return e || err
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// True when a resend cannot duplicate a message: the request did not reach Telegram.
|
|
19
|
+
export function isResendSafeNetworkError(err) {
|
|
20
|
+
const c = rootCause(err)
|
|
21
|
+
const msg = String(c?.message || err?.message || '').toLowerCase()
|
|
22
|
+
if (/not sent to telegram|connect timeout|und_err_connect|econnreset|enotfound|econnrefused|ECONNRESET|ENOTFOUND|ECONNREFUSED/i.test(msg)) {
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
// undici PoolTimeout message: "Request was *not* sent to Telegram."
|
|
26
|
+
if (/pool timeout|request was \*?not\*? sent/i.test(msg)) return true
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// True for a 409 from getUpdates: a second bot instance polls the same token.
|
|
31
|
+
export function isPollingConflict(err) {
|
|
32
|
+
const msg = String(err?.message || '').toLowerCase()
|
|
33
|
+
return /terminated by other getupdates request|another bot instance is running|getupdates.*conflict|conflict.*getupdates/i.test(msg)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// True when the target chat/topic no longer exists (deleted/closed/upgraded).
|
|
37
|
+
// Such sends should not be retried and any pending ask binding must be pruned.
|
|
38
|
+
export function isTopicGoneError(err) {
|
|
39
|
+
const msg = String(err?.message || '').toLowerCase()
|
|
40
|
+
return /thread not found|message thread not found|topic[_ ]?(not found|closed|deleted)|chat (not found|was (upgraded|deleted))|group chat was (upgraded|deleted)/i.test(msg)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Calculates exponential backoff with max cap for polling retries.
|
|
45
|
+
* errorCount is 1-indexed.
|
|
46
|
+
*/
|
|
47
|
+
export function computePollBackoffMs(baseIntervalMs, errorCount, maxDelayMs = 30_000) {
|
|
48
|
+
const count = Math.max(1, Math.min(10, Number(errorCount) || 1))
|
|
49
|
+
const base = Math.max(100, Number(baseIntervalMs) || 500)
|
|
50
|
+
return Math.min(maxDelayMs, base * Math.pow(2, count - 1))
|
|
51
|
+
}
|
package/lib/voice-prefs.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs"
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, renameSync, unlinkSync } from "node:fs"
|
|
2
2
|
import { dirname } from "node:path"
|
|
3
|
+
import { randomUUID } from "node:crypto"
|
|
4
|
+
|
|
5
|
+
function writeJsonAtomicSync(filePath, data) {
|
|
6
|
+
const dir = dirname(filePath)
|
|
7
|
+
mkdirSync(dir, { recursive: true })
|
|
8
|
+
const tmpPath = `${filePath}.${randomUUID().slice(0, 8)}.tmp`
|
|
9
|
+
try {
|
|
10
|
+
writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf8")
|
|
11
|
+
renameSync(tmpPath, filePath)
|
|
12
|
+
} catch (err) {
|
|
13
|
+
try { unlinkSync(tmpPath) } catch {}
|
|
14
|
+
throw err
|
|
15
|
+
}
|
|
16
|
+
}
|
|
3
17
|
|
|
4
18
|
export function createVoicePrefs(filePath) {
|
|
5
19
|
/** @type {Record<string, boolean>} */
|
|
@@ -12,8 +26,9 @@ export function createVoicePrefs(filePath) {
|
|
|
12
26
|
}
|
|
13
27
|
const persist = () => {
|
|
14
28
|
if (!filePath) return
|
|
15
|
-
|
|
16
|
-
|
|
29
|
+
try {
|
|
30
|
+
writeJsonAtomicSync(filePath, state)
|
|
31
|
+
} catch {}
|
|
17
32
|
}
|
|
18
33
|
const key = (userId) => String(Number(userId) || userId || "")
|
|
19
34
|
return {
|
|
@@ -43,4 +58,4 @@ export function shouldSpeakReply({ globalTts, voiceMode, inboundWasVoice, userPr
|
|
|
43
58
|
if (voiceMode === "off") return false
|
|
44
59
|
// mirror: speak if inbound was voice
|
|
45
60
|
return Boolean(inboundWasVoice)
|
|
46
|
-
}
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-messenger-gateway",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.18",
|
|
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",
|
|
@@ -42,10 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"client": {
|
|
44
44
|
"platform": "web",
|
|
45
|
-
"inject": [
|
|
46
|
-
"@deepseek-ai/dsh-client-locale",
|
|
47
|
-
"@deepseek-ai/dsh-client-ui-settings"
|
|
48
|
-
]
|
|
45
|
+
"inject": []
|
|
49
46
|
}
|
|
50
47
|
},
|
|
51
48
|
"peerDependencies": {
|