@dcrays/dcgchat-test 0.4.21 → 0.4.23

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcrays/dcgchat-test",
3
- "version": "0.4.21",
3
+ "version": "0.4.23",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for 书灵墨宝 (WebSocket)",
6
6
  "main": "index.ts",
package/src/channel.ts CHANGED
@@ -8,7 +8,8 @@ import {
8
8
  getDcgchatRuntime,
9
9
  getInfoBySessionKey,
10
10
  getOpenClawConfig,
11
- hasSentMediaKey
11
+ hasSentMediaKey,
12
+ setCronMessageId
12
13
  } from './utils/global.js'
13
14
  import { isWsOpen, mergeDefaultParams, mergeSessionParams, sendFinal, wsSendRaw } from './transport.js'
14
15
  import { dcgLogger, setLogger } from './utils/log.js'
@@ -103,10 +104,26 @@ function outboundChatId(rawTo: string | undefined, normalizedTo: string): string
103
104
  }
104
105
 
105
106
  export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<void> {
107
+ const rawOpt = (opts.sessionKey ?? '').trim()
108
+ const strippedForCron = rawOpt.replace(/^dcg-cron:/i, '').trim()
109
+ const fromIsolatedCron = extractCronJobIdFromIsolatedSessionKey(strippedForCron) !== null
110
+ const fromDcgCronWrapper = rawOpt.toLowerCase().startsWith('dcg-cron:')
111
+
106
112
  let sessionKey = normalizeSessionTarget(opts.sessionKey ?? '')
107
113
  sessionKey = resolveIsolatedCronSessionToJobSessionKey(sessionKey)
114
+
115
+ /** 定时自动执行未走 onRunCronJob,须与 finishedDcgchatCron 共用同一 messageId,否则附件与气泡错位 */
116
+ if (!opts.messageId?.trim() && (fromIsolatedCron || fromDcgCronWrapper) && !getCronMessageId(sessionKey)) {
117
+ setCronMessageId(sessionKey, `${Date.now()}`)
118
+ }
119
+
120
+ const cronMid = getCronMessageId(sessionKey)
108
121
  const baseCtx = getOutboundMsgParams(sessionKey)
109
- const msgCtx = opts.messageId?.trim() ? { ...baseCtx, messageId: opts.messageId.trim() } : baseCtx
122
+ const msgCtx = opts.messageId?.trim()
123
+ ? { ...baseCtx, messageId: opts.messageId.trim() }
124
+ : cronMid
125
+ ? { ...baseCtx, messageId: cronMid }
126
+ : baseCtx
110
127
  if (!isWsOpen()) {
111
128
  dcgLogger(`outbound media skipped -> ws not isWsOpen failed open: ${opts.mediaUrl ?? ''}`)
112
129
  return
@@ -131,7 +148,7 @@ export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<v
131
148
  return
132
149
  }
133
150
  const fileName = mediaUrl?.split(/[\\/]/).pop() || ''
134
- const notMessageId = `${msgCtx?.messageId}`?.length !== 13 || !msgCtx?.messageId
151
+ const notMessageId = `${msgCtx?.messageId}`?.length === 13 || !msgCtx?.messageId
135
152
  try {
136
153
  const botToken = msgCtx.botToken ?? getOpenClawConfig()?.channels?.["dcgchat-test"]?.botToken ?? ''
137
154
  const url = opts.mediaUrl ? await ossUpload(opts.mediaUrl, botToken, 1) : ''
@@ -326,6 +343,9 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
326
343
  const to = resolveIsolatedCronSessionToJobSessionKey(normalizedFromTo)
327
344
  const outboundCtx = getOutboundMsgParams(to)
328
345
  const msgCtx = getParamsMessage(to) ?? outboundCtx
346
+ if (isCron && !getCronMessageId(to)) {
347
+ setCronMessageId(to, `${Date.now()}`)
348
+ }
329
349
  const cronMsgId = getCronMessageId(to)
330
350
  const fallbackMessageId = `${Date.now()}`
331
351
  const messageId = cronMsgId || (isCron ? fallbackMessageId : msgCtx?.messageId || fallbackMessageId)
@@ -7,6 +7,9 @@ import { channelInfo, ENV } from './utils/constant.js'
7
7
  * 自动注入 bestEffort: true,使投递失败时静默降级,
8
8
  * 不影响 cron 执行结果的保存。
9
9
  *
10
+ * 另:拦截高危 exec,避免对话内 `openclaw gateway start` 向已运行网关发 SIGTERM、
11
+ * 以及反复 `openclaw cron add` 阻塞与握手超时——应改用内置 `cron` 工具或 Gateway RPC。
12
+ *
10
13
  * 背景:
11
14
  * - 定时任务的 delivery 设为 announce 模式,如果没有指定 channel,
12
15
  * 投递可能因找不到有效渠道而失败
@@ -79,6 +82,23 @@ function isCronTool(toolName: string): boolean {
79
82
  return toolName === 'cron'
80
83
  }
81
84
 
85
+ /** 非 running 会话(如定时触发)也需跑本钩子的 exec 子串 */
86
+ export function execCommandNeedsCronToolHook(command: string): boolean {
87
+ const c = command.trim()
88
+ if (!c) return false
89
+ if (c.includes('cron create') || c.includes('cron add')) return true
90
+ return /\bopenclaw\s+gateway\s+start\b/i.test(c)
91
+ }
92
+
93
+ const BLOCK_GATEWAY_START =
94
+ '禁止在对话内 exec `openclaw gateway start`:当前会话可能正跑在本网关上,重复启动会先 SIGTERM 旧进程导致断连。' +
95
+ '若仅需确认存活,请改用只读命令,例如 `openclaw gateway health --json` 或 `openclaw gateway probe`(以本机 CLI 为准)。' +
96
+ '若确认网关未运行,请在宿主机/服务管理(systemd 等)中启动,勿由 Agent 代 exec。'
97
+
98
+ const BLOCK_CRON_CLI =
99
+ '请勿用 exec 跑 `openclaw cron add` / `openclaw cron create`:请改用内置 **`cron` 工具**(框架走 Gateway `cron.*`,由本插件自动补全 delivery / sessionKey)。' +
100
+ '高频 exec CLI 易长时间占满子进程并加剧网关 WebSocket 握手压力。'
101
+
82
102
  /**
83
103
  * 从 cron 参数中提取 delivery 配置
84
104
  * cron 工具的参数结构可能是:
@@ -188,14 +208,22 @@ export function cronToolCall(event: { toolName: any; params: any; toolCallId: an
188
208
 
189
209
  return { params: newParams }
190
210
  } else if (toolName === 'exec') {
191
- if (params.command.indexOf('cron create') > -1 || params.command.indexOf('cron add') > -1) {
211
+ const cmd = typeof params.command === 'string' ? params.command : ''
212
+ if (/\bopenclaw\s+gateway\s+start\b/i.test(cmd)) {
213
+ dcgLogger(`[${LOG_TAG}] blocked exec gateway start (${toolCallId})`)
214
+ return { block: true, blockReason: BLOCK_GATEWAY_START }
215
+ }
216
+ if (/\bopenclaw\s+cron\s+(add|create)\b/i.test(cmd)) {
217
+ dcgLogger(`[${LOG_TAG}] blocked exec openclaw cron add/create (${toolCallId})`)
218
+ return { block: true, blockReason: BLOCK_CRON_CLI }
219
+ }
220
+ if (cmd.includes('cron create') || cmd.includes('cron add')) {
192
221
  const newParams = JSON.parse(JSON.stringify(params)) as Record<string, unknown>
193
222
  newParams.command =
194
- params.command.replace('--json', '') + ` --session-key ${sk} --channel ${"dcgchat-test"} --to dcg-cron:${sk} --json`
223
+ cmd.replace(/--json/g, '').trimEnd() + ` --session-key ${sk} --channel ${"dcgchat-test"} --to dcg-cron:${sk} --json`
195
224
  return { params: newParams }
196
- } else {
197
- return undefined
198
225
  }
226
+ return undefined
199
227
  }
200
228
 
201
229
  return undefined
package/src/tool.ts CHANGED
@@ -3,7 +3,7 @@ import { getMsgStatus } from './utils/global.js'
3
3
  import { dcgLogger } from './utils/log.js'
4
4
  import { sendFinal, sendText, wsSendRaw } from './transport.js'
5
5
  import { getEffectiveMsgParams, deleteSessionKeyBySubAgentRunId, setSessionKeyBySubAgentRunId } from './utils/params.js'
6
- import { cronToolCall } from './cronToolCall.js'
6
+ import { cronToolCall, execCommandNeedsCronToolHook } from './cronToolCall.js'
7
7
 
8
8
  type PluginHookName =
9
9
  | 'before_model_resolve'
@@ -330,7 +330,7 @@ function shouldRunBeforeToolCallWithoutRunningSession(event: { toolName?: string
330
330
  if (event?.toolName === 'cron') return true
331
331
  const cmd = event?.params?.command
332
332
  if (event?.toolName === 'exec' && typeof cmd === 'string') {
333
- return cmd.includes('cron create') || cmd.includes('cron add')
333
+ return execCommandNeedsCronToolHook(cmd)
334
334
  }
335
335
  return false
336
336
  }