@dcrays/dcgchat-test 0.4.4 → 0.4.10

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
@@ -3,12 +3,11 @@ import { emptyPluginConfigSchema } from 'openclaw/plugin-sdk'
3
3
  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
- import { channelInfo, ENV } from './src/utils/constant.js'
7
6
  import { setOpenClawConfig } from './src/utils/global.js'
8
7
  import { createDcgchatMessageTool } from './src/tools/messageTool.js'
9
8
 
10
9
  const plugin = {
11
- id: channelInfo[ENV],
10
+ id: "dcgchat-test",
12
11
  name: '书灵墨宝',
13
12
  description: '连接 OpenClaw 与 书灵墨宝 产品(WebSocket)',
14
13
  configSchema: emptyPluginConfigSchema(),
@@ -19,7 +18,6 @@ const plugin = {
19
18
  api.registerChannel({ plugin: dcgchatPlugin })
20
19
  setWorkspaceDir(api.config?.agents?.defaults?.workspace)
21
20
  api.registerTool((ctx) => {
22
- setWorkspaceDir(ctx.workspaceDir)
23
21
  return createDcgchatMessageTool(ctx)
24
22
  })
25
23
  }
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "id": "dcgchat-test",
3
- "channels": ["dcgchat-test"],
3
+ "channels": [
4
+ "dcgchat-test"
5
+ ],
4
6
  "configSchema": {
5
7
  "type": "object",
6
8
  "additionalProperties": false,
7
9
  "properties": {}
8
10
  }
9
- }
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcrays/dcgchat-test",
3
- "version": "0.4.4",
3
+ "version": "0.4.10",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for 书灵墨宝 (WebSocket)",
6
6
  "main": "index.ts",
@@ -16,12 +16,6 @@
16
16
  "websocket",
17
17
  "ai"
18
18
  ],
19
- "scripts": {
20
- "typecheck": "tsc --noEmit",
21
- "build:production": "npx tsx scripts/build.ts production",
22
- "build:prod": "npx tsx scripts/build.ts production",
23
- "build:test": "npx tsx scripts/build.ts test"
24
- },
25
19
  "dependencies": {
26
20
  "ali-oss": "file:src/libs/ali-oss-6.23.0.tgz",
27
21
  "axios": "file:src/libs/axios-1.13.6.tgz",
@@ -37,19 +31,15 @@
37
31
  "id": "dcgchat-test",
38
32
  "label": "书灵墨宝",
39
33
  "selectionLabel": "书灵墨宝",
40
- "docsPath": "/channels/dcgchat",
34
+ "docsPath": "/channels/dcgchat-test",
41
35
  "docsLabel": "dcgchat-test",
42
36
  "blurb": "连接 OpenClaw 与 书灵墨宝 产品",
43
37
  "order": 80
44
38
  },
45
39
  "install": {
46
40
  "npmSpec": "@dcrays/dcgchat-test",
47
- "localPath": "extensions/dcgchat",
41
+ "localPath": "extensions/dcgchat-test",
48
42
  "defaultChoice": "npm"
49
43
  }
50
- },
51
- "devDependencies": {
52
- "openclaw": "^2026.3.13",
53
- "typescript": "~5.8.0"
54
44
  }
55
45
  }
package/src/bot.ts CHANGED
@@ -47,7 +47,15 @@ export function extractAgentIdFromConversationId(conversationId: string): string
47
47
  if (idx <= 0) return null
48
48
  return conversationId.slice(0, idx)
49
49
  }
50
-
50
+ function formatText(text: string): string {
51
+ if (!text) return ''
52
+ const str = String(text).replace(/\s/g, '')
53
+ if (!str) return ''
54
+ if (str.length <= 50) {
55
+ return str
56
+ }
57
+ return str.slice(0, 25) + `...[此处省略${str.length - 50}字]....` + str.slice(-25)
58
+ }
51
59
  async function resolveMediaFromUrls(files: TFileInfo[], botToken: string): Promise<MediaInfo[]> {
52
60
  const core = getDcgchatRuntime()
53
61
  const out: MediaInfo[] = []
@@ -151,7 +159,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
151
159
 
152
160
  const route = core.channel.routing.resolveAgentRoute({
153
161
  cfg: config,
154
- channel: channelInfo[ENV],
162
+ channel: "dcgchat-test",
155
163
  accountId: account.accountId,
156
164
  peer: { kind: 'direct', id: conversationId }
157
165
  })
@@ -191,6 +199,14 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
191
199
  }
