@goodandready/dsh-messenger-gateway 0.4.4 → 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.js +597 -596
- package/lib/client.js +11 -6
- package/lib/forum-mirror.js +105 -105
- package/lib/gateway-callbacks.js +188 -187
- package/lib/gateway-commands.js +587 -584
- package/lib/gateway.js +7 -1
- package/lib/http.js +16 -3
- package/lib/index.js +544 -540
- package/lib/models.js +29 -2
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -430,14 +430,18 @@ window.__ModuleLoader__.load({
|
|
|
430
430
|
}
|
|
431
431
|
const ErrorBoundary = createErrorBoundary()
|
|
432
432
|
|
|
433
|
-
function FallbackChevron() {
|
|
433
|
+
function FallbackChevron(props) {
|
|
434
434
|
return React.createElement(
|
|
435
435
|
'svg',
|
|
436
|
-
{ width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' },
|
|
436
|
+
{ width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', ...props },
|
|
437
437
|
React.createElement('polyline', { points: '6 9 12 15 18 9' })
|
|
438
438
|
)
|
|
439
439
|
}
|
|
440
|
-
|
|
440
|
+
function Chevron(props) {
|
|
441
|
+
const CoreChevron = props?.ctx?.get?.('icons')?.IconChevronDownOutline14
|
|
442
|
+
if (CoreChevron) return React.createElement(CoreChevron, props)
|
|
443
|
+
return React.createElement(FallbackChevron, props)
|
|
444
|
+
}
|
|
441
445
|
|
|
442
446
|
function idsToText(ids) { return (Array.isArray(ids) ? ids : []).map(String).filter(Boolean).join(', ') }
|
|
443
447
|
function textToIds(text) {
|
|
@@ -1512,6 +1516,7 @@ window.__ModuleLoader__.load({
|
|
|
1512
1516
|
|
|
1513
1517
|
function PluginCard(props) {
|
|
1514
1518
|
const [open, setOpen] = React.useState(false)
|
|
1519
|
+
const cardCtx = (props && props.ctx) || (typeof ctx !== 'undefined' ? ctx : undefined)
|
|
1515
1520
|
const t = props?.t || makeT(props?.locale)
|
|
1516
1521
|
React.useEffect(() => {
|
|
1517
1522
|
ensureCss()
|
|
@@ -1529,7 +1534,7 @@ window.__ModuleLoader__.load({
|
|
|
1529
1534
|
'div',
|
|
1530
1535
|
{ className: 'msgw-page-seat' },
|
|
1531
1536
|
React.createElement(ErrorBoundary, null,
|
|
1532
|
-
React.createElement(SettingsPage, { ...props, ctx:
|
|
1537
|
+
React.createElement(SettingsPage, { ...props, ctx: cardCtx, t })
|
|
1533
1538
|
)
|
|
1534
1539
|
)
|
|
1535
1540
|
}
|
|
@@ -1564,7 +1569,7 @@ window.__ModuleLoader__.load({
|
|
|
1564
1569
|
React.createElement(
|
|
1565
1570
|
'span',
|
|
1566
1571
|
{ className: 'msgw-chev', style: { transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .16s' } },
|
|
1567
|
-
React.createElement(Chevron)
|
|
1572
|
+
React.createElement(Chevron, { ctx: cardCtx })
|
|
1568
1573
|
)
|
|
1569
1574
|
),
|
|
1570
1575
|
open
|
|
@@ -1574,7 +1579,7 @@ window.__ModuleLoader__.load({
|
|
|
1574
1579
|
React.createElement(
|
|
1575
1580
|
ErrorBoundary,
|
|
1576
1581
|
null,
|
|
1577
|
-
React.createElement(SettingsPage, { ...props, ctx:
|
|
1582
|
+
React.createElement(SettingsPage, { ...props, ctx: cardCtx, t })
|
|
1578
1583
|
)
|
|
1579
1584
|
)
|
|
1580
1585
|
: null
|
package/lib/forum-mirror.js
CHANGED
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
import { mergeDynamicCommands } from './commands.js'
|
|
2
|
-
import { t } from './locales/index.js'
|
|
3
|
-
import { stripReasoningPreamble, splitText } from './text.js'
|
|
4
|
-
import { stripImageUrls } from './outbound.js'
|
|
5
|
-
|
|
6
|
-
export function collectDynamicSkills(ctx) {
|
|
7
|
-
const skills = []
|
|
8
|
-
try {
|
|
9
|
-
const skillsService = ctx.get?.('skills') ||
|
|
10
|
-
const allSkills = skillsService?.list?.() || []
|
|
11
|
-
for (const s of allSkills) {
|
|
12
|
-
if (!s || !s.name) continue
|
|
13
|
-
if (s.userInvocable === false) continue
|
|
14
|
-
skills.push({
|
|
15
|
-
name: String(s.name),
|
|
16
|
-
description: String(s.description || s.title || `Skill ${s.name}`).slice(0, 256),
|
|
17
|
-
})
|
|
18
|
-
}
|
|
19
|
-
} catch (err) {
|
|
20
|
-
ctx
|
|
21
|
-
}
|
|
22
|
-
return skills
|
|
23
|
-
}
|
|
24
|
-
export async function syncTelegramCommands(gw) {
|
|
25
|
-
const tgAdapter = gw.adapters.get('telegram')
|
|
26
|
-
if (!tgAdapter || typeof tgAdapter.registerCommands !== 'function') return
|
|
27
|
-
const baseCommands = gw.config.telegram?.commands || []
|
|
28
|
-
const dynamicSkills = collectDynamicSkills(gw.ctx)
|
|
29
|
-
const merged = mergeDynamicCommands(baseCommands, dynamicSkills, 100)
|
|
30
|
-
try {
|
|
31
|
-
await tgAdapter.registerCommands(merged)
|
|
32
|
-
ctx
|
|
33
|
-
} catch (err) {
|
|
34
|
-
ctx
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
export async function mirrorSessionToForumTopic(gw, session) {
|
|
38
|
-
if (!session || !session.id) return
|
|
39
|
-
const sessionId = String(session.id)
|
|
40
|
-
if (sessionId.startsWith('msgw-')) return
|
|
41
|
-
if (gw.sessionToThread.has(sessionId)) return
|
|
42
|
-
|
|
43
|
-
const tgCfg = gw.config.telegram || {}
|
|
44
|
-
if (!tgCfg.forumMirrorEnabled || !tgCfg.forumMirrorChatId) return
|
|
45
|
-
|
|
46
|
-
const tgAdapter = gw.adapters.get('telegram')
|
|
47
|
-
if (!tgAdapter || typeof tgAdapter.createForumTopic !== 'function') return
|
|
48
|
-
|
|
49
|
-
const forumChatId = tgCfg.forumMirrorChatId
|
|
50
|
-
const title = String(session.title || session.meta?.name || `Session ${sessionId.slice(0, 8)}`).slice(0, 120)
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const topic = await tgAdapter.createForumTopic(forumChatId, title)
|
|
54
|
-
const threadId = topic?.message_thread_id
|
|
55
|
-
if (!threadId) return
|
|
56
|
-
|
|
57
|
-
const threadKey = `${forumChatId}:${threadId}`
|
|
58
|
-
gw.sessionToThread.set(sessionId, { chatId: forumChatId, threadId })
|
|
59
|
-
gw.threadToSession.set(threadKey, sessionId)
|
|
60
|
-
|
|
61
|
-
const text = t('mirror.created', { sessionId, title }, 'en')
|
|
62
|
-
await tgAdapter.sendTo(forumChatId, { text }, { threadId })
|
|
63
|
-
ctx
|
|
64
|
-
} catch (err) {
|
|
65
|
-
ctx
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
export async function relayTurnToForumMirror(gw, session, event) {
|
|
69
|
-
if (!session || !session.id) return
|
|
70
|
-
const sessionId = String(session.id)
|
|
71
|
-
const threadInfo = gw.sessionToThread.get(sessionId)
|
|
72
|
-
if (!threadInfo) return
|
|
73
|
-
if (gw.pending.has(sessionId)) return
|
|
74
|
-
|
|
75
|
-
const tgAdapter = gw.adapters.get('telegram')
|
|
76
|
-
if (!tgAdapter) return
|
|
77
|
-
|
|
78
|
-
const messages = Array.isArray(session.messages)
|
|
79
|
-
? session.messages
|
|
80
|
-
: (Array.isArray(session.history) ? session.history : [])
|
|
81
|
-
|
|
82
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
83
|
-
const msg = messages[i]
|
|
84
|
-
const role = msg.role || (msg.type === 'user' ? 'user' : 'assistant')
|
|
85
|
-
if (role === 'assistant') {
|
|
86
|
-
let text = ''
|
|
87
|
-
if (typeof msg.content === 'string') text = msg.content
|
|
88
|
-
else if (Array.isArray(msg.content)) {
|
|
89
|
-
text = msg.content
|
|
90
|
-
.map((p) => (typeof p === 'string' ? p : (p?.text || '')))
|
|
91
|
-
.filter(Boolean)
|
|
92
|
-
.join('\n')
|
|
93
|
-
} else if (msg.text) text = msg.text
|
|
94
|
-
const clean = stripReasoningPreamble(stripImageUrls(text)).trim()
|
|
95
|
-
if (clean) {
|
|
96
|
-
const maxLen = Number(gw.config.agent?.maxMessageLength) || 4000
|
|
97
|
-
const chunks = splitText(clean, maxLen)
|
|
98
|
-
for (const chunk of chunks) {
|
|
99
|
-
await tgAdapter.sendTo(threadInfo.chatId, { text: chunk }, { threadId: threadInfo.threadId })
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
break
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
1
|
+
import { mergeDynamicCommands } from './commands.js'
|
|
2
|
+
import { t } from './locales/index.js'
|
|
3
|
+
import { stripReasoningPreamble, splitText } from './text.js'
|
|
4
|
+
import { stripImageUrls } from './outbound.js'
|
|
5
|
+
|
|
6
|
+
export function collectDynamicSkills(ctx) {
|
|
7
|
+
const skills = []
|
|
8
|
+
try {
|
|
9
|
+
const skillsService = ctx.get?.('skills') || ctx?.skills
|
|
10
|
+
const allSkills = skillsService?.list?.() || []
|
|
11
|
+
for (const s of allSkills) {
|
|
12
|
+
if (!s || !s.name) continue
|
|
13
|
+
if (s.userInvocable === false) continue
|
|
14
|
+
skills.push({
|
|
15
|
+
name: String(s.name),
|
|
16
|
+
description: String(s.description || s.title || `Skill ${s.name}`).slice(0, 256),
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
} catch (err) {
|
|
20
|
+
ctx?.logger?.debug?.(`Failed to collect dynamic skills: ${err?.message || err}`)
|
|
21
|
+
}
|
|
22
|
+
return skills
|
|
23
|
+
}
|
|
24
|
+
export async function syncTelegramCommands(gw) {
|
|
25
|
+
const tgAdapter = gw.adapters.get('telegram')
|
|
26
|
+
if (!tgAdapter || typeof tgAdapter.registerCommands !== 'function') return
|
|
27
|
+
const baseCommands = gw.config.telegram?.commands || []
|
|
28
|
+
const dynamicSkills = collectDynamicSkills(gw.ctx)
|
|
29
|
+
const merged = mergeDynamicCommands(baseCommands, dynamicSkills, 100)
|
|
30
|
+
try {
|
|
31
|
+
await tgAdapter.registerCommands(merged)
|
|
32
|
+
gw.ctx?.logger?.info?.(`Synced ${merged.length} Telegram commands (including ${dynamicSkills.length} dynamic skills)`)
|
|
33
|
+
} catch (err) {
|
|
34
|
+
gw.ctx?.logger?.warn?.(`Failed to sync Telegram commands: ${err?.message || err}`)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export async function mirrorSessionToForumTopic(gw, session) {
|
|
38
|
+
if (!session || !session.id) return
|
|
39
|
+
const sessionId = String(session.id)
|
|
40
|
+
if (sessionId.startsWith('msgw-')) return
|
|
41
|
+
if (gw.sessionToThread.has(sessionId)) return
|
|
42
|
+
|
|
43
|
+
const tgCfg = gw.config.telegram || {}
|
|
44
|
+
if (!tgCfg.forumMirrorEnabled || !tgCfg.forumMirrorChatId) return
|
|
45
|
+
|
|
46
|
+
const tgAdapter = gw.adapters.get('telegram')
|
|
47
|
+
if (!tgAdapter || typeof tgAdapter.createForumTopic !== 'function') return
|
|
48
|
+
|
|
49
|
+
const forumChatId = tgCfg.forumMirrorChatId
|
|
50
|
+
const title = String(session.title || session.meta?.name || `Session ${sessionId.slice(0, 8)}`).slice(0, 120)
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const topic = await tgAdapter.createForumTopic(forumChatId, title)
|
|
54
|
+
const threadId = topic?.message_thread_id
|
|
55
|
+
if (!threadId) return
|
|
56
|
+
|
|
57
|
+
const threadKey = `${forumChatId}:${threadId}`
|
|
58
|
+
gw.sessionToThread.set(sessionId, { chatId: forumChatId, threadId })
|
|
59
|
+
gw.threadToSession.set(threadKey, sessionId)
|
|
60
|
+
|
|
61
|
+
const text = t('mirror.created', { sessionId, title }, 'en')
|
|
62
|
+
await tgAdapter.sendTo(forumChatId, { text }, { threadId })
|
|
63
|
+
gw.ctx?.logger?.info?.(`Mirrored session ${sessionId} to Telegram forum topic ${threadId} in ${forumChatId}`)
|
|
64
|
+
} catch (err) {
|
|
65
|
+
gw.ctx?.logger?.warn?.(`Failed to mirror session ${sessionId} to forum topic: ${err?.message || err}`)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export async function relayTurnToForumMirror(gw, session, event) {
|
|
69
|
+
if (!session || !session.id) return
|
|
70
|
+
const sessionId = String(session.id)
|
|
71
|
+
const threadInfo = gw.sessionToThread.get(sessionId)
|
|
72
|
+
if (!threadInfo) return
|
|
73
|
+
if (gw.pending.has(sessionId)) return
|
|
74
|
+
|
|
75
|
+
const tgAdapter = gw.adapters.get('telegram')
|
|
76
|
+
if (!tgAdapter) return
|
|
77
|
+
|
|
78
|
+
const messages = Array.isArray(session.messages)
|
|
79
|
+
? session.messages
|
|
80
|
+
: (Array.isArray(session.history) ? session.history : [])
|
|
81
|
+
|
|
82
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
83
|
+
const msg = messages[i]
|
|
84
|
+
const role = msg.role || (msg.type === 'user' ? 'user' : 'assistant')
|
|
85
|
+
if (role === 'assistant') {
|
|
86
|
+
let text = ''
|
|
87
|
+
if (typeof msg.content === 'string') text = msg.content
|
|
88
|
+
else if (Array.isArray(msg.content)) {
|
|
89
|
+
text = msg.content
|
|
90
|
+
.map((p) => (typeof p === 'string' ? p : (p?.text || '')))
|
|
91
|
+
.filter(Boolean)
|
|
92
|
+
.join('\n')
|
|
93
|
+
} else if (msg.text) text = msg.text
|
|
94
|
+
const clean = stripReasoningPreamble(stripImageUrls(text)).trim()
|
|
95
|
+
if (clean) {
|
|
96
|
+
const maxLen = Number(gw.config.agent?.maxMessageLength) || 4000
|
|
97
|
+
const chunks = splitText(clean, maxLen)
|
|
98
|
+
for (const chunk of chunks) {
|
|
99
|
+
await tgAdapter.sendTo(threadInfo.chatId, { text: chunk }, { threadId: threadInfo.threadId })
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
break
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|