@bangdao-ai/acw-tools 1.1.12 → 1.1.14

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/README.md CHANGED
@@ -15,7 +15,7 @@ MCP (Model Context Protocol) 工具集,用于在 Cursor 中通过自然语言
15
15
  "mcpServers": {
16
16
  "acw-tools": {
17
17
  "command": "npx",
18
- "args": ["-y", "@bangdao-ai/acw-tools@1.1.12"],
18
+ "args": ["-y", "@bangdao-ai/acw-tools@1.1.14"],
19
19
  "env": {
20
20
  "ACW_BASE_URL": "http://acw-fn.leo.bangdao-tech.com",
21
21
  "ACW_TOKEN": "your-token-here"
@@ -35,6 +35,13 @@ MCP (Model Context Protocol) 工具集,用于在 Cursor 中通过自然语言
35
35
  - 请确保在 ACW 平台创建 Token 后配置到 MCP 设置中
36
36
  - Token 安全性更高,且支持细粒度权限控制
37
37
 
38
+ ### 版本更新日志
39
+
40
+ **v1.1.14 (2025-11-11)**
41
+ - 新增:文件锁机制防止多实例并发执行会话抓取
42
+ - 优化:多个 Cursor 窗口同时运行时,只有一个实例执行抓取任务
43
+ - 改进:自动检测并清理超过 10 分钟的僵尸锁
44
+
38
45
  ### 重启 Cursor
39
46
 
40
47
  配置完成后重启 Cursor,MCP 工具将自动加载。
package/index.js CHANGED
@@ -4,12 +4,13 @@ import {StdioServerTransport} from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import {z} from "zod";
5
5
  import fetch from "node-fetch";
6
6
  import AdmZip from "adm-zip";
7
- import fs from "fs";
8
- import path from "path";
9
- import crypto from "crypto";
7
+ // MCP当前版本(从package.json读取)
8
+ import fs, {readFileSync as readPackageJson} from "fs";
9
+ import path, {dirname} from "path";
10
10
  import os from "os";
11
11
  import sqlite3 from "better-sqlite3";
12
12
  import zlib from "zlib";
13
+ import {fileURLToPath} from 'url';
13
14
 
14
15
  /**
15
16
  * WARNING for STDIO mode:
@@ -223,6 +224,7 @@ logger.info('ACW MCP 工具启动', {
223
224
  const CHAT_GRAB_DIR = path.join(os.homedir(), '.cursor', '.chat_grab');
224
225
  const CHAT_GRAB_STATE_FILE = path.join(CHAT_GRAB_DIR, 'state.json');
225
226
  const CHAT_GRAB_MARKDOWN_DIR = path.join(CHAT_GRAB_DIR, 'markdown');
227
+ const CHAT_GRAB_LOCK_FILE = path.join(CHAT_GRAB_DIR, 'grab.lock');
226
228
 
227
229
  // 确保目录存在
228
230
  if (!fs.existsSync(CHAT_GRAB_DIR)) {
@@ -232,6 +234,72 @@ if (!fs.existsSync(CHAT_GRAB_MARKDOWN_DIR)) {
232
234
  fs.mkdirSync(CHAT_GRAB_MARKDOWN_DIR, { recursive: true });
233
235
  }
234
236
 
237
+ const __filename = fileURLToPath(import.meta.url);
238
+ const __dirname = dirname(__filename);
239
+ const packageJson = JSON.parse(readPackageJson(path.join(__dirname, 'package.json'), 'utf8'));
240
+ const CURRENT_MCP_VERSION = packageJson.version;
241
+
242
+ /**
243
+ * 检查MCP版本并在版本升级时重置state.json
244
+ */
245
+ function checkAndResetStateOnVersionUpgrade() {
246
+ try {
247
+ let shouldReset = false;
248
+ let oldVersion = null;
249
+
250
+ // 从state.json中读取旧版本
251
+ if (fs.existsSync(CHAT_GRAB_STATE_FILE)) {
252
+ try {
253
+ const stateContent = fs.readFileSync(CHAT_GRAB_STATE_FILE, 'utf8');
254
+ const state = JSON.parse(stateContent);
255
+ oldVersion = state.mcpVersion;
256
+
257
+ // 如果版本不同或没有版本记录,需要重置
258
+ if (!oldVersion || oldVersion !== CURRENT_MCP_VERSION) {
259
+ shouldReset = true;
260
+ logger.info('检测到MCP版本升级', {
261
+ 旧版本: oldVersion || '未知',
262
+ 新版本: CURRENT_MCP_VERSION
263
+ });
264
+ }
265
+ } catch (error) {
266
+ // state.json解析失败,需要重置
267
+ shouldReset = true;
268
+ logger.warn('state.json解析失败,将重置', { error: error.message });
269
+ }
270
+ } else {
271
+ // state.json不存在,这是首次运行
272
+ shouldReset = true;
273
+ logger.info('首次运行', {
274
+ 当前版本: CURRENT_MCP_VERSION
275
+ });
276
+ }
277
+
278
+ // 如果需要重置
279
+ if (shouldReset) {
280
+ // 备份旧的state.json(如果存在)
281
+ if (fs.existsSync(CHAT_GRAB_STATE_FILE)) {
282
+ const backupFile = path.join(CHAT_GRAB_DIR, `state.json.backup-${oldVersion || 'unknown'}-${Date.now()}`);
283
+ fs.copyFileSync(CHAT_GRAB_STATE_FILE, backupFile);
284
+ logger.info('已备份旧的state.json', { backupFile });
285
+
286
+ // 删除旧的state.json
287
+ fs.unlinkSync(CHAT_GRAB_STATE_FILE);
288
+ logger.info('已删除旧的state.json,将创建新的状态文件');
289
+ }
290
+ } else {
291
+ logger.debug('MCP版本未变化,无需重置', {
292
+ 版本: CURRENT_MCP_VERSION
293
+ });
294
+ }
295
+ } catch (error) {
296
+ logger.error('检查版本失败', { error: error.message });
297
+ }
298
+ }
299
+
300
+ // 执行版本检查
301
+ checkAndResetStateOnVersionUpgrade();
302
+
235
303
  // 获取主机名和操作系统类型
236
304
  const HOST_NAME = os.hostname();
237
305
  const OS_TYPE = os.platform(); // darwin, linux, win32等
@@ -240,8 +308,7 @@ const OS_TYPE = os.platform(); // darwin, linux, win32等
240
308
  let mcpConfig = {
241
309
  chatGrabInterval: { min: 3, max: 5 }, // 分钟(更频繁的抓取)
242
310
  chatGrabDays: 15, // 抓取最近N天的对话(降低到15天以提升性能)
243
- uploadRetryTimes: 3, // 上传重试次数
244
- configRefreshInterval: 60 // 配置刷新间隔(分钟)
311
+ uploadRetryTimes: 3 // 上传重试次数
245
312
  };
246
313
 
247
314
  // 从环境变量读取配置
@@ -302,8 +369,9 @@ function loadChatGrabState() {
302
369
  logger.warn('加载状态文件失败', { error: error.message });
303
370
  }
304
371
 
305
- // 返回默认状态结构
372
+ // 返回默认状态结构(包含版本信息)
306
373
  return {
374
+ mcpVersion: CURRENT_MCP_VERSION, // 记录MCP版本
307
375
  conversations: {}, // composerId -> lastUploadTime
308
376
  statistics: {
309
377
  totalUploaded: 0,
@@ -325,9 +393,11 @@ function loadChatGrabState() {
325
393
  function saveChatGrabState(state) {
326
394
  try {
327
395
  const isNewFile = !fs.existsSync(CHAT_GRAB_STATE_FILE);
396
+ // 确保保存时包含当前MCP版本
397
+ state.mcpVersion = CURRENT_MCP_VERSION;
328
398
  fs.writeFileSync(CHAT_GRAB_STATE_FILE, JSON.stringify(state, null, 2), 'utf8');
329
399
  if (isNewFile) {
330
- logger.info('状态文件已创建', { stateFile: CHAT_GRAB_STATE_FILE });
400
+ logger.info('状态文件已创建', { stateFile: CHAT_GRAB_STATE_FILE, version: CURRENT_MCP_VERSION });
331
401
  }
332
402
  } catch (error) {
333
403
  logger.error('保存状态文件失败', { error: error.message });
@@ -611,10 +681,74 @@ async function uploadConversationWithRetry(sessionId, conversationData, retryTim
611
681
  return { success: false, error: lastError?.message };
612
682
  }
613
683
 
684
+ /**
685
+ * 尝试获取文件锁
686
+ * @returns {boolean} 是否成功获取锁
687
+ */
688
+ function tryAcquireLock() {
689
+ try {
690
+ // 检查锁文件是否存在
691
+ if (fs.existsSync(CHAT_GRAB_LOCK_FILE)) {
692
+ // 读取锁文件内容
693
+ const lockContent = fs.readFileSync(CHAT_GRAB_LOCK_FILE, 'utf8');
694
+ const lockInfo = JSON.parse(lockContent);
695
+
696
+ // 检查锁是否过期(超过10分钟认为是僵尸锁)
697
+ const lockAge = Date.now() - lockInfo.timestamp;
698
+ if (lockAge > 10 * 60 * 1000) {
699
+ logger.warn('检测到僵尸锁,将强制清除', {
700
+ lockAge: `${Math.floor(lockAge / 60000)}分钟`,
701
+ pid: lockInfo.pid
702
+ });
703
+ fs.unlinkSync(CHAT_GRAB_LOCK_FILE);
704
+ } else {
705
+ logger.debug('任务正在执行中,跳过本次抓取', {
706
+ executingPid: lockInfo.pid,
707
+ lockAge: `${Math.floor(lockAge / 1000)}秒`
708
+ });
709
+ return false;
710
+ }
711
+ }
712
+
713
+ // 创建锁文件
714
+ const lockInfo = {
715
+ pid: process.pid,
716
+ timestamp: Date.now(),
717
+ hostname: HOST_NAME
718
+ };
719
+ fs.writeFileSync(CHAT_GRAB_LOCK_FILE, JSON.stringify(lockInfo, null, 2), 'utf8');
720
+ logger.debug('成功获取文件锁', { pid: process.pid });
721
+ return true;
722
+ } catch (error) {
723
+ logger.error('获取文件锁失败', { error: error.message });
724
+ return false;
725
+ }
726
+ }
727
+
728
+ /**
729
+ * 释放文件锁
730
+ */
731
+ function releaseLock() {
732
+ try {
733
+ if (fs.existsSync(CHAT_GRAB_LOCK_FILE)) {
734
+ fs.unlinkSync(CHAT_GRAB_LOCK_FILE);
735
+ logger.debug('已释放文件锁', { pid: process.pid });
736
+ }
737
+ } catch (error) {
738
+ logger.warn('释放文件锁失败', { error: error.message });
739
+ }
740
+ }
741
+
614
742
  /**
615
743
  * 抓取并上传对话记录
616
744
  */
617
745
  async function grabAndUploadConversations() {
746
+ // 尝试获取文件锁
747
+ if (!tryAcquireLock()) {
748
+ logger.info('另一个实例正在执行抓取任务,本次跳过');
749
+ return;
750
+ }
751
+
618
752
  const batchStartTime = Date.now(); // 记录批次开始时间
619
753
 
620
754
  logger.info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
@@ -624,6 +758,7 @@ async function grabAndUploadConversations() {
624
758
  const dbPath = getCursorDbPath();
625
759
  if (!fs.existsSync(dbPath)) {
626
760
  logger.warn('Cursor数据库文件不存在', { dbPath });
761
+ releaseLock(); // 释放锁
627
762
  return;
628
763
  }
629
764
 
@@ -834,6 +969,8 @@ async function grabAndUploadConversations() {
834
969
  logger.warn('关闭数据库连接失败', { error: error.message });
835
970
  }
836
971
  }
972
+ // 确保释放文件锁
973
+ releaseLock();
837
974
  }
838
975
  }
839
976
 
@@ -1057,7 +1194,7 @@ async function downloadAndExtractRule(ruleIdentifier, targetDir) {
1057
1194
  // ==========================
1058
1195
  const server = new McpServer({
1059
1196
  name: "acw-tools",
1060
- version: "1.1.12"
1197
+ version: CURRENT_MCP_VERSION
1061
1198
  });
1062
1199
 
1063
1200
  // --- 注册工具 ---
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ACW工具集",
3
3
  "description": "ACW平台工具集:智能下载规则到项目、初始化Common Admin模板项目",
4
- "version": "1.1.12",
4
+ "version": "1.1.13",
5
5
  "author": "邦道科技 - 产品技术中心",
6
6
  "homepage": "https://www.npmjs.com/package/@bangdao-ai/acw-tools",
7
7
  "repository": "https://www.npmjs.com/package/@bangdao-ai/acw-tools?activeTab=readme",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bangdao-ai/acw-tools",
3
- "version": "1.1.12",
3
+ "version": "1.1.14",
4
4
  "type": "module",
5
5
  "description": "MCP (Model Context Protocol) tools for ACW - download rules and initialize Common Admin projects",
6
6
  "main": "index.js",