@dcrays/dcgchat-test 0.2.13 → 0.2.15

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.2.13",
3
+ "version": "0.2.15",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for 书灵墨宝 (WebSocket)",
6
6
  "main": "index.ts",
package/src/bot.ts CHANGED
@@ -188,50 +188,59 @@ const EXT_LIST = [
188
188
 
189
189
  function extractMobookFiles(text = '') {
190
190
  if (typeof text !== 'string' || !text.trim()) return [];
191
-
191
+
192
192
  const result = new Set();
193
-
194
- // ✅ 支持的文件类型(可扩展)
195
- const EXT = `(${EXT_LIST.join('|')})`
196
-
197
- try {
198
- // 1️⃣ 提取 `xxx.xxx`(反引号包裹)
199
- const backtickMatches = text.match(
200
- new RegExp(`\`([^\\\`]+?\\.${EXT})\``, 'gi')
201
- ) || [];
202
193
 
203
- backtickMatches.forEach(item => {
194
+ // 扩展名
195
+ const EXT = `(${EXT_LIST.join('|')})`;
196
+
197
+ // ✅ 文件名字符(增强:支持中文、符号)
198
+ const FILE_NAME = `[\\w\\u4e00-\\u9fa5::《》()()\\-\\s]+?`;
199
+
200
+ try {
201
+ // 1️⃣ `xxx.xxx`
202
+ const backtickReg = new RegExp(`\`([^\\\`]+?\\.${EXT})\``, 'gi');
203
+ (text.match(backtickReg) || []).forEach(item => {
204
204
  const name = item.replace(/`/g, '').trim();
205
205
  if (isValidFileName(name)) {
206
206
  result.add(`/mobook/${name}`);
207
207
  }
208
208
  });
209
209
 
210
- // 2️⃣ 提取 "/mobook/xxx.xxx"(完整路径)
211
- const fullPathMatches = text.match(
212
- new RegExp(`/mobook/[\\w\\u4e00-\\u9fa5.-]+?\\.${EXT}`, 'gi')
213
- ) || [];
214
-
215
- fullPathMatches.forEach(p => {
210
+ // 2️⃣ /mobook/xxx.xxx
211
+ const fullPathReg = new RegExp(`/mobook/${FILE_NAME}\\.${EXT}`, 'gi');
212
+ (text.match(fullPathReg) || []).forEach(p => {
216
213
  result.add(normalizePath(p));
217
214
  });
218
215
 
219
- // 3️⃣ 提取 “mobook下的 xxx.xxx
220
- const mobookInlineMatches = text.match(
221
- new RegExp(`mobook下的\\s*([\\w\\u4e00-\\u9fa5.-]+?\\.${EXT})`, 'gi')
222
- ) || [];
216
+ // 3️⃣ mobook下的 xxx.xxx
217
+ const inlineReg = new RegExp(`mobook下的\\s*(${FILE_NAME}\\.${EXT})`, 'gi');
218
+ (text.match(inlineReg) || []).forEach(item => {
219
+ const match = item.match(new RegExp(`${FILE_NAME}\\.${EXT}`, 'i'));
220
+ if (match && isValidFileName(match[0])) {
221
+ result.add(`/mobook/${match[0].trim()}`);
222
+ }
223
+ });
224
+
225
+ // 🆕 4️⃣ **xxx.xxx**
226
+ const boldReg = new RegExp(`\\*\\*(${FILE_NAME}\\.${EXT})\\*\\*`, 'gi');
227
+ (text.match(boldReg) || []).forEach(item => {
228
+ const name = item.replace(/\*\*/g, '').trim();
229
+ if (isValidFileName(name)) {
230
+ result.add(`/mobook/${name}`);
231
+ }
232
+ });
223
233
 
224
- mobookInlineMatches.forEach(item => {
225
- const match = item.match(
226
- new RegExp(`([\\w\\u4e00-\\u9fa5.-]+?\\.${EXT})`, 'i')
227
- );
228
- if (match && isValidFileName(match[1])) {
229
- result.add(`/mobook/${match[1]}`);
234
+ // 🆕 5️⃣ xxx.xxx (123字节)
235
+ const looseReg = new RegExp(`(${FILE_NAME}\\.${EXT})\\s*\\(`, 'gi');
236
+ (text.match(looseReg) || []).forEach(item => {
237
+ const name = item.replace(/\s*\(.+$/, '').trim();
238
+ if (isValidFileName(name)) {
239
+ result.add(`/mobook/${name}`);
230
240
  }
231
241
  });
232
242
 
233
243
  } catch (e) {
234
- // 容错:解析异常不影响主流程
235
244
  console.warn('extractMobookFiles error:', e);
236
245
  }
237
246
 
@@ -307,7 +316,7 @@ export async function handleDcgchatMessage(params: {
307
316
 
308
317
  const route = core.channel.routing.resolveAgentRoute({
309
318
  cfg,
310
- channel: "dcgchat-test",
319
+ channel: "dcgchat",
311
320
  accountId: account.accountId,
312
321
  peer: { kind: "direct", id: userId },
313
322
  });
@@ -344,13 +353,13 @@ export async function handleDcgchatMessage(params: {
344
353
  ChatType: "direct",
345
354
  SenderName: userId,
346
355
  SenderId: userId,
347
- Provider: "dcgchat-test" as const,
348
- Surface: "dcgchat-test" as const,
356
+ Provider: "dcgchat" as const,
357
+ Surface: "dcgchat" as const,
349
358
  MessageSid: msg.content.message_id,
350
359
  Timestamp: Date.now(),
351
360
  WasMentioned: true,
352
361
  CommandAuthorized: true,
353
- OriginatingChannel: "dcgchat-test" as const,
362
+ OriginatingChannel: "dcgchat" as const,
354
363
  OriginatingTo: `user:${userId}`,
355
364
  ...mediaPayload,
356
365
  });
package/src/monitor.ts CHANGED
@@ -140,7 +140,7 @@ export async function monitorDcgchatProvider(opts: MonitorDcgchatOpts): Promise<
140
140
  const content = { event_type, operation_type, skill_url, skill_code, skill_id, bot_token, websocket_trace_id };
141
141
  if (event_type === "skill") {
142
142
  if (operation_type === "install" || operation_type === "enable" || operation_type === "update") {
143
- installSkill({ path: skill_url, code: skill_code }, content);
143
+ installSkill({ path: skill_url, code: skill_code }, content, { cfg, accountId: account.accountId, runtime });
144
144
  } else if (operation_type === "remove" || operation_type === "disable") {
145
145
  uninstallSkill({ code: skill_code }, content);
146
146
  } else {
package/src/skill.ts CHANGED
@@ -5,15 +5,24 @@ import unzipper from 'unzipper';
5
5
  import { pipeline } from "stream/promises";
6
6
  import fs from 'fs';
7
7
  import path from 'path';
8
+ import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
8
9
  import { logDcgchat } from './log.js';
9
- import { getWorkspaceDir } from './runtime.js';
10
+ import { getDcgchatRuntime, getWorkspaceDir } from './runtime.js';
10
11
  import { getWsConnection } from './connection.js';
12
+ import { resolveAccount } from './channel.js';
13
+ import { getMsgParams } from './tool.js';
11
14
 
12
15
  type ISkillParams = {
13
16
  path: string;
14
17
  code: string;
15
18
  }
16
19
 
20
+ type SkillContext = {
21
+ cfg: ClawdbotConfig;
22
+ accountId: string;
23
+ runtime?: RuntimeEnv;
24
+ }
25
+
17
26
  function sendEvent(msgContent: Record<string, any>) {
18
27
  const ws = getWsConnection()
19
28
  if (ws?.readyState === WebSocket.OPEN) {
@@ -27,7 +36,77 @@ function sendEvent(msgContent: Record<string, any>) {
27
36
  }
28
37
  }
29
38
 
30
- export async function installSkill(params: ISkillParams, msgContent: Record<string, any>) {
39
+ async function sendNewSessionCommand(ctx: SkillContext) {
40
+ try {
41
+ const core = getDcgchatRuntime();
42
+ const log = ctx.runtime?.log ?? console.log;
43
+ const params = getMsgParams();
44
+ const account = resolveAccount(ctx.cfg, ctx.accountId);
45
+ const userId = String(params.userId);
46
+
47
+ const route = core.channel.routing.resolveAgentRoute({
48
+ cfg: ctx.cfg,
49
+ channel: "dcgchat-test",
50
+ accountId: account.accountId,
51
+ peer: { kind: "direct", id: userId },
52
+ });
53
+
54
+ const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(ctx.cfg);
55
+ const bodyFormatted = core.channel.reply.formatAgentEnvelope({
56
+ channel: "书灵墨宝",
57
+ from: userId,
58
+ timestamp: new Date(),
59
+ envelope: envelopeOptions,
60
+ body: "/new",
61
+ });
62
+
63
+ const ctxPayload = core.channel.reply.finalizeInboundContext({
64
+ Body: bodyFormatted,
65
+ RawBody: "/new",
66
+ CommandBody: "/new",
67
+ From: userId,
68
+ To: userId,
69
+ SessionKey: route.sessionKey,
70
+ AccountId: params.sessionId,
71
+ ChatType: "direct",
72
+ SenderName: userId,
73
+ SenderId: userId,
74
+ Provider: "dcgchat-test" as const,
75
+ Surface: "dcgchat-test" as const,
76
+ MessageSid: Date.now().toString(),
77
+ Timestamp: Date.now(),
78
+ WasMentioned: true,
79
+ CommandAuthorized: true,
80
+ OriginatingChannel: "dcgchat-test" as const,
81
+ OriginatingTo: `user:${userId}`,
82
+ });
83
+
84
+ const noopDispatcher = {
85
+ sendToolResult: () => false,
86
+ sendBlockReply: () => false,
87
+ sendFinalReply: () => false,
88
+ waitForIdle: async () => {},
89
+ getQueuedCounts: () => ({ tool: 0, block: 0, final: 0 }),
90
+ markComplete: () => {},
91
+ };
92
+
93
+ await core.channel.reply.withReplyDispatcher({
94
+ dispatcher: noopDispatcher,
95
+ run: () =>
96
+ core.channel.reply.dispatchReplyFromConfig({
97
+ ctx: ctxPayload,
98
+ cfg: ctx.cfg,
99
+ dispatcher: noopDispatcher,
100
+ }),
101
+ });
102
+
103
+ log(`dcgchat: /new command dispatched silently after skill install`);
104
+ } catch (err) {
105
+ logDcgchat.error(`sendNewSessionCommand failed: ${err}`);
106
+ }
107
+ }
108
+
109
+ export async function installSkill(params: ISkillParams, msgContent: Record<string, any>, ctx?: SkillContext) {
31
110
  const { path: cdnUrl, code } = params;
32
111
  const workspacePath = getWorkspaceDir();
33
112
 
@@ -67,7 +146,14 @@ export async function installSkill(params: ISkillParams, msgContent: Record<stri
67
146
  }
68
147
 
69
148
  try {
70
- const entryPath = entry.path;
149
+ const flags = entry.props?.flags ?? 0;
150
+ const isUtf8 = (flags & 0x800) !== 0;
151
+ let entryPath: string;
152
+ if (!isUtf8 && entry.props?.pathBuffer) {
153
+ entryPath = new TextDecoder('gbk').decode(entry.props.pathBuffer);
154
+ } else {
155
+ entryPath = entry.path;
156
+ }
71
157
  const pathParts = entryPath.split("/");
72
158
 
73
159
  // 检测根目录
@@ -122,6 +208,9 @@ export async function installSkill(params: ISkillParams, msgContent: Record<stri
122
208
  });
123
209
  });
124
210
  sendEvent({ ...msgContent, status: 'ok' })
211
+ if (ctx) {
212
+ await sendNewSessionCommand(ctx);
213
+ }
125
214
  } catch (error) {
126
215
  // 如果安装失败,清理目录
127
216
  if (fs.existsSync(skillDir)) {