@dcrays/dcgchat-test 0.4.3 → 0.4.9

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.3",
3
+ "version": "0.4.9",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for 书灵墨宝 (WebSocket)",
6
6
  "main": "index.ts",
package/src/bot.ts CHANGED
@@ -173,6 +173,8 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
173
173
  sessionKey: dcgSessionKey,
174
174
  real_mobook
175
175
  }
176
+ /** 写入本条消息参数前快照:流式/abort 的 final 须对齐「上一轮」触发的对话 messageId,而非打断指令本身 */
177
+ const priorOutboundCtx = getEffectiveMsgParams(dcgSessionKey)
176
178
  setParamsMessage(dcgSessionKey, mergedParams)
177
179
  dcgLogger(`target alias bound: aliasTarget=${userId} -> sessionKey=${dcgSessionKey}`)
178
180
  const outboundCtx = getEffectiveMsgParams(dcgSessionKey)
@@ -189,6 +191,14 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
189
191
  }
190
192
 
191
193
  try {
194
+ if (msg.content.skills_scope.length > 0 && !msg.content?.agent_clone_code && !ignoreToolCommand.includes(text?.trim())) {
195
+ const workspaceDir = getWorkspaceDir()
196
+ const skill = msg.content.skills_scope[0]
197
+ const skillDir = `${workspaceDir}/skills/${skill.skill_code}`
198
+ const skillText = `用户选择使用此技能:"${skill.skill_code}",技能路径是:"${skillDir}",在目录下查找并`
199
+ text = skill.skill_code ? `${skillText} ${text}` : text
200
+ dcgLogger(`skill: text: ${text}`)
201
+ }
192
202
  // 处理用户上传的文件
193
203
  const files = msg.content.files ?? []
194
204
  let mediaPayload: Record<string, unknown> = {}
@@ -235,12 +245,13 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
235
245
  const getMediaKey = (url: string) => url.split(/[\\/]/).pop() ?? url
236
246
  let streamedTextLen = 0
237
247
 
238
- if (msg.content.skills_scope.length > 0 && !msg.content?.agent_clone_code) {
248
+ if (msg.content.skills_scope.length > 0 && !msg.content?.agent_clone_code && !ignoreToolCommand.includes(text?.trim())) {
239
249
  const workspaceDir = getWorkspaceDir()
240
- const skillCode = msg.content.skills_scope.map((skill) => `${workspaceDir}/skills/${skill.skill_code}`).join('\n')
241
- const skillText = `技能${skillCode} 在目录${skillCode}下,在目录${skillCode}下读取技能 \n`
242
- text = skillText ? `${skillText} \n ${text}` : text
243
- dcgLogger('skill: skillText: ${skillText}')
250
+ const skill = msg.content.skills_scope[0]
251
+ const skillDir = `${workspaceDir}/skills/${skill.skill_code}`
252
+ const skillText = `用户选择使用此技能:"${skill.skill_code}",技能路径是:"${skillDir}",在目录下查找并`
253
+ text = skill.skill_code ? `${skillText} ${text}` : text
254
+ dcgLogger(`skill: text: ${text}`)
244
255
  }
245
256
  const prefixContext = createReplyPrefixContext({
246
257
  cfg: config,
@@ -304,7 +315,8 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
304
315
  })
305
316
  } else if (interruptCommand.includes(text?.trim())) {
306
317
  dcgLogger(`interrupt command: ${text}`)
307
- sendFinal({ ...outboundCtx, messageId: `${Date.now()}` }, 'abort')
318
+ const ctxForAbort = priorOutboundCtx.messageId?.trim() || priorOutboundCtx.sessionId?.trim() ? priorOutboundCtx : outboundCtx
319
+ sendFinal(ctxForAbort.messageId?.trim() ? ctxForAbort : { ...ctxForAbort, messageId: `${Date.now()}` }, 'abort')
308
320
  sendText('会话已终止', outboundCtx)
309
321
  sessionStreamSuppressed.add(dcgSessionKey)
310
322
  const abortOneSession = async (sessionKey: string) => {
package/src/channel.ts CHANGED
@@ -224,10 +224,10 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
224
224
  },
225
225
  agentPrompt: {
226
226
  messageToolHints: () => [
227
- '生成文件后,**尽可能不要**把文件路径、地址直接告诉用户。',
228
- '生成文件后,把文件名告诉用户。',
229
- '使用内置 `message` 或 `dcgchat_message` 发消息时,target 必须是完整 dcgSessionKey(与上下文 SessionKey 相同),禁止填 From、SenderId、WS userId 等纯数字。',
230
- '生成文件后,须通过工具发送文件,勿在文本里直接输出路径或地址。'
227
+ '书灵墨宝 / 内置 `message`:**优先不要传 `target`**,即回复当前会话;OpenClaw 会用工具上下文里的 `currentChannelId`(与入站 `To` / `SessionKey` / `OriginatingTo` 相同)。',
228
+ '仅在需要显式指定时:`target` 必须与上下文中的 **完整 SessionKey 字符串逐字一致**(形如 `agent:…:mobook:direct:…` 或以 `agent:` 开头的路由键)。**禁止**填 `From`、`SenderId`、通道配置里的 WS `userId`、会话 id 纯数字等。',
229
+ '生成文件后,**尽可能不要**把文件路径、地址直接告诉用户;把文件名告诉用户;须通过工具发文件,勿在正文里直接输出可访问路径。',
230
+ '使用 `dcgchat_message` 时同样遵守上述 SessionKey 规则(该工具通常由插件注入当前会话,一般无需自造 target)。'
231
231
  ]
232
232
  },
233
233
  outbound: {
package/src/skill.ts CHANGED
@@ -9,6 +9,7 @@ import { getWsConnection } from './utils/global.js'
9
9
  import { dcgLogger } from './utils/log.js'
10
10
  import { isWsOpen } from './transport.js'
11
11
  import { sendMessageToGateway } from './gateway/socket.js'
12
+ import { decodeZipEntryPath } from './utils/zipPath.js'
12
13
 
13
14
  type ISkillParams = {
14
15
  path: string
@@ -69,13 +70,7 @@ export async function installSkill(params: ISkillParams, msgContent: Record<stri
69
70
  }
70
71
  try {
71
72
  const flags = entry.props?.flags ?? 0
72
- const isUtf8 = (flags & 0x800) !== 0
73
- let entryPath: string
74
- if (!isUtf8 && entry.props?.pathBuffer) {
75
- entryPath = new TextDecoder('gbk').decode(entry.props.pathBuffer)
76
- } else {
77
- entryPath = entry.path
78
- }
73
+ const entryPath = decodeZipEntryPath(entry.props?.pathBuffer, flags, entry.path)
79
74
  const pathParts = entryPath.split('/')
80
75
 
81
76
  // 检测根目录
@@ -37,7 +37,7 @@ function isSafePath(filepath: string, workspaceDir?: string): boolean {
37
37
  if (ws && isPathInsideDir(filepath, ws)) return true
38
38
  const p = toPosixPath(filepath)
39
39
  if (p.startsWith('/workspace/') || p === '/workspace') return true
40
- if (p === '/mobook') return true
40
+ if (p.startsWith('/mobook/') || p === '/mobook') return true
41
41
  return /^[A-Za-z]:\/(workspace|mobook)(\/|$)/.test(p)
42
42
  }
43
43
 
@@ -183,6 +183,15 @@ export function createDcgchatMessageTool(pluginCtx: DcgchatMessageToolContext):
183
183
  sentKeys.add(key)
184
184
  }
185
185
 
186
+ if (args.media?.length && sentFiles.size === 0) {
187
+ return jsonResult({
188
+ success: false,
189
+ error:
190
+ '未能发送任何附件:路径须位于当前 Agent 工作区,或为 /workspace/、/mobook/ 下的真实文件(非空、扩展名在白名单内)。',
191
+ sentMediaCount: 0
192
+ })
193
+ }
194
+
186
195
  let content = args.content ?? ''
187
196
  for (const filepath of sentFiles) {
188
197
  const posix = toPosixPath(filepath)