@dcrays/dcgchat-test 0.3.17 → 0.3.19

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.17",
3
+ "version": "0.3.19",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for 书灵墨宝 (WebSocket)",
6
6
  "main": "index.ts",
package/src/channel.ts CHANGED
@@ -39,12 +39,14 @@ export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<v
39
39
  const url = opts.mediaUrl ? await ossUpload(opts.mediaUrl, botToken, 1) : ''
40
40
  wsSendRaw(msgCtx, {
41
41
  response: opts.text ?? '',
42
+ message_tags: { source: 'file' },
42
43
  files: [{ url, name: fileName }]
43
44
  })
44
45
  dcgLogger(`dcgchat: sendMedia to user ${msgCtx.userId}, file=${fileName}`)
45
46
  } catch (error) {
46
47
  wsSendRaw(msgCtx, {
47
48
  response: opts.text ?? '',
49
+ message_tags: { source: 'file' },
48
50
  files: [{ url: opts.mediaUrl ?? '', name: fileName }]
49
51
  })
50
52
  dcgLogger(`dcgchat: error sendMedia to user ${msgCtx.userId}: ${String(error)}`, 'error')
@@ -142,28 +144,18 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
142
144
  deliveryMode: 'direct',
143
145
  textChunkLimit: 4000,
144
146
  sendText: async (ctx) => {
147
+ const isCron = ctx.to.indexOf('dcg-cron:') >= 0
148
+ const to = ctx.to.replace('dcg-cron:', '')
149
+ dcgLogger(`channel sendText to ${ctx.to} `)
150
+ const outboundCtx = getEffectiveMsgParams(to)
151
+ const cronMsgId = getCronMessageId(to)
152
+ const messageId = !!cronMsgId ? cronMsgId : isCron ? `${Date.now()}` : outboundCtx?.messageId
145
153
  if (isWsOpen()) {
146
- // if (ctx.to.indexOf('cron:') >= 0) {
147
- // const sessionInfo = ctx.to.split(':')[1]
148
- // const sessionId = sessionInfo.split('-')[0]
149
- // const agentId = sessionInfo.split('-')[1]
150
- // const merged = mergeDefaultParams({
151
- // agentId: agentId,
152
- // sessionId: sessionId,
153
- // messageId: `${Date.now()}`,
154
- // is_finish: -1
155
- // })
156
- // wsSendRaw(merged, { response: ctx.text })
157
- // } else {
158
- // }
159
- const outboundCtx = getEffectiveMsgParams(ctx.to)
160
- const messageId = getCronMessageId(ctx.to)
161
154
  if (outboundCtx?.sessionId) {
162
- const newCtx = messageId ? { ...outboundCtx, messageId } : outboundCtx
155
+ const newCtx = { ...outboundCtx, messageId }
163
156
  wsSendRaw(newCtx, { response: ctx.text, is_finish: -1, message_tags: { source: 'channel' } })
164
- dcgLogger(`channel sendText to ${ctx.to} ${ctx.text?.slice(0, 50)}`)
165
157
  } else {
166
- const sessionInfo = ctx.to.split(':')
158
+ const sessionInfo = to.split(':')
167
159
  const sessionId = sessionInfo.at(-1) ?? ''
168
160
  const agentId = sessionInfo.at(-2) ?? ''
169
161
  const merged = mergeDefaultParams({
@@ -171,25 +163,29 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
171
163
  sessionId: `${sessionId}`,
172
164
  messageId: messageId,
173
165
  is_finish: -1,
166
+ message_tags: { source: 'channel' },
174
167
  real_mobook: !sessionId ? 1 : ''
175
168
  })
176
- dcgLogger(`channel sendText to ${ctx.to} ${ctx.text?.slice(0, 50)}`)
177
169
  wsSendRaw(merged, { response: ctx.text })
178
170
  }
179
171
  }
180
172
  return {
181
173
  channel: "dcgchat-test",
182
- messageId: `dcg-${Date.now()}`,
183
- chatId: ctx.to
174
+ messageId: `${messageId}`,
175
+ chatId: to
184
176
  }
185
177
  },
186
178
  sendMedia: async (ctx) => {
187
- const msgCtx = getEffectiveMsgParams(ctx.to)
188
- dcgLogger(`channel sendMedia to ${ctx.to} ${ctx.mediaUrl?.slice(0, 50)}`)
189
- await sendDcgchatMedia({ sessionKey: ctx.to ?? '', mediaUrl: ctx.mediaUrl ?? '' })
179
+ const to = ctx.to.replace('dcg-cron:', '')
180
+ const msgCtx = getEffectiveMsgParams(to)
181
+ const cronMsgId = getCronMessageId(to)
182
+ const isCron = ctx.to.indexOf('dcg-cron:') >= 0
183
+ const messageId = !!cronMsgId ? cronMsgId : isCron ? `${Date.now()}` : msgCtx?.messageId
184
+ dcgLogger(`channel sendMedia to ${ctx.to}`)
185
+ await sendDcgchatMedia({ sessionKey: to ?? '', mediaUrl: ctx.mediaUrl ?? '' })
190
186
  return {
191
187
  channel: "dcgchat-test",
192
- messageId: `dcg-${Date.now()}`,
188
+ messageId: `${messageId}`,
193
189
  chatId: msgCtx.userId?.toString()
194
190
  }
195
191
  }
package/src/cron.ts CHANGED
@@ -59,7 +59,7 @@ async function runCronJobsUpload(msgCtx: IMsgParams): Promise<void> {
59
59
  const url = await ossUpload(jobPath, msgCtx.botToken ?? '', 0)
60
60
  dcgLogger(`定时任务创建成功: ${url}`)
61
61
  if (!msgCtx.sessionKey) {
62
- dcgLogger('runCronJobsUpload: missing sessionKey on msgCtx', 'error')
62
+ dcgLogger(`runCronJobsUpload: missing sessionKey ${JSON.stringify(msgCtx)} on msgCtx`, 'error')
63
63
  return
64
64
  }
65
65
  sendEventMessage(url, msgCtx.sessionKey)
@@ -155,7 +155,7 @@ export const finishedDcgchatCron = async (jobId: string) => {
155
155
  const messageId = getCronMessageId(sessionKey)
156
156
  if (outboundCtx?.sessionId) {
157
157
  const newCtx = messageId ? { ...outboundCtx, messageId } : outboundCtx
158
- sendFinal(newCtx)
158
+ sendFinal(newCtx, 'cron send')
159
159
  } else {
160
160
  const sessionInfo = sessionKey.split(':')
161
161
  const sessionId = sessionInfo.at(-1) ?? ''
@@ -125,7 +125,7 @@ function injectBestEffort(params: Record<string, unknown>, sk: string): Record<s
125
125
  // 顶层 delivery
126
126
  if (newParams.delivery && typeof newParams.delivery === 'object') {
127
127
  ;(newParams.delivery as CronDelivery).bestEffort = true
128
- ;(newParams.delivery as CronDelivery).to = sessionId
128
+ ;(newParams.delivery as CronDelivery).to = `dcg-cron:${sk}`
129
129
  ;(newParams.delivery as CronDelivery).accountId = agentId
130
130
  ;(newParams.delivery as CronDelivery).channel = "dcgchat-test"
131
131
  newParams.sessionKey = sk
@@ -136,7 +136,7 @@ function injectBestEffort(params: Record<string, unknown>, sk: string): Record<s
136
136
  const job = newParams.job as Record<string, unknown> | undefined
137
137
  if (job?.delivery && typeof job.delivery === 'object') {
138
138
  ;(job.delivery as CronDelivery).bestEffort = true
139
- ;(newParams.delivery as CronDelivery).to = sessionId
139
+ ;(newParams.delivery as CronDelivery).to = `dcg-cron:${sk}`
140
140
  ;(newParams.delivery as CronDelivery).accountId = agentId
141
141
  ;(newParams.delivery as CronDelivery).channel = "dcgchat-test"
142
142
  newParams.sessionKey = sk
@@ -176,7 +176,7 @@ export function cronToolCall(event: { toolName: any; params: any; toolCallId: an
176
176
  if (params.command.indexOf('cron create') > -1 || params.command.indexOf('cron add') > -1) {
177
177
  const newParams = JSON.parse(JSON.stringify(params)) as Record<string, unknown>
178
178
  newParams.command =
179
- params.command.replace('--json', '') + ` --session-key ${sk} --channel ${"dcgchat-test"} --to ${sk} --json`
179
+ params.command.replace('--json', '') + ` --session-key ${sk} --channel ${"dcgchat-test"} --to dcg-cron:${sk} --json`
180
180
  return { params: newParams }
181
181
  } else {
182
182
  return params
package/src/transport.ts CHANGED
@@ -154,6 +154,7 @@ export function wsSendRaw(ctx: IMsgParams, content: Record<string, unknown>): bo
154
154
  const ws = getWsConnection()
155
155
  if (isWsOpen()) {
156
156
  ws?.send(JSON.stringify(buildOpenclawBotChat(ctx, content, { mergeChannelDefaults: true })))
157
+ dcgLogger(JSON.stringify(buildOpenclawBotChat(ctx, content, { mergeChannelDefaults: true })))
157
158
  }
158
159
  return true
159
160
  }
@@ -162,7 +163,7 @@ export function sendChunk(text: string, ctx: IMsgParams, chunkIdx: number): bool
162
163
  return wsSend(ctx, { response: text, state: 'chunk', chunk_idx: chunkIdx })
163
164
  }
164
165
 
165
- export function sendFinal(ctx: IMsgParams, tag?: string): boolean {
166
+ export function sendFinal(ctx: IMsgParams, tag: string): boolean {
166
167
  dcgLogger(` message handling complete state: final tag:${tag}`)
167
168
  return wsSend(ctx, { response: '', state: 'final' })
168
169
  }
package/src/types.ts CHANGED
@@ -128,4 +128,5 @@ export interface IMsgParams {
128
128
  sessionKey?: string
129
129
  real_mobook?: string | number
130
130
  is_finish?: number
131
+ message_tags?: Record<string, string>
131
132
  }
@@ -94,11 +94,9 @@ function stripMobookNoise(s: string) {
94
94
  }
95
95
 
96
96
  /**
97
- * 从文本中扫描 `/mobook/` 片段,按最长后缀匹配合法扩展名(兜底,不依赖 FILE_NAME 字符集)
97
+ * 从文本中扫描 `.../mobook/...` `...\mobook\...` 片段,按最长后缀匹配合法扩展名(兜底)
98
98
  */
99
- function collectMobookPathsByScan(text: string, result: Set<string>): void {
100
- const lower = text.toLowerCase()
101
- const needle = '/mobook/'
99
+ function collectMobookPathsAfterNeedle(text: string, lower: string, needle: string, result: Set<string>): void {
102
100
  let from = 0
103
101
  while (from < text.length) {
104
102
  const i = lower.indexOf(needle, from)
@@ -136,8 +134,16 @@ function collectMobookPathsByScan(text: string, result: Set<string>): void {
136
134
  }
137
135
  }
138
136
 
137
+ function collectMobookPathsByScan(text: string, result: Set<string>): void {
138
+ const lower = text.toLowerCase()
139
+ collectMobookPathsAfterNeedle(text, lower, '/mobook/', result)
140
+ collectMobookPathsAfterNeedle(text, lower, '\\mobook\\', result)
141
+ }
142
+
139
143
  export function extractMobookFiles(text = '') {
140
144
  if (typeof text !== 'string' || !text.trim()) return []
145
+ // 全角冒号(中文输入常见)→ 半角,便于匹配 c:\mobook\
146
+ text = text.replace(/\uFF1A/g, ':')
141
147
  const result = new Set<string>()
142
148
  // ✅ 扩展名(必须长扩展名优先,见 EXT_SORTED_FOR_REGEX)
143
149
  const EXT = `(${EXT_SORTED_FOR_REGEX.join('|')})`
@@ -157,6 +163,14 @@ export function extractMobookFiles(text = '') {
157
163
  ;(text.match(fullPathReg) || []).forEach((p) => {
158
164
  result.add(normalizePath(p))
159
165
  })
166
+ // 2️⃣b Windows 实际保存路径:C:\mobook\xxx、c:/mobook/xxx、\mobook\xxx(模型常写反斜杠,原先无法识别)
167
+ const winMobookReg = new RegExp(`(?:[a-zA-Z]:)?[/\\\\]mobook[/\\\\]${FILE_NAME}\\.${EXT}`, 'gi')
168
+ ;(text.match(winMobookReg) || []).forEach((full) => {
169
+ const name = full.replace(/^(?:[a-zA-Z]:)?[/\\\\]mobook[/\\\\]/i, '').trim()
170
+ if (isValidFileName(name)) {
171
+ result.add(normalizePath(`/mobook/${name}`))
172
+ }
173
+ })
160
174
  // 3️⃣ mobook下的 xxx.xxx
161
175
  const inlineReg = new RegExp(`mobook下的\\s*(${FILE_NAME}\\.${EXT})`, 'gi')
162
176
  ;(text.match(inlineReg) || []).forEach((item) => {