@dcrays/dcgchat-test 0.6.12 → 0.7.0

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.
Files changed (60) hide show
  1. package/index.js +306 -0
  2. package/openclaw.plugin.json +42 -12
  3. package/package.json +8 -35
  4. package/README.md +0 -167
  5. package/index.ts +0 -35
  6. package/src/agent.ts +0 -568
  7. package/src/bot.ts +0 -646
  8. package/src/channel/index.ts +0 -329
  9. package/src/channel/outboundMedia.ts +0 -144
  10. package/src/channel/outboundTarget.ts +0 -62
  11. package/src/channel/uploadMediaUrl.ts +0 -76
  12. package/src/cron/message.ts +0 -114
  13. package/src/cron/params.ts +0 -164
  14. package/src/cron/toolGuard.ts +0 -466
  15. package/src/cron/types.ts +0 -15
  16. package/src/gateway/index.ts +0 -430
  17. package/src/gateway/security.ts +0 -95
  18. package/src/gateway/socket.ts +0 -256
  19. package/src/libs/ali-oss-6.23.0.tgz +0 -0
  20. package/src/libs/axios-1.13.6.tgz +0 -0
  21. package/src/libs/md5-2.3.0.tgz +0 -0
  22. package/src/libs/mime-types-3.0.2.tgz +0 -0
  23. package/src/libs/unzipper-0.12.3.tgz +0 -0
  24. package/src/libs/ws-8.19.0.tgz +0 -0
  25. package/src/monitor.ts +0 -269
  26. package/src/request/api.ts +0 -80
  27. package/src/request/oss.ts +0 -200
  28. package/src/request/request.ts +0 -191
  29. package/src/request/userInfo.ts +0 -44
  30. package/src/session.ts +0 -28
  31. package/src/skill.ts +0 -114
  32. package/src/tool.ts +0 -603
  33. package/src/tools/messageTool.ts +0 -334
  34. package/src/tools/toolCallGuard.ts +0 -327
  35. package/src/transport.ts +0 -217
  36. package/src/types.ts +0 -139
  37. package/src/utils/agentErrors.ts +0 -116
  38. package/src/utils/constant.ts +0 -60
  39. package/src/utils/env-config.ts +0 -19
  40. package/src/utils/formatLlmInputEvent.ts +0 -48
  41. package/src/utils/gatewayMsgHandler.ts +0 -147
  42. package/src/utils/global.ts +0 -309
  43. package/src/utils/inboundTurnState.ts +0 -66
  44. package/src/utils/log.ts +0 -77
  45. package/src/utils/mediaAttached.ts +0 -244
  46. package/src/utils/mediaEmitter.ts +0 -54
  47. package/src/utils/outboundAssistantText.ts +0 -117
  48. package/src/utils/params.ts +0 -104
  49. package/src/utils/passTxt.ts +0 -4
  50. package/src/utils/resolveRegisterConfig.ts +0 -33
  51. package/src/utils/searchFile.ts +0 -228
  52. package/src/utils/sessionState.ts +0 -137
  53. package/src/utils/sessionTermination.ts +0 -228
  54. package/src/utils/streamMerge.ts +0 -150
  55. package/src/utils/subagentRunMap.ts +0 -71
  56. package/src/utils/undiciFetchInterceptor.ts +0 -346
  57. package/src/utils/workspaceFilePaths.ts +0 -18
  58. package/src/utils/wsMessageHandler.ts +0 -124
  59. package/src/utils/zipExtract.ts +0 -97
  60. package/src/utils/zipPath.ts +0 -24
