@dcrays/dcgchat-test 0.3.28 → 0.3.31

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.3.28",
3
+ "version": "0.3.31",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for 书灵墨宝 (WebSocket)",
6
6
  "main": "index.ts",
package/src/bot.ts CHANGED
@@ -166,7 +166,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
166
166
  const effectiveAgentId = embeddedAgentId ?? route.agentId
167
167
  const effectiveSessionKey = getSessionKey(msg.content, account.accountId)
168
168
 
169
- setParamsMessage(effectiveSessionKey, {
169
+ const mergedParams = {
170
170
  userId: msg._userId,
171
171
  botToken: msg.content.bot_token,
172
172
  sessionId: conversationId,
@@ -177,7 +177,14 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
177
177
  agentId: msg.content.agent_id ?? '',
178
178
  sessionKey: effectiveSessionKey,
179
179
  real_mobook
180
- })
180
+ }
181
+ setParamsMessage(effectiveSessionKey, mergedParams)
182
+ // 与 OpenClaw 会话投递里仍可能出现的 ctx.to=SenderId(userId)对齐,便于 getOutboundMsgParams 命中
183
+ dcgLogger(
184
+ `target normalize: rawTarget=${userId}, normalizedTarget=${effectiveSessionKey}, conversationId=${conversationId ?? ''}, messageId=${msg.content.message_id}`
185
+ )
186
+ setParamsMessage(userId, mergedParams)
187
+ dcgLogger(`target alias bound: aliasTarget=${userId} -> sessionKey=${effectiveSessionKey}`)
181
188
  const outboundCtx = getEffectiveMsgParams(effectiveSessionKey)
182
189
  const agentEntry =
183
190
  effectiveAgentId && effectiveAgentId !== 'main' ? config.agents?.list?.find((a) => a.id === effectiveAgentId) : undefined
@@ -245,6 +252,9 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
245
252
  OriginatingTo: effectiveSessionKey,
246
253
  ...mediaPayload
247
254
  })
255
+ dcgLogger(
256
+ `inbound context target: rawTarget=${userId}, normalizedTarget=${effectiveSessionKey}, ctx.To=${String(ctxPayload.To ?? '')}, ctx.SessionKey=${String(ctxPayload.SessionKey ?? '')}, ctx.OriginatingTo=${String(ctxPayload.OriginatingTo ?? '')}`
257
+ )
248
258
 
249
259
  const sentMediaKeys = new Set<string>()
250
260
  const getMediaKey = (url: string) => url.split(/[\\/]/).pop() ?? url
@@ -438,11 +448,21 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
438
448
 
439
449
  // Record session metadata
440
450
  const storePath = core.channel.session.resolveStorePath(config.session?.store)
451
+ dcgLogger(
452
+ `record session route: rawTarget=${userId}, normalizedTarget=${effectiveSessionKey}, updateLastRoute.to=${effectiveSessionKey}, accountId=${route.accountId}`
453
+ )
441
454
  core.channel.session
