@goodandready/dsh-messenger-gateway 0.3.21 → 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/LICENSE +1 -1
- package/cordis.patch.yml +1 -1
- package/lib/adapters/discord.js +9 -1
- package/lib/adapters/slack.js +7 -1
- package/lib/adapters/telegram-inbound.js +141 -0
- package/lib/adapters/telegram.js +42 -154
- package/lib/alerts.js +104 -63
- package/lib/api-health.js +38 -0
- package/lib/artifacts.js +118 -118
- package/lib/ask.js +203 -152
- package/lib/client.js +1660 -1312
- package/lib/commands.js +53 -0
- package/lib/config.js +104 -104
- package/lib/content-guard.js +14 -14
- package/lib/documents.js +1 -1
- package/lib/file-manager.js +27 -1
- package/lib/forum-mirror.js +105 -0
- package/lib/gateway-callbacks.js +187 -0
- package/lib/gateway-commands.js +584 -0
- package/lib/gateway-turn.js +285 -0
- package/lib/gateway.js +590 -1737
- package/lib/http.js +12 -1
- package/lib/index.js +48 -8
- package/lib/integrations.js +1 -1
- package/lib/locales/en.js +123 -114
- package/lib/locales/index.js +21 -21
- package/lib/locales/zh.js +123 -114
- package/lib/media.js +1 -9
- package/lib/models.js +6 -3
- package/lib/outbound.js +1 -1
- package/lib/pairing.js +8 -4
- package/lib/personas.js +8 -4
- package/lib/photos.js +1 -1
- package/lib/scheduler.js +14 -4
- package/lib/stream.js +115 -115
- package/lib/text.js +39 -39
- package/lib/topics.js +0 -14
- package/lib/tts.js +0 -10
- package/lib/updater.js +304 -0
- package/lib/voice-prefs.js +8 -4
- package/package.json +69 -65
package/lib/http.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { timingSafeEqual } from 'node:crypto'
|
|
2
|
+
|
|
1
3
|
export async function readBody(req) {
|
|
2
4
|
const chunks = []
|
|
3
5
|
for await (const chunk of req) chunks.push(chunk)
|
|
@@ -15,4 +17,13 @@ export function isTrustedSettingsRequest(req) {
|
|
|
15
17
|
const host = String(req.headers?.host || '')
|
|
16
18
|
if (!origin || !host) return false
|
|
17
19
|
try { return new URL(origin).host === host } catch { return false }
|
|
18
|
-
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function timingSafeCompare(a, b) {
|
|
23
|
+
if (typeof a !== 'string' || typeof b !== 'string') return false
|
|
24
|
+
const bufA = Buffer.from(a, 'utf8')
|
|
25
|
+
const bufB = Buffer.from(b, 'utf8')
|
|
26
|
+
if (bufA.length !== bufB.length) return false
|
|
27
|
+
return timingSafeEqual(bufA, bufB)
|
|
28
|
+
}
|
|
29
|
+
|
package/lib/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { homedir } from 'node:os'
|
|
2
|
-
import { join } from 'node:path'
|
|
2
|
+
import { join, dirname, resolve } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
3
4
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
4
5
|
import { Gateway } from './gateway.js'
|
|
5
|
-
import { readBody, writeJson, isTrustedSettingsRequest } from './http.js'
|
|
6
|
+
import { readBody, writeJson, isTrustedSettingsRequest, timingSafeCompare } from './http.js'
|
|
7
|
+
import { registerPluginUpdater } from './updater.js'
|
|
6
8
|
import {
|
|
7
9
|
createMessengerService, dispatchMessenger, httpStatusForError,
|
|
8
10
|
messengerApiSchema, parseMessengerBody,
|
|
@@ -10,7 +12,7 @@ import {
|
|
|
10
12
|
import { listHomes } from './homes.js'
|
|
11
13
|
import { Config } from './config.js'
|
|
12
14
|
|
|
13
|
-
export const name = 'dsh-messenger-gateway'
|
|
15
|
+
export const name = '@goodandready/dsh-messenger-gateway'
|
|
14
16
|
export const inject = ['agentDefaultModel', 'agents', 'sessions', 'loader', 'settings', 'webServer', 'attachments', 'tools']
|
|
15
17
|
|
|
16
18
|
export const SETTINGS_NAMESPACE = 'dsh-messenger-gateway'
|
|
@@ -63,7 +65,16 @@ function publicConfig(cfg) {
|
|
|
63
65
|
forumMirrorEnabled: Boolean(cfg.telegram.forumMirrorEnabled),
|
|
64
66
|
forumMirrorChatId: cfg.telegram.forumMirrorChatId || '',
|
|
65
67
|
},
|
|
66
|
-
discord: {
|
|
68
|
+
discord: {
|
|
69
|
+
enabled: Boolean(cfg.discord?.enabled),
|
|
70
|
+
botTokenConfigured: Boolean(String(cfg.discord?.botToken || '').trim()),
|
|
71
|
+
webhookConfigured: Boolean(String(cfg.discord?.webhookUrl || '').trim()),
|
|
72
|
+
},
|
|
73
|
+
slack: {
|
|
74
|
+
enabled: Boolean(cfg.slack?.enabled),
|
|
75
|
+
botTokenConfigured: Boolean(String(cfg.slack?.botToken || '').trim()),
|
|
76
|
+
webhookConfigured: Boolean(String(cfg.slack?.webhookUrl || '').trim()),
|
|
77
|
+
},
|
|
67
78
|
media: { maxDocBytes: cfg.media.maxDocBytes, maxTextInjectBytes: cfg.media.maxTextInjectBytes, maxImageBytes: cfg.media.maxImageBytes },
|
|
68
79
|
tts: cfg.tts,
|
|
69
80
|
agent: {
|
|
@@ -251,7 +262,13 @@ export function apply(ctx, config) {
|
|
|
251
262
|
const cfg = source().telegram?.notifyBridge
|
|
252
263
|
if (!cfg?.enabled) return
|
|
253
264
|
if (event.type === 'turn/start') {
|
|
254
|
-
|
|
265
|
+
const now = Date.now()
|
|
266
|
+
if (turnStarts.size > 50) {
|
|
267
|
+
for (const [sid, ts] of turnStarts.entries()) {
|
|
268
|
+
if (now - ts > 7_200_000) turnStarts.delete(sid)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
turnStarts.set(String(session.id), now)
|
|
255
272
|
return
|
|
256
273
|
}
|
|
257
274
|
if (event.type !== 'turn/end') return
|
|
@@ -289,6 +306,7 @@ export function apply(ctx, config) {
|
|
|
289
306
|
const gw = getGw()
|
|
290
307
|
const botInfo = gw?.getBotInfo?.() || {}
|
|
291
308
|
const uptimeSec = gw?.stats?.startedAt ? Math.max(0, Math.round((Date.now() - gw.stats.startedAt) / 1000)) : 0
|
|
309
|
+
const scheduledCount = gw?.scheduler?.list ? (await gw.scheduler.list())?.length || 0 : 0
|
|
292
310
|
writeJson(res, 200, {
|
|
293
311
|
ok: true,
|
|
294
312
|
running: Boolean(gw),
|
|
@@ -307,6 +325,12 @@ export function apply(ctx, config) {
|
|
|
307
325
|
sent: gw?.stats?.sent || 0,
|
|
308
326
|
errors: gw?.stats?.errors || 0,
|
|
309
327
|
startedAt: gw?.stats?.startedAt || 0,
|
|
328
|
+
scheduledCount,
|
|
329
|
+
},
|
|
330
|
+
apiHealth: {
|
|
331
|
+
degraded: (gw?.consecutiveApiFailures || 0) >= 3,
|
|
332
|
+
consecutiveFailures: gw?.consecutiveApiFailures || 0,
|
|
333
|
+
lastError: gw?.lastApiError || null,
|
|
310
334
|
},
|
|
311
335
|
config: publicConfig(source()),
|
|
312
336
|
})
|
|
@@ -400,10 +424,16 @@ export function apply(ctx, config) {
|
|
|
400
424
|
if (payload.telegram?.notifyBridge) {
|
|
401
425
|
nextTg.notifyBridge = { ...cur.telegram.notifyBridge, ...payload.telegram.notifyBridge }
|
|
402
426
|
}
|
|
427
|
+
const nextDiscord = { ...(cur.discord || {}), ...(payload.discord || {}) }
|
|
428
|
+
if (payload.discord && !String(payload.discord.botToken || '').trim()) nextDiscord.botToken = cur.discord?.botToken
|
|
429
|
+
const nextSlack = { ...(cur.slack || {}), ...(payload.slack || {}) }
|
|
430
|
+
if (payload.slack && !String(payload.slack.botToken || '').trim()) nextSlack.botToken = cur.slack?.botToken
|
|
403
431
|
await settingsApi.replace(Config({
|
|
404
432
|
...cur,
|
|
405
433
|
...payload,
|
|
406
434
|
telegram: nextTg,
|
|
435
|
+
discord: nextDiscord,
|
|
436
|
+
slack: nextSlack,
|
|
407
437
|
tts: { ...cur.tts, ...(payload.tts || {}) },
|
|
408
438
|
agent: { ...cur.agent, ...(payload.agent || {}) },
|
|
409
439
|
media: { ...cur.media, ...(payload.media || {}) },
|
|
@@ -471,9 +501,12 @@ export function apply(ctx, config) {
|
|
|
471
501
|
const cfg = source().telegram || {}
|
|
472
502
|
if (cfg.transport !== 'webhook') return writeJson(res, 404, { ok: false, error: 'webhook transport disabled' })
|
|
473
503
|
const secret = String(cfg.webhookSecret || '').trim()
|
|
474
|
-
if (secret) {
|
|
475
|
-
|
|
476
|
-
|
|
504
|
+
if (!secret) {
|
|
505
|
+
return writeJson(res, 403, { ok: false, error: 'webhook secret not configured' })
|
|
506
|
+
}
|
|
507
|
+
const hdr = String(req.headers['x-telegram-bot-api-secret-token'] || '')
|
|
508
|
+
if (!timingSafeCompare(hdr, secret)) {
|
|
509
|
+
return writeJson(res, 403, { ok: false, error: 'bad secret' })
|
|
477
510
|
}
|
|
478
511
|
const gw = getGw()
|
|
479
512
|
const adapter = gw?.getAdapter?.('telegram')
|
|
@@ -495,6 +528,13 @@ export function apply(ctx, config) {
|
|
|
495
528
|
registerMessengerRoute(ctx, getGw, '/dsh-messenger-gateway/messenger/progress', 'progress')
|
|
496
529
|
registerMessengerRoute(ctx, getGw, '/dsh-messenger-gateway/messenger/ask', 'ask')
|
|
497
530
|
|
|
531
|
+
const manifestPath = resolve(dirname(fileURLToPath(import.meta.url)), '../package.json')
|
|
532
|
+
ctx.effect(() => registerPluginUpdater(ctx, {
|
|
533
|
+
manifestPath,
|
|
534
|
+
packageName: '@goodandready/dsh-messenger-gateway',
|
|
535
|
+
endpoint: '/dsh-messenger-gateway/update',
|
|
536
|
+
}), 'dsh-messenger-gateway: plugin updater')
|
|
537
|
+
|
|
498
538
|
ctx.on('dispose', () => { if (gateway) gateway.stop() })
|
|
499
539
|
sync()
|
|
500
540
|
}
|
package/lib/integrations.js
CHANGED
package/lib/locales/en.js
CHANGED
|
@@ -1,114 +1,123 @@
|
|
|
1
|
-
// Canonical English locale dictionary
|
|
2
|
-
export const en = {
|
|
3
|
-
// Command descriptions
|
|
4
|
-
'cmd.help': 'Show commands help',
|
|
5
|
-
'cmd.new': 'Start new session',
|
|
6
|
-
'cmd.stop': 'Interrupt current turn',
|
|
7
|
-
'cmd.model': 'Interactive model selector',
|
|
8
|
-
'cmd.role': 'Switch agent persona or role',
|
|
9
|
-
'cmd.rewind': 'Rewind last N turns',
|
|
10
|
-
'cmd.fork': 'Fork session into new branch',
|
|
11
|
-
'cmd.export': 'Export history to Markdown',
|
|
12
|
-
'cmd.skills': 'List active tools & skills',
|
|
13
|
-
'cmd.files': 'Workspace file explorer',
|
|
14
|
-
'cmd.get': 'Download file from workspace',
|
|
15
|
-
'cmd.remind': 'Set a timer or reminder',
|
|
16
|
-
'cmd.status': 'Gateway and model status',
|
|
17
|
-
'cmd.top': 'System resources (RAM, uptime)',
|
|
18
|
-
'cmd.keyboard': 'Toggle quick action buttons',
|
|
19
|
-
'cmd.voice': 'Voice reply mode',
|
|
20
|
-
'cmd.tts': 'Toggle speech in this chat',
|
|
21
|
-
'cmd.mute': 'Mute notifications in this chat',
|
|
22
|
-
'cmd.unmute': 'Unmute notifications in this chat',
|
|
23
|
-
'cmd.whoami': 'Show your messenger user ID',
|
|
24
|
-
'cmd.pair': 'Approve pairing code',
|
|
25
|
-
'cmd.sethome': 'Set primary notification home',
|
|
26
|
-
'cmd.setalert': 'Set alert notification target',
|
|
27
|
-
'cmd.bind': 'Bind preset or persona to forum topic',
|
|
28
|
-
'cmd.preset': 'Show or switch agent preset',
|
|
29
|
-
'cmd.cron': 'Schedule recurring autonomous report',
|
|
30
|
-
|
|
31
|
-
// Bot replies & statuses
|
|
32
|
-
'msg.start': 'Gateway connected. Send messages to the agent. /help for commands.',
|
|
33
|
-
'msg.steer_added': '↪️ Added to current turn',
|
|
34
|
-
'msg.turn_stopped': 'Turn interrupted.',
|
|
35
|
-
'msg.no_active_session': 'No active session found.',
|
|
36
|
-
'msg.unknown_command': 'Unknown command {cmd}. /help',
|
|
37
|
-
'msg.not_allowed': 'Access denied. Ask the owner to whitelist your ID.',
|
|
38
|
-
'msg.pairing_requested': 'Access required.\nYour ID: {userId}\nPairing code: {code}\n\nThe owner must send to bot:\n/pair {code}',
|
|
39
|
-
'msg.pairing_rate_limit': 'Pairing code already issued. Please wait or ask owner for /pair.',
|
|
40
|
-
'msg.pairing_approved': '✅ User {userId} successfully approved!',
|
|
41
|
-
'msg.pairing_invalid': 'Invalid or expired pairing code.',
|
|
42
|
-
'msg.muted_on': 'Notifications in this chat: muted (/unmute)',
|
|
43
|
-
'msg.muted_off': 'Notifications in this chat: enabled',
|
|
44
|
-
'msg.no_response': '(no response)',
|
|
45
|
-
'msg.agent_error': 'Agent error: {code}: {message}',
|
|
46
|
-
'msg.exception': 'Internal error: {message}',
|
|
47
|
-
|
|
48
|
-
// Photo & media
|
|
49
|
-
'photo.received_one': 'Photo received. Send a question — e.g. "what is in this photo?"',
|
|
50
|
-
'photo.received_many': 'Received {count} attachments. Send your question.',
|
|
51
|
-
'photo.hint_photo': '[Photo attached]',
|
|
52
|
-
'photo.hint_photos': '[{count} photos attached]',
|
|
53
|
-
'doc.hint': '[Document attached: {name}]',
|
|
54
|
-
|
|
55
|
-
// Interactive buttons & ask
|
|
56
|
-
'ask.confirm_title': '⚠️ Confirmation Required\nTool: <code>{tool}</code>{reason}',
|
|
57
|
-
'ask.allow_once': '✅ Allow Once',
|
|
58
|
-
'ask.allow_session': '🛡️ Allow for Session',
|
|
59
|
-
'ask.deny': '❌ Deny',
|
|
60
|
-
'ask.chose': 'Chose: {choice}',
|
|
61
|
-
'ask.done': 'Done',
|
|
62
|
-
'ask.cancel': 'Cancel',
|
|
63
|
-
'ask.canceled': 'Canceled',
|
|
64
|
-
'ask.expired': 'Interaction expired. Please try again.',
|
|
65
|
-
'ask.other_chat': 'Button belongs to another chat',
|
|
66
|
-
|
|
67
|
-
// Model selector
|
|
68
|
-
'model.title': '🤖 <b>Choose Provider:</b>',
|
|
69
|
-
'model.current': 'Current: <code>{current}</code>',
|
|
70
|
-
'model.provider_models': '🤖 <b>Provider:</b> <code>{provider}</code>\nSelect model (page {page}/{total}):',
|
|
71
|
-
'model.no_models': 'No models available for this provider',
|
|
72
|
-
'model.switched': '✅ Model successfully switched to: <b>{provider}/{model}</b>',
|
|
73
|
-
|
|
74
|
-
// Personas & Presets
|
|
75
|
-
'persona.title': '🎭 <b>Available Roles & Personas:</b>',
|
|
76
|
-
'persona.usage': 'Switch role: <code>/role coder</code> (or /role reset)',
|
|
77
|
-
'persona.reset': '🎭 Role reset to default (Default).',
|
|
78
|
-
'persona.switched': '🎭 Role changed to: {icon} <b>{name}</b>\n{description}',
|
|
79
|
-
'persona.unknown': 'Unknown role "{target}". List: /role list',
|
|
80
|
-
'persona.bound_topic': '📌 Topic bound to {kind}: <b>{name}</b>',
|
|
81
|
-
|
|
82
|
-
// Builtin persona descriptions
|
|
83
|
-
'persona.default.name': 'Default',
|
|
84
|
-
'persona.default.desc': 'General assistant, concise and balanced',
|
|
85
|
-
'persona.coder.name': 'Developer',
|
|
86
|
-
'persona.coder.desc': 'Senior developer, code focus without fluff',
|
|
87
|
-
'persona.writer.name': 'Editor',
|
|
88
|
-
'persona.writer.desc': 'Copywriter, clean text and clear tone',
|
|
89
|
-
'persona.analyst.name': 'Analyst',
|
|
90
|
-
'persona.analyst.desc': 'Structured decomposition and data analysis',
|
|
91
|
-
'persona.concise.name': 'Brief',
|
|
92
|
-
'persona.concise.desc': 'Ultra-short replies, 1-2 sentences',
|
|
93
|
-
|
|
94
|
-
// File explorer & export
|
|
95
|
-
'files.title': '📁 <b>Workspace:</b> <code>{path}</code>\nTotal files: {count}\n\n',
|
|
96
|
-
'files.empty': '📁 Directory is empty: <code>{path}</code>',
|
|
97
|
-
'files.download_hint': '\nDownload file: <code>/get <path></code>',
|
|
98
|
-
'files.not_found': 'File not found: {path}',
|
|
99
|
-
'export.title': '📄 Dialog export ({count} messages):',
|
|
100
|
-
'export.empty': 'Session is empty, nothing to export.',
|
|
101
|
-
|
|
102
|
-
// Reminders & Cron
|
|
103
|
-
'remind.scheduled': '⏰ Reminder set for {time} (in {duration}):\n{text}',
|
|
104
|
-
'remind.invalid': 'Invalid time format. Example: /remind 10m check deploy',
|
|
105
|
-
'remind.prefix': '⏰ <b>[Reminder]</b>\n{text}',
|
|
106
|
-
'cron.scheduled': '⏱️ Autonomous cron task scheduled: <code>{id}</code>\nSchedule: <code>{schedule}</code>\nPrompt: {prompt}',
|
|
107
|
-
'cron.list_title': '⏱️ <b>Scheduled Cron Tasks:</b>\n',
|
|
108
|
-
'cron.none': 'No active cron tasks.',
|
|
109
|
-
'cron.cancelled': '✅ Cron task cancelled: <code>{id}</code>',
|
|
110
|
-
'cron.prefix': '⏱️ <b>[Scheduled Report: {id}]</b>\n',
|
|
111
|
-
|
|
112
|
-
// Mirror & Forum
|
|
113
|
-
'mirror.created': '🪞 <b>[DSH Session Mirror]</b>\nSession: <code>{sessionId}</code>\nTitle: <b>{title}</b>\nMessages in this topic are synchronized with DSH.',
|
|
114
|
-
|
|
1
|
+
// Canonical English locale dictionary
|
|
2
|
+
export const en = {
|
|
3
|
+
// Command descriptions
|
|
4
|
+
'cmd.help': 'Show commands help',
|
|
5
|
+
'cmd.new': 'Start new session',
|
|
6
|
+
'cmd.stop': 'Interrupt current turn',
|
|
7
|
+
'cmd.model': 'Interactive model selector',
|
|
8
|
+
'cmd.role': 'Switch agent persona or role',
|
|
9
|
+
'cmd.rewind': 'Rewind last N turns',
|
|
10
|
+
'cmd.fork': 'Fork session into new branch',
|
|
11
|
+
'cmd.export': 'Export history to Markdown',
|
|
12
|
+
'cmd.skills': 'List active tools & skills',
|
|
13
|
+
'cmd.files': 'Workspace file explorer',
|
|
14
|
+
'cmd.get': 'Download file from workspace',
|
|
15
|
+
'cmd.remind': 'Set a timer or reminder',
|
|
16
|
+
'cmd.status': 'Gateway and model status',
|
|
17
|
+
'cmd.top': 'System resources (RAM, uptime)',
|
|
18
|
+
'cmd.keyboard': 'Toggle quick action buttons',
|
|
19
|
+
'cmd.voice': 'Voice reply mode',
|
|
20
|
+
'cmd.tts': 'Toggle speech in this chat',
|
|
21
|
+
'cmd.mute': 'Mute notifications in this chat',
|
|
22
|
+
'cmd.unmute': 'Unmute notifications in this chat',
|
|
23
|
+
'cmd.whoami': 'Show your messenger user ID',
|
|
24
|
+
'cmd.pair': 'Approve pairing code',
|
|
25
|
+
'cmd.sethome': 'Set primary notification home',
|
|
26
|
+
'cmd.setalert': 'Set alert notification target',
|
|
27
|
+
'cmd.bind': 'Bind preset or persona to forum topic',
|
|
28
|
+
'cmd.preset': 'Show or switch agent preset',
|
|
29
|
+
'cmd.cron': 'Schedule recurring autonomous report',
|
|
30
|
+
|
|
31
|
+
// Bot replies & statuses
|
|
32
|
+
'msg.start': 'Gateway connected. Send messages to the agent. /help for commands.',
|
|
33
|
+
'msg.steer_added': '↪️ Added to current turn',
|
|
34
|
+
'msg.turn_stopped': 'Turn interrupted.',
|
|
35
|
+
'msg.no_active_session': 'No active session found.',
|
|
36
|
+
'msg.unknown_command': 'Unknown command {cmd}. /help',
|
|
37
|
+
'msg.not_allowed': 'Access denied. Ask the owner to whitelist your ID.',
|
|
38
|
+
'msg.pairing_requested': 'Access required.\nYour ID: {userId}\nPairing code: {code}\n\nThe owner must send to bot:\n/pair {code}',
|
|
39
|
+
'msg.pairing_rate_limit': 'Pairing code already issued. Please wait or ask owner for /pair.',
|
|
40
|
+
'msg.pairing_approved': '✅ User {userId} successfully approved!',
|
|
41
|
+
'msg.pairing_invalid': 'Invalid or expired pairing code.',
|
|
42
|
+
'msg.muted_on': 'Notifications in this chat: muted (/unmute)',
|
|
43
|
+
'msg.muted_off': 'Notifications in this chat: enabled',
|
|
44
|
+
'msg.no_response': '(no response)',
|
|
45
|
+
'msg.agent_error': 'Agent error: {code}: {message}',
|
|
46
|
+
'msg.exception': 'Internal error: {message}',
|
|
47
|
+
|
|
48
|
+
// Photo & media
|
|
49
|
+
'photo.received_one': 'Photo received. Send a question — e.g. "what is in this photo?"',
|
|
50
|
+
'photo.received_many': 'Received {count} attachments. Send your question.',
|
|
51
|
+
'photo.hint_photo': '[Photo attached]',
|
|
52
|
+
'photo.hint_photos': '[{count} photos attached]',
|
|
53
|
+
'doc.hint': '[Document attached: {name}]',
|
|
54
|
+
|
|
55
|
+
// Interactive buttons & ask
|
|
56
|
+
'ask.confirm_title': '⚠️ Confirmation Required\nTool: <code>{tool}</code>{reason}',
|
|
57
|
+
'ask.allow_once': '✅ Allow Once',
|
|
58
|
+
'ask.allow_session': '🛡️ Allow for Session',
|
|
59
|
+
'ask.deny': '❌ Deny',
|
|
60
|
+
'ask.chose': 'Chose: {choice}',
|
|
61
|
+
'ask.done': 'Done',
|
|
62
|
+
'ask.cancel': 'Cancel',
|
|
63
|
+
'ask.canceled': 'Canceled',
|
|
64
|
+
'ask.expired': 'Interaction expired. Please try again.',
|
|
65
|
+
'ask.other_chat': 'Button belongs to another chat',
|
|
66
|
+
|
|
67
|
+
// Model selector
|
|
68
|
+
'model.title': '🤖 <b>Choose Provider:</b>',
|
|
69
|
+
'model.current': 'Current: <code>{current}</code>',
|
|
70
|
+
'model.provider_models': '🤖 <b>Provider:</b> <code>{provider}</code>\nSelect model (page {page}/{total}):',
|
|
71
|
+
'model.no_models': 'No models available for this provider',
|
|
72
|
+
'model.switched': '✅ Model successfully switched to: <b>{provider}/{model}</b>',
|
|
73
|
+
|
|
74
|
+
// Personas & Presets
|
|
75
|
+
'persona.title': '🎭 <b>Available Roles & Personas:</b>',
|
|
76
|
+
'persona.usage': 'Switch role: <code>/role coder</code> (or /role reset)',
|
|
77
|
+
'persona.reset': '🎭 Role reset to default (Default).',
|
|
78
|
+
'persona.switched': '🎭 Role changed to: {icon} <b>{name}</b>\n{description}',
|
|
79
|
+
'persona.unknown': 'Unknown role "{target}". List: /role list',
|
|
80
|
+
'persona.bound_topic': '📌 Topic bound to {kind}: <b>{name}</b>',
|
|
81
|
+
|
|
82
|
+
// Builtin persona descriptions
|
|
83
|
+
'persona.default.name': 'Default',
|
|
84
|
+
'persona.default.desc': 'General assistant, concise and balanced',
|
|
85
|
+
'persona.coder.name': 'Developer',
|
|
86
|
+
'persona.coder.desc': 'Senior developer, code focus without fluff',
|
|
87
|
+
'persona.writer.name': 'Editor',
|
|
88
|
+
'persona.writer.desc': 'Copywriter, clean text and clear tone',
|
|
89
|
+
'persona.analyst.name': 'Analyst',
|
|
90
|
+
'persona.analyst.desc': 'Structured decomposition and data analysis',
|
|
91
|
+
'persona.concise.name': 'Brief',
|
|
92
|
+
'persona.concise.desc': 'Ultra-short replies, 1-2 sentences',
|
|
93
|
+
|
|
94
|
+
// File explorer & export
|
|
95
|
+
'files.title': '📁 <b>Workspace:</b> <code>{path}</code>\nTotal files: {count}\n\n',
|
|
96
|
+
'files.empty': '📁 Directory is empty: <code>{path}</code>',
|
|
97
|
+
'files.download_hint': '\nDownload file: <code>/get <path></code>',
|
|
98
|
+
'files.not_found': 'File not found: {path}',
|
|
99
|
+
'export.title': '📄 Dialog export ({count} messages):',
|
|
100
|
+
'export.empty': 'Session is empty, nothing to export.',
|
|
101
|
+
|
|
102
|
+
// Reminders & Cron
|
|
103
|
+
'remind.scheduled': '⏰ Reminder set for {time} (in {duration}):\n{text}',
|
|
104
|
+
'remind.invalid': 'Invalid time format. Example: /remind 10m check deploy',
|
|
105
|
+
'remind.prefix': '⏰ <b>[Reminder]</b>\n{text}',
|
|
106
|
+
'cron.scheduled': '⏱️ Autonomous cron task scheduled: <code>{id}</code>\nSchedule: <code>{schedule}</code>\nPrompt: {prompt}',
|
|
107
|
+
'cron.list_title': '⏱️ <b>Scheduled Cron Tasks:</b>\n',
|
|
108
|
+
'cron.none': 'No active cron tasks.',
|
|
109
|
+
'cron.cancelled': '✅ Cron task cancelled: <code>{id}</code>',
|
|
110
|
+
'cron.prefix': '⏱️ <b>[Scheduled Report: {id}]</b>\n',
|
|
111
|
+
|
|
112
|
+
// Mirror & Forum
|
|
113
|
+
'mirror.created': '🪞 <b>[DSH Session Mirror]</b>\nSession: <code>{sessionId}</code>\nTitle: <b>{title}</b>\nMessages in this topic are synchronized with DSH.',
|
|
114
|
+
// Updates & Maintenance
|
|
115
|
+
'cmd.update': 'Check or install plugin update',
|
|
116
|
+
'update.installing': '⏳ <b>Updating plugin…</b>\nRunning DSH package installation in background…',
|
|
117
|
+
'update.already_latest': '✅ Plugin is already up to date (<code>v{version}</code>).',
|
|
118
|
+
'update.success': '🎉 <b>Successfully updated to v{version}!</b>\nPlease restart the service to apply changes: <code>sudo systemctl restart dsh-web.service</code>',
|
|
119
|
+
'update.failed': '❌ <b>Update failed:</b> {error}',
|
|
120
|
+
'update.available': '📦 <b>Update Available!</b>\nCurrent version: <code>v{current}</code>\nLatest release: <code>v{latest}</code>\n\nRun <code>/update now</code> to install.',
|
|
121
|
+
'update.current': '✅ <b>Messenger Gateway is up to date.</b>\nCurrent version: <code>v{version}</code>.',
|
|
122
|
+
'update.check_failed': '⚠️ Failed to check for updates: {error}',
|
|
123
|
+
}
|
package/lib/locales/index.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { en } from './en.js'
|
|
2
|
-
import { zh } from './zh.js'
|
|
3
|
-
|
|
4
|
-
export const locales = { en, zh }
|
|
5
|
-
|
|
6
|
-
export function getLocaleDictionary(locale = 'en') {
|
|
7
|
-
const norm = String(locale || '').toLowerCase()
|
|
8
|
-
if (norm.startsWith('zh')) return zh
|
|
9
|
-
return en
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function t(key, params = {}, locale = 'en') {
|
|
13
|
-
const dict = getLocaleDictionary(locale)
|
|
14
|
-
let template = dict[key] || en[key] || key
|
|
15
|
-
for (const [k, v] of Object.entries(params)) {
|
|
16
|
-
template = template.replaceAll(`{${k}}`, String(v))
|
|
17
|
-
}
|
|
18
|
-
return template
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export { en, zh }
|
|
1
|
+
import { en } from './en.js'
|
|
2
|
+
import { zh } from './zh.js'
|
|
3
|
+
|
|
4
|
+
export const locales = { en, zh }
|
|
5
|
+
|
|
6
|
+
export function getLocaleDictionary(locale = 'en') {
|
|
7
|
+
const norm = String(locale || '').toLowerCase()
|
|
8
|
+
if (norm.startsWith('zh')) return zh
|
|
9
|
+
return en
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function t(key, params = {}, locale = 'en') {
|
|
13
|
+
const dict = getLocaleDictionary(locale)
|
|
14
|
+
let template = dict[key] || en[key] || key
|
|
15
|
+
for (const [k, v] of Object.entries(params)) {
|
|
16
|
+
template = template.replaceAll(`{${k}}`, String(v))
|
|
17
|
+
}
|
|
18
|
+
return template
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { en, zh }
|