@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 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
- const workspaceDir = ctx.workspaceDir
22
- setWorkspaceDir(workspaceDir)
23
- return null
22
+ setWorkspaceDir(ctx.workspaceDir)
23
+ return createDcgchatMessageTool(ctx)
24
24
  })
25
25
  }
26
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcrays/dcgchat-test",
3
- "version": "0.3.37",
3
+ "version": "0.3.38",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for 书灵墨宝 (WebSocket)",
6
6
  "main": "index.ts",
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
+ }