@dcrays/dcgchat-test 0.4.0 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcrays/dcgchat-test",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for 书灵墨宝 (WebSocket)",
6
6
  "main": "index.ts",
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 = `技能${skillCode} 在目录${skillCode}下,在目录${skillCode}下查找技能 \n`
241
+ const skillText = `技能${skillCode} 在目录${skillCode}下,在目录${skillCode}下读取技能 \n`
242
242
  text = skillText ? `${skillText} \n ${text}` : text
243
243
  }
244
244
  const prefixContext = createReplyPrefixContext({
@@ -18,15 +18,26 @@ function toPosixPath(p: string): string {
18
18
  return path.normalize(p.trim()).replace(/\\/g, '/')
19
19
  }
20
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
+
21
30
  /**
22
- * 允许发送的挂载根:Unix/macOS/Linux 为 /workspace、/mobook;
23
- * Windows 为盘符路径如 C:/workspace、D:\\mobook\\...(规范化后比较)。
31
+ * 允许发送的路径:
32
+ * - 当前 Agent 工作区根及其子路径(`workspaceDir`,如 ~/.openclaw/workspace-xxx/output/...);
33
+ * - 兼容旧挂载:Unix `/workspace`、`/mobook`;Windows 盘符下 `workspace`、`mobook`。
24
34
  */
25
- function isSafePath(filepath: string): boolean {
35
+ function isSafePath(filepath: string, workspaceDir?: string): boolean {
36
+ const ws = workspaceDir?.trim()
37
+ if (ws && isPathInsideDir(filepath, ws)) return true
26
38
  const p = toPosixPath(filepath)
27
39
  if (p.startsWith('/workspace/') || p === '/workspace') return true
28
- if (p.startsWith('/mobook/') || p === '/mobook') return true
29
- // Windows: C:/workspace/...、c:/mobook/...
40
+ if (p === '/mobook') return true
30
41
  return /^[A-Za-z]:\/(workspace|mobook)(\/|$)/.test(p)
31
42
  }
32
43
 
@@ -64,7 +75,7 @@ const messageToolParameters = {
64
75
  file: {
65
76
  type: 'string',
66
77
  description:
67
- '文件路径。Unix/macOS/Linux /workspace/output/report.pdf;Windows 如 C:/workspace/output/report.pdf C:\\workspace\\output\\report.pdf'
78
+ '文件绝对路径:须在「当前 Agent 工作区」目录下(如 /root/.openclaw/workspace-xxx/output/28337/slices_result.json),或为兼容环境的 /workspace/、/mobook/(Windows 盘符下 workspace、mobook)'
68
79
  }
69
80
  },
70
81
  required: ['file']
@@ -74,12 +85,32 @@ const messageToolParameters = {
74
85
  oneOf: [{ required: ['content'] }, { required: ['media'] }]
75
86
  }
76
87
 
77
- function extractPaths(text?: string): string[] {
88
+ /** 从正文提取可发送的文件路径(固定挂载 + 当前工作区前缀)。 */
89
+ function extractPaths(text: string | undefined, workspaceDir?: string): string[] {
78
90
  if (!text) return []
79
91
  const unix = text.match(/\/workspace\/[^\s]+|\/mobook\/[^\s]+/g) ?? []
80
- // Windows: C:\workspace\...、C:/mobook/...(不含空白)
81
92
  const win = text.match(/[A-Za-z]:[/\\](?:workspace|mobook)[/\\][^\s]+/g) ?? []
82
- return [...new Set([...unix, ...win])]
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])]
83
114
  }
84
115
 
85
116
  function isSafeFile(filepath: string) {
@@ -104,8 +135,8 @@ export function createDcgchatMessageTool(pluginCtx: DcgchatMessageToolContext):
104
135
  向用户发送消息。
105
136
  若传 target,target 必须是 sessionKey,不能是 userId。
106
137
  如果发送附件:必须使用 media 字段
107
- 支持路径目录(Unix/macOS/Linux:/workspace/、/mobook/;Windows:盘符下 workspace、mobook,如 C:/workspace/):
108
- 禁止直接输出路径文本
138
+ 文件路径须在当前 Agent 工作区目录下(随部署变化,如 ~/.openclaw/workspace-xxx/...),或为兼容环境的 /workspace/、/mobook/(Windows 盘符下 workspace、mobook)。
139
+ 禁止在正文中直接输出可访问路径(应通过 media 发送)
109
140
  `,
110
141
  parameters: messageToolParameters,
111
142
  execute: async (_toolCallId, args, signal) => {
@@ -123,12 +154,13 @@ export function createDcgchatMessageTool(pluginCtx: DcgchatMessageToolContext):
123
154
  try {
124
155
  const sentFiles = new Set<string>()
125
156
  const sentKeys = new Set<string>()
157
+ const workspaceDir = pluginCtx.workspaceDir
126
158
 
127
159
  if (args.media?.length) {
128
160
  for (const media of args.media) {
129
161
  const filepath = media.file
130
162
  if (!filepath) continue
131
- if (!isSafePath(filepath)) continue
163
+ if (!isSafePath(filepath, workspaceDir)) continue
132
164
  if (!isSafeFile(filepath)) continue
133
165
  const key = pathKey(filepath)
134
166
  if (sentKeys.has(key)) continue
@@ -139,9 +171,9 @@ export function createDcgchatMessageTool(pluginCtx: DcgchatMessageToolContext):
139
171
  }
140
172
  }
141
173
 
142
- const fallbackPaths = extractPaths(args.content)
174
+ const fallbackPaths = extractPaths(args.content, workspaceDir)
143
175
  for (const filepath of fallbackPaths) {
144
- if (!isSafePath(filepath)) continue
176
+ if (!isSafePath(filepath, workspaceDir)) continue
145
177
  if (!isSafeFile(filepath)) continue
146
178
  const key = pathKey(filepath)
147
179
  if (sentKeys.has(key)) continue