442
455
  .recordInboundSession({
443
456
  storePath,
444
457
  sessionKey: effectiveSessionKey,
445
458
  ctx: ctxPayload,
459
+ // 与 Telegram/Discord 等一致:写入 deliveryContext.to,否则投递可能回退为 From(userId),channel sendMedia 里 ctx.to 会变成数字 userId
460
+ updateLastRoute: {
461
+ sessionKey: effectiveSessionKey,
462
+ channel: "dcgchat-test",
463
+ to: effectiveSessionKey,
464
+ accountId: route.accountId
465
+ },
446
466
  onRecordError: (err) => {
447
467
  dcgLogger(` session record error: ${String(err)}`, 'error')
448
468
  }
package/src/channel.ts CHANGED
@@ -5,7 +5,7 @@ import { ossUpload } from './request/oss.js'
5
5
  import { addSentMediaKey, getCronMessageId, getOpenClawConfig, hasSentMediaKey } from './utils/global.js'
6
6
  import { isWsOpen, mergeDefaultParams, mergeSessionParams, sendFinal, wsSendRaw } from './transport.js'
7
7
  import { dcgLogger, setLogger } from './utils/log.js'
8
- import { getOutboundMsgParams } from './utils/params.js'
8
+ import { getOutboundMsgParams, getParamsMessage } from './utils/params.js'
9
9
  import { startDcgchatGatewaySocket } from './gateway/socket.js'
10
10
 
11
11
  export type DcgchatMediaSendOptions = {
@@ -15,8 +15,15 @@ export type DcgchatMediaSendOptions = {
15
15
  text?: string
16
16
  }
17
17
 
18
+ function normalizeSessionTarget(rawTo: string): string {
19
+ const cleaned = rawTo.replace('dcg-cron:', '').trim()
20
+ if (!cleaned) return ''
21
+ return getParamsMessage(cleaned)?.sessionKey?.trim() || cleaned
22
+ }
23
+
18
24
  export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<void> {
19
- const msgCtx = getOutboundMsgParams(opts.sessionKey ?? '')
25
+ const sessionKey = normalizeSessionTarget(opts.sessionKey ?? '')
26
+ const msgCtx = getOutboundMsgParams(sessionKey)
20
27
  if (!isWsOpen()) {
21
28
  dcgLogger(`outbound media skipped -> ws not open: ${opts.mediaUrl ?? ''}`)
22
29
  return
@@ -36,21 +43,20 @@ export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<v
36
43
 
37
44
  try {
38
45
  const botToken = msgCtx.botToken ?? getOpenClawConfig()?.channels?.["dcgchat-test"]?.botToken ?? ''
39
- console.log('🚀 ~ sendDcgchatMedia ~ botToken:', botToken)
40
46
  const url = opts.mediaUrl ? await ossUpload(opts.mediaUrl, botToken, 1) : ''
41
47
  wsSendRaw(msgCtx, {
42
48
  response: opts.text ?? '',
43
49
  message_tags: { source: 'file' },
44
50
  files: [{ url, name: fileName }]
45
51
  })
46
- dcgLogger(`dcgchat: sendMedia to user ${msgCtx.userId}, file=${fileName}`)
52
+ dcgLogger(`dcgchat: sendMedia session=${sessionKey}, file=${fileName}`)
47
53
  } catch (error) {
48
54
  wsSendRaw(msgCtx, {
49
55
  response: opts.text ?? '',
50
56
  message_tags: { source: 'file' },
51
57
  files: [{ url: opts.mediaUrl ?? '', name: fileName }]
52
58
  })
53
- dcgLogger(`dcgchat: error sendMedia to user ${msgCtx.userId}: ${String(error)}`, 'error')
59
+ dcgLogger(`dcgchat: error sendMedia session=${sessionKey}: ${String(error)}`, 'error')
54
60
  }
55
61
  }
56
62
 
@@ -101,11 +107,17 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
101
107
  enabled: { type: 'boolean' },
102
108
  wsUrl: { type: 'string' },
103
109
  botToken: { type: 'string' },
104
- userId: { type: 'string' },
110
+ userId: { type: 'string', description: 'WebSocket 连接参数 _userId,与 message 工具的 target(effectiveSessionKey)无关' },
105
111
  appId: { type: 'string' },
106
112
  domainId: { type: 'string' },
107
113
  capabilities: { type: 'array', items: { type: 'string' } }
108
114
  }
115
+ },
116
+ uiHints: {
117
+ userId: {
118
+ label: 'WS 连接 _userId',
119
+ help: '仅用于拼接网关 WebSocket URL 的查询参数,不是 Agent 发消息时的 target。发消息请使用 effectiveSessionKey(与入站上下文 SessionKey 相同,格式如 agent:main:mobook:direct:…)。'
120
+ }
109
121
  }
110
122
  },
111
123
  config: {
@@ -135,18 +147,31 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
135
147
  })
136
148
  },
137
149
  messaging: {
138
- normalizeTarget: (raw) => raw?.trim() || undefined,
150
+ normalizeTarget: (raw) => normalizeSessionTarget(raw ?? '') || undefined,
139
151
  targetResolver: {
140
152
  looksLikeId: (raw) => Boolean(raw?.trim()),
141
- hint: 'userId'
153
+ hint: 'effectiveSessionKey(与 SessionKey 一致;勿填配置里的 WS userId'
142
154
  }
143
155
  },
