@dcrays/dcgchat 0.2.34 → 0.3.18
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 -1
- package/package.json +1 -1
- package/src/bot.ts +175 -94
- package/src/channel.ts +52 -31
- package/src/cron.ts +174 -0
- package/src/cronToolCall.ts +187 -0
- package/src/gateway/index.ts +466 -0
- package/src/gateway/security.ts +95 -0
- package/src/gateway/socket.ts +283 -0
- package/src/monitor.ts +48 -44
- package/src/request/api.ts +4 -18
- package/src/request/oss.ts +5 -4
- package/src/request/request.ts +2 -2
- package/src/skill.ts +2 -0
- package/src/tool.ts +72 -69
- package/src/transport.ts +142 -49
- package/src/types.ts +13 -8
- package/src/utils/constant.ts +2 -2
- package/src/utils/global.ts +41 -13
- package/src/utils/params.ts +67 -0
- package/src/utils/searchFile.ts +18 -4
package/src/tool.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { OpenClawPluginApi } from 'openclaw/plugin-sdk'
|
|
2
|
-
import {
|
|
2
|
+
import { getMsgStatus } from './utils/global.js'
|
|
3
3
|
import { dcgLogger } from './utils/log.js'
|
|
4
|
-
import {
|
|
4
|
+
import { sendFinal, sendText, wsSendRaw } from './transport.js'
|
|
5
|
+
import { getCurrentSessionKey, getEffectiveMsgParams } from './utils/params.js'
|
|
6
|
+
import { channelInfo, ENV } from './utils/constant.js'
|
|
7
|
+
import { cronToolCall } from './cronToolCall.js'
|
|
5
8
|
|
|
6
9
|
let toolCallId = ''
|
|
7
10
|
let toolName = ''
|
|
@@ -30,8 +33,10 @@ type PluginHookName =
|
|
|
30
33
|
| 'subagent_ended'
|
|
31
34
|
| 'gateway_start'
|
|
32
35
|
| 'gateway_stop'
|
|
36
|
+
|
|
37
|
+
// message_received 没有 sessionKey 前置到bot中执行
|
|
33
38
|
const eventList = [
|
|
34
|
-
{ event: 'message_received', message: '' },
|
|
39
|
+
// { event: 'message_received', message: '' },
|
|
35
40
|
// {event: 'before_model_resolve', message: ''},
|
|
36
41
|
// {event: 'before_prompt_build', message: '正在查阅背景资料,构建思考逻辑'},
|
|
37
42
|
// {event: 'before_agent_start', message: '书灵墨宝已就位,准备开始执行任务'},
|
|
@@ -49,80 +54,78 @@ const eventList = [
|
|
|
49
54
|
{ event: 'after_tool_call', message: '' }
|
|
50
55
|
]
|
|
51
56
|
|
|
52
|
-
function sendToolCallMessage(text: string, toolCallId: string, isCover: number) {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
response: '',
|
|
73
|
-
session_id: params?.sessionId,
|
|
74
|
-
message_id: params?.messageId || Date.now().toString()
|
|
75
|
-
}
|
|
76
|
-
})
|
|
77
|
-
)
|
|
78
|
-
}
|
|
57
|
+
function sendToolCallMessage(sk: string, text: string, toolCallId: string, isCover: number) {
|
|
58
|
+
const params = getEffectiveMsgParams(sk)
|
|
59
|
+
wsSendRaw(params, {
|
|
60
|
+
is_finish: -1,
|
|
61
|
+
tool_call_id: toolCallId,
|
|
62
|
+
is_cover: isCover,
|
|
63
|
+
thinking_content: text,
|
|
64
|
+
response: ''
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 深拷贝 params 并注入 bestEffort: true
|
|
70
|
+
*/
|
|
71
|
+
interface CronDelivery {
|
|
72
|
+
mode?: string
|
|
73
|
+
channel?: string
|
|
74
|
+
to?: string
|
|
75
|
+
bestEffort?: boolean
|
|
76
|
+
[key: string]: unknown
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
export function monitoringToolMessage(api: OpenClawPluginApi) {
|
|
82
80
|
for (const item of eventList) {
|
|
83
|
-
api.on(item.event as PluginHookName, (event: any) => {
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
81
|
+
api.on(item.event as PluginHookName, (event: any, args: any) => {
|
|
82
|
+
const sk = args?.sessionKey as string
|
|
83
|
+
if (sk) {
|
|
84
|
+
const status = getMsgStatus(sk)
|
|
85
|
+
if (status === 'running') {
|
|
86
|
+
if (['after_tool_call', 'before_tool_call'].includes(item.event)) {
|
|
87
|
+
const { result: _result, ...rest } = event
|
|
88
|
+
dcgLogger(`工具调用结果: ~ event:${item.event} ~ params:${JSON.stringify(rest)}`)
|
|
89
|
+
|
|
90
|
+
if (item.event === 'before_tool_call') {
|
|
91
|
+
return cronToolCall(rest, sk)
|
|
92
|
+
}
|
|
93
|
+
const text = JSON.stringify({
|
|
94
|
+
type: item.event,
|
|
95
|
+
specialIdentification: 'dcgchat_tool_call_special_identification',
|
|
96
|
+
callId: event.toolCallId || event.runId || Date.now().toString(),
|
|
97
|
+
...rest,
|
|
98
|
+
status: item.event === 'after_tool_call' ? 'finished' : 'running'
|
|
99
|
+
})
|
|
100
|
+
sendToolCallMessage(
|
|
101
|
+
sk,
|
|
102
|
+
text,
|
|
103
|
+
event.toolCallId || event.runId || Date.now().toString(),
|
|
104
|
+
item.event === 'after_tool_call' ? 1 : 0
|
|
105
|
+
)
|
|
106
|
+
} else if (item.event) {
|
|
107
|
+
const msgCtx = getEffectiveMsgParams(sk)
|
|
108
|
+
if (item.event === 'llm_output') {
|
|
109
|
+
if (event.lastAssistant?.errorMessage === '1003-额度不足请充值') {
|
|
110
|
+
const message = '您的积分已消耗完,您可以通过充值积分来继续使用'
|
|
111
|
+
sendText(message, msgCtx, { message_tags: { insufficient_balance: 1 }, is_finish: -1 })
|
|
112
|
+
sendFinal(msgCtx, '积分不足')
|
|
113
|
+
return
|
|
111
114
|
}
|
|
112
|
-
sendText(ctx, message, { message_tags: { insufficient_balance: 1 }, is_finish: -1 })
|
|
113
|
-
sendFinal(ctx)
|
|
114
|
-
return
|
|
115
115
|
}
|
|
116
|
+
const text = JSON.stringify({
|
|
117
|
+
type: item.event,
|
|
118
|
+
specialIdentification: 'dcgchat_tool_call_special_identification',
|
|
119
|
+
toolName: '',
|
|
120
|
+
callId: event.runId || Date.now().toString(),
|
|
121
|
+
params: item.message
|
|
122
|
+
})
|
|
123
|
+
sendToolCallMessage(sk, text, event.runId || Date.now().toString(), 0)
|
|
124
|
+
dcgLogger(`工具调用结果: ~ event:${item.event} ${status}`)
|
|
116
125
|
}
|
|
117
|
-
const text = JSON.stringify({
|
|
118
|
-
type: item.event,
|
|
119
|
-
specialIdentification: 'dcgchat_tool_call_special_identification',
|
|
120
|
-
toolName: '',
|
|
121
|
-
callId: event.runId || Date.now().toString(),
|
|
122
|
-
params: item.message
|
|
123
|
-
})
|
|
124
|
-
sendToolCallMessage(text, event.runId || Date.now().toString(), 0)
|
|
125
126
|
}
|
|
127
|
+
} else {
|
|
128
|
+
dcgLogger(`工具调用结果: ~ event:${item.event} ~ 没有sessionKey 为执行`)
|
|
126
129
|
}
|
|
127
130
|
})
|
|
128
131
|
}
|
package/src/transport.ts
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { getWsConnection } from './utils/global.js'
|
|
2
2
|
import { dcgLogger } from './utils/log.js'
|
|
3
|
+
import type { IMsgParams } from './types.js'
|
|
4
|
+
import { getEffectiveMsgParams, getParamsDefaults } from './utils/params.js'
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
/** 用 sessionKey 从 map 取参,再合并 overrides(channel 出站、媒体等) */
|
|
7
|
+
export function mergeSessionParams(sessionKey: string, overrides?: Partial<IMsgParams>): IMsgParams {
|
|
8
|
+
const base = getEffectiveMsgParams(sessionKey)
|
|
9
|
+
if (!overrides) return base
|
|
10
|
+
return { ...base, ...overrides }
|
|
11
|
+
}
|
|
12
|
+
export function mergeDefaultParams(overrides?: Partial<IMsgParams>): IMsgParams {
|
|
13
|
+
const base = getParamsDefaults()
|
|
14
|
+
if (!overrides) return base
|
|
15
|
+
return { ...base, ...overrides }
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
export
|
|
16
|
-
_userId: number
|
|
18
|
+
export type InboundMsgForContext = {
|
|
19
|
+
_userId: number | string
|
|
17
20
|
content: {
|
|
18
21
|
bot_token: string
|
|
19
22
|
domain_id?: string
|
|
@@ -23,86 +26,176 @@ export function createMsgContext(msg: {
|
|
|
23
26
|
session_id: string
|
|
24
27
|
message_id: string
|
|
25
28
|
}
|
|
26
|
-
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type OpenclawBotChatEnvelope = {
|
|
32
|
+
messageType: 'openclaw_bot_chat'
|
|
33
|
+
_userId: number | undefined
|
|
34
|
+
source: 'client'
|
|
35
|
+
content: Record<string, unknown>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isInboundWire(arg: unknown): arg is InboundMsgForContext {
|
|
39
|
+
return Boolean(arg && typeof arg === 'object' && '_userId' in arg && 'content' in arg)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** 下行 WebSocket 帧 → 内部上下文(字段缺省用 channel 配置补) */
|
|
43
|
+
function inboundToCtx(msg: InboundMsgForContext, d: IMsgParams): IMsgParams {
|
|
44
|
+
const c = msg.content
|
|
27
45
|
return {
|
|
28
|
-
userId: msg._userId,
|
|
29
|
-
botToken:
|
|
30
|
-
domainId:
|
|
31
|
-
appId:
|
|
32
|
-
botId:
|
|
33
|
-
agentId:
|
|
34
|
-
sessionId:
|
|
35
|
-
messageId:
|
|
46
|
+
userId: Number(msg._userId ?? d.userId),
|
|
47
|
+
botToken: c.bot_token ?? d.botToken,
|
|
48
|
+
domainId: String(c.domain_id ?? d.domainId),
|
|
49
|
+
appId: String(c.app_id ?? d.appId),
|
|
50
|
+
botId: c.bot_id,
|
|
51
|
+
agentId: c.agent_id,
|
|
52
|
+
sessionId: c.session_id,
|
|
53
|
+
messageId: c.message_id
|
|
36
54
|
}
|
|
37
55
|
}
|
|
38
56
|
|
|
39
|
-
|
|
57
|
+
/** 上行:与配置合并缺省后再 `...ctx` 覆盖(原 wsSendRaw) */
|
|
58
|
+
function mergeOutboundWithDefaults(ctx: IMsgParams, d: IMsgParams): IMsgParams {
|
|
59
|
+
return {
|
|
60
|
+
userId: Number(ctx.userId ?? d.userId),
|
|
61
|
+
botToken: ctx.botToken ?? d.botToken,
|
|
62
|
+
domainId: String(ctx.domainId ?? d.domainId),
|
|
63
|
+
appId: String(ctx.appId ?? d.appId),
|
|
64
|
+
...ctx
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 组装完整 wire `content` 对象:先写会话/机器人基础字段(回落到 d),再合并调用方传入的 payload。
|
|
70
|
+
* `content` 在使用处构造(如 response、state、files),同名键可覆盖基础字段。
|
|
71
|
+
*/
|
|
72
|
+
export function buildWireContent(base: IMsgParams, d: IMsgParams, content: Record<string, unknown>): Record<string, unknown> {
|
|
73
|
+
const resolvedBotToken = base.botToken ?? d.botToken
|
|
74
|
+
const domain = base.domainId ?? d.domainId
|
|
75
|
+
const app = base.appId ?? d.appId
|
|
40
76
|
return {
|
|
41
|
-
bot_token:
|
|
42
|
-
domain_id:
|
|
43
|
-
app_id:
|
|
44
|
-
bot_id:
|
|
45
|
-
agent_id:
|
|
46
|
-
session_id:
|
|
47
|
-
message_id:
|
|
48
|
-
...
|
|
77
|
+
bot_token: base.botToken || resolvedBotToken,
|
|
78
|
+
domain_id: base.domainId || domain,
|
|
79
|
+
app_id: base.appId || app,
|
|
80
|
+
bot_id: base.botId,
|
|
81
|
+
agent_id: base.agentId,
|
|
82
|
+
session_id: base.sessionId,
|
|
83
|
+
message_id: base.messageId || Date.now().toString(),
|
|
84
|
+
...content
|
|
49
85
|
}
|
|
50
86
|
}
|
|
51
87
|
|
|
52
|
-
|
|
88
|
+
/** 上行:在已合并的 ctx 上套 openclaw_bot_chat 信封(messageType / _userId / source + content) */
|
|
89
|
+
function buildOutboundOpenclawBotChatEnvelope(
|
|
90
|
+
ctx: IMsgParams,
|
|
91
|
+
content: Record<string, unknown>,
|
|
92
|
+
opts?: { mergeChannelDefaults?: boolean }
|
|
93
|
+
): OpenclawBotChatEnvelope {
|
|
94
|
+
const d = getParamsDefaults()
|
|
95
|
+
const base = opts?.mergeChannelDefaults ? mergeOutboundWithDefaults(ctx, d) : ctx
|
|
53
96
|
return {
|
|
54
|
-
messageType: 'openclaw_bot_chat'
|
|
55
|
-
_userId:
|
|
56
|
-
source: 'client'
|
|
57
|
-
content:
|
|
97
|
+
messageType: 'openclaw_bot_chat',
|
|
98
|
+
_userId: base.userId,
|
|
99
|
+
source: 'client',
|
|
100
|
+
content: buildWireContent(base, d, content)
|
|
58
101
|
}
|
|
59
102
|
}
|
|
60
103
|
|
|
104
|
+
/**
|
|
105
|
+
* 下行解析为 DcgchatMsgContext,或上行组装 openclaw_bot_chat 信封。
|
|
106
|
+
* 上行时 `content` 由调用方传入;基础参数来自 `ctx` 与 `getParamsDefaults()`(可选 mergeChannelDefaults,同原 wsSendRaw)。
|
|
107
|
+
*/
|
|
108
|
+
export function buildOpenclawBotChat(msg: InboundMsgForContext): IMsgParams
|
|
109
|
+
export function buildOpenclawBotChat(
|
|
110
|
+
ctx: IMsgParams,
|
|
111
|
+
content: Record<string, unknown>,
|
|
112
|
+
opts?: { mergeChannelDefaults?: boolean }
|
|
113
|
+
): OpenclawBotChatEnvelope
|
|
114
|
+
export function buildOpenclawBotChat(
|
|
115
|
+
arg1: InboundMsgForContext | IMsgParams,
|
|
116
|
+
arg2?: Record<string, unknown>,
|
|
117
|
+
opts?: { mergeChannelDefaults?: boolean }
|
|
118
|
+
): IMsgParams | OpenclawBotChatEnvelope {
|
|
119
|
+
const d = getParamsDefaults()
|
|
120
|
+
|
|
121
|
+
if (arg2 === undefined && isInboundWire(arg1)) {
|
|
122
|
+
return inboundToCtx(arg1, d)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const ctx = arg1 as IMsgParams
|
|
126
|
+
return buildOutboundOpenclawBotChatEnvelope(ctx, arg2 ?? {}, opts)
|
|
127
|
+
}
|
|
128
|
+
|
|
61
129
|
export function isWsOpen(): boolean {
|
|
62
130
|
const isOpen = getWsConnection()?.readyState === WebSocket.OPEN
|
|
63
131
|
if (!isOpen) {
|
|
64
|
-
dcgLogger(`socket not ready ${getWsConnection()?.readyState}`, 'error')
|
|
132
|
+
dcgLogger(`server socket not ready ${getWsConnection()?.readyState}`, 'error')
|
|
65
133
|
}
|
|
66
134
|
return isOpen
|
|
67
135
|
}
|
|
68
136
|
|
|
69
137
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
138
|
+
* 聊天流路径:content 单独 JSON.stringify(双重编码),符合 dcgchat 协议。
|
|
139
|
+
* `ctx` 须由调用方用 getEffectiveMsgParams(sessionKey) 等解析好;`content` 为完整业务 payload。
|
|
72
140
|
*/
|
|
73
|
-
export function wsSend(ctx:
|
|
141
|
+
export function wsSend(ctx: IMsgParams, content: Record<string, unknown>): boolean {
|
|
74
142
|
const ws = getWsConnection()
|
|
75
143
|
if (ws?.readyState !== WebSocket.OPEN) return false
|
|
76
|
-
const envelope =
|
|
144
|
+
const envelope = buildOpenclawBotChat(ctx, content)
|
|
77
145
|
ws.send(JSON.stringify({ ...envelope, content: JSON.stringify(envelope.content) }))
|
|
78
146
|
return true
|
|
79
147
|
}
|
|
80
148
|
|
|
81
149
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
150
|
+
* 媒体 / channel 出站:content 保持嵌套对象(单次编码)。
|
|
151
|
+
* `ctx` 须由调用方解析(如需合并覆盖可先 mergeSessionParams)。
|
|
84
152
|
*/
|
|
85
|
-
export function wsSendRaw(ctx:
|
|
153
|
+
export function wsSendRaw(ctx: IMsgParams, content: Record<string, unknown>): boolean {
|
|
86
154
|
const ws = getWsConnection()
|
|
87
155
|
if (isWsOpen()) {
|
|
88
|
-
ws?.send(JSON.stringify(
|
|
156
|
+
ws?.send(JSON.stringify(buildOpenclawBotChat(ctx, content, { mergeChannelDefaults: true })))
|
|
89
157
|
}
|
|
90
158
|
return true
|
|
91
159
|
}
|
|
92
160
|
|
|
93
|
-
export function sendChunk(ctx:
|
|
94
|
-
return wsSend(ctx, { response: text, state: 'chunk' })
|
|
161
|
+
export function sendChunk(text: string, ctx: IMsgParams, chunkIdx: number): boolean {
|
|
162
|
+
return wsSend(ctx, { response: text, state: 'chunk', chunk_idx: chunkIdx })
|
|
95
163
|
}
|
|
96
164
|
|
|
97
|
-
export function sendFinal(ctx:
|
|
98
|
-
dcgLogger(` message handling complete state: final`)
|
|
165
|
+
export function sendFinal(ctx: IMsgParams, tag?: string): boolean {
|
|
166
|
+
dcgLogger(` message handling complete state: final tag:${tag}`)
|
|
99
167
|
return wsSend(ctx, { response: '', state: 'final' })
|
|
100
168
|
}
|
|
101
169
|
|
|
102
|
-
export function sendText(
|
|
170
|
+
export function sendText(text: string, ctx: IMsgParams, event?: Record<string, unknown>): boolean {
|
|
103
171
|
return wsSend(ctx, { response: text, ...event })
|
|
104
172
|
}
|
|
105
173
|
|
|
106
|
-
export function sendError(
|
|
174
|
+
export function sendError(errorMsg: string, ctx: IMsgParams): boolean {
|
|
107
175
|
return wsSend(ctx, { response: `[错误] ${errorMsg}`, state: 'final' })
|
|
108
176
|
}
|
|
177
|
+
|
|
178
|
+
export function sendEventMessage(url: string, sessionKey: string) {
|
|
179
|
+
const ctx = getEffectiveMsgParams(sessionKey)
|
|
180
|
+
const ws = getWsConnection()
|
|
181
|
+
if (isWsOpen()) {
|
|
182
|
+
ws?.send(
|
|
183
|
+
JSON.stringify({
|
|
184
|
+
messageType: 'openclaw_bot_event',
|
|
185
|
+
source: 'client',
|
|
186
|
+
content: {
|
|
187
|
+
event_type: 'cron',
|
|
188
|
+
operation_type: 'install',
|
|
189
|
+
bot_token: ctx.botToken,
|
|
190
|
+
domain_id: ctx.domainId,
|
|
191
|
+
app_id: ctx.appId,
|
|
192
|
+
oss_url: url,
|
|
193
|
+
bot_id: ctx.botId,
|
|
194
|
+
agent_id: ctx.agentId,
|
|
195
|
+
session_id: ctx.sessionId,
|
|
196
|
+
message_id: ctx.messageId || Date.now().toString()
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -44,6 +44,7 @@ export type InboundMessage = {
|
|
|
44
44
|
bot_id?: string
|
|
45
45
|
agent_id?: string
|
|
46
46
|
session_id: string
|
|
47
|
+
real_mobook: string | number
|
|
47
48
|
message_id: string
|
|
48
49
|
text: string
|
|
49
50
|
files?: {
|
|
@@ -115,12 +116,16 @@ export interface IStsTokenReq {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
export interface IMsgParams {
|
|
118
|
-
userId
|
|
119
|
-
|
|
120
|
-
sessionId
|
|
121
|
-
messageId
|
|
122
|
-
domainId
|
|
123
|
-
appId
|
|
124
|
-
botId
|
|
125
|
-
agentId
|
|
119
|
+
userId?: number
|
|
120
|
+
botToken?: string
|
|
121
|
+
sessionId?: string
|
|
122
|
+
messageId?: string
|
|
123
|
+
domainId?: string
|
|
124
|
+
appId?: string
|
|
125
|
+
botId?: string
|
|
126
|
+
agentId?: string
|
|
127
|
+
/** 与 OpenClaw 路由一致,用于 map 与异步链路(工具 / HTTP / cron)对齐当前会话 */
|
|
128
|
+
sessionKey?: string
|
|
129
|
+
real_mobook?: string | number
|
|
130
|
+
is_finish?: number
|
|
126
131
|
}
|
package/src/utils/constant.ts
CHANGED
|
@@ -2,6 +2,6 @@ export const ENV: 'production' | 'test' | 'develop' = 'production'
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
export const systemCommand = ['/new', '/status']
|
|
5
|
-
export const interruptCommand = ['
|
|
5
|
+
export const interruptCommand = ['chat.stop']
|
|
6
6
|
|
|
7
|
-
export const ignoreToolCommand = ['/search', '/abort', '/queue interrupt', ...systemCommand, ...interruptCommand]
|
|
7
|
+
export const ignoreToolCommand = ['/search', '/abort', '/stop', '/queue interrupt', ...systemCommand, ...interruptCommand]
|
package/src/utils/global.ts
CHANGED
|
@@ -24,7 +24,6 @@ export function getOpenClawConfig(): OpenClawConfig | null {
|
|
|
24
24
|
|
|
25
25
|
import type { OpenClawConfig, PluginRuntime } from 'openclaw/plugin-sdk'
|
|
26
26
|
import { dcgLogger } from './log.js'
|
|
27
|
-
import { IMsgParams } from '../types.js'
|
|
28
27
|
import { channelInfo, ENV } from './constant.js'
|
|
29
28
|
|
|
30
29
|
const path = require('path')
|
|
@@ -69,20 +68,22 @@ export function getDcgchatRuntime(): PluginRuntime {
|
|
|
69
68
|
return runtime as PluginRuntime
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
export function setMsgParams(params: any) {
|
|
74
|
-
msgParams = params
|
|
75
|
-
}
|
|
76
|
-
export function getMsgParams() {
|
|
77
|
-
return msgParams
|
|
78
|
-
}
|
|
71
|
+
export type MsgSessionStatus = 'running' | 'finished' | ''
|
|
79
72
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
73
|
+
const msgStatusBySessionKey = new Map<string, MsgSessionStatus>()
|
|
74
|
+
|
|
75
|
+
export function setMsgStatus(sessionKey: string, status: MsgSessionStatus) {
|
|
76
|
+
if (!sessionKey?.trim()) return
|
|
77
|
+
if (status === '') {
|
|
78
|
+
msgStatusBySessionKey.delete(sessionKey)
|
|
79
|
+
} else {
|
|
80
|
+
msgStatusBySessionKey.set(sessionKey, status)
|
|
81
|
+
}
|
|
83
82
|
}
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
|
|
84
|
+
export function getMsgStatus(sessionKey: string): MsgSessionStatus {
|
|
85
|
+
if (!sessionKey?.trim()) return ''
|
|
86
|
+
return msgStatusBySessionKey.get(sessionKey) ?? ''
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
const getMediaKey = (url: string) => url.split(/[\\/]/).pop() ?? url
|
|
@@ -115,3 +116,30 @@ export function clearSentMediaKeys(messageId?: string) {
|
|
|
115
116
|
sentMediaKeysBySession.clear()
|
|
116
117
|
}
|
|
117
118
|
}
|
|
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",
|
|
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
|
+
const cronMessageIdMap = new Map<string, string>()
|
|
134
|
+
|
|
135
|
+
export function setCronMessageId(sk: string, messageId: string) {
|
|
136
|
+
cronMessageIdMap.set(sk, messageId)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function getCronMessageId(sk: string): string {
|
|
140
|
+
return cronMessageIdMap.get(sk) ?? ''
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function removeCronMessageId(sk: string) {
|
|
144
|
+
cronMessageIdMap.delete(sk)
|
|
145
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { channelInfo, ENV } from './constant.js'
|
|
2
|
+
import { getOpenClawConfig } from './global.js'
|
|
3
|
+
import type { DcgchatConfig, IMsgParams } from '../types.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* map key 是 session_key,value 为该会话下已 merge 后的消息参数。
|
|
7
|
+
*/
|
|
8
|
+
const paramsMessageMap = new Map<string, IMsgParams>()
|
|
9
|
+
|
|
10
|
+
/** 最近一次 setParamsMessage 的 key,供不传参的 getEffectiveMsgParams() 使用 */
|
|
11
|
+
let currentSessionKey: string | null = null
|
|
12
|
+
|
|
13
|
+
/** 从 OpenClaw 配置读取当前 channel 的基础参数(唯一来源,供 transport / resolve 等复用) */
|
|
14
|
+
export function getParamsDefaults(): IMsgParams {
|
|
15
|
+
const ch = (getOpenClawConfig()?.channels?.["dcgchat"] as DcgchatConfig | undefined) ?? {}
|
|
16
|
+
return {
|
|
17
|
+
userId: Number(ch.userId ?? 0),
|
|
18
|
+
botToken: ch.botToken ?? '',
|
|
19
|
+
sessionId: '',
|
|
20
|
+
messageId: '',
|
|
21
|
+
domainId: String(ch.domainId ?? '1000'),
|
|
22
|
+
appId: String(ch.appId ?? '100'),
|
|
23
|
+
botId: '',
|
|
24
|
+
agentId: '',
|
|
25
|
+
sessionKey: ''
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function resolveParamsMessage(params: Partial<IMsgParams>): IMsgParams {
|
|
30
|
+
const defaults = getParamsDefaults()
|
|
31
|
+
return {
|
|
32
|
+
userId: Number(params.userId ?? defaults.userId),
|
|
33
|
+
botToken: params.botToken ?? defaults.botToken,
|
|
34
|
+
sessionId: params.sessionId ?? defaults.sessionId,
|
|
35
|
+
messageId: params.messageId ?? defaults.messageId,
|
|
36
|
+
domainId: String(params.domainId ?? defaults.domainId),
|
|
37
|
+
appId: String(params.appId ?? defaults.appId),
|
|
38
|
+
botId: params.botId ?? defaults.botId,
|
|
39
|
+
agentId: params.agentId ?? defaults.agentId,
|
|
40
|
+
sessionKey: params.sessionKey ?? defaults.sessionKey
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 统一取值入口:显式 sessionKey,或回落到当前会话;再与配置缺省 merge,保证字段完整。
|
|
46
|
+
*/
|
|
47
|
+
export function getEffectiveMsgParams(sessionKey?: string): IMsgParams {
|
|
48
|
+
const key = sessionKey ?? currentSessionKey
|
|
49
|
+
const stored = key ? paramsMessageMap.get(key) : undefined
|
|
50
|
+
return stored ? resolveParamsMessage(stored) : getParamsDefaults()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function setParamsMessage(sessionKey: string, params: Partial<IMsgParams>) {
|
|
54
|
+
if (!sessionKey) return
|
|
55
|
+
currentSessionKey = sessionKey
|
|
56
|
+
const previous = paramsMessageMap.get(sessionKey)
|
|
57
|
+
const base = previous ? resolveParamsMessage(previous) : getParamsDefaults()
|
|
58
|
+
paramsMessageMap.set(sessionKey, resolveParamsMessage({ ...base, ...params, sessionKey }))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getParamsMessage(sessionKey: string): IMsgParams | undefined {
|
|
62
|
+
return paramsMessageMap.get(sessionKey)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getCurrentSessionKey(): string | null {
|
|
66
|
+
return currentSessionKey
|
|
67
|
+
}
|
package/src/utils/searchFile.ts
CHANGED
|
@@ -94,11 +94,9 @@ function stripMobookNoise(s: string) {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
|
-
* 从文本中扫描
|
|
97
|
+
* 从文本中扫描 `.../mobook/...` 或 `...\mobook\...` 片段,按最长后缀匹配合法扩展名(兜底)
|
|
98
98
|
*/
|
|
99
|
-
function
|
|
100
|
-
const lower = text.toLowerCase()
|
|
101
|
-
const needle = '/mobook/'
|
|
99
|
+
function collectMobookPathsAfterNeedle(text: string, lower: string, needle: string, result: Set<string>): void {
|
|
102
100
|
let from = 0
|
|
103
101
|
while (from < text.length) {
|
|
104
102
|
const i = lower.indexOf(needle, from)
|
|
@@ -136,8 +134,16 @@ function collectMobookPathsByScan(text: string, result: Set<string>): void {
|
|
|
136
134
|
}
|
|
137
135
|
}
|
|
138
136
|
|
|
137
|
+
function collectMobookPathsByScan(text: string, result: Set<string>): void {
|
|
138
|
+
const lower = text.toLowerCase()
|
|
139
|
+
collectMobookPathsAfterNeedle(text, lower, '/mobook/', result)
|
|
140
|
+
collectMobookPathsAfterNeedle(text, lower, '\\mobook\\', result)
|
|
141
|
+
}
|
|
142
|
+
|
|
139
143
|
export function extractMobookFiles(text = '') {
|
|
140
144
|
if (typeof text !== 'string' || !text.trim()) return []
|
|
145
|
+
// 全角冒号(中文输入常见)→ 半角,便于匹配 c:\mobook\
|
|
146
|
+
text = text.replace(/\uFF1A/g, ':')
|
|
141
147
|
const result = new Set<string>()
|
|
142
148
|
// ✅ 扩展名(必须长扩展名优先,见 EXT_SORTED_FOR_REGEX)
|
|
143
149
|
const EXT = `(${EXT_SORTED_FOR_REGEX.join('|')})`
|
|
@@ -157,6 +163,14 @@ export function extractMobookFiles(text = '') {
|
|
|
157
163
|
;(text.match(fullPathReg) || []).forEach((p) => {
|
|
158
164
|
result.add(normalizePath(p))
|
|
159
165
|
})
|
|
166
|
+
// 2️⃣b Windows 实际保存路径:C:\mobook\xxx、c:/mobook/xxx、\mobook\xxx(模型常写反斜杠,原先无法识别)
|
|
167
|
+
const winMobookReg = new RegExp(`(?:[a-zA-Z]:)?[/\\\\]mobook[/\\\\]${FILE_NAME}\\.${EXT}`, 'gi')
|
|
168
|
+
;(text.match(winMobookReg) || []).forEach((full) => {
|
|
169
|
+
const name = full.replace(/^(?:[a-zA-Z]:)?[/\\\\]mobook[/\\\\]/i, '').trim()
|
|
170
|
+
if (isValidFileName(name)) {
|
|
171
|
+
result.add(normalizePath(`/mobook/${name}`))
|
|
172
|
+
}
|
|
173
|
+
})
|
|
160
174
|
// 3️⃣ mobook下的 xxx.xxx
|
|
161
175
|
const inlineReg = new RegExp(`mobook下的\\s*(${FILE_NAME}\\.${EXT})`, 'gi')
|
|
162
176
|
;(text.match(inlineReg) || []).forEach((item) => {
|