@dcrays/dcgchat-test 0.3.41 → 0.4.1
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/package.json +1 -1
- package/src/agent.ts +15 -23
- package/src/bot.ts +1 -1
- package/src/session.ts +2 -2
- package/src/tools/messageTool.ts +84 -22
- package/src/utils/wsMessageHandler.ts +2 -2
package/package.json
CHANGED
package/src/agent.ts
CHANGED
|
@@ -32,20 +32,12 @@ function sendEvent(msgContent: Record<string, any>) {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
workspace: string
|
|
38
|
-
name?: string
|
|
39
|
-
description?: string
|
|
40
|
-
msgContent?: Record<string, any>
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** 若 workspace-${code}/agent 存在,则复制到 agents/${code}/agent */
|
|
44
|
-
function copyAgentsFiles(code: string) {
|
|
35
|
+
/** 若 workspace-${clone_code}/agent 存在,则复制到 agents/${clone_code}/agent */
|
|
36
|
+
function copyAgentsFiles(clone_code: string) {
|
|
45
37
|
const workspacePath = getWorkspaceDir()
|
|
46
38
|
if (!workspacePath) return
|
|
47
|
-
const workspaceDir = path.join(workspacePath, '../', `workspace-${
|
|
48
|
-
const agentDir = path.join(workspacePath, '../', `agents/${
|
|
39
|
+
const workspaceDir = path.join(workspacePath, '../', `workspace-${clone_code}`)
|
|
40
|
+
const agentDir = path.join(workspacePath, '../', `agents/${clone_code}`)
|
|
49
41
|
const sourceAgent = path.join(workspaceDir, 'agent')
|
|
50
42
|
try {
|
|
51
43
|
if (!fs.existsSync(sourceAgent)) return
|
|
@@ -62,15 +54,15 @@ function copyAgentsFiles(code: string) {
|
|
|
62
54
|
}
|
|
63
55
|
|
|
64
56
|
export async function onCreateAgent(params: Record<string, any>) {
|
|
65
|
-
const {
|
|
57
|
+
const { clone_code, name, description } = params
|
|
66
58
|
try {
|
|
67
|
-
await sendMessageToGateway(JSON.stringify({ method: 'agents.create', params: { name:
|
|
59
|
+
await sendMessageToGateway(JSON.stringify({ method: 'agents.create', params: { name: clone_code, workspace: clone_code } }))
|
|
68
60
|
} catch (err: unknown) {
|
|
69
61
|
dcgLogger(`agents.create failed: ${String(err)}`, 'error')
|
|
70
62
|
}
|
|
71
63
|
// Update config.name to the user-supplied display name (may contain CJK, spaces, etc.)
|
|
72
64
|
try {
|
|
73
|
-
await sendMessageToGateway(JSON.stringify({ method: 'agents.update', params: { name: name, agentId:
|
|
65
|
+
await sendMessageToGateway(JSON.stringify({ method: 'agents.update', params: { name: name, agentId: clone_code } }))
|
|
74
66
|
} catch (err: unknown) {
|
|
75
67
|
dcgLogger(`agents.update failed: ${String(err)}`, 'error')
|
|
76
68
|
}
|
|
@@ -79,7 +71,7 @@ export async function onCreateAgent(params: Record<string, any>) {
|
|
|
79
71
|
await sendMessageToGateway(
|
|
80
72
|
JSON.stringify({
|
|
81
73
|
method: 'agents.files.set',
|
|
82
|
-
params: { agentId:
|
|
74
|
+
params: { agentId: clone_code, name: 'IDENTITY.md', content: description.trim() }
|
|
83
75
|
})
|
|
84
76
|
)
|
|
85
77
|
} catch {
|
|
@@ -91,26 +83,26 @@ export async function onCreateAgent(params: Record<string, any>) {
|
|
|
91
83
|
await sendMessageToGateway(
|
|
92
84
|
JSON.stringify({
|
|
93
85
|
method: 'agents.files.set',
|
|
94
|
-
params: { agentId:
|
|
86
|
+
params: { agentId: clone_code, name: 'USER.md', content: name.trim() }
|
|
95
87
|
})
|
|
96
88
|
)
|
|
97
89
|
} catch {
|
|
98
90
|
// Non-fatal
|
|
99
91
|
}
|
|
100
92
|
}
|
|
101
|
-
copyAgentsFiles(
|
|
93
|
+
copyAgentsFiles(clone_code)
|
|
102
94
|
sendEvent({ ...params, status: 'ok' })
|
|
103
95
|
}
|
|
104
96
|
|
|
105
97
|
export async function createAgent(msgContent: Record<string, any>) {
|
|
106
|
-
const { url,
|
|
107
|
-
if (!url || !
|
|
108
|
-
dcgLogger(`createAgent failed empty url&
|
|
98
|
+
const { url, clone_code } = msgContent
|
|
99
|
+
if (!url || !clone_code) {
|
|
100
|
+
dcgLogger(`createAgent failed empty url&clone_code: ${JSON.stringify(msgContent)}`, 'error')
|
|
109
101
|
sendEvent({ ...msgContent, status: 'fail' })
|
|
110
102
|
return
|
|
111
103
|
}
|
|
112
104
|
const workspacePath = getWorkspaceDir()
|
|
113
|
-
const workspaceDir = path.join(workspacePath, '../', `workspace-${
|
|
105
|
+
const workspaceDir = path.join(workspacePath, '../', `workspace-${clone_code}`)
|
|
114
106
|
|
|
115
107
|
// 如果目标目录已存在,先删除
|
|
116
108
|
if (fs.existsSync(workspaceDir)) {
|
|
@@ -159,7 +151,7 @@ export async function createAgent(msgContent: Record<string, any>) {
|
|
|
159
151
|
return
|
|
160
152
|
}
|
|
161
153
|
|
|
162
|
-
const targetPath = path.join(
|
|
154
|
+
const targetPath = path.join(workspaceDir, newPath)
|
|
163
155
|
|
|
164
156
|
if (entry.type === 'Directory') {
|
|
165
157
|
fs.mkdirSync(targetPath, { recursive: true })
|
package/src/bot.ts
CHANGED
|
@@ -238,7 +238,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
238
238
|
if (msg.content.skills_scope.length > 0) {
|
|
239
239
|
const workspaceDir = getWorkspaceDir()
|
|
240
240
|
const skillCode = msg.content.skills_scope.map((skill) => `${workspaceDir}/skills/${skill.skill_code}`).join('\n')
|
|
241
|
-
const skillText =
|
|
241
|
+
const skillText = `技能${skillCode} 在目录${skillCode}下,在目录${skillCode}下读取技能 \n`
|
|
242
242
|
text = skillText ? `${skillText} \n ${text}` : text
|
|
243
243
|
}
|
|
244
244
|
const prefixContext = createReplyPrefixContext({
|
package/src/session.ts
CHANGED
|
@@ -6,11 +6,11 @@ interface TSession {
|
|
|
6
6
|
agent_id: string
|
|
7
7
|
session_id: string
|
|
8
8
|
agent_clone_code?: string
|
|
9
|
-
account_id
|
|
9
|
+
account_id: string
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export const onRemoveSession = async ({ agent_id, session_id, agent_clone_code, account_id }: TSession) => {
|
|
13
|
-
const sessionKey = getSessionKey({ agent_id, session_id }, account_id)
|
|
13
|
+
const sessionKey = getSessionKey({ agent_id, session_id, agent_clone_code }, account_id)
|
|
14
14
|
if (!session_id) {
|
|
15
15
|
dcgLogger('onRemoveSession: empty session_id', 'error')
|
|
16
16
|
return
|
package/src/tools/messageTool.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
import type { AnyAgentTool } from 'openclaw/plugin-sdk'
|
|
4
5
|
import { jsonResult } from 'openclaw/plugin-sdk'
|
|
@@ -12,7 +13,39 @@ export type DcgchatMessageToolContext = {
|
|
|
12
13
|
workspaceDir?: string
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
/** 统一为 POSIX 风格斜杠,便于跨平台判断(不改变语义,仅用于匹配)。 */
|
|
17
|
+
function toPosixPath(p: string): string {
|
|
18
|
+
return path.normalize(p.trim()).replace(/\\/g, '/')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** `filepath` 解析后在 `rootDir` 内或等于 `rootDir`(防 `..` 逃逸)。 */
|
|
22
|
+
function isPathInsideDir(filepath: string, rootDir: string): boolean {
|
|
23
|
+
const root = path.resolve(rootDir)
|
|
24
|
+
const resolved = path.resolve(filepath)
|
|
25
|
+
const rel = path.relative(root, resolved)
|
|
26
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) return false
|
|
27
|
+
return true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 允许发送的路径:
|
|
32
|
+
* - 当前 Agent 工作区根及其子路径(`workspaceDir`,如 ~/.openclaw/workspace-xxx/output/...);
|
|
33
|
+
* - 兼容旧挂载:Unix `/workspace`、`/mobook`;Windows 盘符下 `workspace`、`mobook`。
|
|
34
|
+
*/
|
|
35
|
+
function isSafePath(filepath: string, workspaceDir?: string): boolean {
|
|
36
|
+
const ws = workspaceDir?.trim()
|
|
37
|
+
if (ws && isPathInsideDir(filepath, ws)) return true
|
|
38
|
+
const p = toPosixPath(filepath)
|
|
39
|
+
if (p.startsWith('/workspace/') || p === '/workspace') return true
|
|
40
|
+
if (p === '/mobook') return true
|
|
41
|
+
return /^[A-Za-z]:\/(workspace|mobook)(\/|$)/.test(p)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 同一路径在 Windows 上可能大小写不同,用于 Set 去重。 */
|
|
45
|
+
function pathKey(filepath: string): string {
|
|
46
|
+
const n = path.normalize(filepath.trim())
|
|
47
|
+
return os.platform() === 'win32' ? n.toLowerCase() : n
|
|
48
|
+
}
|
|
16
49
|
|
|
17
50
|
const fileType1 = ['.webp', '.gif', '.bmp', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.pdf', '.txt', '.rtf', '.odt', '.json']
|
|
18
51
|
const fileType2 = ['.xml', '.csv', '.yaml', '.yml', '.html', '.htm', '.md', '.markdown', '.css', '.js', '.ts', '.png', '.jpg', '.jpeg']
|
|
@@ -26,8 +59,7 @@ const messageToolParameters = {
|
|
|
26
59
|
properties: {
|
|
27
60
|
target: {
|
|
28
61
|
type: 'string',
|
|
29
|
-
description:
|
|
30
|
-
'目标会话键(sessionKey),必须与当前会话 SessionKey 一致,禁止填写 userId。'
|
|
62
|
+
description: '目标会话键(sessionKey),必须与当前会话 SessionKey 一致,禁止填写 userId。'
|
|
31
63
|
},
|
|
32
64
|
content: {
|
|
33
65
|
type: 'string',
|
|
@@ -42,7 +74,8 @@ const messageToolParameters = {
|
|
|
42
74
|
properties: {
|
|
43
75
|
file: {
|
|
44
76
|
type: 'string',
|
|
45
|
-
description:
|
|
77
|
+
description:
|
|
78
|
+
'文件绝对路径:须在「当前 Agent 工作区」目录下(如 /root/.openclaw/workspace-xxx/output/28337/slices_result.json),或为兼容环境的 /workspace/、/mobook/(Windows 盘符下 workspace、mobook)'
|
|
46
79
|
}
|
|
47
80
|
},
|
|
48
81
|
required: ['file']
|
|
@@ -52,14 +85,32 @@ const messageToolParameters = {
|
|
|
52
85
|
oneOf: [{ required: ['content'] }, { required: ['media'] }]
|
|
53
86
|
}
|
|
54
87
|
|
|
55
|
-
|
|
88
|
+
/** 从正文提取可发送的文件路径(固定挂载 + 当前工作区前缀)。 */
|
|
89
|
+
function extractPaths(text: string | undefined, workspaceDir?: string): string[] {
|
|
56
90
|
if (!text) return []
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
91
|
+
const unix = text.match(/\/workspace\/[^\s]+|\/mobook\/[^\s]+/g) ?? []
|
|
92
|
+
const win = text.match(/[A-Za-z]:[/\\](?:workspace|mobook)[/\\][^\s]+/g) ?? []
|
|
93
|
+
const underWs: string[] = []
|
|
94
|
+
const ws = workspaceDir?.trim()
|
|
95
|
+
if (ws) {
|
|
96
|
+
const variants = new Set<string>()
|
|
97
|
+
variants.add(ws)
|
|
98
|
+
variants.add(toPosixPath(ws))
|
|
99
|
+
if (path.sep === '\\') variants.add(ws.replace(/\//g, '\\'))
|
|
100
|
+
for (const prefix of variants) {
|
|
101
|
+
if (!prefix) continue
|
|
102
|
+
let from = 0
|
|
103
|
+
while (from < text.length) {
|
|
104
|
+
const i = text.indexOf(prefix, from)
|
|
105
|
+
if (i === -1) break
|
|
106
|
+
let end = i + prefix.length
|
|
107
|
+
while (end < text.length && !/\s/.test(text[end])) end++
|
|
108
|
+
underWs.push(text.slice(i, end))
|
|
109
|
+
from = i + 1
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return [...new Set([...unix, ...win, ...underWs])]
|
|
63
114
|
}
|
|
64
115
|
|
|
65
116
|
function isSafeFile(filepath: string) {
|
|
@@ -67,7 +118,7 @@ function isSafeFile(filepath: string) {
|
|
|
67
118
|
const stat = fs.statSync(filepath)
|
|
68
119
|
if (!stat.isFile()) return false
|
|
69
120
|
if (stat.size === 0) return false
|
|
70
|
-
const ext = path.extname(filepath)
|
|
121
|
+
const ext = path.extname(filepath).toLowerCase()
|
|
71
122
|
return SAFE_EXTENSIONS.has(ext)
|
|
72
123
|
}
|
|
73
124
|
|
|
@@ -84,10 +135,8 @@ export function createDcgchatMessageTool(pluginCtx: DcgchatMessageToolContext):
|
|
|
84
135
|
向用户发送消息。
|
|
85
136
|
若传 target,target 必须是 sessionKey,不能是 userId。
|
|
86
137
|
如果发送附件:必须使用 media 字段
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
/mobook/
|
|
90
|
-
禁止直接输出路径文本
|
|
138
|
+
文件路径须在当前 Agent 工作区目录下(随部署变化,如 ~/.openclaw/workspace-xxx/...),或为兼容环境的 /workspace/、/mobook/(Windows 盘符下 workspace、mobook)。
|
|
139
|
+
禁止在正文中直接输出可访问路径(应通过 media 发送)
|
|
91
140
|
`,
|
|
92
141
|
parameters: messageToolParameters,
|
|
93
142
|
execute: async (_toolCallId, args, signal) => {
|
|
@@ -104,33 +153,46 @@ export function createDcgchatMessageTool(pluginCtx: DcgchatMessageToolContext):
|
|
|
104
153
|
|
|
105
154
|
try {
|
|
106
155
|
const sentFiles = new Set<string>()
|
|
156
|
+
const sentKeys = new Set<string>()
|
|
157
|
+
const workspaceDir = pluginCtx.workspaceDir
|
|
107
158
|
|
|
108
159
|
if (args.media?.length) {
|
|
109
160
|
for (const media of args.media) {
|
|
110
161
|
const filepath = media.file
|
|
111
162
|
if (!filepath) continue
|
|
112
|
-
if (!isSafePath(filepath)) continue
|
|
163
|
+
if (!isSafePath(filepath, workspaceDir)) continue
|
|
113
164
|
if (!isSafeFile(filepath)) continue
|
|
114
|
-
|
|
165
|
+
const key = pathKey(filepath)
|
|
166
|
+
if (sentKeys.has(key)) continue
|
|
115
167
|
|
|
116
168
|
await sendDcgchatMedia({ sessionKey, mediaUrl: filepath })
|
|
117
169
|
sentFiles.add(filepath)
|
|
170
|
+
sentKeys.add(key)
|
|
118
171
|
}
|
|
119
172
|
}
|
|
120
173
|
|
|
121
|
-
const fallbackPaths = extractPaths(args.content)
|
|
174
|
+
const fallbackPaths = extractPaths(args.content, workspaceDir)
|
|
122
175
|
for (const filepath of fallbackPaths) {
|
|
123
|
-
if (!isSafePath(filepath)) continue
|
|
176
|
+
if (!isSafePath(filepath, workspaceDir)) continue
|
|
124
177
|
if (!isSafeFile(filepath)) continue
|
|
125
|
-
|
|
178
|
+
const key = pathKey(filepath)
|
|
179
|
+
if (sentKeys.has(key)) continue
|
|
126
180
|
|
|
127
181
|
await sendDcgchatMedia({ sessionKey, mediaUrl: filepath })
|
|
128
182
|
sentFiles.add(filepath)
|
|
183
|
+
sentKeys.add(key)
|
|
129
184
|
}
|
|
130
185
|
|
|
131
186
|
let content = args.content ?? ''
|
|
132
187
|
for (const filepath of sentFiles) {
|
|
133
|
-
|
|
188
|
+
const posix = toPosixPath(filepath)
|
|
189
|
+
const variants = posix === filepath ? [filepath] : [filepath, posix]
|
|
190
|
+
const seen = new Set<string>()
|
|
191
|
+
for (const v of variants) {
|
|
192
|
+
if (!v || seen.has(v)) continue
|
|
193
|
+
seen.add(v)
|
|
194
|
+
content = content.split(v).join('')
|
|
195
|
+
}
|
|
134
196
|
}
|
|
135
197
|
content = content.trim()
|
|
136
198
|
|
|
@@ -39,8 +39,8 @@ export async function handleParsedWsMessage(parsed: ParsedWsPayload, rawPayload:
|
|
|
39
39
|
dcgLogger(`openclaw_bot_event unknown event_type: ${event_type}, ${rawPayload}`)
|
|
40
40
|
}
|
|
41
41
|
} else if (event_type === 'agent') {
|
|
42
|
-
if (operation_type === '
|
|
43
|
-
createAgent(parsed.content)
|
|
42
|
+
if (operation_type === 'create') {
|
|
43
|
+
await createAgent(parsed.content)
|
|
44
44
|
} else {
|
|
45
45
|
dcgLogger(`openclaw_bot_event unknown event_type: ${event_type}, ${rawPayload}`)
|
|
46
46
|
}
|