@goodandready/dsh-messenger-gateway 0.4.3 → 0.4.5
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/adapters/telegram-inbound.js +141 -141
- package/lib/adapters/telegram.js +2 -1
- package/lib/alerts.js +105 -105
- package/lib/api-health.js +38 -38
- package/lib/client.js +11 -6
- package/lib/commands.js +126 -126
- package/lib/forum-mirror.js +6 -6
- package/lib/gateway-callbacks.js +2 -1
- package/lib/gateway-commands.js +3 -0
- package/lib/gateway-turn.js +288 -285
- package/lib/gateway.js +15 -11
- package/lib/http.js +16 -3
- package/lib/index.js +13 -9
- package/lib/models.js +29 -2
- package/lib/scheduler.js +169 -169
- package/lib/telegram-errors.js +0 -0
- package/package.json +1 -1
package/lib/gateway.js
CHANGED
|
@@ -1,30 +1,28 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises'
|
|
2
1
|
import { randomUUID } from 'node:crypto'
|
|
3
|
-
import { join
|
|
2
|
+
import { join } from 'node:path'
|
|
4
3
|
import { homedir } from 'node:os'
|
|
5
|
-
import { fileURLToPath } from 'node:url'
|
|
6
4
|
import { installModelSelection } from '@deepseek-ai/dsh-agent'
|
|
7
5
|
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
|
8
6
|
import { SessionId } from '@deepseek-ai/dsh-session'
|
|
9
7
|
import createAdapters from './adapters/index.js'
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
8
|
+
import { sessionKey } from './topics.js'
|
|
9
|
+
import { resolveNamedHome, listHomes } from './homes.js'
|
|
10
|
+
import { assistantText } from './text.js'
|
|
11
|
+
import { collectAssistantParts } from './outbound.js'
|
|
12
|
+
import { extractTextDelta, extractToolName } from './stream.js'
|
|
14
13
|
import { createVoicePrefs } from './voice-prefs.js'
|
|
15
14
|
import { executeMessengerAsk, releaseCallbacks, rejectPendingAsk } from './ask.js'
|
|
16
15
|
import { createPersonaStore, BUILTIN_PERSONAS } from './personas.js'
|
|
17
16
|
import { formatAlertMessage, resolveAlertTarget } from './alerts.js'
|
|
18
17
|
import { createScheduler } from './scheduler.js'
|
|
19
|
-
import { getStoredModelSelection } from './models.js'
|
|
20
18
|
import { createPairingStore } from './pairing.js'
|
|
21
19
|
import { isTopicGoneError } from './telegram-errors.js'
|
|
22
20
|
import { ensureContentArray } from './content-guard.js'
|
|
23
21
|
import { ApiHealthTracker } from './api-health.js'
|
|
24
22
|
import { t } from './locales/index.js'
|
|
25
|
-
import { handleGatewayCommand
|
|
23
|
+
import { handleGatewayCommand } from './gateway-commands.js'
|
|
26
24
|
import { handleGatewayCallback } from './gateway-callbacks.js'
|
|
27
|
-
import { runGatewayTurn, buildUserContent, answerApproval
|
|
25
|
+
import { runGatewayTurn, buildUserContent, answerApproval } from './gateway-turn.js'
|
|
28
26
|
import {
|
|
29
27
|
collectDynamicSkills, syncTelegramCommands,
|
|
30
28
|
mirrorSessionToForumTopic, relayTurnToForumMirror,
|
|
@@ -521,7 +519,13 @@ export class Gateway {
|
|
|
521
519
|
for (const [key, chat] of this.chats) {
|
|
522
520
|
if (!chat.turnActive && now - chat.lastUsed > timeout) {
|
|
523
521
|
if (chat.agent?.session?.id) {
|
|
524
|
-
|
|
522
|
+
const sid = String(chat.agent.session.id)
|
|
523
|
+
this.sessionToChat.delete(sid)
|
|
524
|
+
const threadInfo = this.sessionToThread.get(sid)
|
|
525
|
+
if (threadInfo) {
|
|
526
|
+
this.threadToSession.delete(`${threadInfo.chatId}:${threadInfo.threadId}`)
|
|
527
|
+
this.sessionToThread.delete(sid)
|
|
528
|
+
}
|
|
525
529
|
}
|
|
526
530
|
this.chats.delete(key)
|
|
527
531
|
chat.dispose().catch(() => {})
|
package/lib/http.js
CHANGED
|
@@ -13,10 +13,23 @@ export function writeJson(res, status, body) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function isTrustedSettingsRequest(req) {
|
|
16
|
-
const origin = String(req.headers?.origin || '')
|
|
17
16
|
const host = String(req.headers?.host || '')
|
|
18
|
-
if (!
|
|
19
|
-
|
|
17
|
+
if (!host) return false
|
|
18
|
+
|
|
19
|
+
const origin = String(req.headers?.origin || '')
|
|
20
|
+
if (origin) {
|
|
21
|
+
try { return new URL(origin).host === host } catch { return false }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const secFetchSite = String(req.headers?.['sec-fetch-site'] || '').toLowerCase()
|
|
25
|
+
if (secFetchSite === 'same-origin') return true
|
|
26
|
+
|
|
27
|
+
const referer = String(req.headers?.referer || '')
|
|
28
|
+
if (referer) {
|
|
29
|
+
try { return new URL(referer).host === host } catch { return false }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return false
|
|
20
33
|
}
|
|
21
34
|
|
|
22
35
|
export function timingSafeCompare(a, b) {
|
package/lib/index.js
CHANGED
|
@@ -378,15 +378,19 @@ export function apply(ctx, config) {
|
|
|
378
378
|
return writeJson(res, 400, { ok: false, error: 'invalid json' })
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
381
|
+
if (source().webhooks?.enabled === false) {
|
|
382
|
+
return writeJson(res, 404, { ok: false, error: 'webhooks disabled' })
|
|
383
|
+
}
|
|
384
|
+
const expectedSecret = String(source().webhooks?.secret || '').trim()
|
|
385
|
+
if (!expectedSecret) {
|
|
386
|
+
return writeJson(res, 403, { ok: false, error: 'webhook secret not configured' })
|
|
387
|
+
}
|
|
388
|
+
const authHeader = req.headers?.authorization || ''
|
|
389
|
+
const bearer = authHeader.startsWith('Bearer ') ? authHeader.slice(7).trim() : ''
|
|
390
|
+
const tokenHeader = req.headers?.['x-webhook-secret'] || ''
|
|
391
|
+
const provided = bearer || tokenHeader || payload.secret
|
|
392
|
+
if (!timingSafeCompare(provided, expectedSecret)) {
|
|
393
|
+
return writeJson(res, 401, { ok: false, error: 'unauthorized' })
|
|
390
394
|
}
|
|
391
395
|
|
|
392
396
|
const { target, text, home, chatId, threadId, platform = 'telegram', files } = payload
|
package/lib/models.js
CHANGED
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
export const MODEL_PAGE_SIZE = 10
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
let catalogCache = new WeakMap()
|
|
4
|
+
|
|
5
|
+
export function clearModelCatalogCache() {
|
|
6
|
+
catalogCache = new WeakMap()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function listModelCatalog(ctx, fallback = {}, options = {}) {
|
|
10
|
+
const llm = ctx?.get?.('llm') || ctx?.llm
|
|
11
|
+
const ttlMs = typeof options.ttlMs === 'number' ? options.ttlMs : 60_000
|
|
12
|
+
const bypass = Boolean(options.bypassCache)
|
|
13
|
+
const now = Date.now()
|
|
14
|
+
|
|
15
|
+
if (llm && typeof llm === 'object' && !bypass) {
|
|
16
|
+
const cached = catalogCache.get(llm)
|
|
17
|
+
if (cached && (now - cached.timestamp < ttlMs)) {
|
|
18
|
+
return {
|
|
19
|
+
providers: [...cached.providers],
|
|
20
|
+
modelsByProvider: new Map(cached.modelsByProvider),
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
4
25
|
const result = {
|
|
5
26
|
providers: [],
|
|
6
27
|
modelsByProvider: new Map(),
|
|
7
28
|
}
|
|
8
29
|
|
|
9
30
|
// 1. Try ctx.llm if available
|
|
10
|
-
const llm = ctx?.get?.('llm') || ctx?.llm
|
|
11
31
|
if (llm?.listProviders) {
|
|
12
32
|
try {
|
|
13
33
|
const providers = await llm.listProviders()
|
|
@@ -25,6 +45,13 @@ export async function listModelCatalog(ctx, fallback = {}) {
|
|
|
25
45
|
result.modelsByProvider.set(pId, [])
|
|
26
46
|
}
|
|
27
47
|
}
|
|
48
|
+
if (llm && typeof llm === 'object' && result.providers.length > 0) {
|
|
49
|
+
catalogCache.set(llm, {
|
|
50
|
+
timestamp: now,
|
|
51
|
+
providers: result.providers,
|
|
52
|
+
modelsByProvider: new Map(result.modelsByProvider),
|
|
53
|
+
})
|
|
54
|
+
}
|
|
28
55
|
} catch (err) {
|
|
29
56
|
/* safe fallback when ctx.llm.listProviders is unavailable or throws */
|
|
30
57
|
}
|
package/lib/scheduler.js
CHANGED
|
@@ -1,170 +1,170 @@
|
|
|
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, options = {}) {
|
|
37
|
-
const logger = options?.logger || console
|
|
38
|
-
let tasks = []
|
|
39
|
-
let loaded = false
|
|
40
|
-
let timer = null
|
|
41
|
-
|
|
42
|
-
async function load() {
|
|
43
|
-
try {
|
|
44
|
-
const raw = await readFile(filePath, 'utf8')
|
|
45
|
-
tasks = JSON.parse(raw)
|
|
46
|
-
} catch (err) {
|
|
47
|
-
if (err?.code !== 'ENOENT') {
|
|
48
|
-
logger?.debug?.('[dsh-messenger-gateway] scheduler load error:', err?.message || err)
|
|
49
|
-
}
|
|
50
|
-
tasks = []
|
|
51
|
-
}
|
|
52
|
-
loaded = true
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const RETENTION_MS = 7 * 86400 * 1000
|
|
56
|
-
|
|
57
|
-
async function save() {
|
|
58
|
-
if (!filePath) return
|
|
59
|
-
try {
|
|
60
|
-
const now = Date.now()
|
|
61
|
-
tasks = tasks.filter((t) => {
|
|
62
|
-
if (t.status === 'pending') return true
|
|
63
|
-
const finishTime = t.firedAt || t.createdAt || 0
|
|
64
|
-
return (now - finishTime) < RETENTION_MS
|
|
65
|
-
})
|
|
66
|
-
await writeJsonAtomic(filePath, tasks)
|
|
67
|
-
} catch (err) {
|
|
68
|
-
logger?.warn?.('[dsh-messenger-gateway] scheduler save failed:', err?.message || err)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function checkDue() {
|
|
73
|
-
if (!loaded) await load()
|
|
74
|
-
const now = Date.now()
|
|
75
|
-
const due = tasks.filter((t) => t.status === 'pending' && t.dueAt <= now)
|
|
76
|
-
|
|
77
|
-
for (const task of due) {
|
|
78
|
-
if (task.recurring && Number(task.intervalMs) > 0) {
|
|
79
|
-
task.dueAt = now + Number(task.intervalMs)
|
|
80
|
-
task.lastFiredAt = now
|
|
81
|
-
} else {
|
|
82
|
-
task.status = 'fired'
|
|
83
|
-
task.firedAt = now
|
|
84
|
-
}
|
|
85
|
-
if (typeof onDue === 'function') {
|
|
86
|
-
try {
|
|
87
|
-
await onDue(task)
|
|
88
|
-
} catch (err) {
|
|
89
|
-
logger?.warn?.('[dsh-messenger-gateway] scheduler task execution failed:', err?.message || err)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (due.length > 0) {
|
|
95
|
-
await save()
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function start(intervalMs = 5000) {
|
|
100
|
-
if (timer) clearInterval(timer)
|
|
101
|
-
timer = setInterval(() => {
|
|
102
|
-
checkDue().catch(() => {})
|
|
103
|
-
}, intervalMs)
|
|
104
|
-
timer.unref?.()
|
|
105
|
-
checkDue().catch(() => {})
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function stop() {
|
|
109
|
-
if (timer) {
|
|
110
|
-
clearInterval(timer)
|
|
111
|
-
timer = null
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async function schedule(taskData) {
|
|
116
|
-
if (!loaded) await load()
|
|
117
|
-
const id = taskData.id || randomUUID().slice(0, 8)
|
|
118
|
-
const task = {
|
|
119
|
-
id,
|
|
120
|
-
platform: taskData.platform || 'telegram',
|
|
121
|
-
chatId: taskData.chatId,
|
|
122
|
-
threadId: taskData.threadId || 0,
|
|
123
|
-
userId: taskData.userId,
|
|
124
|
-
text: taskData.text,
|
|
125
|
-
prompt: taskData.prompt || taskData.text || '',
|
|
126
|
-
dueAt: taskData.dueAt,
|
|
127
|
-
createdAt: Date.now(),
|
|
128
|
-
status: 'pending',
|
|
129
|
-
recurring: Boolean(taskData.recurring),
|
|
130
|
-
intervalMs: taskData.intervalMs || 0,
|
|
131
|
-
cronExpr: taskData.cronExpr || '',
|
|
132
|
-
}
|
|
133
|
-
tasks.push(task)
|
|
134
|
-
await save()
|
|
135
|
-
return task
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async function list(chatId) {
|
|
139
|
-
if (!loaded) await load()
|
|
140
|
-
const now = Date.now()
|
|
141
|
-
return tasks.filter((t) => (chatId ? t.chatId === chatId : true) && t.status === 'pending' && t.dueAt > now)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async function listRecurring(chatId) {
|
|
145
|
-
if (!loaded) await load()
|
|
146
|
-
return tasks.filter((t) => (chatId ? t.chatId === chatId : true) && t.status === 'pending' && t.recurring)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async function cancel(id, chatId) {
|
|
150
|
-
if (!loaded) await load()
|
|
151
|
-
const task = tasks.find((t) => t.id === id && (chatId ? t.chatId === chatId : true) && t.status === 'pending')
|
|
152
|
-
if (!task) return false
|
|
153
|
-
task.status = 'cancelled'
|
|
154
|
-
await save()
|
|
155
|
-
return true
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return {
|
|
159
|
-
load,
|
|
160
|
-
save,
|
|
161
|
-
start,
|
|
162
|
-
stop,
|
|
163
|
-
schedule,
|
|
164
|
-
list,
|
|
165
|
-
listRecurring,
|
|
166
|
-
cancel,
|
|
167
|
-
checkDue,
|
|
168
|
-
getTasks: () => tasks,
|
|
169
|
-
}
|
|
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, options = {}) {
|
|
37
|
+
const logger = options?.logger || console
|
|
38
|
+
let tasks = []
|
|
39
|
+
let loaded = false
|
|
40
|
+
let timer = null
|
|
41
|
+
|
|
42
|
+
async function load() {
|
|
43
|
+
try {
|
|
44
|
+
const raw = await readFile(filePath, 'utf8')
|
|
45
|
+
tasks = JSON.parse(raw)
|
|
46
|
+
} catch (err) {
|
|
47
|
+
if (err?.code !== 'ENOENT') {
|
|
48
|
+
logger?.debug?.('[dsh-messenger-gateway] scheduler load error:', err?.message || err)
|
|
49
|
+
}
|
|
50
|
+
tasks = []
|
|
51
|
+
}
|
|
52
|
+
loaded = true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const RETENTION_MS = 7 * 86400 * 1000
|
|
56
|
+
|
|
57
|
+
async function save() {
|
|
58
|
+
if (!filePath) return
|
|
59
|
+
try {
|
|
60
|
+
const now = Date.now()
|
|
61
|
+
tasks = tasks.filter((t) => {
|
|
62
|
+
if (t.status === 'pending') return true
|
|
63
|
+
const finishTime = t.firedAt || t.createdAt || 0
|
|
64
|
+
return (now - finishTime) < RETENTION_MS
|
|
65
|
+
})
|
|
66
|
+
await writeJsonAtomic(filePath, tasks)
|
|
67
|
+
} catch (err) {
|
|
68
|
+
logger?.warn?.('[dsh-messenger-gateway] scheduler save failed:', err?.message || err)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function checkDue() {
|
|
73
|
+
if (!loaded) await load()
|
|
74
|
+
const now = Date.now()
|
|
75
|
+
const due = tasks.filter((t) => t.status === 'pending' && t.dueAt <= now)
|
|
76
|
+
|
|
77
|
+
for (const task of due) {
|
|
78
|
+
if (task.recurring && Number(task.intervalMs) > 0) {
|
|
79
|
+
task.dueAt = now + Number(task.intervalMs)
|
|
80
|
+
task.lastFiredAt = now
|
|
81
|
+
} else {
|
|
82
|
+
task.status = 'fired'
|
|
83
|
+
task.firedAt = now
|
|
84
|
+
}
|
|
85
|
+
if (typeof onDue === 'function') {
|
|
86
|
+
try {
|
|
87
|
+
await onDue(task)
|
|
88
|
+
} catch (err) {
|
|
89
|
+
logger?.warn?.('[dsh-messenger-gateway] scheduler task execution failed:', err?.message || err)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (due.length > 0) {
|
|
95
|
+
await save()
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function start(intervalMs = 5000) {
|
|
100
|
+
if (timer) clearInterval(timer)
|
|
101
|
+
timer = setInterval(() => {
|
|
102
|
+
checkDue().catch(() => {})
|
|
103
|
+
}, intervalMs)
|
|
104
|
+
timer.unref?.()
|
|
105
|
+
checkDue().catch(() => {})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function stop() {
|
|
109
|
+
if (timer) {
|
|
110
|
+
clearInterval(timer)
|
|
111
|
+
timer = null
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function schedule(taskData) {
|
|
116
|
+
if (!loaded) await load()
|
|
117
|
+
const id = taskData.id || randomUUID().slice(0, 8)
|
|
118
|
+
const task = {
|
|
119
|
+
id,
|
|
120
|
+
platform: taskData.platform || 'telegram',
|
|
121
|
+
chatId: taskData.chatId,
|
|
122
|
+
threadId: taskData.threadId || 0,
|
|
123
|
+
userId: taskData.userId,
|
|
124
|
+
text: taskData.text,
|
|
125
|
+
prompt: taskData.prompt || taskData.text || '',
|
|
126
|
+
dueAt: taskData.dueAt,
|
|
127
|
+
createdAt: Date.now(),
|
|
128
|
+
status: 'pending',
|
|
129
|
+
recurring: Boolean(taskData.recurring),
|
|
130
|
+
intervalMs: taskData.intervalMs || 0,
|
|
131
|
+
cronExpr: taskData.cronExpr || '',
|
|
132
|
+
}
|
|
133
|
+
tasks.push(task)
|
|
134
|
+
await save()
|
|
135
|
+
return task
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function list(chatId) {
|
|
139
|
+
if (!loaded) await load()
|
|
140
|
+
const now = Date.now()
|
|
141
|
+
return tasks.filter((t) => (chatId ? t.chatId === chatId : true) && t.status === 'pending' && t.dueAt > now)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function listRecurring(chatId) {
|
|
145
|
+
if (!loaded) await load()
|
|
146
|
+
return tasks.filter((t) => (chatId ? t.chatId === chatId : true) && t.status === 'pending' && t.recurring)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function cancel(id, chatId) {
|
|
150
|
+
if (!loaded) await load()
|
|
151
|
+
const task = tasks.find((t) => t.id === id && (chatId ? t.chatId === chatId : true) && t.status === 'pending')
|
|
152
|
+
if (!task) return false
|
|
153
|
+
task.status = 'cancelled'
|
|
154
|
+
await save()
|
|
155
|
+
return true
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
load,
|
|
160
|
+
save,
|
|
161
|
+
start,
|
|
162
|
+
stop,
|
|
163
|
+
schedule,
|
|
164
|
+
list,
|
|
165
|
+
listRecurring,
|
|
166
|
+
cancel,
|
|
167
|
+
checkDue,
|
|
168
|
+
getTasks: () => tasks,
|
|
169
|
+
}
|
|
170
170
|
}
|
package/lib/telegram-errors.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-messenger-gateway",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
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",
|