@dcrays/dcgchat-test 0.4.2 → 0.4.4

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/README.md ADDED
@@ -0,0 +1,83 @@
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
+ - [ ] 错误消息类型
package/index.ts CHANGED
@@ -3,11 +3,12 @@ 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'
6
7
  import { setOpenClawConfig } from './src/utils/global.js'
7
8
  import { createDcgchatMessageTool } from './src/tools/messageTool.js'
8
9
 
9
10
  const plugin = {
10
- id: "dcgchat-test",
11
+ id: channelInfo[ENV],
11
12
  name: '书灵墨宝',
12
13
  description: '连接 OpenClaw 与 书灵墨宝 产品(WebSocket)',
13
14
  configSchema: emptyPluginConfigSchema(),
@@ -1,11 +1,9 @@
1
1
  {
2
2
  "id": "dcgchat-test",
3
- "channels": [
4
- "dcgchat-test"
5
- ],
3
+ "channels": ["dcgchat-test"],
6
4
  "configSchema": {
7
5
  "type": "object",
8
6
  "additionalProperties": false,
9
7
  "properties": {}
10
8
  }
11
- }
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcrays/dcgchat-test",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for 书灵墨宝 (WebSocket)",
6
6
  "main": "index.ts",
@@ -16,6 +16,12 @@
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
+ },
19
25
  "dependencies": {
20
26
  "ali-oss": "file:src/libs/ali-oss-6.23.0.tgz",
21
27
  "axios": "file:src/libs/axios-1.13.6.tgz",
@@ -31,15 +37,19 @@
31
37
  "id": "dcgchat-test",
32
38
  "label": "书灵墨宝",
33
39
  "selectionLabel": "书灵墨宝",
34
- "docsPath": "/channels/dcgchat-test",
40
+ "docsPath": "/channels/dcgchat",
35
41
  "docsLabel": "dcgchat-test",
36
42
  "blurb": "连接 OpenClaw 与 书灵墨宝 产品",
37
43
  "order": 80
38
44
  },
39
45
  "install": {
40
46
  "npmSpec": "@dcrays/dcgchat-test",
41
- "localPath": "extensions/dcgchat-test",
47
+ "localPath": "extensions/dcgchat",
42
48
  "defaultChoice": "npm"
43
49
  }
50
+ },
51
+ "devDependencies": {
52
+ "openclaw": "^2026.3.13",
53
+ "typescript": "~5.8.0"
44
54
  }
45
55
  }
package/src/bot.ts CHANGED
@@ -151,7 +151,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
151
151
 
152
152
  const route = core.channel.routing.resolveAgentRoute({
153
153
  cfg: config,
154
- channel: "dcgchat-test",
154
+ channel: channelInfo[ENV],
155
155
  accountId: account.accountId,
156
156
  peer: { kind: 'direct', id: conversationId }
157
157
  })
@@ -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)
@@ -218,13 +220,13 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
218
220
  ChatType: 'direct',
219
221
  SenderName: agentDisplayName,
220
222
  SenderId: userId,
221
- Provider: "dcgchat-test",
222
- Surface: "dcgchat-test",
223
+ Provider: channelInfo[ENV],
224
+ Surface: channelInfo[ENV],
223
225
  MessageSid: msg.content.message_id,
224
226
  Timestamp: Date.now(),
225
227
  WasMentioned: true,
226
228
  CommandAuthorized: true,
227
- OriginatingChannel: "dcgchat-test",
229
+ OriginatingChannel: channelInfo[ENV],
228
230
  OriginatingTo: dcgSessionKey,
229
231
  Target: dcgSessionKey,
230
232
  SourceTarget: dcgSessionKey,
@@ -237,14 +239,19 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
237
239
 
238
240
  if (msg.content.skills_scope.length > 0 && !msg.content?.agent_clone_code) {
239
241
  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
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
249
+ dcgLogger(`skill: text: ${text}`)
243
250
  }
244
251
  const prefixContext = createReplyPrefixContext({
245
252
  cfg: config,
246
253
  agentId: effectiveAgentId ?? '',
247
- channel: "dcgchat-test",
254
+ channel: channelInfo[ENV],
248
255
  accountId: account.accountId
249
256
  })
