@dcrays/dcgchat-test 0.3.34 → 0.3.36
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 +1 -1
- package/src/agent.ts +204 -0
- package/src/bot.ts +4 -5
- package/src/monitor.ts +3 -53
- package/src/session.ts +19 -0
- package/src/skill.ts +2 -7
- package/src/types.ts +1 -0
- package/src/utils/global.ts +15 -13
- package/src/utils/wsMessageHandler.ts +64 -0
- package/src/utils/zipPath.ts +24 -0
package/package.json
CHANGED
package/src/agent.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
/** @ts-ignore */
|
|
3
|
+
import unzipper from 'unzipper'
|
|
4
|
+
import { pipeline } from 'stream/promises'
|
|
5
|
+
import fs from 'fs'
|
|
6
|
+
import path from 'path'
|
|
7
|
+
import { getWorkspaceDir } from './utils/global.js'
|
|
8
|
+
import { getWsConnection } from './utils/global.js'
|
|
9
|
+
import { dcgLogger } from './utils/log.js'
|
|
10
|
+
import { isWsOpen } from './transport.js'
|
|
11
|
+
import { sendMessageToGateway } from './gateway/socket.js'
|
|
12
|
+
import { decodeZipEntryPath } from './utils/zipPath.js'
|
|
13
|
+
|
|
14
|
+
type IAgentParams = {
|
|
15
|
+
url: string
|
|
16
|
+
agent_code: string
|
|
17
|
+
agent_name: string
|
|
18
|
+
agent_description: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function sendEvent(msgContent: Record<string, any>) {
|
|
22
|
+
const ws = getWsConnection()
|
|
23
|
+
if (isWsOpen()) {
|
|
24
|
+
ws?.send(
|
|
25
|
+
JSON.stringify({
|
|
26
|
+
messageType: 'openclaw_bot_event',
|
|
27
|
+
source: 'client',
|
|
28
|
+
content: msgContent
|
|
29
|
+
})
|
|
30
|
+
)
|
|
31
|
+
dcgLogger(`agent安装: ${JSON.stringify(msgContent)}`)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ICreateAgentParams {
|
|
36
|
+
code: string
|
|
37
|
+
workspace: string
|
|
38
|
+
name?: string
|
|
39
|
+
description?: string
|
|
40
|
+
msgContent?: Record<string, any>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 若 workspace-${code}/agent 存在,则复制到 agents/${code}/agent */
|
|
44
|
+
function copyAgentsFiles(code: string) {
|
|
45
|
+
const workspacePath = getWorkspaceDir()
|
|
46
|
+
if (!workspacePath) return
|
|
47
|
+
const workspaceDir = path.join(workspacePath, '../', `workspace-${code}`)
|
|
48
|
+
const agentDir = path.join(workspacePath, '../', `agents/${code}`)
|
|
49
|
+
const sourceAgent = path.join(workspaceDir, 'agent')
|
|
50
|
+
try {
|
|
51
|
+
if (!fs.existsSync(sourceAgent)) return
|
|
52
|
+
if (!fs.statSync(sourceAgent).isDirectory()) return
|
|
53
|
+
fs.mkdirSync(agentDir, { recursive: true })
|
|
54
|
+
const dest = path.join(agentDir, 'agent')
|
|
55
|
+
if (fs.existsSync(dest)) {
|
|
56
|
+
fs.rmSync(dest, { recursive: true, force: true })
|
|
57
|
+
}
|
|
58
|
+
fs.cpSync(sourceAgent, dest, { recursive: true })
|
|
59
|
+
} catch (err: unknown) {
|
|
60
|
+
dcgLogger(`copyAgentsFiles failed: ${String(err)}`, 'error')
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function onCreateAgent(params: Record<string, any>) {
|
|
65
|
+
const { code, name, description } = params
|
|
66
|
+
try {
|
|
67
|
+
await sendMessageToGateway(JSON.stringify({ method: 'agents.create', params: { name: code, workspace: code } }))
|
|
68
|
+
} catch (err: unknown) {
|
|
69
|
+
dcgLogger(`agents.create failed: ${String(err)}`, 'error')
|
|
70
|
+
}
|
|
71
|
+
// Update config.name to the user-supplied display name (may contain CJK, spaces, etc.)
|
|
72
|
+
try {
|
|
73
|
+
await sendMessageToGateway(JSON.stringify({ method: 'agents.update', params: { name: name, agentId: code } }))
|
|
74
|
+
} catch (err: unknown) {
|
|
75
|
+
dcgLogger(`agents.update failed: ${String(err)}`, 'error')
|
|
76
|
+
}
|
|
77
|
+
if (description?.trim()) {
|
|
78
|
+
try {
|
|
79
|
+
await sendMessageToGateway(
|
|
80
|
+
JSON.stringify({
|
|
81
|
+
method: 'agents.files.set',
|
|
82
|
+
params: { agentId: code, name: 'IDENTITY.md', content: description.trim() }
|
|
83
|
+
})
|
|
84
|
+
)
|
|
85
|
+
} catch {
|
|
86
|
+
// Non-fatal
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (name?.trim()) {
|
|
90
|
+
try {
|
|
91
|
+
await sendMessageToGateway(
|
|
92
|
+
JSON.stringify({
|
|
93
|
+
method: 'agents.files.set',
|
|
94
|
+
params: { agentId: code, name: 'USER.md', content: name.trim() }
|
|
95
|
+
})
|
|
96
|
+
)
|
|
97
|
+
} catch {
|
|
98
|
+
// Non-fatal
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
copyAgentsFiles(code)
|
|
102
|
+
sendEvent({ ...params, status: 'ok' })
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function createAgent(msgContent: Record<string, any>) {
|
|
106
|
+
const { url, code } = msgContent
|
|
107
|
+
if (!url || !code) {
|
|
108
|
+
dcgLogger(`createAgent failed empty url&code: ${JSON.stringify(msgContent)}`, 'error')
|
|
109
|
+
sendEvent({ ...msgContent, status: 'fail' })
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
const workspacePath = getWorkspaceDir()
|
|
113
|
+
const workspaceDir = path.join(workspacePath, '../', `workspace-${code}`)
|
|
114
|
+
|
|
115
|
+
// 如果目标目录已存在,先删除
|
|
116
|
+
if (fs.existsSync(workspaceDir)) {
|
|
117
|
+
fs.rmSync(workspaceDir, { recursive: true, force: true })
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
// 下载 zip 文件
|
|
122
|
+
const response = await axios({
|
|
123
|
+
method: 'get',
|
|
124
|
+
url,
|
|
125
|
+
responseType: 'stream'
|
|
126
|
+
})
|
|
127
|
+
// 创建目标目录
|
|
128
|
+
fs.mkdirSync(workspaceDir, { recursive: true })
|
|
129
|
+
// 解压文件到目标目录,跳过顶层文件夹
|
|
130
|
+
await new Promise((resolve, reject) => {
|
|
131
|
+
const tasks: Promise<void>[] = []
|
|
132
|
+
let rootDir: string | null = null
|
|
133
|
+
let hasError = false
|
|
134
|
+
|
|
135
|
+
response.data
|
|
136
|
+
.pipe(unzipper.Parse())
|
|
137
|
+
.on('entry', (entry: any) => {
|
|
138
|
+
if (hasError) {
|
|
139
|
+
entry.autodrain()
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const flags = entry.props?.flags ?? 0
|
|
144
|
+
const entryPath = decodeZipEntryPath(entry.props?.pathBuffer, flags, entry.path)
|
|
145
|
+
const pathParts = entryPath.split('/')
|
|
146
|
+
|
|
147
|
+
// 检测根目录
|
|
148
|
+
if (!rootDir && pathParts.length > 1) {
|
|
149
|
+
rootDir = pathParts[0]
|
|
150
|
+
}
|
|
151
|
+
let newPath = entryPath
|
|
152
|
+
// 移除顶层文件夹
|
|
153
|
+
if (rootDir && entryPath.startsWith(rootDir + '/')) {
|
|
154
|
+
newPath = entryPath.slice(rootDir.length + 1)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!newPath) {
|
|
158
|
+
entry.autodrain()
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const targetPath = path.join(workspacePath, newPath)
|
|
163
|
+
|
|
164
|
+
if (entry.type === 'Directory') {
|
|
165
|
+
fs.mkdirSync(targetPath, { recursive: true })
|
|
166
|
+
entry.autodrain()
|
|
167
|
+
} else {
|
|
168
|
+
const parentDir = path.dirname(targetPath)
|
|
169
|
+
fs.mkdirSync(parentDir, { recursive: true })
|
|
170
|
+
const writeStream = fs.createWriteStream(targetPath)
|
|
171
|
+
const task = pipeline(entry, writeStream).catch((err) => {
|
|
172
|
+
hasError = true
|
|
173
|
+
throw new Error(`解压文件失败 ${entryPath}: ${err.message}`)
|
|
174
|
+
})
|
|
175
|
+
tasks.push(task)
|
|
176
|
+
}
|
|
177
|
+
} catch (err) {
|
|
178
|
+
hasError = true
|
|
179
|
+
entry.autodrain()
|
|
180
|
+
reject(new Error(`处理entry失败: ${err}`))
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
.on('close', async () => {
|
|
184
|
+
try {
|
|
185
|
+
await Promise.all(tasks)
|
|
186
|
+
resolve(null)
|
|
187
|
+
} catch (err) {
|
|
188
|
+
reject(err)
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
.on('error', (err: { message: any }) => {
|
|
192
|
+
hasError = true
|
|
193
|
+
reject(new Error(`解压流错误: ${err.message}`))
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
await onCreateAgent(msgContent)
|
|
197
|
+
} catch (error) {
|
|
198
|
+
// 如果安装失败,清理目录
|
|
199
|
+
if (fs.existsSync(workspaceDir)) {
|
|
200
|
+
fs.rmSync(workspaceDir, { recursive: true, force: true })
|
|
201
|
+
}
|
|
202
|
+
sendEvent({ ...msgContent, status: 'fail' })
|
|
203
|
+
}
|
|
204
|
+
}
|
package/src/bot.ts
CHANGED
|
@@ -151,7 +151,6 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
151
151
|
const core = getDcgchatRuntime()
|
|
152
152
|
|
|
153
153
|
const conversationId = msg.content.session_id?.trim()
|
|
154
|
-
const agentId = msg.content.agent_id?.trim()
|
|
155
154
|
const real_mobook = msg.content.real_mobook?.toString().trim()
|
|
156
155
|
|
|
157
156
|
const route = core.channel.routing.resolveAgentRoute({
|
|
@@ -252,9 +251,9 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
252
251
|
OriginatingTo: effectiveSessionKey,
|
|
253
252
|
...mediaPayload
|
|
254
253
|
})
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
254
|
+
dcgLogger(
|
|
255
|
+
`inbound context target: rawTarget=${userId}, normalizedTarget=${effectiveSessionKey}, ctx.To=${String(ctxPayload.To ?? '')}, ctx.SessionKey=${String(ctxPayload.SessionKey ?? '')}, ctx.OriginatingTo=${String(ctxPayload.OriginatingTo ?? '')}`
|
|
256
|
+
)
|
|
258
257
|
|
|
259
258
|
const sentMediaKeys = new Set<string>()
|
|
260
259
|
const getMediaKey = (url: string) => url.split(/[\\/]/).pop() ?? url
|
|
@@ -318,7 +317,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
318
317
|
})
|
|
319
318
|
} else if (interruptCommand.includes(text?.trim())) {
|
|
320
319
|
dcgLogger(`interrupt command: ${text}`)
|
|
321
|
-
|
|
320
|
+
sendFinal(outboundCtx, 'abort')
|
|
322
321
|
sendText('会话已终止', outboundCtx)
|
|
323
322
|
sessionStreamSuppressed.add(effectiveSessionKey)
|
|
324
323
|
const runId = activeRunIdBySessionKey.get(effectiveSessionKey)
|
package/src/monitor.ts
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import type { ClawdbotConfig, RuntimeEnv } from 'openclaw/plugin-sdk'
|
|
2
2
|
import WebSocket from 'ws'
|
|
3
|
-
import { handleDcgchatMessage } from './bot.js'
|
|
4
3
|
import { resolveAccount } from './channel.js'
|
|
5
|
-
import { setWsConnection, getOpenClawConfig
|
|
6
|
-
import type { InboundMessage } from './types.js'
|
|
7
|
-
import { installSkill, uninstallSkill } from './skill.js'
|
|
4
|
+
import { setWsConnection, getOpenClawConfig } from './utils/global.js'
|
|
8
5
|
import { dcgLogger } from './utils/log.js'
|
|
9
|
-
import { onDisabledCronJob, onEnabledCronJob, onRemoveCronJob, onRunCronJob } from './cron.js'
|
|
10
|
-
import { ignoreToolCommand } from './utils/constant.js'
|
|
11
6
|
import { isWsOpen } from './transport.js'
|
|
7
|
+
import { handleParsedWsMessage } from './utils/wsMessageHandler.js'
|
|
12
8
|
|
|
13
9
|
export type MonitorDcgchatOpts = {
|
|
14
10
|
config?: ClawdbotConfig
|
|
@@ -128,53 +124,7 @@ export async function monitorDcgchatProvider(opts: MonitorDcgchatOpts): Promise<
|
|
|
128
124
|
return
|
|
129
125
|
}
|
|
130
126
|
|
|
131
|
-
|
|
132
|
-
const msg = parsed as unknown as InboundMessage
|
|
133
|
-
// 与 monitor 原逻辑一致:工具类指令不进入 running,避免误触工具链监控
|
|
134
|
-
const effectiveSessionKey = getSessionKey(msg.content, account.accountId)
|
|
135
|
-
if (!ignoreToolCommand.includes(msg.content.text?.trim() ?? '')) {
|
|
136
|
-
setMsgStatus(effectiveSessionKey, 'running')
|
|
137
|
-
} else {
|
|
138
|
-
setMsgStatus(effectiveSessionKey, 'finished')
|
|
139
|
-
}
|
|
140
|
-
await handleDcgchatMessage(msg, account.accountId)
|
|
141
|
-
} else if (parsed.messageType == 'openclaw_bot_event') {
|
|
142
|
-
const { event_type, operation_type } = parsed.content ? parsed.content : ({} as Record<string, any>)
|
|
143
|
-
if (event_type === 'skill') {
|
|
144
|
-
const { skill_url, skill_code, skill_id, bot_token, websocket_trace_id } = parsed.content
|
|
145
|
-
const content = {
|
|
146
|
-
event_type,
|
|
147
|
-
operation_type,
|
|
148
|
-
skill_url,
|
|
149
|
-
skill_code,
|
|
150
|
-
skill_id,
|
|
151
|
-
bot_token,
|
|
152
|
-
websocket_trace_id
|
|
153
|
-
}
|
|
154
|
-
if (operation_type === 'install' || operation_type === 'enable' || operation_type === 'update') {
|
|
155
|
-
installSkill({ path: skill_url, code: skill_code }, content)
|
|
156
|
-
} else if (operation_type === 'remove' || operation_type === 'disable') {
|
|
157
|
-
uninstallSkill({ code: skill_code }, content)
|
|
158
|
-
} else {
|
|
159
|
-
dcgLogger(`openclaw_bot_event unknown event_type: ${event_type}, ${data.toString()}`)
|
|
160
|
-
}
|
|
161
|
-
} else if (event_type === 'cron') {
|
|
162
|
-
const { job_id, message_id } = parsed.content
|
|
163
|
-
if (operation_type === 'remove') {
|
|
164
|
-
await onRemoveCronJob(job_id)
|
|
165
|
-
} else if (operation_type === 'enable') {
|
|
166
|
-
await onEnabledCronJob(job_id)
|
|
167
|
-
} else if (operation_type === 'disable') {
|
|
168
|
-
await onDisabledCronJob(job_id)
|
|
169
|
-
} else if (operation_type === 'run') {
|
|
170
|
-
await onRunCronJob(job_id, message_id)
|
|
171
|
-
}
|
|
172
|
-
} else {
|
|
173
|
-
dcgLogger(`openclaw_bot_event unknown operation_type: ${operation_type}, ${data.toString()}`)
|
|
174
|
-
}
|
|
175
|
-
} else {
|
|
176
|
-
dcgLogger(`ignoring unknown messageType: ${parsed.messageType}`, 'error')
|
|
177
|
-
}
|
|
127
|
+
await handleParsedWsMessage(parsed, payloadStr, account.accountId)
|
|
178
128
|
})
|
|
179
129
|
|
|
180
130
|
ws.on('close', (code, reason) => {
|
package/src/session.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { sendMessageToGateway } from './gateway/socket.js'
|
|
2
|
+
import { getSessionKey } from './utils/global.js'
|
|
3
|
+
import { dcgLogger } from './utils/log.js'
|
|
4
|
+
|
|
5
|
+
interface TSession {
|
|
6
|
+
agent_id: string
|
|
7
|
+
session_id: string
|
|
8
|
+
agent_clone_code?: string
|
|
9
|
+
account_id?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const onRemoveSession = async ({ agent_id, session_id, agent_clone_code, account_id }: TSession) => {
|
|
13
|
+
const sessionKey = getSessionKey({ agent_id, session_id }, account_id)
|
|
14
|
+
if (!session_id) {
|
|
15
|
+
dcgLogger('onRemoveSession: empty session_id', 'error')
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
sendMessageToGateway(JSON.stringify({ method: 'sessions.delete', params: { key: sessionKey, deleteTranscript: true } }))
|
|
19
|
+
}
|
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
|
|
@@ -69,13 +70,7 @@ export async function installSkill(params: ISkillParams, msgContent: Record<stri
|
|
|
69
70
|
}
|
|
70
71
|
try {
|
|
71
72
|
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
|
-
}
|
|
73
|
+
const entryPath = decodeZipEntryPath(entry.props?.pathBuffer, flags, entry.path)
|
|
79
74
|
const pathParts = entryPath.split('/')
|
|
80
75
|
|
|
81
76
|
// 检测根目录
|
package/src/types.ts
CHANGED
package/src/utils/global.ts
CHANGED
|
@@ -117,19 +117,6 @@ export function clearSentMediaKeys(messageId?: string) {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
export const getSessionKey = (content: any, accountId: string) => {
|
|
121
|
-
const { real_mobook, agent_id, conversation_id, session_id } = content
|
|
122
|
-
const core = getDcgchatRuntime()
|
|
123
|
-
|
|
124
|
-
const route = core.channel.routing.resolveAgentRoute({
|
|
125
|
-
cfg: getOpenClawConfig() as OpenClawConfig,
|
|
126
|
-
channel: "dcgchat-test",
|
|
127
|
-
accountId: accountId || 'default',
|
|
128
|
-
peer: { kind: 'direct', id: session_id }
|
|
129
|
-
})
|
|
130
|
-
return real_mobook == '1' ? route.sessionKey : `agent:main:mobook:direct:${agent_id}:${session_id}`.toLowerCase()
|
|
131
|
-
}
|
|
132
|
-
|
|
133
120
|
const cronMessageIdMap = new Map<string, string>()
|
|
134
121
|
|
|
135
122
|
export function setCronMessageId(sk: string, messageId: string) {
|
|
@@ -144,6 +131,21 @@ export function removeCronMessageId(sk: string) {
|
|
|
144
131
|
cronMessageIdMap.delete(sk)
|
|
145
132
|
}
|
|
146
133
|
|
|
134
|
+
export const getSessionKey = (content: any, accountId: string) => {
|
|
135
|
+
const { real_mobook, agent_id, agent_clone_code, session_id } = content
|
|
136
|
+
const core = getDcgchatRuntime()
|
|
137
|
+
|
|
138
|
+
const anentCode = agent_clone_code || 'main'
|
|
139
|
+
|
|
140
|
+
const route = core.channel.routing.resolveAgentRoute({
|
|
141
|
+
cfg: getOpenClawConfig() as OpenClawConfig,
|
|
142
|
+
channel: "dcgchat-test",
|
|
143
|
+
accountId: accountId || 'default',
|
|
144
|
+
peer: { kind: 'direct', id: session_id }
|
|
145
|
+
})
|
|
146
|
+
return real_mobook == '1' ? route.sessionKey : `agent:${anentCode}:mobook:direct:${agent_id}:${session_id}`.toLowerCase()
|
|
147
|
+
}
|
|
148
|
+
|
|
147
149
|
export function getInfoBySessionKey(sk: string): { sessionId: string; agentId: string } {
|
|
148
150
|
const sessionInfo = sk.split(':')
|
|
149
151
|
return { sessionId: sessionInfo.at(-1) ?? '', agentId: sessionInfo.at(-2) ?? '' }
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { handleDcgchatMessage } from '../bot.js'
|
|
2
|
+
import { setMsgStatus, getSessionKey } from './global.js'
|
|
3
|
+
import type { InboundMessage } from '../types.js'
|
|
4
|
+
import { installSkill, uninstallSkill } from '../skill.js'
|
|
5
|
+
import { dcgLogger } from './log.js'
|
|
6
|
+
import { onDisabledCronJob, onEnabledCronJob, onRemoveCronJob, onRunCronJob } from '../cron.js'
|
|
7
|
+
import { ignoreToolCommand } from './constant.js'
|
|
8
|
+
import { createAgent } from '../agent.js'
|
|
9
|
+
|
|
10
|
+
export type ParsedWsPayload = {
|
|
11
|
+
messageType?: string
|
|
12
|
+
content: any
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 处理 WebSocket 已解析 JSON 且 content 已二次 parse 后的业务消息(openclaw_bot_chat / openclaw_bot_event)。
|
|
17
|
+
*/
|
|
18
|
+
export async function handleParsedWsMessage(parsed: ParsedWsPayload, rawPayload: string, accountId: string): Promise<void> {
|
|
19
|
+
if (parsed.messageType == 'openclaw_bot_chat') {
|
|
20
|
+
const msg = parsed as unknown as InboundMessage
|
|
21
|
+
// 与 monitor 原逻辑一致:工具类指令不进入 running,避免误触工具链监控
|
|
22
|
+
const effectiveSessionKey = getSessionKey(msg.content, accountId)
|
|
23
|
+
if (!ignoreToolCommand.includes(msg.content.text?.trim() ?? '')) {
|
|
24
|
+
setMsgStatus(effectiveSessionKey, 'running')
|
|
25
|
+
} else {
|
|
26
|
+
setMsgStatus(effectiveSessionKey, 'finished')
|
|
27
|
+
}
|
|
28
|
+
await handleDcgchatMessage(msg, accountId)
|
|
29
|
+
} else if (parsed.messageType == 'openclaw_bot_event') {
|
|
30
|
+
const { event_type, operation_type } = parsed.content ? parsed.content : ({} as Record<string, any>)
|
|
31
|
+
if (event_type === 'skill') {
|
|
32
|
+
const { skill_url, skill_code, skill_id, bot_token, websocket_trace_id } = parsed.content
|
|
33
|
+
const content = { event_type, operation_type, skill_url, skill_code, skill_id, bot_token, websocket_trace_id }
|
|
34
|
+
if (operation_type === 'install' || operation_type === 'enable' || operation_type === 'update') {
|
|
35
|
+
installSkill({ path: skill_url, code: skill_code }, content)
|
|
36
|
+
} else if (operation_type === 'remove' || operation_type === 'disable') {
|
|
37
|
+
uninstallSkill({ code: skill_code }, content)
|
|
38
|
+
} else {
|
|
39
|
+
dcgLogger(`openclaw_bot_event unknown event_type: ${event_type}, ${rawPayload}`)
|
|
40
|
+
}
|
|
41
|
+
} else if (event_type === 'agent') {
|
|
42
|
+
if (operation_type === 'install') {
|
|
43
|
+
createAgent(parsed.content)
|
|
44
|
+
} else {
|
|
45
|
+
dcgLogger(`openclaw_bot_event unknown event_type: ${event_type}, ${rawPayload}`)
|
|
46
|
+
}
|
|
47
|
+
} else if (event_type === 'cron') {
|
|
48
|
+
const { job_id, message_id } = parsed.content
|
|
49
|
+
if (operation_type === 'remove') {
|
|
50
|
+
await onRemoveCronJob(job_id)
|
|
51
|
+
} else if (operation_type === 'enable') {
|
|
52
|
+
await onEnabledCronJob(job_id)
|
|
53
|
+
} else if (operation_type === 'disable') {
|
|
54
|
+
await onDisabledCronJob(job_id)
|
|
55
|
+
} else if (operation_type === 'run') {
|
|
56
|
+
await onRunCronJob(job_id, message_id)
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
dcgLogger(`openclaw_bot_event unknown operation_type: ${operation_type}, ${rawPayload}`)
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
dcgLogger(`ignoring unknown messageType: ${parsed.messageType}`, 'error')
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZIP 文件名编码:规范要求 UTF-8 时设置 0x800;很多工具未设标志但仍写 UTF-8 字节。
|
|
3
|
+
* 无标志时若一律按 GBK 解码,会得到「鍥句功…」类乱码。先严格 UTF-8,失败再 GBK(兼容 Windows 中文 ZIP)。
|
|
4
|
+
*/
|
|
5
|
+
export function decodeZipEntryPath(
|
|
6
|
+
pathBuffer: Buffer | Uint8Array | undefined,
|
|
7
|
+
flags: number,
|
|
8
|
+
fallbackPath: string
|
|
9
|
+
): string {
|
|
10
|
+
if ((flags & 0x800) !== 0) {
|
|
11
|
+
if (pathBuffer) {
|
|
12
|
+
return new TextDecoder('utf-8').decode(pathBuffer)
|
|
13
|
+
}
|
|
14
|
+
return fallbackPath
|
|
15
|
+
}
|
|
16
|
+
if (pathBuffer && pathBuffer.length > 0) {
|
|
17
|
+
try {
|
|
18
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(pathBuffer)
|
|
19
|
+
} catch {
|
|
20
|
+
return new TextDecoder('gbk').decode(pathBuffer)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return fallbackPath
|
|
24
|
+
}
|