@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 +1 -3
- package/openclaw.plugin.json +4 -2
- package/package.json +3 -13
- package/src/bot.ts +31 -26
- package/src/channel.ts +15 -16
- package/src/cronToolCall.ts +3 -3
- package/src/skill.ts +3 -9
- package/src/tools/messageTool.ts +10 -1
- package/src/utils/constant.ts +0 -4
- package/src/utils/gatewayMsgHanlder.ts +0 -1
- package/src/utils/global.ts +3 -3
- package/src/utils/log.ts +1 -2
- package/src/utils/params.ts +1 -1
- package/README.md +0 -83
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:
|
|
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
|
}
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dcrays/dcgchat-test",
|
|
3
|
-
"version": "0.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:
|
|
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:
|
|
224
|
-
Surface:
|
|
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:
|
|
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
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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?.[
|
|
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?.[
|
|
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?.[
|
|
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:
|
|
127
|
+
id: "dcgchat-test",
|
|
129
128
|
meta: {
|
|
130
|
-
id:
|
|
129
|
+
id: "dcgchat-test",
|
|
131
130
|
label: '书灵墨宝',
|
|
132
131
|
selectionLabel: '书灵墨宝',
|
|
133
132
|
docsPath: '/channels/dcgchat',
|
|
134
|
-
docsLabel:
|
|
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.${
|
|
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 =
|
|
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
|
-
'
|
|
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:
|
|
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:
|
|
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:
|
|
300
|
+
channel: "dcgchat-test",
|
|
302
301
|
messageId,
|
|
303
302
|
chatId: to || ''
|
|
304
303
|
}
|
package/src/cronToolCall.ts
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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 ${
|
|
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
|
|
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
|
// 检测根目录
|
package/src/tools/messageTool.ts
CHANGED
|
@@ -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)
|
package/src/utils/constant.ts
CHANGED
|
@@ -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
|
}
|
package/src/utils/global.ts
CHANGED
|
@@ -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?.[
|
|
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
|
-
`${
|
|
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:
|
|
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()} [${
|
|
13
|
+
console[type](`书灵墨宝🚀 ~ ${new Date().toISOString()} [${"dcgchat-test"}]: ${message}`)
|
|
15
14
|
}
|
|
16
15
|
}
|
package/src/utils/params.ts
CHANGED
|
@@ -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?.[
|
|
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
|
-
- [ ] 错误消息类型
|