250
257
 
@@ -303,7 +310,14 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
303
310
  })
304
311
  } else if (interruptCommand.includes(text?.trim())) {
305
312
  dcgLogger(`interrupt command: ${text}`)
306
- sendFinal({ ...outboundCtx, messageId: `${Date.now()}` }, 'abort')
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
+ )
307
321
  sendText('会话已终止', outboundCtx)
308
322
  sessionStreamSuppressed.add(dcgSessionKey)
309
323
  const abortOneSession = async (sessionKey: string) => {
@@ -336,7 +350,6 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
336
350
  clearSentMediaKeys(msg.content.message_id)
337
351
  clearParamsMessage(dcgSessionKey)
338
352
  clearParamsMessage(userId)
339
-
340
353
  sendFinal(outboundCtx, 'stop')
341
354
  return
342
355
  } else {
@@ -427,7 +440,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
427
440
  ctx: ctxPayload,
428
441
  updateLastRoute: {
429
442
  sessionKey: dcgSessionKey,
430
- channel: "dcgchat-test",
443
+ channel: channelInfo[ENV],
431
444
  to: dcgSessionKey,
432
445
  accountId: route.accountId
433
446
  },
package/src/channel.ts CHANGED
@@ -11,12 +11,13 @@ 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'
14
15
  import { dcgLogger, setLogger } from './utils/log.js'
15
16
  import { getOutboundMsgParams, getParamsMessage } from './utils/params.js'
16
17
  import { startDcgchatGatewaySocket } from './gateway/socket.js'
17
18
 
18
19
  function dcgchatChannelCfg(): DcgchatConfig {
19
- return (getOpenClawConfig()?.channels?.["dcgchat-test"] as DcgchatConfig | undefined) ?? {}
20
+ return (getOpenClawConfig()?.channels?.[channelInfo[ENV]] as DcgchatConfig | undefined) ?? {}
20
21
  }
21
22
 
22
23
  /** `agent:<code>:mobook:direct:<agentId>:<sessionId>`(与 getSessionKey 非 real_mobook 分支一致) */
@@ -90,7 +91,7 @@ export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<v
90
91
  const fileName = mediaUrl?.split(/[\\/]/).pop() || ''
91
92
 
92
93
  try {
93
- const botToken = msgCtx.botToken ?? getOpenClawConfig()?.channels?.["dcgchat-test"]?.botToken ?? ''
94
+ const botToken = msgCtx.botToken ?? getOpenClawConfig()?.channels?.[channelInfo[ENV]]?.botToken ?? ''
94
95
  const url = opts.mediaUrl ? await ossUpload(opts.mediaUrl, botToken, 1) : ''
95
96
  wsSendRaw(msgCtx, {
96
97
  response: opts.text ?? '',
@@ -110,7 +111,7 @@ export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<v
110
111
 
111
112
  export function resolveAccount(cfg: OpenClawConfig, accountId?: string | null): ResolvedDcgchatAccount {
112
113
  const id = accountId ?? DEFAULT_ACCOUNT_ID
113
- const raw = (cfg.channels?.["dcgchat-test"] as DcgchatConfig | undefined) ?? {}
114
+ const raw = (cfg.channels?.[channelInfo[ENV]] as DcgchatConfig | undefined) ?? {}
114
115
  return {
115
116
  accountId: id,
116
117
  enabled: raw.enabled !== false,
@@ -124,13 +125,13 @@ export function resolveAccount(cfg: OpenClawConfig, accountId?: string | null):
124
125
  }
125
126
 
126
127
  export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
127
- id: "dcgchat-test",
128
+ id: channelInfo[ENV],
128
129
  meta: {
129
- id: "dcgchat-test",
130
+ id: channelInfo[ENV],
130
131
  label: '书灵墨宝',
131
132
  selectionLabel: '书灵墨宝',
132
133
  docsPath: '/channels/dcgchat',
133
- docsLabel: "dcgchat-test",
134
+ docsLabel: channelInfo[ENV],
134
135
  blurb: '连接 OpenClaw 与 书灵墨宝 产品',
135
136
  order: 80
136
137
  },
@@ -147,7 +148,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
147
148
  // blockStreaming: true,
148
149
  },
149
150
  /** 当前构建的 channel id + 兼容旧配置键 `channels.dcgchat` */
150
- reload: { configPrefixes: [`channels.${"dcgchat-test"}`, 'channels.dcgchat'] },
151
+ reload: { configPrefixes: [`channels.${channelInfo[ENV]}`, 'channels.dcgchat'] },
151
152
  configSchema: {
152
153
  schema: {
153
154
  type: 'object',
@@ -183,7 +184,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
183
184
  resolveAccount: (cfg, accountId) => resolveAccount(cfg, accountId),
184
185
  defaultAccountId: () => DEFAULT_ACCOUNT_ID,
185
186
  setAccountEnabled: ({ cfg, enabled }) => {
186
- const channelKey = "dcgchat-test"
187
+ const channelKey = channelInfo[ENV]
187
188
  const prev = (cfg.channels?.[channelKey as keyof NonNullable<typeof cfg.channels>] as Record<string, unknown> | undefined) ?? {}
188
189
  return {
189
190
  ...cfg,
@@ -271,7 +272,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
271
272
  }
272
273
  }
273
274
  return {
274
- channel: "dcgchat-test",
275
+ channel: channelInfo[ENV],
275
276
  messageId: `${messageId}`,
276
277
  chatId: to
277
278
  }
@@ -288,7 +289,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
288
289
  if (!outboundCtx?.sessionId) {
289
290
  dcgLogger(`channel sendMedia to ${ctx.to} -> sessionId not found`, 'error')
290
291
  return {
291
- channel: "dcgchat-test",
292
+ channel: channelInfo[ENV],
292
293
  messageId,
293
294
  chatId: to || ''
294
295
  }
@@ -297,7 +298,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
297
298
  dcgLogger(`channel sendMedia to ${ctx.to}`)
298
299
  await sendDcgchatMedia({ sessionKey: to || '', mediaUrl: ctx.mediaUrl || '' })
299
300
  return {
300
- channel: "dcgchat-test",
301
+ channel: channelInfo[ENV],
301
302
  messageId,
302
303
  chatId: to || ''
303
304
  }
package/src/cron.ts CHANGED
@@ -40,11 +40,6 @@ export function readCronJob(jobPath: string, jobId: string): Record<string, any>
40
40
  }
41
41
  }
42
42
 
43
- function msgParamsToCtx(p: IMsgParams): IMsgParams | null {
44
- if (!p?.botToken) return null
45
- return p
46
- }
47
-
48
43
  const CRON_UPLOAD_DEBOUNCE_MS = 2400
49
44
 
50
45
  let cronUploadFlushTimer: ReturnType<typeof setTimeout> | null = null
@@ -63,9 +58,10 @@ async function runCronJobsUpload(sessionKey: string): Promise<void> {
63
58
  event_type: 'cron',
64
59
  operation_type: 'install',
65
60
  session_id: sessionId,
66
- agent_id: agentId
61
+ agent_id: agentId,
62
+ oss_url: url
67
63
  }
68
- sendEventMessage(url, params)
64
+ sendEventMessage(params)
69
65
  } catch (error) {
70
66
  dcgLogger(`${jobPath} upload failed: ${error}`, 'error')
71
67
  }
@@ -170,6 +166,7 @@ export const finishedDcgchatCron = async (jobId: string) => {
170
166
  sendFinal(merged, 'cron send')
171
167
  }
172
168
  const ws = getWsConnection()
169
+ const baseContent = getParamsDefaults()
173
170
  if (isWsOpen()) {
174
171
  ws?.send(
175
172
  JSON.stringify({
@@ -178,15 +175,17 @@ export const finishedDcgchatCron = async (jobId: string) => {
178
175
  content: {
179
176
  event_type: 'notify',
180
177
  operation_type: 'cron',
178
+ bot_token: baseContent.botToken,
179
+ app_id: baseContent.appId,
181
180
  session_id: sessionId,
182
- agentId: agentId,
181
+ agent_id: agentId,
183
182
  real_mobook: !sessionId ? 1 : '',
184
183
  title: name
185
184
  }
186
185
  })
187
186
  )
188
- dcgLogger(`定时任务执行成功: ${id}`)
189
187
  }
188
+ dcgLogger(`定时任务执行成功: ${id}`)
190
189
  removeCronMessageId(sessionKey)
191
190
  dcgLogger(`finishedDcgchatCron: job=${id} sessionKey=${sessionKey}`)
192
191
  }
@@ -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 = "dcgchat-test"
130
+ ;(newParams.delivery as CronDelivery).channel = channelInfo[ENV]
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 = "dcgchat-test"
141
+ ;(newParams.delivery as CronDelivery).channel = channelInfo[ENV]
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 ${"dcgchat-test"} --to dcg-cron:${sk} --json`
179
+ params.command.replace('--json', '') + ` --session-key ${sk} --channel ${channelInfo[ENV]} --to dcg-cron:${sk} --json`
180
180
  return { params: newParams }
181
181
  } else {
182
182
  return params
package/src/skill.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import axios from 'axios'
2
+ /** @ts-ignore */
3
+ import unzipper from 'unzipper'
4
+ import { pipeline } from 'stream/promises'
2
5
  import fs from 'fs'
3
6
  import path from 'path'
4
7
  import { getWorkspaceDir } from './utils/global.js'
@@ -6,7 +9,6 @@ import { getWsConnection } from './utils/global.js'
6
9
  import { dcgLogger } from './utils/log.js'
7
10
  import { isWsOpen } from './transport.js'
8
11
  import { sendMessageToGateway } from './gateway/socket.js'
9
- import { extractZipBufferToDirectory } from './utils/zipExtract.js'
10
12
 
11
13
  type ISkillParams = {
12
14
  path: string
@@ -44,13 +46,87 @@ export async function installSkill(params: ISkillParams, msgContent: Record<stri
44
46
  }
45
47
 
46
48
  try {
49
+ // 下载 zip 文件
47
50
  const response = await axios({
48
51
  method: 'get',
49
52
  url: cdnUrl,
50
- responseType: 'arraybuffer'
53
+ responseType: 'stream'
51
54
  })
55
+ // 创建目标目录
52
56
  fs.mkdirSync(skillDir, { recursive: true })
53
- await extractZipBufferToDirectory(Buffer.from(response.data), skillDir)
57
+ // 解压文件到目标目录,跳过顶层文件夹
58
+ await new Promise((resolve, reject) => {
59
+ const tasks: Promise<void>[] = []
60
+ let rootDir: string | null = null
61
+ let hasError = false
62
+
63
+ response.data
64
+ .pipe(unzipper.Parse())
65
+ .on('entry', (entry: any) => {
66
+ if (hasError) {
67
+ entry.autodrain()
68
+ return
69
+ }
70
+ try {
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
+ }
79
+ const pathParts = entryPath.split('/')
80
+
81
+ // 检测根目录
82
+ if (!rootDir && pathParts.length > 1) {
83
+ rootDir = pathParts[0]
84
+ }
85
+ let newPath = entryPath
86
+ // 移除顶层文件夹
87
+ if (rootDir && entryPath.startsWith(rootDir + '/')) {
88
+ newPath = entryPath.slice(rootDir.length + 1)
89
+ }
90
+
91
+ if (!newPath) {
92
+ entry.autodrain()
93
+ return
94
+ }
95
+
96
+ const targetPath = path.join(skillDir, newPath)
97
+
98
+ if (entry.type === 'Directory') {
99
+ fs.mkdirSync(targetPath, { recursive: true })
100
+ entry.autodrain()
101
+ } else {
102
+ const parentDir = path.dirname(targetPath)
103
+ fs.mkdirSync(parentDir, { recursive: true })
104
+ const writeStream = fs.createWriteStream(targetPath)
105
+ const task = pipeline(entry, writeStream).catch((err) => {
106
+ hasError = true
107
+ throw new Error(`解压文件失败 ${entryPath}: ${err.message}`)
108
+ })
109
+ tasks.push(task)
110
+ }
111
+ } catch (err) {
112
+ hasError = true
113
+ entry.autodrain()
114
+ reject(new Error(`处理entry失败: ${err}`))
115
+ }
116
+ })
117
+ .on('close', async () => {
118
+ try {
119
+ await Promise.all(tasks)
120
+ resolve(null)
121
+ } catch (err) {
122
+ reject(err)
123
+ }
124
+ })
125
+ .on('error', (err: { message: any }) => {
126
+ hasError = true
127
+ reject(new Error(`解压流错误: ${err.message}`))
128
+ })
129
+ })
54
130
  sendEvent({ ...msgContent, status: 'ok' })
55
131
  sendMessageToGateway(JSON.stringify({ method: 'skills.status', params: {} }))
56
132
  } catch (error) {
package/src/transport.ts CHANGED
@@ -181,7 +181,7 @@ export function sendError(errorMsg: string, ctx: IMsgParams): boolean {
181
181
  return wsSend(ctx, { response: `[错误] ${errorMsg}`, state: 'final' })
182
182
  }
183
183
 
184
- export function sendEventMessage(url: string, params: Record<string, string> = {}) {
184
+ export function sendEventMessage(params: Record<string, string> = {}) {
185
185
  const ctx = getParamsDefaults()
186
186
  const ws = getWsConnection()
187
187
  if (isWsOpen()) {
@@ -193,7 +193,6 @@ export function sendEventMessage(url: string, params: Record<string, string> = {
193
193
  bot_token: ctx.botToken,
194
194
  domain_id: ctx.domainId,
195
195
  app_id: ctx.appId,
196
- oss_url: url,
197
196
  bot_id: ctx.botId,
198
197
  ...params
199
198
  }
@@ -1,5 +1,9 @@
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
+ }
3
7
 
4
8
  export const systemCommand = ['/new', '/status']
5
9
  export const interruptCommand = ['/stop']
@@ -14,8 +14,10 @@ export function handleGatewayEventMessage(msg: { event?: string; payload?: Recor
14
14
  const sessionKey = getSessionKeyBySubAgentRunId(pl.runId)
15
15
  const outboundCtx = getEffectiveMsgParams(sessionKey)
16
16
  if (pl.data?.delta) {
17
- dcgLogger(`[Gateway] 收到agent事件: ${JSON.stringify(msg).slice(0, 100)}`)
18
- if (outboundCtx.sessionId) sendChunk(pl.data.delta as string, outboundCtx, 0)
17
+ if (outboundCtx.sessionId) {
18
+ dcgLogger(`[Gateway] 收到agent事件: ${JSON.stringify(msg).slice(0, 100)}`)
19
+ sendChunk(pl.data.delta as string, outboundCtx, 0)
20
+ }
19
21
  }
20
22
  }
21
23
  if (msg.event === 'cron') {
@@ -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?.["dcgchat-test"]?.appId == 110 ? '.mobook' : '.openclaw',
33
+ config?.channels?.[channelInfo[ENV]]?.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
- `${"dcgchat-test"} runtime not initialized`
58
+ `${channelInfo[ENV]} 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: "dcgchat-test",
133
+ channel: channelInfo[ENV],
134
134
  accountId: accountId || 'default',
135
135
  peer: { kind: 'direct', id: session_id }
136
136
  })
package/src/utils/log.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { RuntimeEnv } from 'openclaw/plugin-sdk'
2
+ import { channelInfo, ENV } from './constant.js'
2
3
 
3
4
  let logger: RuntimeEnv | null = null
4
5
 
@@ -10,6 +11,6 @@ export function dcgLogger(message: string, type: 'log' | 'error' = 'log'): void
10
11
  if (logger) {
11
12
  logger[type](`书灵墨宝🚀 ~ [${new Date().toISOString()}] ${message}`)
12
13
  } else {
13
- console[type](`书灵墨宝🚀 ~ ${new Date().toISOString()} [${"dcgchat-test"}]: ${message}`)
14
+ console[type](`书灵墨宝🚀 ~ ${new Date().toISOString()} [${channelInfo[ENV]}]: ${message}`)
14
15
  }
15
16
  }
@@ -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?.["dcgchat-test"] as DcgchatConfig | undefined) ?? {}
12
+ const ch = (getOpenClawConfig()?.channels?.[channelInfo[ENV]] as DcgchatConfig | undefined) ?? {}
13
13
  return {
14
14
  userId: Number(ch.userId ?? 0),
15
15
  botToken: ch.botToken ?? '',