192
200
 
193
201
  try {
202
+ if (msg.content.skills_scope.length > 0 && !msg.content?.agent_clone_code && !ignoreToolCommand.includes(text?.trim())) {
203
+ const workspaceDir = getWorkspaceDir()
204
+ const skill = msg.content.skills_scope[0]
205
+ const skillDir = `${workspaceDir}/skills/${skill.skill_code}`
206
+ const skillText = `用户选择使用此技能:"${skill.skill_code}",技能路径是:"${skillDir}",在目录下查找并`
207
+ text = skill.skill_code ? `${skillText} ${text}` : text
208
+ dcgLogger(`skill: text: ${text}`)
209
+ }
194
210
  // 处理用户上传的文件
195
211
  const files = msg.content.files ?? []
196
212
  let mediaPayload: Record<string, unknown> = {}
@@ -220,13 +236,13 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
220
236
  ChatType: 'direct',
221
237
  SenderName: agentDisplayName,
222
238
  SenderId: userId,
223
- Provider: channelInfo[ENV],
224
- Surface: channelInfo[ENV],
239
+ Provider: "dcgchat-test",
240
+ Surface: "dcgchat-test",
225
241
  MessageSid: msg.content.message_id,
226
242
  Timestamp: Date.now(),
227
243
  WasMentioned: true,
228
244
  CommandAuthorized: true,
229
- OriginatingChannel: channelInfo[ENV],
245
+ OriginatingChannel: "dcgchat-test",
230
246
  OriginatingTo: dcgSessionKey,
231
247
  Target: dcgSessionKey,
232
248
  SourceTarget: dcgSessionKey,
@@ -237,21 +253,18 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
237
253
  const getMediaKey = (url: string) => url.split(/[\\/]/).pop() ?? url
238
254
  let streamedTextLen = 0
239
255
 
240
- if (msg.content.skills_scope.length > 0 && !msg.content?.agent_clone_code) {
256
+ if (msg.content.skills_scope.length > 0 && !msg.content?.agent_clone_code && !ignoreToolCommand.includes(text?.trim())) {
241
257
  const workspaceDir = getWorkspaceDir()
242
- const skillText = msg.content.skills_scope
243
- .map((skill) => {
244
- const skillDir = `${workspaceDir}/skills/${skill.skill_code}`
245
- return `技能${skill.skill_code} 在目录${skillDir}下,在目录${skillDir}下读取技能 \n`
246
- })
247
- .join('\n')
248
- text = skillText ? `${skillText} ${text}` : text
258
+ const skill = msg.content.skills_scope[0]
259
+ const skillDir = `${workspaceDir}/skills/${skill.skill_code}`
260
+ const skillText = `用户选择使用此技能:"${skill.skill_code}",技能路径是:"${skillDir}",在目录下查找并`
261
+ text = skill.skill_code ? `${skillText} ${text}` : text
249
262
  dcgLogger(`skill: text: ${text}`)
250
263
  }
251
264
  const prefixContext = createReplyPrefixContext({
252
265
  cfg: config,
253
266
  agentId: effectiveAgentId ?? '',
254
- channel: channelInfo[ENV],
267
+ channel: "dcgchat-test",
255
268
  accountId: account.accountId
256
269
  })
257
270
 
@@ -269,6 +282,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
269
282
  sentMediaKeys.add(key)
270
283
  await sendDcgchatMedia({ sessionKey: dcgSessionKey, mediaUrl, text: '' })
271
284
  }
285
+ dcgLogger(`[deliver]: len=${payload?.text?.length} sessionId=${outboundCtx.sessionId} ${formatText(payload?.text ?? '')}`)
272
286
  },
273
287
  onError: (err: unknown, info: { kind: string }) => {
274
288
  setMsgStatus(dcgSessionKey, 'finished')
@@ -310,14 +324,8 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
310
324
  })