@@ -1,200 +0,0 @@
1
- import { extname } from 'node:path'
2
- import { fileURLToPath } from 'node:url'
3
- // @ts-ignore
4
- import OSS from 'ali-oss'
5
- import { getStsToken, getUserToken } from './api.js'
6
- import { dcgLogger } from '../utils/log.js'
7
-
8
- /** 分片大小:OSS 要求每片 ≥100 KB(最后一片可更小) */
9
- const MULTIPART_PART_SIZE = 1024 * 1024
10
-
11
- /** ali-oss 默认 timeout 为 60s,大文件单 PUT 或慢网易触发 ResponseTimeoutError */
12
- const OSS_HTTP_TIMEOUT_MS = 15 * 60 * 1000
13
-
14
- /** 归一化入参,避免 file://、包装对象、TypedArray 等导致 SDK 识别失败 */
15
- function coerceOssFileInput(input: File | string | Buffer): File | string | Buffer {
16
- if (typeof input === 'string') {
17
- const t = input.trim()
18
- if (t.startsWith('file:')) {
19
- try {
20
- return fileURLToPath(t)
21
- } catch {
22
- return input
23
- }
24
- }
25
- return input
26
- }
27
- if (Buffer.isBuffer(input)) {
28
- return input
29
- }
30
- if (input && typeof input === 'object') {
31
- if (ArrayBuffer.isView(input) && !(input instanceof DataView) && !Buffer.isBuffer(input)) {
32
- const v = input as ArrayBufferView
33
- return Buffer.from(v.buffer, v.byteOffset, v.byteLength)
34
- }
35
- const o = input as unknown as Record<string, unknown>
36
- const p = o.path ?? o.filePath
37
- if (typeof p === 'string' && p.trim()) return p.trim()
38
- }
39
- return input
40
- }
41
-
42
- /** 常见可在浏览器内联预览的类型(避免一律 application/octet-stream 触发下载) */
43
- const PREVIEW_EXT_MIME: Record<string, string> = {
44
- '.jpg': 'image/jpeg',
45
- '.jpeg': 'image/jpeg',
46
- '.png': 'image/png',
47
- '.gif': 'image/gif',
48
- '.webp': 'image/webp',
49
- '.bmp': 'image/bmp',
50
- '.svg': 'image/svg+xml',
51
- '.ico': 'image/x-icon',
52
- '.avif': 'image/avif',
53
- '.heic': 'image/heic',
54
- '.heif': 'image/heif',
55
- '.pdf': 'application/pdf',
56
- '.mp4': 'video/mp4',
57
- '.webm': 'video/webm',
58
- '.mov': 'video/quicktime',
59
- '.mp3': 'audio/mpeg',
60
- '.wav': 'audio/wav',
61
- '.ogg': 'audio/ogg',
62
- '.opus': 'audio/opus',
63
- '.m4a': 'audio/mp4',
64
- '.aac': 'audio/aac',
65
- '.flac': 'audio/flac',
66
- '.txt': 'text/plain; charset=utf-8',
67
- '.log': 'text/plain; charset=utf-8',
68
- '.csv': 'text/csv; charset=utf-8',
69
- '.html': 'text/html; charset=utf-8',
70
- '.htm': 'text/html; charset=utf-8',
71
- '.css': 'text/css; charset=utf-8',
72
- '.js': 'text/javascript; charset=utf-8',
73
- '.mjs': 'text/javascript; charset=utf-8',
74
- '.json': 'application/json; charset=utf-8',
75
- '.xml': 'application/xml; charset=utf-8',
76
- '.md': 'text/markdown; charset=utf-8',
77
- '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
78
- '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
79
- '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
80
- }
81
-
82
- function mimeFromPathOrName(pathOrName: string): string | undefined {
83
- const base = pathOrName.split(/[/\\]/).pop() ?? pathOrName
84
- const ext = extname(base).toLowerCase()
85
- return ext ? PREVIEW_EXT_MIME[ext] : undefined
86
- }
87
-
88
- /** 解析上传 Content-Type,并配合 Content-Disposition: inline 便于直链预览 */
89
- function resolveMime(input: File | string | Buffer, fileNameHint?: string): string {
90
- if (typeof input === 'string') {
91
- return mimeFromPathOrName(input) ?? 'application/octet-stream'
92
- }
93
- if (Buffer.isBuffer(input)) {
94
- if (fileNameHint) {
95
- const fromName = mimeFromPathOrName(fileNameHint)
96
- if (fromName) return fromName
97
- }
98
- return 'application/octet-stream'
99
- }
100
- const declared = input.type?.trim()
101
- if (declared && declared !== 'application/octet-stream') {
102
- return declared
103
- }
104
- const name = typeof input.name === 'string' && input.name ? input.name : ''
105
- if (name) {
106
- const fromName = mimeFromPathOrName(name)
107
- if (fromName) return fromName
108
- }
109
- return declared || 'application/octet-stream'
110
- }
111
-
112
- /**
113
- * 将 File/路径/Buffer 转为 ali-oss 接受的类型。
114
- * 本地路径保持为字符串:put 内部用 contentLength + ReadStream,大文件也稳定。
115
- */
116
- async function toUploadContent(input: File | string | Buffer): Promise<{ content: Buffer | string; fileName: string }> {
117
- if (Buffer.isBuffer(input)) {
118
- return { content: input, fileName: 'file' }
119
- }
120
- if (typeof input === 'string') {
121
- return {
122
- content: input,
123
- fileName: input.split(/[/\\]/).pop() ?? 'file'
124
- }
125
- }
126
- const buf = Buffer.from(await input.arrayBuffer())
127
- const n = (input as { name?: string }).name
128
- return { content: buf, fileName: typeof n === 'string' && n ? n : 'file' }
129
- }
130
-
131
- export type OssUploadOptions = {
132
- /** 分片上传进度,p 为 0~1(仅大 Buffer 分片时触发) */
133
- onProgress?: (p: number) => void
134
- /** HTTP 超时(毫秒),覆盖默认 15 分钟;可传 `30 * 60 * 1000` 等 */
135
- timeoutMs?: number
136
- }
137
-
138
- export const ossUpload = async (
139
- rawFile: File | string | Buffer,
140
- botToken: string,
141
- isPrivate: 0 | 1 = 1,
142
- uploadOptions?: OssUploadOptions
143
- ) => {
144
- await getUserToken(botToken)
145
-
146
- const file = coerceOssFileInput(rawFile)
147
- const { content, fileName } = await toUploadContent(file)
148
- const data = await getStsToken(fileName, botToken, isPrivate)
149
- const mime = resolveMime(file, fileName)
150
- const onProgress = uploadOptions?.onProgress
151
-
152
- const options: OSS.Options = {
153
- // 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
154
- accessKeyId: data.tempAccessKeyId,
155
- accessKeySecret: data.tempAccessKeySecret,
156
- // 从STS服务获取的安全令牌(SecurityToken)。
157
- stsToken: data.tempSecurityToken,
158
- // 填写Bucket名称。
159
- bucket: data.bucket,
160
- endpoint: data.endPoint,
161
- region: data.region,
162
- secure: true,
163
- cname: true,
164
- authorizationV4: true,
165
- timeout: uploadOptions?.timeoutMs ?? OSS_HTTP_TIMEOUT_MS
166
- }
167
-
168
- const client = new OSS(options)
169
-
170
- const name = `${data.uploadDir}${data.ossFileKey}`
171
-
172
- try {
173
- let objectResult: OSS.PutObjectResult | OSS.CompleteMultipartUploadResult
174
-
175
- const multipartUploadOptions: OSS.MultipartUploadOptions = {
176
- progress: (p: number) => {
177
- onProgress?.(p)
178
- },
179
- parallel: 4,
180
- partSize: MULTIPART_PART_SIZE,
181
- mime,
182
- /** 直链打开时优先内联展示,而非附件下载 */
183
- headers: {
184
- 'Content-Disposition': 'inline'
185
- }
186
- }
187
- objectResult = await client.multipartUpload(name, content, multipartUploadOptions)
188
-
189
- if (objectResult?.res?.status !== 200) {
190
- dcgLogger(`OSS 上传失败, ${objectResult?.res?.status}`, 'error')
191
- return ''
192
- }
193
- const requestUrls = objectResult?.res?.requestUrls || []
194
- const url = requestUrls[0] || ''
195
- dcgLogger(`OSS 上传成功, ${isPrivate === 1 ? objectResult.name || url : url}`)
196
- return isPrivate === 1 ? objectResult.name || url : url
197
- } catch (error) {
198
- dcgLogger(`OSS 上传失败: ${error}`, 'error')
199
- }
200
- }
@@ -1,191 +0,0 @@
1
- import axios from 'axios'
2
- // @ts-ignore
3
- import md5 from 'md5'
4
- import type { IResponse } from '../types.js'
5
- import { getUserTokenCache } from './userInfo.js'
6
- import { getEffectiveMsgParams } from '../utils/params.js'
7
- import { ENV } from '../utils/constant.js'
8
- import { dcgLogger } from '../utils/log.js'
9
-
10
- export const apiUrlMap = {
11
- production: 'https://api-gateway.shuwenda.com',
12
- test: 'https://api-gateway.shuwenda.icu',
13
- develop: 'https://shenyu-dev.shuwenda.icu'
14
- }
15
-
16
- export const appKey = {
17
- production: '2A1C74D315CB4A01BF3DA8983695AFE2',
18
- test: '7374A073CCBD4C8CA84FAD33896F0B69',
19
- develop: '7374A073CCBD4C8CA84FAD33896F0B69'
20
- }
21
-
22
- export const signKey = {
23
- production: '34E9023008EA445AAE6CC075CC954F46',
24
- test: 'FE93D3322CB94E978CE95BD4AA2A37D7',
25
- develop: 'FE93D3322CB94E978CE95BD4AA2A37D7'
26
- }
27
-
28
- export const version = '1.0.0'
29
-
30
- /**
31
- * 根据 axios 请求配置生成等价 curl,便于复制给后端排查
32
- */
33
- function toCurl(config: {
34
- baseURL?: string
35
- url?: string
36
- method?: string
37
- headers?: Record<string, string | number | undefined>
38
- data?: unknown
39
- }): string {
40
- const base = config.baseURL ?? ''
41
- const path = config.url ?? ''
42
- const url = path.startsWith('http') ? path : `${base.replace(/\/$/, '')}/${path.replace(/^\//, '')}`
43
- const method = (config.method ?? 'GET').toUpperCase()
44
- const headers = config.headers ?? {}
45
- const parts = ['curl', '-X', method, `'${url}'`]
46
- for (const [k, v] of Object.entries(headers)) {
47
- if (v !== undefined && v !== '') {
48
- parts.push('-H', `'${k}: ${v}'`)
49
- }
50
- }
51
- if (method !== 'GET' && config.data !== undefined) {
52
- const body = typeof config.data === 'string' ? config.data : JSON.stringify(config.data)
53
- parts.push('-d', `'${body.replace(/'/g, "'\\''")}'`)
54
- }
55
- return parts.join(' ')
56
- }
57
-
58
- /**
59
- * 生成签名
60
- * @param {Object} body 请求体
61
- * @param {number} timestamp 时间戳
62
- * @param {string} path 请求地址
63
- * @param {'production' | 'test' | 'develop'} ENV 请求环境
64
- * @param {string} version 版本号
65
- * @returns {string} 大写 MD5 签名
66
- */
67
- export function getSignature(
68
- body: Record<string, unknown>,
69
- timestamp: number,
70
- path: string,
71
- ENV: 'production' | 'test' | 'develop',
72
- version: string = '1.0.0'
73
- ) {
74
- // 1. 构造 map
75
- const map = { timestamp, path, version, ...body }
76
- // 2. 按 key 进行自然排序
77
- const sortedKeys = Object.keys(map).sort()
78
- // 3. 拼接 key + value
79
- const signStr =
80
- sortedKeys
81
- .map((key) => {
82
- const val = map[key as keyof typeof map]
83
- return val === undefined ? '' : `${key}${typeof val === 'object' ? JSON.stringify(val) : val}`
84
- })
85
- .join('') + signKey[ENV]
86
- // 4. MD5 加密并转大写
87
- return md5(signStr).toUpperCase()
88
- }
89
-
90
- function buildHeaders(data: Record<string, unknown>, url: string, userToken?: string) {
91
- const timestamp = Date.now()
92
-
93
- const headers: Record<string, string | number> = {
94
- 'Content-Type': 'application/json',
95
- appKey: appKey[ENV],
96
- sign: getSignature(data, timestamp, url, ENV, version),
97
- timestamp,
98
- version
99
- }
100
-
101
- // 如果提供了 userToken,添加到 headers
102
- if (userToken) {
103
- headers.authorization = userToken
104
- }
105
-
106
- return headers
107
- }
108
-
109
- const axiosInstance = axios.create({
110
- baseURL: apiUrlMap[ENV],
111
- timeout: 60000
112
- })
113
-
114
- // 请求拦截器:自动注入 userToken
115
- axiosInstance.interceptors.request.use(
116
- (config) => {
117
- // 如果请求配置中已经有 authorization,优先使用
118
- if (config.headers?.authorization) {
119
- return config
120
- }
121
-
122
- // 从请求上下文中获取 botToken(需要在调用时设置)
123
- const botToken = (config as any).__botToken as string | undefined
124
- if (botToken) {
125
- const cachedToken = getUserTokenCache(botToken)
126
- if (cachedToken) {
127
- config.headers = config.headers || {}
128
- config.headers.authorization = cachedToken
129
- }
130
- }
131
-
132
- return config
133
- },
134
- (error) => {
135
- return Promise.reject(error)
136
- }
137
- )
138
-
139
- // 响应拦截器:打印 curl 便于调试
140
- axiosInstance.interceptors.response.use(
141
- (response) => {
142
- return response.data
143
- },
144
- (error) => {
145
- const config = error.config ?? {}
146
- const curl = toCurl(config)
147
- dcgLogger(`[request] curl for backend (failed request): ${curl}`, 'error')
148
- return Promise.reject(error)
149
- }
150
- )
151
-
152
- /**
153
- * POST 请求(支持可选的 userToken 和 botToken)
154
- * @param url 请求路径
155
- * @param data 请求体
156
- * @param options 可选配置
157
- * @param options.userToken 直接提供的 userToken(优先级最高)
158
- * @param options.botToken 用于从缓存获取 userToken 的 botToken
159
- */
160
- export function post<T = Record<string, unknown>, R = unknown>(
161
- url: string,
162
- data: T,
163
- options?: {
164
- userToken?: string
165
- botToken?: string
166
- }
167
- ): Promise<IResponse<R>> {
168
- const params = getEffectiveMsgParams() || { appId: 100 }
169
- const config: any = {
170
- method: 'POST',
171
- url,
172
- data: {
173
- ...data,
174
- _appId: params.appId
175
- },
176
- headers: buildHeaders(
177
- {
178
- ...data,
179
- _appId: params.appId
180
- } as Record<string, unknown>,
181
- url,
182
- options?.userToken
183
- )
184
- }
185
-
186
- // 将 botToken 附加到配置中,供请求拦截器使用
187
- if (options?.botToken) {
188
- config.__botToken = options.botToken
189
- }
190
- return axiosInstance.request(config)
191
- }
@@ -1,44 +0,0 @@
1
- /**
2
- * userToken 缓存管理模块
3
- * 负责维护 botToken -> userToken 的映射关系,支持自动过期
4
- */
5
-
6
- // userToken 缓存配置
7
- const TOKEN_CACHE_DURATION = 60 * 60 * 1000 // 1小时
8
-
9
- type TokenCacheEntry = {
10
- token: string
11
- expiresAt: number
12
- }
13
-
14
- // 内存缓存:botToken -> { token, expiresAt }
15
- const tokenCache = new Map<string, TokenCacheEntry>()
16
-
17
- /**
18
- * 设置 userToken 缓存
19
- * @param botToken 机器人 token
20
- * @param userToken 用户 token
21
- */
22
- export function setUserTokenCache(botToken: string, userToken: string): void {
23
- const expiresAt = Date.now() + TOKEN_CACHE_DURATION
24
- tokenCache.set(botToken, { token: userToken, expiresAt })
25
- }
26
-
27
- /**
28
- * 获取 userToken 缓存(自动检查过期)
29
- * @param botToken 机器人 token
30
- * @returns userToken 或 null(未找到或已过期)
31
- */
32
- export function getUserTokenCache(botToken: string): string | null {
33
- const entry = tokenCache.get(botToken)
34
- if (!entry) {
35
- return null
36
- }
37
-
38
- // 检查是否过期
39
- if (Date.now() >= entry.expiresAt) {
40
- tokenCache.delete(botToken)
41
- return null
42
- }
43
- return entry.token
44
- }
package/src/session.ts DELETED
@@ -1,28 +0,0 @@
1
- import { sendGatewayRpc } from './gateway/socket.js'
2
- import { getSessionKey } from './utils/global.js'
3
- import { dcgLogger } from './utils/log.js'
4
- import { cleanupTerminatedSession } from './utils/sessionTermination.js'
5
-
6
- interface TSession {
7
- agent_id: string
8
- session_id: string
9
- agent_clone_code?: string
10
- account_id: string
11
- }
12
-
13
- export const onRemoveSession = async ({ agent_id, session_id, agent_clone_code, account_id }: TSession) => {
14
- const sessionKey = getSessionKey({ agent_id, session_id, agent_clone_code }, account_id)
15
- if (!session_id) {
16
- dcgLogger('onRemoveSession: empty session_id', 'error')
17
- return
18
- }
19
-
20
- try {
21
- await sendGatewayRpc({ method: 'sessions.delete', params: { key: sessionKey, deleteTranscript: true } })
22
- } catch (e) {
23
- const error = e instanceof Error ? e : new Error(String(e))
24
- dcgLogger(`onRemoveSession: ${error.message}`, 'error')
25
- } finally {
26
- cleanupTerminatedSession(sessionKey)
27
- }
28
- }
package/src/skill.ts DELETED
@@ -1,114 +0,0 @@
1
- import axios from 'axios'
2
- import fsPromise from 'fs/promises'
3
- import path from 'path'
4
- import { getWorkspaceDir } from './utils/global.js'
5
- import { getWsConnection } from './utils/global.js'
6
- import { dcgLogger } from './utils/log.js'
7
- import { isWsOpen } from './transport.js'
8
- import { sendGatewayRpc } from './gateway/socket.js'
9
- import { extractZipBufferToDirectory } from './utils/zipExtract.js'
10
- import { retryWithBackoff } from './utils/constant.js'
11
-
12
- type ISkillParams = {
13
- path: string
14
- code: string
15
- }
16
-
17
- interface SkillEventMessage {
18
- event_type?: string
19
- operation_type?: string
20
- skill_url?: string
21
- skill_code?: string
22
- skill_id?: string
23
- bot_token?: string
24
- websocket_trace_id?: string
25
- status?: string
26
- [key: string]: unknown
27
- }
28
-
29
- function sendEvent(msgContent: SkillEventMessage) {
30
- const ws = getWsConnection()
31
- if (isWsOpen()) {
32
- const msg = JSON.stringify({
33
- messageType: 'openclaw_bot_event',
34
- source: 'client',
35
- content: msgContent
36
- });
37
- ws?.send(msg);
38
- dcgLogger(`[Send]技能安装: ${msg}`)
39
- }
40
- }
41
-
42
- const SKILL_CODE_REGEXP = /^[A-Za-z][A-Za-z0-9_-]*$/;
43
-
44
- function normalizeCode(code: string) {
45
- const trimmedCode = code?.trim() || ''
46
- if (!trimmedCode) {
47
- dcgLogger('skill code is empty', 'error')
48
- return ''
49
- }
50
-
51
- if (!SKILL_CODE_REGEXP.test(trimmedCode)) {
52
- dcgLogger(`invalid skill code: ${trimmedCode}`, 'error')
53
- return ''
54
- }
55
-
56
- return trimmedCode
57
- }
58
-
59
- export async function installSkill(params: ISkillParams, msgContent: SkillEventMessage) {
60
- const { path: cdnUrl } = params
61
-
62
- const code = normalizeCode(params.code)
63
- if (!code) {
64
- sendEvent({ ...msgContent, status: 'fail' })
65
- return
66
- }
67
-
68
- const workspacePath = getWorkspaceDir()
69
-
70
- // 确保 skills 目录存在
71
- const skillsDir = path.join(workspacePath, 'skills')
72
- await fsPromise.mkdir(skillsDir, { recursive: true });
73
-
74
- // 如果目标目录已存在,先删除
75
- const skillDir = path.join(skillsDir, code)
76
- await fsPromise.rm(skillDir, { recursive: true, force: true }).catch((e) =>
77
- dcgLogger(`skill cleanup before install failed: ${String(e)}`)
78
- );
79
-
80
- try {
81
- const response = await retryWithBackoff(() => axios({
82
- method: 'get',
83
- url: cdnUrl,
84
- responseType: 'arraybuffer'
85
- }), 3)
86
- await fsPromise.mkdir(skillDir, { recursive: true })
87
- // 与 extractZipBufferToDirectory 一致:仅当全体条目共用一个顶层目录时剥掉该层;根上多文件/多文件夹并列则不剥
88
- await extractZipBufferToDirectory(Buffer.from(response.data), skillDir)
89
- sendEvent({ ...msgContent, status: 'ok' })
90
- sendGatewayRpc({ method: 'skills.status', params: {} })
91
- } catch (error) {
92
- // 如果安装失败,清理目录
93
- await fsPromise.rm(skillDir, { recursive: true, force: true }).catch((e) =>
94
- dcgLogger(`skill cleanup after install failure failed: ${String(e)}`)
95
- )
96
-
97
- sendEvent({ ...msgContent, status: 'fail' })
98
- }
99
- }
100
-
101
- export async function uninstallSkill(params: Omit<ISkillParams, 'path'>, msgContent: SkillEventMessage) {
102
- const code = normalizeCode(params.code)
103
- if (!code) {
104
- sendEvent({ ...msgContent, status: 'fail' })
105
- return
106
- }
107
-
108
- const workspacePath = getWorkspaceDir()
109
- if (workspacePath) {
110
- const skillDir = path.join(workspacePath, 'skills', code)
111
- await fsPromise.rm(skillDir, { recursive: true, force: true }).catch((e) => dcgLogger(`skill cleanup failed: ${String(e)}`))
112
- }
113
- sendEvent({ ...msgContent, status: 'ok' })
114
- }