156
+ agentPrompt: {
157
+ messageToolHints: () => [
158
+ '书灵墨宝:message 工具的 target 必须填 effectiveSessionKey(与当前会话 SessionKey / OriginatingTo 相同),形如 agent:main:mobook:direct:<agent_id>:<session_id>;不要填 channels.dcgchat.userId 或纯数字 userId。',
159
+ 'OpenClaw 自带的 target 字段说明里仍可能出现 “user id”,在本频道请忽略该字样,一律按 effectiveSessionKey 理解。'
160
+ ]
161
+ },
144
162
  outbound: {
145
163
  deliveryMode: 'direct',
164
+ resolveTarget: ({ to }) => {
165
+ const normalized = normalizeSessionTarget(to ?? '')
166
+ if (!normalized) {
167
+ return { ok: false, error: new Error('target is empty') }
168
+ }
169
+ return { ok: true, to: normalized }
170
+ },
146
171
  textChunkLimit: 4000,
147
172
  sendText: async (ctx) => {
148
173
  const isCron = ctx.to.indexOf('dcg-cron:') >= 0
149
- const to = ctx.to.replace('dcg-cron:', '')
174
+ const to = normalizeSessionTarget(ctx.to)
150
175
  dcgLogger(`channel sendText to ${ctx.to} `)
151
176
  const outboundCtx = getOutboundMsgParams(to)
152
177
  const cronMsgId = getCronMessageId(to)
@@ -176,17 +201,29 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
176
201
  }
177
202
  },
178
203
  sendMedia: async (ctx) => {
179
- const to = ctx.to.replace('dcg-cron:', '')
180
- const msgCtx = getOutboundMsgParams(to)
181
- const cronMsgId = getCronMessageId(to)
182
204
  const isCron = ctx.to.indexOf('dcg-cron:') >= 0
183
- const messageId = !!cronMsgId ? cronMsgId : isCron ? `${Date.now()}` : msgCtx?.messageId
205
+ const to = normalizeSessionTarget(ctx.to)
206
+ const outboundCtx = getOutboundMsgParams(to)
207
+ const msgCtx = getParamsMessage(to) ?? outboundCtx
208
+ const cronMsgId = getCronMessageId(to)
209
+ const fallbackMessageId = `${Date.now()}`
210
+ const messageId = cronMsgId || (isCron ? fallbackMessageId : msgCtx?.messageId || fallbackMessageId)
211
+
212
+ if (!outboundCtx?.sessionId) {
213
+ dcgLogger(`channel sendMedia to ${ctx.to} -> sessionId not found`, 'error')
214
+ return {
215
+ channel: "dcgchat-test",
216
+ messageId,
217
+ chatId: to || ''
218
+ }
219
+ }
220
+
184
221
  dcgLogger(`channel sendMedia to ${ctx.to}`)
185
- await sendDcgchatMedia({ sessionKey: to ?? '', mediaUrl: ctx.mediaUrl ?? '' })
222
+ await sendDcgchatMedia({ sessionKey: to || '', mediaUrl: ctx.mediaUrl || '' })
186
223
  return {
187
224
  channel: "dcgchat-test",
188
- messageId: `${messageId}`,
189
- chatId: msgCtx.userId?.toString()
225
+ messageId,
226
+ chatId: to || ''
190
227
  }
191
228
  }
192
229
  },
@@ -47,8 +47,8 @@ export function getEffectiveMsgParams(sessionKey?: string): IMsgParams {
47
47
  }
48
48
 
49
49
  /**
50
- * Agent `message` 工具常把 `target` 设为用户 ID(如 "150"),而 `setParamsMessage` 使用的 key 是
51
- * `effectiveSessionKey`(如 `agent:main:mobook:direct:...`)。若按 preferredKey 查不到 map,
50
+ * Agent `message` 工具的 `target` 应为 `effectiveSessionKey`(如 `agent:main:mobook:direct:...`)。
51
+ * `setParamsMessage` 使用的 key 与此一致。若按 preferredKey 查不到 map,
52
52
  * 则回落到当前会话 `currentSessionKey`,避免拿到空 `messageId` / `sessionId` 导致无文件卡片、WS 上下文错误。
53
53
  */
54
54
  export function getOutboundMsgParams(preferredKey: string): IMsgParams {