311
325
  } else if (interruptCommand.includes(text?.trim())) {
312
326
  dcgLogger(`interrupt command: ${text}`)
313
- const ctxForAbort =
314
- priorOutboundCtx.messageId?.trim() || priorOutboundCtx.sessionId?.trim()
315
- ? priorOutboundCtx
316
- : outboundCtx
317
- sendFinal(
318
- ctxForAbort.messageId?.trim() ? ctxForAbort : { ...ctxForAbort, messageId: `${Date.now()}` },
319
- 'abort'
320
- )
327
+ const ctxForAbort = priorOutboundCtx.messageId?.trim() || priorOutboundCtx.sessionId?.trim() ? priorOutboundCtx : outboundCtx
328
+ sendFinal(ctxForAbort.messageId?.trim() ? ctxForAbort : { ...ctxForAbort, messageId: `${Date.now()}` }, 'abort')
321
329
  sendText('会话已终止', outboundCtx)
322
330
  sessionStreamSuppressed.add(dcgSessionKey)
323
331
  const abortOneSession = async (sessionKey: string) => {
@@ -398,9 +406,6 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
398
406
  const prev = streamChunkIdxBySessionKey.get(dcgSessionKey) ?? 0
399
407
  streamChunkIdxBySessionKey.set(dcgSessionKey, prev + 1)
400
408
  sendChunk(delta, outboundCtx, prev)
401
- dcgLogger(
402
- `[stream]: chunkIdx=${prev} len=${delta.length} sessionId=${outboundCtx.sessionId} ${delta.slice(0, 100)}`
403
- )
404
409
  }
405
410
  streamedTextLen = payload.text.length
406
411
  } else {
@@ -440,7 +445,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
440
445
  ctx: ctxPayload,
441
446
  updateLastRoute: {
442
447
  sessionKey: dcgSessionKey,
443
- channel: channelInfo[ENV],
448
+ channel: "dcgchat-test",
444
449
  to: dcgSessionKey,
445
450
  accountId: route.accountId
446
451
  },
package/src/channel.ts CHANGED
@@ -11,13 +11,12 @@ import {
11
11
  hasSentMediaKey
12
12
  } from './utils/global.js'
13
13
  import { isWsOpen, mergeDefaultParams, mergeSessionParams, sendFinal, wsSendRaw } from './transport.js'
14
- import { channelInfo, ENV } from './utils/constant.js'
15
14
  import { dcgLogger, setLogger } from './utils/log.js'
16
15
  import { getOutboundMsgParams, getParamsMessage } from './utils/params.js'
17
16
  import { startDcgchatGatewaySocket } from './gateway/socket.js'
18
17
 
19
18
  function dcgchatChannelCfg(): DcgchatConfig {
20
- return (getOpenClawConfig()?.channels?.[channelInfo[ENV]] as DcgchatConfig | undefined) ?? {}
19
+ return (getOpenClawConfig()?.channels?.["dcgchat-test"] as DcgchatConfig | undefined) ?? {}
21
20
  }
22
21
 
23
22
  /** `agent:<code>:mobook:direct:<agentId>:<sessionId>`(与 getSessionKey 非 real_mobook 分支一致) */
@@ -91,7 +90,7 @@ export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<v
91
90
  const fileName = mediaUrl?.split(/[\\/]/).pop() || ''
92
91
 
93
92
  try {
94
- const botToken = msgCtx.botToken ?? getOpenClawConfig()?.channels?.[channelInfo[ENV]]?.botToken ?? ''
93
+ const botToken = msgCtx.botToken ?? getOpenClawConfig()?.channels?.["dcgchat-test"]?.botToken ?? ''
95
94
  const url = opts.mediaUrl ? await ossUpload(opts.mediaUrl, botToken, 1) : ''
96
95
  wsSendRaw(msgCtx, {
97
96
  response: opts.text ?? '',
@@ -111,7 +110,7 @@ export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<v
111
110
 
112
111
  export function resolveAccount(cfg: OpenClawConfig, accountId?: string | null): ResolvedDcgchatAccount {
113
112
  const id = accountId ?? DEFAULT_ACCOUNT_ID
114
- const raw = (cfg.channels?.[channelInfo[ENV]] as DcgchatConfig | undefined) ?? {}
113
+ const raw = (cfg.channels?.["dcgchat-test"] as DcgchatConfig | undefined) ?? {}
115
114
  return {
116
115
  accountId: id,
117
116
  enabled: raw.enabled !== false,
@@ -125,13 +124,13 @@ export function resolveAccount(cfg: OpenClawConfig, accountId?: string | null):
125
124
  }
126
125
 
127
126
  export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
128
- id: channelInfo[ENV],
127
+ id: "dcgchat-test",
129
128
  meta: {
130
- id: channelInfo[ENV],
129
+ id: "dcgchat-test",
131
130
  label: '书灵墨宝',
132
131
  selectionLabel: '书灵墨宝',
133
132
  docsPath: '/channels/dcgchat',
134
- docsLabel: channelInfo[ENV],
133
+ docsLabel: "dcgchat-test",
135
134
  blurb: '连接 OpenClaw 与 书灵墨宝 产品',
136
135
  order: 80
137
136
  },
@@ -148,7 +147,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
148
147
  // blockStreaming: true,
149
148
  },
150
149
  /** 当前构建的 channel id + 兼容旧配置键 `channels.dcgchat` */
151
- reload: { configPrefixes: [`channels.${channelInfo[ENV]}`, 'channels.dcgchat'] },
150
+ reload: { configPrefixes: [`channels.${"dcgchat-test"}`, 'channels.dcgchat'] },
152
151
  configSchema: {
153
152
  schema: {
154
153
  type: 'object',
@@ -184,7 +183,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
184
183
  resolveAccount: (cfg, accountId) => resolveAccount(cfg, accountId),
185
184
  defaultAccountId: () => DEFAULT_ACCOUNT_ID,
186
185
  setAccountEnabled: ({ cfg, enabled }) => {
187
- const channelKey = channelInfo[ENV]
186
+ const channelKey = "dcgchat-test"
188
187
  const prev = (cfg.channels?.[channelKey as keyof NonNullable<typeof cfg.channels>] as Record<string, unknown> | undefined) ?? {}
189
188
  return {
190
189
  ...cfg,
@@ -225,10 +224,10 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
225
224
  },
226
225
  agentPrompt: {
227
226
  messageToolHints: () => [
228
- '生成文件后,**尽可能不要**把文件路径、地址直接告诉用户。',
229
- '生成文件后,把文件名告诉用户。',
230
- '使用内置 `message` 或 `dcgchat_message` 发消息时,target 必须是完整 dcgSessionKey(与上下文 SessionKey 相同),禁止填 From、SenderId、WS userId 等纯数字。',
231
- '生成文件后,须通过工具发送文件,勿在文本里直接输出路径或地址。'
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)。'
232
231
  ]
233
232
  },
234
233
  outbound: {
@@ -272,7 +271,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
272
271
  }
273
272
  }
274
273
  return {
275
- channel: channelInfo[ENV],
274
+ channel: "dcgchat-test",
276
275
  messageId: `${messageId}`,
277
276
  chatId: to
278
277
  }
@@ -289,7 +288,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
289
288
  if (!outboundCtx?.sessionId) {
290
289
  dcgLogger(`channel sendMedia to ${ctx.to} -> sessionId not found`, 'error')
291
290
  return {
292
- channel: channelInfo[ENV],
291
+ channel: "dcgchat-test",
293
292
  messageId,
294
293
  chatId: to || ''
295
294
  }
@@ -298,7 +297,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
298
297
  dcgLogger(`channel sendMedia to ${ctx.to}`)
299
298
  await sendDcgchatMedia({ sessionKey: to || '', mediaUrl: ctx.mediaUrl || '' })
300
299
  return {
301
- channel: channelInfo[ENV],
300
+ channel: "dcgchat-test",
302
301
  messageId,
303
302
  chatId: to || ''
304
303
  }
@@ -127,7 +127,7 @@ function injectBestEffort(params: Record<string, unknown>, sk: string): Record<s
127
127
  ;(newParams.delivery as CronDelivery).bestEffort = true
128
128
  ;(newParams.delivery as CronDelivery).to = `dcg-cron:${sk}`
129
129
  ;(newParams.delivery as CronDelivery).accountId = agentId
130
- ;(newParams.delivery as CronDelivery).channel = channelInfo[ENV]
130
+ ;(newParams.delivery as CronDelivery).channel = "dcgchat-test"
131
131
  newParams.sessionKey = sk
132
132
  return newParams
133
133
  }
@@ -138,7 +138,7 @@ function injectBestEffort(params: Record<string, unknown>, sk: string): Record<s
138
138
  ;(job.delivery as CronDelivery).bestEffort = true
139
139
  ;(newParams.delivery as CronDelivery).to = `dcg-cron:${sk}`
140
140
  ;(newParams.delivery as CronDelivery).accountId = agentId
141
- ;(newParams.delivery as CronDelivery).channel = channelInfo[ENV]
141
+ ;(newParams.delivery as CronDelivery).channel = "dcgchat-test"
142
142
  newParams.sessionKey = sk
143
143
  return newParams
144
144
  }
@@ -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 ${channelInfo[ENV]} --to dcg-cron:${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/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
@@ -33,14 +34,13 @@ export async function installSkill(params: ISkillParams, msgContent: Record<stri
33
34
  const { path: cdnUrl, code } = params
34
35
  const workspacePath = getWorkspaceDir()
35
36
 
36
- const skillDir = path.join(workspacePath, 'skills', code)
37
-
38
37
  // 确保 skills 目录存在
39
38
  const skillsDir = path.join(workspacePath, 'skills')
40
39
  if (!fs.existsSync(skillsDir)) {
41
40
  fs.mkdirSync(skillsDir, { recursive: true })
42
41
  }
43
42
  // 如果目标目录已存在,先删除
43
+ const skillDir = path.join(workspacePath, 'skills', code)
44
44
  if (fs.existsSync(skillDir)) {
45
45
  fs.rmSync(skillDir, { recursive: true, force: true })
46
46
  }
@@ -69,13 +69,7 @@ export async function installSkill(params: ISkillParams, msgContent: Record<stri
69
69
  }
70
70
  try {
71
71
  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
- }
72
+ const entryPath = decodeZipEntryPath(entry.props?.pathBuffer, flags, entry.path)
79
73
  const pathParts = entryPath.split('/')
80
74
 
81
75
  // 检测根目录
@@ -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)
@@ -1,9 +1,5 @@
1
1
  export const ENV: 'production' | 'test' | 'develop' = 'test'
2
2
 
3
- export const channelInfo: Record<string, string> = {
4
- production: 'dcgchat',
5
- test: 'dcgchat-test'
6
- }
7
3
 
8
4
  export const systemCommand = ['/new', '/status']
9
5
  export const interruptCommand = ['/stop']
@@ -15,7 +15,6 @@ export function handleGatewayEventMessage(msg: { event?: string; payload?: Recor
15
15
  const outboundCtx = getEffectiveMsgParams(sessionKey)
16
16
  if (pl.data?.delta) {
17
17
  if (outboundCtx.sessionId) {
18
- dcgLogger(`[Gateway] 收到agent事件: ${JSON.stringify(msg).slice(0, 100)}`)
19
18
  sendChunk(pl.data.delta as string, outboundCtx, 0)
20
19
  }
21
20
  }
@@ -30,7 +30,7 @@ export function getOpenClawConfig(): OpenClawConfig | null {
30
30
  function getWorkspacePath(): string | null {
31
31
  const workspacePath = path.join(
32
32
  os.homedir(),
33
- config?.channels?.[channelInfo[ENV]]?.appId == 110 ? '.mobook' : '.openclaw',
33
+ config?.channels?.["dcgchat-test"]?.appId == 110 ? '.mobook' : '.openclaw',
34
34
  'workspace'
35
35
  )
36
36
  if (fs.existsSync(workspacePath)) {
@@ -55,7 +55,7 @@ export function getWorkspaceDir(): string {
55
55
  }
56
56
 
57
57
  const { setRuntime: setDcgchatRuntime, getRuntime: getDcgchatRuntime } = createPluginRuntimeStore<PluginRuntime>(
58
- `${channelInfo[ENV]} runtime not initialized`
58
+ `${"dcgchat-test"} runtime not initialized`
59
59
  )
60
60
  export { setDcgchatRuntime, getDcgchatRuntime }
61
61
 
@@ -130,7 +130,7 @@ export const getSessionKey = (content: any, accountId: string) => {
130
130
 
131
131
  const route = core.channel.routing.resolveAgentRoute({
132
132
  cfg: getOpenClawConfig() as OpenClawConfig,
133
- channel: channelInfo[ENV],
133
+ channel: "dcgchat-test",
134
134
  accountId: accountId || 'default',
135
135
  peer: { kind: 'direct', id: session_id }
136
136
  })
package/src/utils/log.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { RuntimeEnv } from 'openclaw/plugin-sdk'
2
- import { channelInfo, ENV } from './constant.js'
3
2
 
4
3
  let logger: RuntimeEnv | null = null
5
4
 
@@ -11,6 +10,6 @@ export function dcgLogger(message: string, type: 'log' | 'error' = 'log'): void
11
10
  if (logger) {
12
11
  logger[type](`书灵墨宝🚀 ~ [${new Date().toISOString()}] ${message}`)
13
12
  } else {
14
- console[type](`书灵墨宝🚀 ~ ${new Date().toISOString()} [${channelInfo[ENV]}]: ${message}`)
13
+ console[type](`书灵墨宝🚀 ~ ${new Date().toISOString()} [${"dcgchat-test"}]: ${message}`)
15
14
  }
16
15
  }
@@ -9,7 +9,7 @@ const paramsMessageMap = new Map<string, IMsgParams>()
9
9
 
10
10
  /** 从 OpenClaw 配置读取当前 channel 的基础参数(唯一来源,供 transport / resolve 等复用) */
11
11
  export function getParamsDefaults(): IMsgParams {
12
- const ch = (getOpenClawConfig()?.channels?.[channelInfo[ENV]] as DcgchatConfig | undefined) ?? {}
12
+ const ch = (getOpenClawConfig()?.channels?.["dcgchat-test"] as DcgchatConfig | undefined) ?? {}
13
13
  return {
14
14
  userId: Number(ch.userId ?? 0),
15
15
  botToken: ch.botToken ?? '',
package/README.md DELETED
@@ -1,83 +0,0 @@
1
- # OpenClaw 书灵墨宝 插件
2
-
3
- 连接 OpenClaw 与 书灵墨宝 产品的通道插件。
4
-
5
- ## 架构
6
-
7
- ```
8
- ┌──────────┐ WebSocket ┌──────────────┐ WebSocket ┌─────────────────────┐
9
- │ Web 前端 │ ←───────────────→ │ 公司后端服务 │ ←───────────────→ │ OpenClaw(工作电脑) │
10
- └──────────┘ └──────────────┘ (OpenClaw 主动连) └─────────────────────┘
11
- ```
12
-
13
- - OpenClaw 插件**主动连接**后端的 WebSocket 服务(不需要公网 IP)
14
- - 后端收到用户消息后转发给 OpenClaw,OpenClaw 回复后发回后端
15
-
16
- ## 快速开始
17
-
18
- ### 1. 安装插件
19
-
20
- ```bash
21
- pnpm openclaw plugins install -l /path/to/openclaw-dcgchat
22
- ```
23
-
24
- ### 2. 配置
25
-
26
- ```bash
27
- openclaw config set channels.dcgchat.enabled true
28
- openclaw config set channels.dcgchat.wsUrl "ws://your-backend:8080/openclaw/ws"
29
- ```
30
-
31
- ### 3. 启动
32
-
33
- ```bash
34
- pnpm openclaw gateway
35
- ```
36
-
37
- ## 消息协议(MVP)
38
-
39
- ### 下行:后端 → OpenClaw(用户消息)
40
-
41
- ```json
42
- { "type": "message", "userId": "user_001", "text": "你好" }
43
- ```
44
-
45
- ### 上行:OpenClaw → 后端(Agent 回复)
46
-
47
- ```json
48
- { "type": "reply", "userId": "user_001", "text": "你好!有什么可以帮你的?" }
49
- ```
50
-
51
- ## 配置项
52
-
53
- | 配置键 | 类型 | 说明 |
54
- |--------|------|------|
55
- | `channels.dcgchat.enabled` | boolean | 是否启用 |
56
- | `channels.dcgchat.wsUrl` | string | 后端 WebSocket 地址 |
57
-
58
- ## 开发
59
-
60
- ```bash
61
- # 安装依赖
62
- pnpm install
63
-
64
- # 类型检查
65
- pnpm typecheck
66
- ```
67
-
68
- ## 文件结构
69
-
70
- - `index.ts` - 插件入口
71
- - `src/channel.ts` - ChannelPlugin 定义
72
- - `src/runtime.ts` - 插件 runtime
73
- - `src/types.ts` - 类型定义
74
- - `src/monitor.ts` - WebSocket 连接与断线重连
75
- - `src/bot.ts` - 消息处理与 Agent 调用
76
-
77
- ## 后续迭代
78
-
79
- - [ ] Token 认证
80
- - [ ] 流式输出
81
- - [ ] Typing 指示
82
- - [ ] messageId 去重
83
- - [ ] 错误消息类型