@goodandready/dsh-messenger-gateway 0.3.19 → 0.3.21
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 +40 -2
- package/README.ru.md +10 -0
- package/README.zh.md +10 -0
- package/lib/adapters/discord.js +65 -11
- package/lib/adapters/slack.js +72 -12
- package/lib/adapters/telegram.js +708 -690
- package/lib/alerts.js +10 -10
- package/lib/artifacts.js +117 -117
- package/lib/ask.js +151 -151
- package/lib/client.js +104 -104
- package/lib/commands.js +73 -47
- package/lib/config.js +104 -98
- package/lib/content-guard.js +14 -14
- package/lib/documents.js +9 -9
- package/lib/file-manager.js +9 -9
- package/lib/gateway.js +1737 -1406
- package/lib/index.js +500 -488
- package/lib/locales/en.js +114 -0
- package/lib/locales/index.js +21 -0
- package/lib/locales/zh.js +114 -0
- package/lib/models.js +1 -1
- package/lib/personas.js +67 -14
- package/lib/photos.js +2 -2
- package/lib/scheduler.js +159 -142
- package/lib/stream.js +4 -2
- package/lib/telegram-errors.js +0 -0
- package/lib/text.js +39 -39
- package/package.json +1 -1
package/lib/scheduler.js
CHANGED
|
@@ -1,143 +1,160 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises'
|
|
2
|
-
import { randomUUID } from 'node:crypto'
|
|
3
|
-
import { writeJsonAtomic } from './storage-atomic.js'
|
|
4
|
-
|
|
5
|
-
export function parseRelativeTime(input) {
|
|
6
|
-
if (!input) return null
|
|
7
|
-
const raw = String(input).trim().toLowerCase()
|
|
8
|
-
const match = /^(\d+)\s*(s|sec
|
|
9
|
-
if (!match) return null
|
|
10
|
-
|
|
11
|
-
const val = parseInt(match[1], 10)
|
|
12
|
-
if (!Number.isFinite(val) || val <= 0) return null
|
|
13
|
-
|
|
14
|
-
const unit = (match[2] || 'm').toLowerCase()
|
|
15
|
-
if (['s', 'sec', '
|
|
16
|
-
if (['m', 'min', '
|
|
17
|
-
if (['h', 'hr', '
|
|
18
|
-
if (['d', 'day', '
|
|
19
|
-
|
|
20
|
-
return null
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function formatRemaining(ms) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
let
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
tasks =
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { randomUUID } from 'node:crypto'
|
|
3
|
+
import { writeJsonAtomic } from './storage-atomic.js'
|
|
4
|
+
|
|
5
|
+
export function parseRelativeTime(input) {
|
|
6
|
+
if (!input) return null
|
|
7
|
+
const raw = String(input).trim().toLowerCase()
|
|
8
|
+
const match = /^(\d+)\s*(s|sec|m|min|h|hr|d|day|秒|分|小时|天)?$/i.exec(raw)
|
|
9
|
+
if (!match) return null
|
|
10
|
+
|
|
11
|
+
const val = parseInt(match[1], 10)
|
|
12
|
+
if (!Number.isFinite(val) || val <= 0) return null
|
|
13
|
+
|
|
14
|
+
const unit = (match[2] || 'm').toLowerCase()
|
|
15
|
+
if (['s', 'sec', '秒'].includes(unit)) return val * 1000
|
|
16
|
+
if (['m', 'min', '分'].includes(unit)) return val * 60 * 1000
|
|
17
|
+
if (['h', 'hr', '小时'].includes(unit)) return val * 3600 * 1000
|
|
18
|
+
if (['d', 'day', '天'].includes(unit)) return val * 86400 * 1000
|
|
19
|
+
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function formatRemaining(ms, locale = 'en') {
|
|
24
|
+
const isZh = String(locale || '').toLowerCase().startsWith('zh')
|
|
25
|
+
if (ms <= 0) return isZh ? '刚刚' : 'now'
|
|
26
|
+
const sec = Math.ceil(ms / 1000)
|
|
27
|
+
if (sec < 60) return isZh ? `${sec}秒` : `${sec}s`
|
|
28
|
+
const min = Math.ceil(sec / 60)
|
|
29
|
+
if (min < 60) return isZh ? `${min}分` : `${min}m`
|
|
30
|
+
const hr = Math.floor(min / 60)
|
|
31
|
+
const remMin = min % 60
|
|
32
|
+
if (isZh) return remMin ? `${hr}小时 ${remMin}分` : `${hr}小时`
|
|
33
|
+
return remMin ? `${hr}h ${remMin}m` : `${hr}h`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createScheduler(filePath, onDue) {
|
|
37
|
+
let tasks = []
|
|
38
|
+
let loaded = false
|
|
39
|
+
let timer = null
|
|
40
|
+
|
|
41
|
+
async function load() {
|
|
42
|
+
try {
|
|
43
|
+
const raw = await readFile(filePath, 'utf8')
|
|
44
|
+
tasks = JSON.parse(raw)
|
|
45
|
+
} catch {
|
|
46
|
+
tasks = []
|
|
47
|
+
}
|
|
48
|
+
loaded = true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const RETENTION_MS = 7 * 86400 * 1000
|
|
52
|
+
|
|
53
|
+
async function save() {
|
|
54
|
+
if (!filePath) return
|
|
55
|
+
try {
|
|
56
|
+
const now = Date.now()
|
|
57
|
+
tasks = tasks.filter((t) => {
|
|
58
|
+
if (t.status === 'pending') return true
|
|
59
|
+
const finishTime = t.firedAt || t.createdAt || 0
|
|
60
|
+
return (now - finishTime) < RETENTION_MS
|
|
61
|
+
})
|
|
62
|
+
await writeJsonAtomic(filePath, tasks)
|
|
63
|
+
} catch {}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function checkDue() {
|
|
67
|
+
if (!loaded) await load()
|
|
68
|
+
const now = Date.now()
|
|
69
|
+
const due = tasks.filter((t) => t.status === 'pending' && t.dueAt <= now)
|
|
70
|
+
|
|
71
|
+
for (const task of due) {
|
|
72
|
+
if (task.recurring && Number(task.intervalMs) > 0) {
|
|
73
|
+
task.dueAt = now + Number(task.intervalMs)
|
|
74
|
+
task.lastFiredAt = now
|
|
75
|
+
} else {
|
|
76
|
+
task.status = 'fired'
|
|
77
|
+
task.firedAt = now
|
|
78
|
+
}
|
|
79
|
+
if (typeof onDue === 'function') {
|
|
80
|
+
try { await onDue(task) } catch {}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (due.length > 0) {
|
|
85
|
+
await save()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function start(intervalMs = 5000) {
|
|
90
|
+
if (timer) clearInterval(timer)
|
|
91
|
+
timer = setInterval(() => {
|
|
92
|
+
checkDue().catch(() => {})
|
|
93
|
+
}, intervalMs)
|
|
94
|
+
timer.unref?.()
|
|
95
|
+
checkDue().catch(() => {})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function stop() {
|
|
99
|
+
if (timer) {
|
|
100
|
+
clearInterval(timer)
|
|
101
|
+
timer = null
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function schedule(taskData) {
|
|
106
|
+
if (!loaded) await load()
|
|
107
|
+
const id = taskData.id || randomUUID().slice(0, 8)
|
|
108
|
+
const task = {
|
|
109
|
+
id,
|
|
110
|
+
platform: taskData.platform || 'telegram',
|
|
111
|
+
chatId: taskData.chatId,
|
|
112
|
+
threadId: taskData.threadId || 0,
|
|
113
|
+
userId: taskData.userId,
|
|
114
|
+
text: taskData.text,
|
|
115
|
+
prompt: taskData.prompt || taskData.text || '',
|
|
116
|
+
dueAt: taskData.dueAt,
|
|
117
|
+
createdAt: Date.now(),
|
|
118
|
+
status: 'pending',
|
|
119
|
+
recurring: Boolean(taskData.recurring),
|
|
120
|
+
intervalMs: taskData.intervalMs || 0,
|
|
121
|
+
cronExpr: taskData.cronExpr || '',
|
|
122
|
+
}
|
|
123
|
+
tasks.push(task)
|
|
124
|
+
await save()
|
|
125
|
+
return task
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function list(chatId) {
|
|
129
|
+
if (!loaded) await load()
|
|
130
|
+
const now = Date.now()
|
|
131
|
+
return tasks.filter((t) => (chatId ? t.chatId === chatId : true) && t.status === 'pending' && t.dueAt > now)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function listRecurring(chatId) {
|
|
135
|
+
if (!loaded) await load()
|
|
136
|
+
return tasks.filter((t) => (chatId ? t.chatId === chatId : true) && t.status === 'pending' && t.recurring)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function cancel(id, chatId) {
|
|
140
|
+
if (!loaded) await load()
|
|
141
|
+
const task = tasks.find((t) => t.id === id && (chatId ? t.chatId === chatId : true) && t.status === 'pending')
|
|
142
|
+
if (!task) return false
|
|
143
|
+
task.status = 'cancelled'
|
|
144
|
+
await save()
|
|
145
|
+
return true
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
load,
|
|
150
|
+
save,
|
|
151
|
+
start,
|
|
152
|
+
stop,
|
|
153
|
+
schedule,
|
|
154
|
+
list,
|
|
155
|
+
listRecurring,
|
|
156
|
+
cancel,
|
|
157
|
+
checkDue,
|
|
158
|
+
getTasks: () => tasks,
|
|
159
|
+
}
|
|
143
160
|
}
|
package/lib/stream.js
CHANGED
|
@@ -11,8 +11,10 @@ export function extractToolName(eventData) {
|
|
|
11
11
|
return name ? String(name) : ''
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export function formatProgressLine(toolName) {
|
|
15
|
-
if (!toolName)
|
|
14
|
+
export function formatProgressLine(toolName, locale = 'en') {
|
|
15
|
+
if (!toolName) {
|
|
16
|
+
return String(locale || '').toLowerCase().startsWith('zh') ? '⏳ 思考中…' : '⏳ Thinking…'
|
|
17
|
+
}
|
|
16
18
|
return `🔧 ${toolName}…`
|
|
17
19
|
}
|
|
18
20
|
|
package/lib/telegram-errors.js
CHANGED
|
File without changes
|
package/lib/text.js
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
export function splitText(text, maxLen) {
|
|
2
|
-
const raw = String(text ?? '')
|
|
3
|
-
if (raw.length <= maxLen) return [raw]
|
|
4
|
-
const chunks = []
|
|
5
|
-
let rest = raw
|
|
6
|
-
while (rest.length > maxLen) {
|
|
7
|
-
let cut = rest.lastIndexOf('\n', maxLen)
|
|
8
|
-
if (cut < maxLen / 2) cut = rest.lastIndexOf(' ', maxLen)
|
|
9
|
-
if (cut < maxLen / 2) cut = maxLen
|
|
10
|
-
chunks.push(rest.slice(0, cut).trim())
|
|
11
|
-
rest = rest.slice(cut).trimStart()
|
|
12
|
-
}
|
|
13
|
-
if (rest.length > 0) chunks.push(rest)
|
|
14
|
-
return chunks
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function assistantText(message) {
|
|
18
|
-
return (message?.content || [])
|
|
19
|
-
.filter((block) => block.type === 'text')
|
|
20
|
-
.map((block) => block.text)
|
|
21
|
-
.join('')
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
/** Drop English reasoning preamble before the user-facing reply (Telegram). */
|
|
26
|
-
export function stripReasoningPreamble(text) {
|
|
27
|
-
const raw = String(text ?? '').trim()
|
|
28
|
-
if (!raw) return raw
|
|
29
|
-
const paragraphs = raw.split(/\n\n+/)
|
|
30
|
-
for (let i = 0; i < paragraphs.length; i++) {
|
|
31
|
-
const p = paragraphs[i]
|
|
32
|
-
const cyrillic = (p.match(/[\u0400-\u04FF]/g) || []).length
|
|
33
|
-
const latin = (p.match(/[A-Za-z]/g) || []).length
|
|
34
|
-
if (cyrillic >= 12 && cyrillic > latin) return paragraphs.slice(i).join('\n\n').trim()
|
|
35
|
-
}
|
|
36
|
-
return raw
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export const MESSENGER_RELAY_INSTRUCTION = '
|
|
1
|
+
export function splitText(text, maxLen) {
|
|
2
|
+
const raw = String(text ?? '')
|
|
3
|
+
if (raw.length <= maxLen) return [raw]
|
|
4
|
+
const chunks = []
|
|
5
|
+
let rest = raw
|
|
6
|
+
while (rest.length > maxLen) {
|
|
7
|
+
let cut = rest.lastIndexOf('\n', maxLen)
|
|
8
|
+
if (cut < maxLen / 2) cut = rest.lastIndexOf(' ', maxLen)
|
|
9
|
+
if (cut < maxLen / 2) cut = maxLen
|
|
10
|
+
chunks.push(rest.slice(0, cut).trim())
|
|
11
|
+
rest = rest.slice(cut).trimStart()
|
|
12
|
+
}
|
|
13
|
+
if (rest.length > 0) chunks.push(rest)
|
|
14
|
+
return chunks
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function assistantText(message) {
|
|
18
|
+
return (message?.content || [])
|
|
19
|
+
.filter((block) => block.type === 'text')
|
|
20
|
+
.map((block) => block.text)
|
|
21
|
+
.join('')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/** Drop English reasoning preamble before the user-facing reply (Telegram). */
|
|
26
|
+
export function stripReasoningPreamble(text) {
|
|
27
|
+
const raw = String(text ?? '').trim()
|
|
28
|
+
if (!raw) return raw
|
|
29
|
+
const paragraphs = raw.split(/\n\n+/)
|
|
30
|
+
for (let i = 0; i < paragraphs.length; i++) {
|
|
31
|
+
const p = paragraphs[i]
|
|
32
|
+
const cyrillic = (p.match(/[\u0400-\u04FF]/g) || []).length
|
|
33
|
+
const latin = (p.match(/[A-Za-z]/g) || []).length
|
|
34
|
+
if (cyrillic >= 12 && cyrillic > latin) return paragraphs.slice(i).join('\n\n').trim()
|
|
35
|
+
}
|
|
36
|
+
return raw
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const MESSENGER_RELAY_INSTRUCTION = 'Channel: Messenger. Reply concisely and directly to the user. Do not leak internal reasoning or planning traces.'
|
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.21",
|
|
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",
|