@goodandready/dsh-messenger-gateway 0.3.20 → 0.3.22
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 +73 -11
- package/lib/adapters/slack.js +78 -12
- package/lib/adapters/telegram.js +712 -708
- package/lib/alerts.js +10 -10
- package/lib/artifacts.js +117 -117
- package/lib/ask.js +151 -151
- package/lib/client.js +424 -105
- package/lib/commands.js +74 -70
- package/lib/config.js +104 -104
- package/lib/content-guard.js +2 -2
- package/lib/documents.js +9 -9
- package/lib/file-manager.js +9 -9
- package/lib/gateway.js +1772 -1539
- package/lib/index.js +532 -500
- package/lib/locales/en.js +123 -0
- package/lib/locales/index.js +21 -0
- package/lib/locales/zh.js +123 -0
- package/lib/models.js +1 -1
- package/lib/personas.js +67 -14
- package/lib/photos.js +2 -2
- package/lib/scheduler.js +29 -12
- package/lib/stream.js +4 -2
- package/lib/text.js +1 -1
- package/lib/updater.js +304 -0
- package/package.json +1 -1
package/lib/commands.js
CHANGED
|
@@ -1,70 +1,74 @@
|
|
|
1
|
-
export const DEFAULT_TELEGRAM_COMMANDS = [
|
|
2
|
-
{ command: 'start', description: '
|
|
3
|
-
{ command: 'help', description: '
|
|
4
|
-
{ command: 'new', description: '
|
|
5
|
-
{ command: 'whoami', description: '
|
|
6
|
-
{ command: 'stop', description: '
|
|
7
|
-
{ command: 'pair', description: '
|
|
8
|
-
{ command: 'sethome', description: '
|
|
9
|
-
{ command: 'home', description: '
|
|
10
|
-
{ command: 'model', description: '
|
|
11
|
-
{ command: 'top', description: '
|
|
12
|
-
{ command: 'topic', description: '
|
|
13
|
-
{ command: 'status', description: '
|
|
14
|
-
{ command: 'setalert', description: '
|
|
15
|
-
{ command: 'alert', description: '
|
|
16
|
-
{ command: 'keyboard', description: '
|
|
17
|
-
{ command: 'role', description: '
|
|
18
|
-
{ command: '
|
|
19
|
-
{ command: '
|
|
20
|
-
{ command: '
|
|
21
|
-
{ command: '
|
|
22
|
-
{ command: '
|
|
23
|
-
{ command: '
|
|
24
|
-
{ command: '
|
|
25
|
-
{ command: '
|
|
26
|
-
{ command: '
|
|
27
|
-
{ command: '
|
|
28
|
-
{ command: '
|
|
29
|
-
{ command: '
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (!
|
|
61
|
-
let
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
seen.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
1
|
+
export const DEFAULT_TELEGRAM_COMMANDS = [
|
|
2
|
+
{ command: 'start', description: 'Connect and show greeting' },
|
|
3
|
+
{ command: 'help', description: 'Show commands help' },
|
|
4
|
+
{ command: 'new', description: 'Start a new agent session' },
|
|
5
|
+
{ command: 'whoami', description: 'Show your messenger user ID' },
|
|
6
|
+
{ command: 'stop', description: 'Interrupt current turn' },
|
|
7
|
+
{ command: 'pair', description: 'Approve pairing code: /pair CODE' },
|
|
8
|
+
{ command: 'sethome', description: 'Set home: /sethome [name]' },
|
|
9
|
+
{ command: 'home', description: 'List registered home channels' },
|
|
10
|
+
{ command: 'model', description: 'Interactive model selector' },
|
|
11
|
+
{ command: 'top', description: 'System resources: CPU, RAM, uptime' },
|
|
12
|
+
{ command: 'topic', description: 'Create forum topic: /topic <name>' },
|
|
13
|
+
{ command: 'status', description: 'Gateway and bot status' },
|
|
14
|
+
{ command: 'setalert', description: 'Designate channel for alerts' },
|
|
15
|
+
{ command: 'alert', description: 'Alert channel status: /alert [test]' },
|
|
16
|
+
{ command: 'keyboard', description: 'Toggle quick actions: /keyboard on|off' },
|
|
17
|
+
{ command: 'role', description: 'Switch persona: /role [name]' },
|
|
18
|
+
{ command: 'bind', description: 'Bind preset/persona to forum topic: /bind <name>' },
|
|
19
|
+
{ command: 'preset', description: 'Show or switch agent preset: /preset [name]' },
|
|
20
|
+
{ command: 'cron', description: 'Manage recurring reports: /cron' },
|
|
21
|
+
{ command: 'skills', description: 'List active tools & skills' },
|
|
22
|
+
{ command: 'tools', description: 'List active tools' },
|
|
23
|
+
{ command: 'fork', description: 'Fork session into new branch' },
|
|
24
|
+
{ command: 'export', description: 'Export history to Markdown' },
|
|
25
|
+
{ command: 'rewind', description: 'Rewind turns: /rewind [N]' },
|
|
26
|
+
{ command: 'files', description: 'Workspace file explorer: /files [dir]' },
|
|
27
|
+
{ command: 'get', description: 'Download file to messenger: /get <path>' },
|
|
28
|
+
{ command: 'remind', description: 'Set reminder: /remind <time> <text>' },
|
|
29
|
+
{ command: 'voice', description: 'Voice reply mode: /voice on|off|status' },
|
|
30
|
+
{ command: 'tts', description: 'Speech in this chat: /tts on|off|status' },
|
|
31
|
+
{ command: 'mute', description: 'Mute notifications in this chat' },
|
|
32
|
+
{ command: 'unmute', description: 'Unmute notifications in this chat' },
|
|
33
|
+
{ command: 'update', description: 'Check or install plugin update: /update [now]' },
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
export function normalizeTelegramCommands(commands) {
|
|
37
|
+
const source = Array.isArray(commands) && commands.length ? commands : DEFAULT_TELEGRAM_COMMANDS
|
|
38
|
+
const out = []
|
|
39
|
+
const seen = new Set()
|
|
40
|
+
for (const raw of source) {
|
|
41
|
+
if (!raw || typeof raw !== 'object') continue
|
|
42
|
+
let command = String(raw.command || '').trim().replace(/^\//, '')
|
|
43
|
+
const description = String(raw.description || '').trim()
|
|
44
|
+
if (!command || !description) continue
|
|
45
|
+
command = command.slice(0, 32).toLowerCase()
|
|
46
|
+
if (seen.has(command)) continue
|
|
47
|
+
seen.add(command)
|
|
48
|
+
out.push({ command, description: description.slice(0, 256) })
|
|
49
|
+
}
|
|
50
|
+
return out.length ? out : [...DEFAULT_TELEGRAM_COMMANDS]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function mergeDynamicCommands(baseCommands = DEFAULT_TELEGRAM_COMMANDS, dynamicCommands = [], maxTotal = 100) {
|
|
54
|
+
const normalizedBase = normalizeTelegramCommands(baseCommands)
|
|
55
|
+
const out = [...normalizedBase]
|
|
56
|
+
const seen = new Set(out.map((c) => c.command))
|
|
57
|
+
|
|
58
|
+
for (const item of dynamicCommands || []) {
|
|
59
|
+
if (out.length >= maxTotal) break
|
|
60
|
+
if (!item || typeof item !== 'object') continue
|
|
61
|
+
let command = String(item.command || item.name || '').trim().replace(/^\//, '').toLowerCase()
|
|
62
|
+
// Telegram format: lowercase alphanumeric and underscore only, 1-32 chars
|
|
63
|
+
command = command.replace(/[^a-z0-9_]/g, '_').slice(0, 32)
|
|
64
|
+
if (!command || seen.has(command)) continue
|
|
65
|
+
let description = String(item.description || item.title || `Skill ${command}`).trim()
|
|
66
|
+
if (!description) description = `Skill ${command}`
|
|
67
|
+
description = description.slice(0, 256)
|
|
68
|
+
seen.add(command)
|
|
69
|
+
out.push({ command, description })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return out.slice(0, maxTotal)
|
|
73
|
+
}
|
|
74
|
+
|
package/lib/config.js
CHANGED
|
@@ -1,104 +1,104 @@
|
|
|
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
|
-
statusIndicator: z.boolean().default(false).description('Opt-in: set bot short description to Online/Offline (bots have no presence)'),
|
|
35
|
-
statusOnline: z.string().default('Online'),
|
|
36
|
-
statusOffline: z.string().default('Offline'),
|
|
37
|
-
transport: z.union([z.const('poll'), z.const('webhook')]).default('poll'),
|
|
38
|
-
webhookUrl: z.string().default(''),
|
|
39
|
-
webhookSecret: z.string().role('secret').default(''),
|
|
40
|
-
webhookPath: z.string().default('/dsh-messenger-gateway/telegram/webhook'),
|
|
41
|
-
voiceMode: z.union([z.const('mirror'), z.const('always'), z.const('off')]).default('mirror')
|
|
42
|
-
.description('mirror: TTS when inbound was voice; always/off override (per-user /voice wins)'),
|
|
43
|
-
quickActions: z.boolean().default(false).description('Show quick actions keyboard in Telegram (/new, /stop, /voice, /status)'),
|
|
44
|
-
artifactPreviews: z.boolean().default(true).description('Render diagrams and formatted tables as previews'),
|
|
45
|
-
notifyBridge: z.object({
|
|
46
|
-
enabled: z.boolean().default(false),
|
|
47
|
-
events: z.array(z.string()).default(['task_done', 'error']),
|
|
48
|
-
home: z.string().default('default'),
|
|
49
|
-
excludeSessionPrefixes: z.array(z.string()).default(['msgw-']),
|
|
50
|
-
}).default({ enabled: false }),
|
|
51
|
-
alerts: z.object({
|
|
52
|
-
enabled: z.boolean().default(false),
|
|
53
|
-
chatId: z.union([z.number(), z.string()]).default(''),
|
|
54
|
-
threadId: z.number().default(0),
|
|
55
|
-
home: z.string().default(''),
|
|
56
|
-
events: z.array(z.string()).default(['error', 'pairing']),
|
|
57
|
-
}).default({ enabled: false }),
|
|
58
|
-
forumMirror: z.object({
|
|
59
|
-
enabled: z.boolean().default(false),
|
|
60
|
-
chatId: z.union([z.number(), z.string()]).default(''),
|
|
61
|
-
}).default({ enabled: false }),
|
|
62
|
-
forumMirrorChatId: z.union([z.number(), z.string()]).default(''),
|
|
63
|
-
forumMirrorEnabled: z.boolean().default(false),
|
|
64
|
-
}),
|
|
65
|
-
discord: z.object({
|
|
66
|
-
enabled: z.boolean().default(false),
|
|
67
|
-
botToken: z.string().role('secret').default(''),
|
|
68
|
-
webhookUrl: z.string().default(''),
|
|
69
|
-
}).default({ enabled: false }),
|
|
70
|
-
slack: z.object({
|
|
71
|
-
enabled: z.boolean().default(false),
|
|
72
|
-
botToken: z.string().role('secret').default(''),
|
|
73
|
-
webhookUrl: z.string().default(''),
|
|
74
|
-
}).default({ enabled: false }),
|
|
75
|
-
webhooks: z.object({
|
|
76
|
-
enabled: z.boolean().default(true),
|
|
77
|
-
secret: z.string().role('secret').default(''),
|
|
78
|
-
}).default({ enabled: true }),
|
|
79
|
-
media: z.object({
|
|
80
|
-
cacheDir: z.string().default(''),
|
|
81
|
-
maxDocBytes: z.number().default(20 * 1024 * 1024),
|
|
82
|
-
maxTextInjectBytes: z.number().default(100 * 1024),
|
|
83
|
-
maxImageBytes: z.number().default(20 * 1024 * 1024),
|
|
84
|
-
}),
|
|
85
|
-
tts: z.object({
|
|
86
|
-
enabled: z.boolean().default(false),
|
|
87
|
-
maxChars: z.number().default(4000),
|
|
88
|
-
voiceSummary: z.boolean().default(false).description('Speak concise TL;DR summary while full text is sent to chat'),
|
|
89
|
-
}),
|
|
90
|
-
agent: z.object({
|
|
91
|
-
provider: z.string().default(''),
|
|
92
|
-
model: z.string().default(''),
|
|
93
|
-
cwd: z.string().default(''),
|
|
94
|
-
instructionPrefix: z.string().default(''),
|
|
95
|
-
maxMessageLength: z.number().default(4000),
|
|
96
|
-
idleTimeoutMs: z.number().default(3_600_000),
|
|
97
|
-
turnTimeoutMs: z.number().default(600_000),
|
|
98
|
-
photoOnlyMode: z.union([z.const('prompt'), z.const('run')]).default('prompt'),
|
|
99
|
-
sessionScope: z.union([z.const('chat'), z.const('user')]).default('user')
|
|
100
|
-
.description('In groups: user = per-user session; chat = shared session for the chat/topic'),
|
|
101
|
-
}),
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
export const Config = PluginConfig
|
|
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: unformatted plain text'),
|
|
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
|
+
statusIndicator: z.boolean().default(false).description('Opt-in: set bot short description to Online/Offline (bots have no presence)'),
|
|
35
|
+
statusOnline: z.string().default('Online'),
|
|
36
|
+
statusOffline: z.string().default('Offline'),
|
|
37
|
+
transport: z.union([z.const('poll'), z.const('webhook')]).default('poll'),
|
|
38
|
+
webhookUrl: z.string().default(''),
|
|
39
|
+
webhookSecret: z.string().role('secret').default(''),
|
|
40
|
+
webhookPath: z.string().default('/dsh-messenger-gateway/telegram/webhook'),
|
|
41
|
+
voiceMode: z.union([z.const('mirror'), z.const('always'), z.const('off')]).default('mirror')
|
|
42
|
+
.description('mirror: TTS when inbound was voice; always/off override (per-user /voice wins)'),
|
|
43
|
+
quickActions: z.boolean().default(false).description('Show quick actions keyboard in Telegram (/new, /stop, /voice, /status)'),
|
|
44
|
+
artifactPreviews: z.boolean().default(true).description('Render diagrams and formatted tables as previews'),
|
|
45
|
+
notifyBridge: z.object({
|
|
46
|
+
enabled: z.boolean().default(false),
|
|
47
|
+
events: z.array(z.string()).default(['task_done', 'error']),
|
|
48
|
+
home: z.string().default('default'),
|
|
49
|
+
excludeSessionPrefixes: z.array(z.string()).default(['msgw-']),
|
|
50
|
+
}).default({ enabled: false }),
|
|
51
|
+
alerts: z.object({
|
|
52
|
+
enabled: z.boolean().default(false),
|
|
53
|
+
chatId: z.union([z.number(), z.string()]).default(''),
|
|
54
|
+
threadId: z.number().default(0),
|
|
55
|
+
home: z.string().default(''),
|
|
56
|
+
events: z.array(z.string()).default(['error', 'pairing']),
|
|
57
|
+
}).default({ enabled: false }),
|
|
58
|
+
forumMirror: z.object({
|
|
59
|
+
enabled: z.boolean().default(false),
|
|
60
|
+
chatId: z.union([z.number(), z.string()]).default(''),
|
|
61
|
+
}).default({ enabled: false }),
|
|
62
|
+
forumMirrorChatId: z.union([z.number(), z.string()]).default(''),
|
|
63
|
+
forumMirrorEnabled: z.boolean().default(false),
|
|
64
|
+
}),
|
|
65
|
+
discord: z.object({
|
|
66
|
+
enabled: z.boolean().default(false),
|
|
67
|
+
botToken: z.string().role('secret').default(''),
|
|
68
|
+
webhookUrl: z.string().default(''),
|
|
69
|
+
}).default({ enabled: false }),
|
|
70
|
+
slack: z.object({
|
|
71
|
+
enabled: z.boolean().default(false),
|
|
72
|
+
botToken: z.string().role('secret').default(''),
|
|
73
|
+
webhookUrl: z.string().default(''),
|
|
74
|
+
}).default({ enabled: false }),
|
|
75
|
+
webhooks: z.object({
|
|
76
|
+
enabled: z.boolean().default(true),
|
|
77
|
+
secret: z.string().role('secret').default(''),
|
|
78
|
+
}).default({ enabled: true }),
|
|
79
|
+
media: z.object({
|
|
80
|
+
cacheDir: z.string().default(''),
|
|
81
|
+
maxDocBytes: z.number().default(20 * 1024 * 1024),
|
|
82
|
+
maxTextInjectBytes: z.number().default(100 * 1024),
|
|
83
|
+
maxImageBytes: z.number().default(20 * 1024 * 1024),
|
|
84
|
+
}),
|
|
85
|
+
tts: z.object({
|
|
86
|
+
enabled: z.boolean().default(false),
|
|
87
|
+
maxChars: z.number().default(4000),
|
|
88
|
+
voiceSummary: z.boolean().default(false).description('Speak concise TL;DR summary while full text is sent to chat'),
|
|
89
|
+
}),
|
|
90
|
+
agent: z.object({
|
|
91
|
+
provider: z.string().default(''),
|
|
92
|
+
model: z.string().default(''),
|
|
93
|
+
cwd: z.string().default(''),
|
|
94
|
+
instructionPrefix: z.string().default(''),
|
|
95
|
+
maxMessageLength: z.number().default(4000),
|
|
96
|
+
idleTimeoutMs: z.number().default(3_600_000),
|
|
97
|
+
turnTimeoutMs: z.number().default(600_000),
|
|
98
|
+
photoOnlyMode: z.union([z.const('prompt'), z.const('run')]).default('prompt'),
|
|
99
|
+
sessionScope: z.union([z.const('chat'), z.const('user')]).default('user')
|
|
100
|
+
.description('In groups: user = per-user session; chat = shared session for the chat/topic'),
|
|
101
|
+
}),
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
export const Config = PluginConfig
|
package/lib/content-guard.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
export function ensureContentArray(content) {
|
|
8
8
|
if (Array.isArray(content)) return content
|
|
9
9
|
if (typeof content === 'string') {
|
|
10
|
-
return content ? [{ type: 'text', text: content }] : [{ type: 'text', text: '(
|
|
10
|
+
return content ? [{ type: 'text', text: content }] : [{ type: 'text', text: '(empty message)' }]
|
|
11
11
|
}
|
|
12
12
|
if (content && typeof content === 'object' && content.type) return [content]
|
|
13
|
-
return [{ type: 'text', text: String(content ?? '(
|
|
13
|
+
return [{ type: 'text', text: String(content ?? '(empty message)') }]
|
|
14
14
|
}
|
package/lib/documents.js
CHANGED
|
@@ -5,16 +5,16 @@ import { inflateRawSync } from 'node:zlib'
|
|
|
5
5
|
/** Format a cached inbound document/video path for the agent (fs tools). */
|
|
6
6
|
export function formatInboundDocument(att, parsed = null) {
|
|
7
7
|
const kind = att?.kind || 'document'
|
|
8
|
-
const labels = { video: '
|
|
9
|
-
const label = labels[kind] || '
|
|
8
|
+
const labels = { video: 'Video', sticker: 'Sticker', animation: 'GIF', document: 'Document' }
|
|
9
|
+
const label = labels[kind] || 'File'
|
|
10
10
|
const name = att?.name ? ` ${att.name}` : ''
|
|
11
11
|
const mime = att?.mime ? ` (${att.mime})` : ''
|
|
12
12
|
const emoji = att?.emoji ? ` emoji=${att.emoji}` : ''
|
|
13
|
-
let base = `[${label}${name}${mime}${emoji}]\
|
|
13
|
+
let base = `[${label}${name}${mime}${emoji}]\nPath: ${att.path}`
|
|
14
14
|
|
|
15
15
|
if (parsed?.text) {
|
|
16
|
-
const truncNote = parsed.truncated ? ' (
|
|
17
|
-
base += `\n\n[
|
|
16
|
+
const truncNote = parsed.truncated ? ' (content truncated)' : ''
|
|
17
|
+
base += `\n\n[Extracted text from file${truncNote}]:\n\`\`\`\n${parsed.text}\n\`\`\``
|
|
18
18
|
}
|
|
19
19
|
return base
|
|
20
20
|
}
|
|
@@ -25,11 +25,11 @@ export function documentOnlyHint(attachments, userText) {
|
|
|
25
25
|
if (!docs.length) return ''
|
|
26
26
|
if (docs.length === 1) {
|
|
27
27
|
const k = docs[0].kind
|
|
28
|
-
if (k === 'sticker') return '[
|
|
29
|
-
if (k === 'video' || k === 'animation') return '[
|
|
30
|
-
return '[
|
|
28
|
+
if (k === 'sticker') return '[User sent a sticker]'
|
|
29
|
+
if (k === 'video' || k === 'animation') return '[User sent a video]'
|
|
30
|
+
return '[User sent a file]'
|
|
31
31
|
}
|
|
32
|
-
return `[
|
|
32
|
+
return `[User sent ${docs.length} files]`
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export const TEXT_INJECT_EXTS = new Set([
|
package/lib/file-manager.js
CHANGED
|
@@ -32,7 +32,7 @@ export async function listFiles(baseCwd, subPath = '.', opts = {}) {
|
|
|
32
32
|
try {
|
|
33
33
|
st = await stat(target)
|
|
34
34
|
} catch (err) {
|
|
35
|
-
return { ok: false, error:
|
|
35
|
+
return { ok: false, error: `Directory not found: ${err.message}` }
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
if (!st.isDirectory()) {
|
|
@@ -41,7 +41,7 @@ export async function listFiles(baseCwd, subPath = '.', opts = {}) {
|
|
|
41
41
|
isDirectory: false,
|
|
42
42
|
currentPath: relativePath,
|
|
43
43
|
entries: [{ name: relativePath, size: st.size, isDirectory: false }],
|
|
44
|
-
formattedText: `📄 <b>${relativePath}</b> (${formatFileSize(st.size)})\
|
|
44
|
+
formattedText: `📄 <b>${relativePath}</b> (${formatFileSize(st.size)})\nDownload: <code>/get ${relativePath}</code>`,
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -81,12 +81,12 @@ export async function listFiles(baseCwd, subPath = '.', opts = {}) {
|
|
|
81
81
|
const sliced = entries.slice(0, maxEntries)
|
|
82
82
|
|
|
83
83
|
const lines = [
|
|
84
|
-
`📂 <b
|
|
84
|
+
`📂 <b>Files: <code>${relativePath === '.' ? '/' : `/${relativePath}`}</code></b> (${total} items):`,
|
|
85
85
|
'',
|
|
86
86
|
]
|
|
87
87
|
|
|
88
88
|
if (relativePath !== '.') {
|
|
89
|
-
lines.push('📁 <code>..</code> (
|
|
89
|
+
lines.push('📁 <code>..</code> (parent folder: <code>/files ..</code>)')
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
for (const e of sliced) {
|
|
@@ -98,10 +98,10 @@ export async function listFiles(baseCwd, subPath = '.', opts = {}) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
if (total > maxEntries) {
|
|
101
|
-
lines.push(`\n<i>...
|
|
101
|
+
lines.push(`\n<i>... and ${total - maxEntries} more files</i>`)
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
lines.push('\
|
|
104
|
+
lines.push('\nDownload file: <code>/get <file></code>\nNavigate directory: <code>/files <dir></code>')
|
|
105
105
|
|
|
106
106
|
return {
|
|
107
107
|
ok: true,
|
|
@@ -119,17 +119,17 @@ export async function getFileForDownload(baseCwd, relativePath, maxBytes = 50 *
|
|
|
119
119
|
try {
|
|
120
120
|
st = await stat(target)
|
|
121
121
|
} catch (err) {
|
|
122
|
-
return { ok: false, error:
|
|
122
|
+
return { ok: false, error: `File not found: ${safeRel}` }
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
if (st.isDirectory()) {
|
|
126
|
-
return { ok: false, error:
|
|
126
|
+
return { ok: false, error: `"${safeRel}" is a directory, not a file. List: /files ${safeRel}` }
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
if (st.size > maxBytes) {
|
|
130
130
|
return {
|
|
131
131
|
ok: false,
|
|
132
|
-
error:
|
|
132
|
+
error: `File is too large (${formatFileSize(st.size)} > ${formatFileSize(maxBytes)}).`,
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|