@dcrays/dcgchat-test 0.3.39 → 0.3.40

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
@@ -4,8 +4,7 @@ import { dcgchatPlugin } from './src/channel.js'
4
4
  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
- import { startDcgchatGatewaySocket } from './src/gateway/socket.js'
8
- import { createDcgchatMessageTool } from './src/tools/meeageToll.js'
7
+ import { createDcgchatMessageTool } from './src/tools/messageTool.js'
9
8
 
10
9
  const plugin = {
11
10
  id: "dcgchat-test",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcrays/dcgchat-test",
3
- "version": "0.3.39",
3
+ "version": "0.3.40",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for 书灵墨宝 (WebSocket)",
6
6
  "main": "index.ts",
package/src/bot.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  import path from 'node:path'
2
- import fs from 'node:fs'
3
- import os from 'node:os'
4
- import type { PluginRuntime, ReplyPayload } from 'openclaw/plugin-sdk'
5
- import { createPluginRuntimeStore, createReplyPrefixContext, createTypingCallbacks } from 'openclaw/plugin-sdk'
2
+ import type { ReplyPayload } from 'openclaw/plugin-sdk'
3
+ import { createReplyPrefixContext, createTypingCallbacks } from 'openclaw/plugin-sdk'
6
4
  import type { InboundMessage } from './types.js'
7
5
  import {
8
6
  clearSentMediaKeys,
@@ -126,20 +124,18 @@ function resolveReplyMediaList(payload: ReplyPayload): string[] {
126
124
  if (payload.mediaUrls?.length) return payload.mediaUrls.filter(Boolean)
127
125
  return payload.mediaUrl ? [payload.mediaUrl] : []
128
126
  }
127
+
129
128
  const typingCallbacks = createTypingCallbacks({
130
- start: async () => {
131
- console.log('typing start')
132
- },
129
+ start: async () => {},
133
130
  onStartError: (err) => {
134
- console.log('typing start error', err)
131
+ dcgLogger(`typing start error: ${String(err)}`, 'error')
135
132
  }
136
133
  })
134
+
137
135
  /**
138
136
  * 处理一条用户消息,调用 Agent 并返回回复
139
137
  */
140
138
  export async function handleDcgchatMessage(msg: InboundMessage, accountId: string): Promise<void> {
141
- let finalSent = false
142
-
143
139
  let completeText = ''
144
140
  const config = getOpenClawConfig()
145
141
  if (!config) {
@@ -164,7 +160,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
164
160
  const embeddedAgentId = extractAgentIdFromConversationId(conversationId)
165
161
 
166
162
  const effectiveAgentId = embeddedAgentId ?? route.agentId
167
- const effectiveSessionKey = getSessionKey(msg.content, account.accountId)
163
+ const dcgSessionKey = getSessionKey(msg.content, account.accountId)
168
164
 
169
165
  const mergedParams = {
170
166
  userId: msg._userId,
@@ -175,13 +171,12 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
175
171
  appId: msg.content.app_id,
176
172
  botId: msg.content.bot_id ?? '',
177
173
  agentId: msg.content.agent_id ?? '',
178
- sessionKey: effectiveSessionKey,
174
+ sessionKey: dcgSessionKey,
179
175
  real_mobook
180
176
  }
181
- setParamsMessage(effectiveSessionKey, mergedParams)
182
- setParamsMessage(userId, mergedParams)
183
- dcgLogger(`target alias bound: aliasTarget=${userId} -> sessionKey=${effectiveSessionKey}`)
184
- const outboundCtx = getEffectiveMsgParams(effectiveSessionKey)
177
+ setParamsMessage(dcgSessionKey, mergedParams)
178
+ dcgLogger(`target alias bound: aliasTarget=${userId} -> sessionKey=${dcgSessionKey}`)
179
+ const outboundCtx = getEffectiveMsgParams(dcgSessionKey)
185
180
  const agentEntry =
186
181
  effectiveAgentId && effectiveAgentId !== 'main' ? config.agents?.list?.find((a) => a.id === effectiveAgentId) : undefined
187
182
  const agentDisplayName = agentEntry?.name ?? (effectiveAgentId && effectiveAgentId !== 'main' ? effectiveAgentId : undefined)
@@ -218,8 +213,8 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
218
213
  RawBody: text,
219
214
  CommandBody: text,
220
215
  From: userId,
221
- To: effectiveSessionKey,
222
- SessionKey: effectiveSessionKey,
216
+ To: dcgSessionKey,
217
+ SessionKey: dcgSessionKey,
223
218
  AccountId: route.accountId,
224
219
  ChatType: 'direct',
225
220
  SenderName: agentDisplayName,
@@ -231,9 +226,9 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
231
226
  WasMentioned: true,
232
227
  CommandAuthorized: true,
233
228
  OriginatingChannel: "dcgchat-test",
234
- OriginatingTo: effectiveSessionKey,
235
- Target: effectiveSessionKey,
236
- SourceTarget: effectiveSessionKey,
229
+ OriginatingTo: dcgSessionKey,
230
+ Target: dcgSessionKey,
231
+ SourceTarget: dcgSessionKey,
237
232
  ...mediaPayload
238
233
  })
239
234
 
@@ -254,26 +249,22 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
254
249
  humanDelay: core.channel.reply.resolveHumanDelayConfig(config, route.agentId),
255
250
  onReplyStart: async () => {},
256
251
  deliver: async (payload: ReplyPayload, info) => {
257
- if (sessionStreamSuppressed.has(effectiveSessionKey)) return
252
+ if (sessionStreamSuppressed.has(dcgSessionKey)) return
258
253
  const mediaList = resolveReplyMediaList(payload)
259
254
  for (const mediaUrl of mediaList) {
260
255
  const key = getMediaKey(mediaUrl)
261
256
  if (sentMediaKeys.has(key)) continue
262
257
  sentMediaKeys.add(key)
263
- await sendDcgchatMedia({ sessionKey: effectiveSessionKey, mediaUrl, text: '' })
258
+ await sendDcgchatMedia({ sessionKey: dcgSessionKey, mediaUrl, text: '' })
264
259
  }
265
260
  },
266
261
  onError: (err: unknown, info: { kind: string }) => {
267
- setMsgStatus(effectiveSessionKey, 'finished')
262
+ setMsgStatus(dcgSessionKey, 'finished')
268
263
  sendFinal(outboundCtx, 'error')
269
- dcgLogger(`${info.kind} reply failed: ${String(err)}`, 'error')
270
- activeRunIdBySessionKey.delete(effectiveSessionKey)
271
- streamChunkIdxBySessionKey.delete(effectiveSessionKey)
272
- if (sessionStreamSuppressed.has(effectiveSessionKey)) {
273
- dcgLogger(`${info.kind} reply failed (stream suppressed): ${String(err)}`, 'error')
274
- return
275
- }
276
- dcgLogger(`${info.kind} reply failed: ${String(err)}`, 'error')
264
+ activeRunIdBySessionKey.delete(dcgSessionKey)
265
+ streamChunkIdxBySessionKey.delete(dcgSessionKey)
266
+ const suppressed = sessionStreamSuppressed.has(dcgSessionKey)
267
+ dcgLogger(`${info.kind} reply failed${suppressed ? ' (stream suppressed)' : ''}: ${String(err)}`, 'error')
277
268
  },
278
269
  onIdle: () => {
279
270
  typingCallbacks.onIdle?.()
@@ -282,8 +273,8 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
282
273
 
283
274
  try {
284
275
  if (!interruptCommand.includes(text?.trim())) {
285
- sessionStreamSuppressed.delete(effectiveSessionKey)
286
- streamChunkIdxBySessionKey.set(effectiveSessionKey, 0)
276
+ sessionStreamSuppressed.delete(dcgSessionKey)
277
+ streamChunkIdxBySessionKey.set(dcgSessionKey, 0)
287
278
  }
288
279
 
289
280
  if (systemCommand.includes(text?.trim())) {
@@ -300,7 +291,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
300
291
  ...replyOptions,
301
292
  onModelSelected: prefixContext.onModelSelected,
302
293
  onAgentRunStart: (runId) => {
303
- activeRunIdBySessionKey.set(effectiveSessionKey, runId)
294
+ activeRunIdBySessionKey.set(dcgSessionKey, runId)
304
295
  }
305
296
  }
306
297
  })
@@ -309,7 +300,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
309
300
  dcgLogger(`interrupt command: ${text}`)
310
301
  sendFinal(outboundCtx, 'abort')
311
302
  sendText('会话已终止', outboundCtx)
312
- sessionStreamSuppressed.add(effectiveSessionKey)
303
+ sessionStreamSuppressed.add(dcgSessionKey)
313
304
 
314
305
  const abortOneSession = async (sessionKey: string) => {
315
306
  try {
@@ -319,11 +310,11 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
319
310
  }
320
311
  }
321
312
 
322
- const keysToAbort = new Set<string>(getChildSessionKeysTrackedForRequester(effectiveSessionKey))
313
+ const keysToAbort = new Set<string>(getChildSessionKeysTrackedForRequester(dcgSessionKey))
323
314
  try {
324
315
  const listed = await sendGatewayRpc<{ sessions?: Array<{ key?: string }> }>({
325
316
  method: 'sessions.list',
326
- params: { spawnedBy: effectiveSessionKey, limit: 256 }
317
+ params: { spawnedBy: dcgSessionKey, limit: 256 }
327
318
  })
328
319
  for (const s of listed?.sessions ?? []) {
329
320
  const k = typeof s?.key === 'string' ? s.key.trim() : ''
@@ -335,29 +326,29 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
335
326
  for (const sk of keysToAbort) {
336
327
  await abortOneSession(sk)
337
328
  }
338
- await abortOneSession(effectiveSessionKey)
329
+ await abortOneSession(dcgSessionKey)
339
330
 
340
331
  try {
341
332
  await sendGatewayRpc({
342
333
  method: 'sessions.reset',
343
- params: { key: effectiveSessionKey, reason: 'reset' }
334
+ params: { key: dcgSessionKey, reason: 'reset' }
344
335
  })
345
336
  } catch (e) {
346
337
  dcgLogger(`sessions.reset: ${String(e)}`, 'error')
347
338
  }
348
339
 
349
- activeRunIdBySessionKey.delete(effectiveSessionKey)
350
- streamChunkIdxBySessionKey.delete(effectiveSessionKey)
351
- resetSubagentStateForRequesterSession(effectiveSessionKey)
352
- setMsgStatus(effectiveSessionKey, 'finished')
340
+ activeRunIdBySessionKey.delete(dcgSessionKey)
341
+ streamChunkIdxBySessionKey.delete(dcgSessionKey)
342
+ resetSubagentStateForRequesterSession(dcgSessionKey)
343
+ setMsgStatus(dcgSessionKey, 'finished')
353
344
  clearSentMediaKeys(msg.content.message_id)
354
- clearParamsMessage(effectiveSessionKey)
345
+ clearParamsMessage(dcgSessionKey)
355
346
  clearParamsMessage(userId)
356
347
 
357
348
  sendFinal(outboundCtx, 'stop')
358
349
  return
359
350
  } else {
360
- const params = getEffectiveMsgParams(effectiveSessionKey)
351
+ const params = getEffectiveMsgParams(dcgSessionKey)
361
352
  if (!ignoreToolCommand.includes(text?.trim())) {
362
353
  wsSendRaw(params, {
363
354
  is_finish: -1,
@@ -385,10 +376,10 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
385
376
  ...replyOptions,
386
377
  onModelSelected: prefixContext.onModelSelected,
387
378
  onAgentRunStart: (runId) => {
388
- activeRunIdBySessionKey.set(effectiveSessionKey, runId)
379
+ activeRunIdBySessionKey.set(dcgSessionKey, runId)
389
380
  },
390
381
  onPartialReply: async (payload: ReplyPayload) => {
391
- if (sessionStreamSuppressed.has(effectiveSessionKey)) return
382
+ if (sessionStreamSuppressed.has(dcgSessionKey)) return
392
383
 
393
384
  if (payload.text) {
394
385
  completeText = payload.text
@@ -399,8 +390,8 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
399
390
  ? payload.text.slice(streamedTextLen)
400
391
  : payload.text
401
392
  if (delta.trim()) {
402
- const prev = streamChunkIdxBySessionKey.get(effectiveSessionKey) ?? 0
403
- streamChunkIdxBySessionKey.set(effectiveSessionKey, prev + 1)
393
+ const prev = streamChunkIdxBySessionKey.get(dcgSessionKey) ?? 0
394
+ streamChunkIdxBySessionKey.set(dcgSessionKey, prev + 1)
404
395
  sendChunk(delta, outboundCtx, prev)
405
396
  dcgLogger(`[stream]: chunkIdx=${prev} len=${delta.length} user=${msg._userId} ${delta.slice(0, 100)}`)
406
397
  }
@@ -414,7 +405,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
414
405
  const key = getMediaKey(mediaUrl)
415
406
  if (sentMediaKeys.has(key)) continue
416
407
  sentMediaKeys.add(key)
417
- await sendDcgchatMedia({ sessionKey: effectiveSessionKey, mediaUrl, text: '' })
408
+ await sendDcgchatMedia({ sessionKey: dcgSessionKey, mediaUrl, text: '' })
418
409
  }
419
410
  }
420
411
  }
@@ -426,26 +417,26 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
426
417
  }
427
418
 
428
419
  if (![...systemCommand, ...interruptCommand].includes(text?.trim())) {
429
- if (sessionStreamSuppressed.has(effectiveSessionKey)) {
430
- sessionStreamSuppressed.delete(effectiveSessionKey)
420
+ if (sessionStreamSuppressed.has(dcgSessionKey)) {
421
+ sessionStreamSuppressed.delete(dcgSessionKey)
431
422
  }
432
423
  }
433
424
  clearSentMediaKeys(msg.content.message_id)
434
425
  const storePath = core.channel.session.resolveStorePath(config.session?.store)
435
- await waitUntilSubagentsIdle(effectiveSessionKey, { timeoutMs: 600_000 })
426
+ await waitUntilSubagentsIdle(dcgSessionKey, { timeoutMs: 600_000 })
436
427
  sendFinal(outboundCtx, 'end')
437
428
  dcgLogger(
438
- `record session route: rawTarget=${userId}, normalizedTarget=${effectiveSessionKey}, updateLastRoute.to=${effectiveSessionKey}, accountId=${route.accountId}`
429
+ `record session route: rawTarget=${userId}, normalizedTarget=${dcgSessionKey}, updateLastRoute.to=${dcgSessionKey}, accountId=${route.accountId}`
439
430
  )
440
431
  core.channel.session
441
432
  .recordInboundSession({
442
433
  storePath,
443
- sessionKey: effectiveSessionKey,
434
+ sessionKey: dcgSessionKey,
444
435
  ctx: ctxPayload,
445
436
  updateLastRoute: {
446
- sessionKey: effectiveSessionKey,
437
+ sessionKey: dcgSessionKey,
447
438
  channel: "dcgchat-test",
448
- to: effectiveSessionKey,
439
+ to: dcgSessionKey,
449
440
  accountId: route.accountId
450
441
  },
451
442
  onRecordError: (err) => {
package/src/channel.ts CHANGED
@@ -105,7 +105,8 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
105
105
  effects: true
106
106
  // blockStreaming: true,
107
107
  },
108
- reload: { configPrefixes: ['channels.dcgchat'] },
108
+ /** 当前构建的 channel id + 兼容旧配置键 `channels.dcgchat` */
109
+ reload: { configPrefixes: [`channels.${"dcgchat-test"}`, 'channels.dcgchat'] },
109
110
  configSchema: {
110
111
  schema: {
111
112
  type: 'object',
@@ -114,7 +115,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
114
115
  enabled: { type: 'boolean' },
115
116
  wsUrl: { type: 'string' },
116
117
  botToken: { type: 'string' },
117
- userId: { type: 'string', description: 'WebSocket 连接参数 _userId,与 message 工具的 target(effectiveSessionKey)无关' },
118
+ userId: { type: 'string', description: 'WebSocket 连接参数 _userId,与 message 工具的 target(dcgSessionKey)无关' },
118
119
  appId: { type: 'string' },
119
120
  domainId: { type: 'string' },
120
121
  capabilities: { type: 'array', items: { type: 'string' } }
@@ -123,7 +124,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
123
124
  uiHints: {
124
125
  userId: {
125
126
  label: 'WS 连接 _userId',
126
- help: '仅用于拼接网关 WebSocket URL 的查询参数,不是 Agent 发消息时的 target。发消息请使用 effectiveSessionKey(与入站上下文 SessionKey 相同,格式如 agent:main:mobook:direct:<agent_id>:<session_id>)。'
127
+ help: '仅用于拼接网关 WebSocket URL 的查询参数,不是 Agent 发消息时的 target。发消息请使用 dcgSessionKey(与入站上下文 SessionKey 相同)。'
127
128
  }
128
129
  }
129
130
  },
@@ -131,16 +132,18 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
131
132
  listAccountIds: () => [DEFAULT_ACCOUNT_ID],
132
133
  resolveAccount: (cfg, accountId) => resolveAccount(cfg, accountId),
133
134
  defaultAccountId: () => DEFAULT_ACCOUNT_ID,
134
- setAccountEnabled: ({ cfg, enabled }) => ({
135
- ...cfg,
136
- channels: {
137
- ...cfg.channels,
138
- dcgchat: {
139
- ...(cfg.channels?.["dcgchat-test"] as Record<string, unknown> | undefined),
140
- enabled
135
+ setAccountEnabled: ({ cfg, enabled }) => {
136
+ const channelKey = "dcgchat-test"
137
+ const prev =
138
+ (cfg.channels?.[channelKey as keyof NonNullable<typeof cfg.channels>] as Record<string, unknown> | undefined) ?? {}
139
+ return {
140
+ ...cfg,
141
+ channels: {
142
+ ...cfg.channels,
143
+ [channelKey]: { ...prev, enabled }
141
144
  }
142
145
  }
143
- }),
146
+ },
144
147
  isConfigured: (account) => account.configured,
145
148
  describeAccount: (account) => ({
146
149
  accountId: account.accountId,
@@ -157,13 +160,14 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
157
160
  normalizeTarget: (raw) => raw || undefined,
158
161
  targetResolver: {
159
162
  looksLikeId: (raw) => Boolean(raw?.trim()),
160
- hint: 'effectiveSessionKey(与 SessionKey 一致;勿填配置里的 WS userId)'
163
+ hint: 'dcgSessionKey(与 SessionKey 一致;勿填配置里的 WS userId)'
161
164
  }
162
165
  },
163
166
  agentPrompt: {
164
167
  messageToolHints: () => [
165
168
  '生成文件后,**尽可能不要**把文件路径、地址直接告诉用户。',
166
169
  '生成文件后,把文件名告诉用户。',
170
+ '调用 message 工具时,target 必须填写 dcgSessionKey(即 SessionKey),绝不能填写 WS userId。',
167
171
  '生成文件后,必须调用 message 工具发送文件,不可以直接在文本回复里包含文件路径、文件名、地址。'
168
172
  ]
169
173
  },
@@ -212,7 +212,7 @@ async function connectPersistentGateway(): Promise<void> {
212
212
  startPingTimer(gw)
213
213
  dcgLogger(`Gateway 持久连接成功 connId=${gw.getConnId() ?? '?'}`)
214
214
  } catch (e) {
215
- dcgLogger(`Gateway 连接失败11: ${e}`, 'error')
215
+ dcgLogger(`Gateway 连接失败: ${e}`, 'error')
216
216
  persistentConn = null
217
217
  clearPingTimer()
218
218
  if (!socketStopped) {
@@ -230,8 +230,10 @@ export function startDcgchatGatewaySocket(): void {
230
230
  socketStopped = false
231
231
  clearReconnectTimer()
232
232
  if (startupConnectTimer != null) return
233
- startupConnectTimer = null
234
- void connectPersistentGateway()
233
+ startupConnectTimer = setTimeout(() => {
234
+ startupConnectTimer = null
235
+ void connectPersistentGateway()
236
+ }, 0)
235
237
  }
236
238
 
237
239
  /**
package/src/tool.ts CHANGED
@@ -205,7 +205,6 @@ export function getActiveSubagentCount(sessionKey: string): number {
205
205
  * 注意:须在收到 `subagent_spawned` 之后才会计入;仅 spawning 未 spawned 的不会阻塞。
206
206
  */
207
207
  export function waitUntilSubagentsIdle(sessionKey: string, opts?: { timeoutMs?: number; signal?: AbortSignal }): Promise<void> {
208
- console.log('🚀 ~ waitUntilSubagentsIdle ~ sessionKey:', sessionKey)
209
208
  const sk = sessionKey?.trim()
210
209
  if (!sk) return Promise.resolve()
211
210
 
@@ -2,15 +2,15 @@ import fs from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import type { AnyAgentTool } from 'openclaw/plugin-sdk'
4
4
  import { jsonResult } from 'openclaw/plugin-sdk'
5
+ import { sendDcgchatMedia } from '../channel.js'
6
+ import { getOutboundMsgParams } from '../utils/params.js'
7
+ import { sendText } from '../transport.js'
5
8
 
6
9
  /** 与 `registerTool` 工厂入参一致(主包未导出 `OpenClawPluginToolContext` 时仅用所需字段)。 */
7
10
  export type DcgchatMessageToolContext = {
8
11
  sessionKey?: string
9
12
  workspaceDir?: string
10
13
  }
11
- import { sendDcgchatMedia } from '../channel.js'
12
- import { getOutboundMsgParams } from '../utils/params.js'
13
- import { sendText } from '../transport.js'
14
14
 
15
15
  const SAFE_PREFIXES = ['/workspace/', '/mobook/']
16
16
 
@@ -22,7 +22,13 @@ const SAFE_EXTENSIONS = new Set([...fileType1, ...fileType2, ...fileType3, ...fi
22
22
 
23
23
  const messageToolParameters = {
24
24
  type: 'object',
25
+ additionalProperties: false,
25
26
  properties: {
27
+ target: {
28
+ type: 'string',
29
+ description:
30
+ '目标会话键(sessionKey),必须与当前会话 SessionKey 一致,禁止填写 userId。'
31
+ },
26
32
  content: {
27
33
  type: 'string',
28
34
  description: '发送文本内容'
@@ -32,6 +38,7 @@ const messageToolParameters = {
32
38
  description: '发送附件',
33
39
  items: {
34
40
  type: 'object',
41
+ additionalProperties: false,
35
42
  properties: {
36
43
  file: {
37
44
  type: 'string',
@@ -41,7 +48,8 @@ const messageToolParameters = {
41
48
  required: ['file']
42
49
  }
43
50
  }
44
- }
51
+ },
52
+ oneOf: [{ required: ['content'] }, { required: ['media'] }]
45
53
  }
46
54
 
47
55
  function extractPaths(text?: string) {
@@ -68,12 +76,12 @@ function isSafeFile(filepath: string) {
68
76
  * 通过注册时的 `OpenClawPluginToolContext.sessionKey` 出站,不再使用非标准的 `execute(args, ctx)`。
69
77
  */
70
78
  export function createDcgchatMessageTool(pluginCtx: DcgchatMessageToolContext): AnyAgentTool {
71
- console.log('🚀 ~ createDcgchatMessageTool ~ pluginCtx:', pluginCtx)
72
79
  return {
73
80
  name: 'message',
74
81
  label: 'message',
75
82
  description: `
76
83
  向用户发送消息。
84
+ 若传 target,target 必须是 sessionKey,不能是 userId。
77
85
  如果发送附件:必须使用 media 字段
78
86
  支持路径目录:
79
87
  /workspace/
package/src/transport.ts CHANGED
@@ -152,11 +152,14 @@ export function wsSend(ctx: IMsgParams, content: Record<string, unknown>): boole
152
152
  */
153
153
  export function wsSendRaw(ctx: IMsgParams, content: Record<string, unknown>, isLog = true): boolean {
154
154
  const ws = getWsConnection()
155
- if (isWsOpen()) {
156
- ws?.send(JSON.stringify(buildOpenclawBotChat(ctx, content, { mergeChannelDefaults: true })))
157
- if (isLog) {
158
- dcgLogger('已发送:' + JSON.stringify(buildOpenclawBotChat(ctx, content, { mergeChannelDefaults: true })))
159
- }
155
+ if (ws?.readyState !== WebSocket.OPEN) {
156
+ dcgLogger(`server socket not ready ${ws?.readyState}`, 'error')
157
+ return false
158
+ }
159
+ const envelope = buildOpenclawBotChat(ctx, content, { mergeChannelDefaults: true })
160
+ ws.send(JSON.stringify(envelope))
161
+ if (isLog) {
162
+ dcgLogger('已发送:' + JSON.stringify(envelope))
160
163
  }
161
164
  return true
162
165
  }
@@ -1,6 +1,12 @@
1
- /** socket connection */
2
1
  import type WebSocket from 'ws'
2
+ import fs from 'node:fs'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import { createPluginRuntimeStore, type OpenClawConfig, type PluginRuntime } from 'openclaw/plugin-sdk'
6
+ import { channelInfo, ENV } from './constant.js'
7
+ import { dcgLogger } from './log.js'
3
8
 
9
+ /** socket connection */
4
10
  let ws: WebSocket | null = null
5
11
 
6
12
  export function setWsConnection(next: WebSocket | null) {
@@ -11,7 +17,6 @@ export function getWsConnection(): WebSocket | null {
11
17
  return ws
12
18
  }
13
19
 
14
- // OpenClawConfig
15
20
  let config: OpenClawConfig | null = null
16
21
 
17
22
  export function setOpenClawConfig(next: OpenClawConfig | null) {
@@ -22,15 +27,7 @@ export function getOpenClawConfig(): OpenClawConfig | null {
22
27
  return config
23
28
  }
24
29
 
25
- import { createPluginRuntimeStore, type OpenClawConfig, type PluginRuntime } from 'openclaw/plugin-sdk'
26
- import { dcgLogger } from './log.js'
27
- import { channelInfo, ENV } from './constant.js'
28
-
29
- const path = require('path')
30
- const fs = require('fs')
31
- const os = require('os')
32
-
33
- function getWorkspacePath() {
30
+ function getWorkspacePath(): string | null {
34
31
  const workspacePath = path.join(
35
32
  os.homedir(),
36
33
  config?.channels?.["dcgchat-test"]?.appId == 110 ? '.mobook' : '.openclaw',
@@ -42,19 +39,21 @@ function getWorkspacePath() {
42
39
  return null
43
40
  }
44
41
 
45
- let workspaceDir: string = getWorkspacePath()
42
+ let workspaceDir: string = getWorkspacePath() ?? ''
46
43
 
47
44
  export function setWorkspaceDir(dir?: string) {
48
45
  if (dir) {
49
46
  workspaceDir = dir
50
47
  }
51
48
  }
49
+
52
50
  export function getWorkspaceDir(): string {
53
51
  if (!workspaceDir) {
54
52
  dcgLogger?.('Workspace directory not initialized', 'error')
55
53
  }
56
54
  return workspaceDir
57
55
  }
56
+
58
57
  const { setRuntime: setDcgchatRuntime, getRuntime: getDcgchatRuntime } = createPluginRuntimeStore<PluginRuntime>(
59
58
  `${"dcgchat-test"} runtime not initialized`
60
59
  )
@@ -127,7 +126,7 @@ export const getSessionKey = (content: any, accountId: string) => {
127
126
  const { real_mobook, agent_id, agent_clone_code, session_id } = content
128
127
  const core = getDcgchatRuntime()
129
128
 
130
- const anentCode = agent_clone_code || 'main'
129
+ const agentCode = agent_clone_code || 'main'
131
130
 
132
131
  const route = core.channel.routing.resolveAgentRoute({
133
132
  cfg: getOpenClawConfig() as OpenClawConfig,
@@ -135,7 +134,7 @@ export const getSessionKey = (content: any, accountId: string) => {
135
134
  accountId: accountId || 'default',
136
135
  peer: { kind: 'direct', id: session_id }
137
136
  })
138
- return real_mobook == '1' ? route.sessionKey : `agent:${anentCode}:mobook:direct:${agent_id}:${session_id}`.toLowerCase()
137
+ return real_mobook == '1' ? route.sessionKey : `agent:${agentCode}:mobook:direct:${agent_id}:${session_id}`.toLowerCase()
139
138
  }
140
139
 
141
140
  export function getInfoBySessionKey(sk: string): { sessionId: string; agentId: string } {
@@ -47,7 +47,7 @@ export function getEffectiveMsgParams(sessionKey?: string): IMsgParams {
47
47
  }
48
48
 
49
49
  /**
50
- * Agent `message` 工具的 `target` 应为 `effectiveSessionKey`(如 `agent:main:mobook:direct:...`)。
50
+ * Agent `message` 工具的 `target` 应为 `dcgSessionKey`(如 `agent:main:mobook:direct:...`)。
51
51
  * `setParamsMessage` 使用的 key 与此一致。若按 preferredKey 查不到 map,
52
52
  * 则回落到当前会话 `currentSessionKey`,避免拿到空 `messageId` / `sessionId` 导致无文件卡片、WS 上下文错误。
53
53
  */