@dcrays/dcgchat-test 0.3.37 → 0.3.38
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/index.ts +3 -3
- package/package.json +1 -1
- package/src/bot.ts +0 -21
- package/src/tools/meeageToll.ts +145 -0
package/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { setDcgchatRuntime, setWorkspaceDir } from './src/utils/global.js'
|
|
|
5
5
|
import { monitoringToolMessage } from './src/tool.js'
|
|
6
6
|
import { setOpenClawConfig } from './src/utils/global.js'
|
|
7
7
|
import { startDcgchatGatewaySocket } from './src/gateway/socket.js'
|
|
8
|
+
import { createDcgchatMessageTool } from './src/tools/meeageToll.js'
|
|
8
9
|
|
|
9
10
|
const plugin = {
|
|
10
11
|
id: "dcgchat-test",
|
|
@@ -18,9 +19,8 @@ const plugin = {
|
|
|
18
19
|
api.registerChannel({ plugin: dcgchatPlugin })
|
|
19
20
|
setWorkspaceDir(api.config?.agents?.defaults?.workspace)
|
|
20
21
|
api.registerTool((ctx) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return null
|
|
22
|
+
setWorkspaceDir(ctx.workspaceDir)
|
|
23
|
+
return createDcgchatMessageTool(ctx)
|
|
24
24
|
})
|
|
25
25
|
}
|
|
26
26
|
}
|
package/package.json
CHANGED
package/src/bot.ts
CHANGED
|
@@ -428,27 +428,6 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
428
428
|
if (![...systemCommand, ...interruptCommand].includes(text?.trim())) {
|
|
429
429
|
if (sessionStreamSuppressed.has(effectiveSessionKey)) {
|
|
430
430
|
sessionStreamSuppressed.delete(effectiveSessionKey)
|
|
431
|
-
} else {
|
|
432
|
-
for (const file of extractMobookFiles(completeText)) {
|
|
433
|
-
const candidates: string[] = [file]
|
|
434
|
-
candidates.push(path.join(getWorkspaceDir(), file))
|
|
435
|
-
candidates.push(path.join(getWorkspaceDir(), file.replace(/^\//, '')))
|
|
436
|
-
const underMobook = file.replace(/^\/mobook\//i, '').replace(/^mobook[\\/]/i, '')
|
|
437
|
-
if (underMobook) {
|
|
438
|
-
if (process.platform === 'win32') {
|
|
439
|
-
candidates.push(path.join('C:\\', 'mobook', underMobook))
|
|
440
|
-
} else if (process.platform === 'darwin') {
|
|
441
|
-
candidates.push(path.join(os.homedir(), 'mobook', underMobook))
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
const resolved = candidates.find((p) => fs.existsSync(p))
|
|
445
|
-
if (!resolved) continue
|
|
446
|
-
try {
|
|
447
|
-
await sendDcgchatMedia({ sessionKey: effectiveSessionKey, mediaUrl: resolved, text: '' })
|
|
448
|
-
} catch (err) {
|
|
449
|
-
dcgLogger(` sendDcgchatMedia error: ${String(err)}`, 'error')
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
431
|
}
|
|
453
432
|
}
|
|
454
433
|
clearSentMediaKeys(msg.content.message_id)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { Type } from '@sinclair/typebox'
|
|
4
|
+
import type { AnyAgentTool } from 'openclaw/plugin-sdk'
|
|
5
|
+
import { jsonResult } from 'openclaw/plugin-sdk'
|
|
6
|
+
|
|
7
|
+
/** 与 `registerTool` 工厂入参一致(主包未导出 `OpenClawPluginToolContext` 时仅用所需字段)。 */
|
|
8
|
+
export type DcgchatMessageToolContext = {
|
|
9
|
+
sessionKey?: string
|
|
10
|
+
workspaceDir?: string
|
|
11
|
+
}
|
|
12
|
+
import { sendDcgchatMedia } from '../channel.js'
|
|
13
|
+
import { getOutboundMsgParams } from '../utils/params.js'
|
|
14
|
+
import { sendText } from '../transport.js'
|
|
15
|
+
|
|
16
|
+
const SAFE_PREFIXES = ['/workspace/', '/mobook/']
|
|
17
|
+
|
|
18
|
+
const fileType1 = ['.webp', '.gif', '.bmp', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.pdf', '.txt', '.rtf', '.odt', '.json']
|
|
19
|
+
const fileType2 = ['.xml', '.csv', '.yaml', '.yml', '.html', '.htm', '.md', '.markdown', '.css', '.js', '.ts', '.png', '.jpg', '.jpeg']
|
|
20
|
+
const fileType3 = ['.zip', '.rar', '.7z', '.tar', '.gz', '.bz2', '.xz', '.exe', '.dmg', '.pkg', '.apk', '.ipa', '.log', '.dat', '.bin']
|
|
21
|
+
const fileType4 = ['.svg', '.ico', '.mp3', '.wav', '.ogg', '.aac', '.m4a', '.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv', '.webm']
|
|
22
|
+
const SAFE_EXTENSIONS = new Set([...fileType1, ...fileType2, ...fileType3, ...fileType4])
|
|
23
|
+
|
|
24
|
+
const messageToolParameters = {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
content: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: '发送文本内容'
|
|
30
|
+
},
|
|
31
|
+
media: {
|
|
32
|
+
type: 'array',
|
|
33
|
+
description: '发送附件',
|
|
34
|
+
items: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
file: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
description: '文件路径,例如 /workspace/output/report.pdf'
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
required: ['file']
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function extractPaths(text?: string) {
|
|
49
|
+
if (!text) return []
|
|
50
|
+
const regex = /(\/workspace\/[^\s]+|\/mobook\/[^\s]+)/g
|
|
51
|
+
return text.match(regex) ?? []
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isSafePath(filepath: string) {
|
|
55
|
+
return SAFE_PREFIXES.some((prefix) => filepath.startsWith(prefix))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isSafeFile(filepath: string) {
|
|
59
|
+
if (!fs.existsSync(filepath)) return false
|
|
60
|
+
const stat = fs.statSync(filepath)
|
|
61
|
+
if (!stat.isFile()) return false
|
|
62
|
+
if (stat.size === 0) return false
|
|
63
|
+
const ext = path.extname(filepath)
|
|
64
|
+
return SAFE_EXTENSIONS.has(ext)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 书灵墨宝 message 工具:须符合 OpenClaw `AgentTool`(execute 返回 `AgentToolResult`)。
|
|
69
|
+
* 通过注册时的 `OpenClawPluginToolContext.sessionKey` 出站,不再使用非标准的 `execute(args, ctx)`。
|
|
70
|
+
*/
|
|
71
|
+
export function createDcgchatMessageTool(pluginCtx: DcgchatMessageToolContext): AnyAgentTool {
|
|
72
|
+
console.log('🚀 ~ createDcgchatMessageTool ~ pluginCtx:', pluginCtx)
|
|
73
|
+
return {
|
|
74
|
+
name: 'message',
|
|
75
|
+
label: 'message',
|
|
76
|
+
description: `
|
|
77
|
+
向用户发送消息。
|
|
78
|
+
如果发送附件:必须使用 media 字段
|
|
79
|
+
支持路径目录:
|
|
80
|
+
/workspace/
|
|
81
|
+
/mobook/
|
|
82
|
+
禁止直接输出路径文本
|
|
83
|
+
`,
|
|
84
|
+
parameters: messageToolParameters,
|
|
85
|
+
execute: async (_toolCallId, args, signal) => {
|
|
86
|
+
if (signal?.aborted) {
|
|
87
|
+
const err = new Error('Message send aborted')
|
|
88
|
+
err.name = 'AbortError'
|
|
89
|
+
throw err
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const sessionKey = pluginCtx.sessionKey?.trim()
|
|
93
|
+
if (!sessionKey) {
|
|
94
|
+
return jsonResult({ error: '缺少 sessionKey,无法向当前会话发送消息' })
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const sentFiles = new Set<string>()
|
|
99
|
+
|
|
100
|
+
if (args.media?.length) {
|
|
101
|
+
for (const media of args.media) {
|
|
102
|
+
const filepath = media.file
|
|
103
|
+
if (!filepath) continue
|
|
104
|
+
if (!isSafePath(filepath)) continue
|
|
105
|
+
if (!isSafeFile(filepath)) continue
|
|
106
|
+
if (sentFiles.has(filepath)) continue
|
|
107
|
+
|
|
108
|
+
await sendDcgchatMedia({ sessionKey, mediaUrl: filepath })
|
|
109
|
+
sentFiles.add(filepath)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const fallbackPaths = extractPaths(args.content)
|
|
114
|
+
for (const filepath of fallbackPaths) {
|
|
115
|
+
if (!isSafePath(filepath)) continue
|
|
116
|
+
if (!isSafeFile(filepath)) continue
|
|
117
|
+
if (sentFiles.has(filepath)) continue
|
|
118
|
+
|
|
119
|
+
await sendDcgchatMedia({ sessionKey, mediaUrl: filepath })
|
|
120
|
+
sentFiles.add(filepath)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let content = args.content ?? ''
|
|
124
|
+
for (const filepath of sentFiles) {
|
|
125
|
+
content = content.replace(filepath, '')
|
|
126
|
+
}
|
|
127
|
+
content = content.trim()
|
|
128
|
+
|
|
129
|
+
if (content.length > 0) {
|
|
130
|
+
const msgCtx = getOutboundMsgParams(sessionKey)
|
|
131
|
+
sendText(content, msgCtx)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return jsonResult({
|
|
135
|
+
success: true,
|
|
136
|
+
sentMediaCount: sentFiles.size
|
|
137
|
+
})
|
|
138
|
+
} catch (err) {
|
|
139
|
+
return jsonResult({
|
|
140
|
+
error: err instanceof Error ? err.message : String(err)
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|