@goodandready/dsh-messenger-gateway 0.1.0 → 0.2.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/README.md +68 -19
- package/lib/adapters/index.js +12 -2
- package/lib/adapters/telegram.js +324 -47
- package/lib/client.js +471 -46
- package/lib/commands.js +30 -0
- package/lib/config.js +75 -0
- package/lib/documents.js +19 -5
- package/lib/gateway.js +476 -55
- package/lib/groups.js +72 -0
- package/lib/homes.js +58 -0
- package/lib/index.js +261 -39
- package/lib/messenger-api.js +5 -5
- package/lib/pairing.js +110 -0
- package/lib/stream.js +64 -0
- package/lib/telegram-format.js +39 -0
- package/lib/text.js +18 -1
- package/lib/topics.js +23 -4
- package/lib/tts.js +82 -2
- package/lib/voice-prefs.js +44 -0
- package/package.json +15 -9
- package/docs/architecture/2026-08-23-messenger-gateway-design.md +0 -30
- package/docs/deployment/0.1.0-install.md +0 -28
- package/docs/testing/0.1.0-smoke.md +0 -12
package/lib/config.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import z from '@deepseek-ai/schemastery'
|
|
2
|
+
|
|
3
|
+
const HomeEntry = z.object({
|
|
4
|
+
name: z.string().default('default'),
|
|
5
|
+
chatId: z.union([z.number(), z.string()]),
|
|
6
|
+
threadId: z.number().default(0),
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
export const PluginConfig = z.object({
|
|
10
|
+
enabled: z.boolean().default(true),
|
|
11
|
+
internalBaseURL: z.string().default('http://127.0.0.1:3080'),
|
|
12
|
+
telegram: z.object({
|
|
13
|
+
enabled: z.boolean().default(false),
|
|
14
|
+
botToken: z.string().role('secret').default(''),
|
|
15
|
+
allowedUserIds: z.array(z.number()).default([]),
|
|
16
|
+
pollTimeoutSeconds: z.number().default(50),
|
|
17
|
+
pollIntervalMs: z.number().default(500),
|
|
18
|
+
commands: z.array(z.object({
|
|
19
|
+
command: z.string(),
|
|
20
|
+
description: z.string(),
|
|
21
|
+
})).default([]),
|
|
22
|
+
textFormat: z.union([z.const('html'), z.const('plain')]).default('html').description('html: Markdown→Telegram HTML; plain: без разметки'),
|
|
23
|
+
homeChatId: z.union([z.number(), z.string()]).default('').description('Legacy default home chatId'),
|
|
24
|
+
homeThreadId: z.number().default(0),
|
|
25
|
+
homes: z.array(HomeEntry).default([]).description('Named homes (forum topics / channels)'),
|
|
26
|
+
pairingEnabled: z.boolean().default(true).description('Issue pairing codes to unknown users when allowlist is non-empty'),
|
|
27
|
+
streaming: z.boolean().default(false).description('If true, edit one Telegram message while tokens arrive; default off'),
|
|
28
|
+
streamEditIntervalMs: z.number().default(1200),
|
|
29
|
+
progressEnabled: z.boolean().default(true),
|
|
30
|
+
approvalsEnabled: z.boolean().default(true),
|
|
31
|
+
groupsEnabled: z.boolean().default(true).description('Process group/supergroup messages'),
|
|
32
|
+
groupRequireMention: z.boolean().default(true).description('In groups, only respond to @mention, reply-to-bot, or /commands'),
|
|
33
|
+
reactionsEnabled: z.boolean().default(true).description('React with 👀 while processing a turn'),
|
|
34
|
+
transport: z.union([z.const('poll'), z.const('webhook')]).default('poll'),
|
|
35
|
+
webhookUrl: z.string().default(''),
|
|
36
|
+
webhookSecret: z.string().role('secret').default(''),
|
|
37
|
+
webhookPath: z.string().default('/dsh-messenger-gateway/telegram/webhook'),
|
|
38
|
+
voiceMode: z.union([z.const('mirror'), z.const('always'), z.const('off')]).default('mirror')
|
|
39
|
+
.description('mirror: TTS when inbound was voice; always/off override (per-user /voice wins)'),
|
|
40
|
+
notifyBridge: z.object({
|
|
41
|
+
enabled: z.boolean().default(false),
|
|
42
|
+
events: z.array(z.string()).default(['task_done', 'error']),
|
|
43
|
+
home: z.string().default('default'),
|
|
44
|
+
excludeSessionPrefixes: z.array(z.string()).default(['msgw-']),
|
|
45
|
+
}).default({ enabled: false }),
|
|
46
|
+
}),
|
|
47
|
+
discord: z.object({
|
|
48
|
+
enabled: z.boolean().default(false),
|
|
49
|
+
botToken: z.string().role('secret').default(''),
|
|
50
|
+
}),
|
|
51
|
+
media: z.object({
|
|
52
|
+
cacheDir: z.string().default(''),
|
|
53
|
+
maxDocBytes: z.number().default(20 * 1024 * 1024),
|
|
54
|
+
maxTextInjectBytes: z.number().default(100 * 1024),
|
|
55
|
+
maxImageBytes: z.number().default(20 * 1024 * 1024),
|
|
56
|
+
}),
|
|
57
|
+
tts: z.object({
|
|
58
|
+
enabled: z.boolean().default(false),
|
|
59
|
+
maxChars: z.number().default(4000),
|
|
60
|
+
}),
|
|
61
|
+
agent: z.object({
|
|
62
|
+
provider: z.string().default(''),
|
|
63
|
+
model: z.string().default(''),
|
|
64
|
+
cwd: z.string().default(''),
|
|
65
|
+
instructionPrefix: z.string().default(''),
|
|
66
|
+
maxMessageLength: z.number().default(4000),
|
|
67
|
+
idleTimeoutMs: z.number().default(3_600_000),
|
|
68
|
+
turnTimeoutMs: z.number().default(600_000),
|
|
69
|
+
photoOnlyMode: z.union([z.const('prompt'), z.const('run')]).default('prompt'),
|
|
70
|
+
sessionScope: z.union([z.const('chat'), z.const('user')]).default('user')
|
|
71
|
+
.description('In groups: user = per-user session; chat = shared session for the chat/topic'),
|
|
72
|
+
}),
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
export const Config = PluginConfig
|
package/lib/documents.js
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
/** Format a cached inbound document/video path for the agent (fs tools). */
|
|
2
2
|
export function formatInboundDocument(att) {
|
|
3
3
|
const kind = att?.kind || 'document'
|
|
4
|
-
const
|
|
4
|
+
const labels = { video: 'Видео', sticker: 'Стикер', animation: 'GIF', document: 'Документ' }
|
|
5
|
+
const label = labels[kind] || 'Файл'
|
|
5
6
|
const name = att?.name ? ` ${att.name}` : ''
|
|
6
7
|
const mime = att?.mime ? ` (${att.mime})` : ''
|
|
7
|
-
|
|
8
|
+
const emoji = att?.emoji ? ` emoji=${att.emoji}` : ''
|
|
9
|
+
return `[${label}${name}${mime}${emoji}]\nПуть: ${att.path}`
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export function documentOnlyHint(attachments, userText) {
|
|
11
13
|
if (String(userText || '').trim()) return ''
|
|
12
|
-
const docs = attachments.filter((a) =>
|
|
14
|
+
const docs = attachments.filter((a) => ['document', 'video', 'sticker', 'animation'].includes(a.kind))
|
|
13
15
|
if (!docs.length) return ''
|
|
14
|
-
if (docs.length === 1)
|
|
16
|
+
if (docs.length === 1) {
|
|
17
|
+
const k = docs[0].kind
|
|
18
|
+
if (k === 'sticker') return '[Пользователь отправил стикер]'
|
|
19
|
+
if (k === 'video' || k === 'animation') return '[Пользователь отправил видео]'
|
|
20
|
+
return '[Пользователь отправил файл]'
|
|
21
|
+
}
|
|
15
22
|
return `[Пользователь отправил ${docs.length} файла]`
|
|
16
|
-
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const TEXT_INJECT_EXTS = new Set([
|
|
26
|
+
'.md', '.txt', '.csv', '.log', '.json', '.xml', '.yaml', '.yml', '.toml', '.ini', '.cfg',
|
|
27
|
+
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py', '.sh', '.bash', '.zsh', '.ps1',
|
|
28
|
+
'.go', '.rs', '.java', '.kt', '.c', '.h', '.cpp', '.hpp', '.cs', '.rb', '.php',
|
|
29
|
+
'.html', '.css', '.scss', '.sql', '.r', '.swift', '.vue', '.svelte',
|
|
30
|
+
])
|