claw-subagent-service 0.0.19 → 0.0.21

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/cli.js CHANGED
@@ -47,11 +47,24 @@ function installService() {
47
47
  console.log('[CLI] 安装系统服务...');
48
48
 
49
49
  const platform = process.platform;
50
- const execPath = process.execPath;
51
50
 
52
51
  if (platform === 'win32') {
53
52
  // Windows: 使用 node-windows 注册服务(正确处理路径引号)
54
53
  console.log('[CLI] 使用 node-windows 安装服务...');
54
+
55
+ // 强制删除可能存在的旧服务(确保配置更新,包括环境变量)
56
+ try {
57
+ console.log('[CLI] 清理旧服务...');
58
+ execSync(`net stop "${SERVICE_NAME}" 2>nul`, { stdio: 'ignore', timeout: 10000 });
59
+ } catch (e) {}
60
+ try {
61
+ execSync(`sc.exe delete "${SERVICE_NAME}" 2>nul`, { stdio: 'ignore', timeout: 10000 });
62
+ console.log('[CLI] 旧服务已删除');
63
+ } catch (e) {}
64
+ try {
65
+ execSync('timeout /t 2 /nobreak >nul 2>&1', { stdio: 'ignore', timeout: 5000 });
66
+ } catch (e) {}
67
+
55
68
  try {
56
69
  const userHome = process.env.USERPROFILE || os.homedir();
57
70
  const Service = require('node-windows').Service;
@@ -62,10 +75,11 @@ function installService() {
62
75
  nodeOptions: ['--harmony', '--max_old_space_size=4096'],
63
76
  env: {
64
77
  USERPROFILE: userHome,
65
- HOME: userHome
78
+ HOME: userHome,
79
+ CLAW_SERVICE_HOME: userHome
66
80
  }
67
81
  });
68
- console.log(`[CLI] 服务环境变量 USERPROFILE=${userHome}`);
82
+ console.log(`[CLI] 服务环境变量 USERPROFILE=${userHome}, CLAW_SERVICE_HOME=${userHome}`);
69
83
 
70
84
  svc.on('install', () => {
71
85
  console.log('[CLI] 服务安装成功');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "虾说静态服务",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -58,7 +58,8 @@ if (platform === 'win32') {
58
58
  abortOnError: false,
59
59
  env: {
60
60
  USERPROFILE: userHome,
61
- HOME: userHome
61
+ HOME: userHome,
62
+ CLAW_SERVICE_HOME: userHome
62
63
  }
63
64
  });
64
65
 
@@ -51,6 +51,19 @@ function installAndStartService() {
51
51
 
52
52
  console.log('[postinstall] 正在注册系统服务...');
53
53
 
54
+ // 强制删除可能存在的旧服务(确保配置更新,包括环境变量)
55
+ try {
56
+ console.log('[postinstall] 清理旧服务...');
57
+ execSync(`net stop "${SERVICE_NAME}" 2>nul`, { stdio: 'ignore', timeout: 10000 });
58
+ } catch (e) {}
59
+ try {
60
+ execSync(`sc.exe delete "${SERVICE_NAME}" 2>nul`, { stdio: 'ignore', timeout: 10000 });
61
+ console.log('[postinstall] 旧服务已删除');
62
+ } catch (e) {}
63
+ try {
64
+ execSync('timeout /t 2 /nobreak >nul 2>&1', { stdio: 'ignore', timeout: 5000 });
65
+ } catch (e) {}
66
+
54
67
  try {
55
68
  // 使用 node-windows 注册服务(正确处理路径引号)
56
69
  const Service = require('node-windows').Service;
@@ -62,10 +75,11 @@ function installAndStartService() {
62
75
  nodeOptions: ['--harmony', '--max_old_space_size=4096'],
63
76
  env: {
64
77
  USERPROFILE: userHome,
65
- HOME: userHome
78
+ HOME: userHome,
79
+ CLAW_SERVICE_HOME: userHome
66
80
  }
67
81
  });
68
- console.log(`[postinstall] 服务环境变量 USERPROFILE=${userHome}`);
82
+ console.log(`[postinstall] 服务环境变量 USERPROFILE=${userHome}, CLAW_SERVICE_HOME=${userHome}`);
69
83
 
70
84
  svc.on('install', () => {
71
85
  console.log('[postinstall] 服务注册成功,正在启动...');
@@ -1,35 +1,104 @@
1
1
  /**
2
2
  * npm preuninstall 钩子
3
3
  * 在卸载旧版本前停止并删除 Windows 服务,释放文件锁
4
+ *
5
+ * 设计原则:完全同步执行,多层兜底,不依赖 node-windows 异步事件。
6
+ * npm 运行此脚本时会等待进程退出,因此所有操作必须阻塞完成。
4
7
  */
5
- const path = require('path');
6
8
  const { execSync } = require('child_process');
7
9
 
8
10
  const SERVICES = ['SilentNodeService', 'claw-subagent-service'];
9
- const DAEMON_PATH = path.join(__dirname, '..', 'service', 'daemon.js');
11
+
12
+ /**
13
+ * 强制停止服务进程并删除服务注册表
14
+ */
15
+ function forceCleanupService(name) {
16
+ console.log(`[preuninstall] 开始清理服务: ${name}`);
17
+
18
+ // 1. 发送停止指令(服务可能正在运行)
19
+ try {
20
+ execSync(`net stop "${name}" 2>nul`, { stdio: 'ignore', timeout: 15000 });
21
+ console.log(`[preuninstall] 服务 ${name} 已发送停止指令`);
22
+ } catch (e) {
23
+ // 服务可能未运行,忽略
24
+ }
25
+
26
+ // 2. 通过 sc queryex 获取服务 PID 并强制终止
27
+ // 这是最精确的杀进程方式,直接终止锁定文件的进程
28
+ try {
29
+ const output = execSync(`sc queryex "${name}" 2>nul`, {
30
+ encoding: 'utf-8',
31
+ timeout: 5000,
32
+ stdio: ['ignore', 'pipe', 'ignore'],
33
+ });
34
+ const pidMatch = output.match(/PID\s*:\s*(\d+)/);
35
+ if (pidMatch) {
36
+ const pid = pidMatch[1];
37
+ console.log(`[preuninstall] 发现服务 PID=${pid},强制终止...`);
38
+ try {
39
+ execSync(`taskkill /f /pid ${pid} 2>nul`, { stdio: 'ignore', timeout: 10000 });
40
+ console.log(`[preuninstall] PID ${pid} 已强制终止`);
41
+ } catch (e) {
42
+ // PID 可能已退出
43
+ }
44
+ }
45
+ } catch (e) {
46
+ // 服务不存在或未运行
47
+ }
48
+
49
+ // 3. 按进程名强制杀掉 wrapper 可执行文件(node-windows 生成的 .exe)
50
+ try {
51
+ execSync(`taskkill /f /im "${name}.exe" 2>nul`, { stdio: 'ignore', timeout: 10000 });
52
+ console.log(`[preuninstall] ${name}.exe 已强制终止`);
53
+ } catch (e) {
54
+ // 进程可能不存在
55
+ }
56
+
57
+ // 4. 兜底:通过 PowerShell 按命令行路径匹配,杀掉所有包含 claw-subagent 的 node 进程
58
+ try {
59
+ execSync(
60
+ `powershell -NoProfile -Command "Get-Process node -ErrorAction SilentlyContinue | Where-Object {\$_.Path -like '*claw-subagent*'} | Stop-Process -Force -ErrorAction SilentlyContinue" 2>nul`,
61
+ { stdio: 'ignore', timeout: 15000 }
62
+ );
63
+ console.log(`[preuninstall] 相关 node 进程已清理`);
64
+ } catch (e) {
65
+ // 忽略
66
+ }
67
+
68
+ // 5. 使用 node-windows 卸载服务(删除 wrapper 文件)
69
+ try {
70
+ const Service = require('node-windows').Service;
71
+ const DAEMON_PATH = require('path').join(__dirname, '..', 'service', 'daemon.js');
72
+ const svc = new Service({ name, script: DAEMON_PATH });
73
+ svc.uninstall();
74
+ console.log(`[preuninstall] node-windows 卸载指令已发送`);
75
+ } catch (e) {
76
+ // node-windows 可能未安装或已卸载
77
+ }
78
+
79
+ // 6. 最终兜底:使用 sc.exe 直接从注册表删除服务
80
+ try {
81
+ execSync(`sc.exe delete "${name}" 2>nul`, { stdio: 'ignore', timeout: 10000 });
82
+ console.log(`[preuninstall] 服务 ${name} 已从注册表删除`);
83
+ } catch (e) {
84
+ // 服务可能已不存在
85
+ }
86
+
87
+ // 7. 短暂停顿,让操作系统回收文件句柄
88
+ try {
89
+ execSync('timeout /t 2 /nobreak >nul 2>&1', { stdio: 'ignore', timeout: 5000 });
90
+ } catch (e) {}
91
+ }
10
92
 
11
93
  if (process.platform === 'win32') {
12
94
  for (const name of SERVICES) {
13
- try {
14
- execSync(`net stop "${name}" 2>nul`, { stdio: 'ignore', timeout: 10000 });
15
- } catch (e) {}
16
-
17
- // 使用 node-windows 卸载服务(删除 wrapper 文件,释放文件锁)
18
- try {
19
- const Service = require('node-windows').Service;
20
- const svc = new Service({ name, script: DAEMON_PATH });
21
- svc.uninstall();
22
- } catch (e) {}
23
-
24
- // 兜底 - 使用 sc.exe 删除服务
25
- try {
26
- execSync(`sc.exe delete "${name}" 2>nul`, { stdio: 'ignore', timeout: 10000 });
27
- } catch (e) {}
95
+ forceCleanupService(name);
28
96
  }
29
97
 
30
- // 等待系统释放文件句柄
98
+ // 最后再等待几秒,确保 Windows 完全释放文件锁
31
99
  try {
32
- execSync('timeout /t 2 /nobreak >nul 2>&1', { stdio: 'ignore', timeout: 5000 });
100
+ execSync('timeout /t 3 /nobreak >nul 2>&1', { stdio: 'ignore', timeout: 5000 });
101
+ console.log('[preuninstall] 文件锁清理完成,npm 可以继续卸载');
33
102
  } catch (e) {}
34
103
  }
35
104
 
@@ -1,44 +1,79 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const os = require('os');
4
-
5
- function loadConfig() {
6
- // Read from ~/.claw-bridge/config.json
7
- const clawBridgePath = path.join(os.homedir(), '.claw-bridge', 'config.json');
8
- let clawBridgeConfig = {};
9
- if (fs.existsSync(clawBridgePath)) {
10
- try {
11
- clawBridgeConfig = JSON.parse(fs.readFileSync(clawBridgePath, 'utf-8'));
12
- } catch (e) {
13
- console.error('读取 claw-bridge 配置失败:', e);
14
- }
15
- }
16
-
17
- // Read from local rongcloud-config.json
18
- const localConfigPath = path.join(__dirname, '..', '..', 'rongcloud-config.json');
19
- let localConfig = {};
20
- if (fs.existsSync(localConfigPath)) {
21
- try {
22
- localConfig = JSON.parse(fs.readFileSync(localConfigPath, 'utf-8'));
23
- } catch (e) {
24
- console.error('读取本地配置失败:', e);
25
- }
26
- }
27
-
28
- return {
29
- appKey: process.env.DM_APP_KEY || localConfig.appKey || 'bmdehs6pbyyks',
30
- token: localConfig.token || clawBridgeConfig.token,
31
- accountId: localConfig.accountId || clawBridgeConfig.nodeId,
32
- nodeName: clawBridgeConfig.nodeName || 'cli-client',
33
- secretKey: localConfig.secretKey || 'secret_key',
34
- nickname: localConfig.nickname || 'CLI客户端',
35
- reconnectInterval: localConfig.reconnectInterval || 60,
36
- heartbeatInterval: localConfig.heartbeatInterval || 20,
37
- openclawPort: localConfig.openclawPort || 18789,
38
- scriptTimeout: localConfig.scriptTimeout || 180,
39
- successKeyword: localConfig.successKeyword || 'Success',
40
- chatTimeout: localConfig.chatTimeout || 600
41
- };
42
- }
43
-
44
- module.exports = { loadConfig };
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ /**
6
+ * 获取实际用户主目录
7
+ * Windows 服务以 SYSTEM 运行时 os.homedir() 返回 systemprofile,
8
+ * 优先使用 CLAW_SERVICE_HOME / USERPROFILE 环境变量,最后扫描 C:\Users
9
+ */
10
+ function getRealHomeDir() {
11
+ const envHome = process.env.CLAW_SERVICE_HOME || process.env.USERPROFILE || process.env.HOME;
12
+ if (envHome && !envHome.includes('systemprofile')) {
13
+ return envHome;
14
+ }
15
+ const homeDir = os.homedir();
16
+ if (!homeDir.includes('systemprofile')) {
17
+ return homeDir;
18
+ }
19
+ // SYSTEM 账户兜底:扫描 C:\Users 找包含 .claw-bridge 的实际用户目录
20
+ const usersDir = 'C:\\Users';
21
+ if (fs.existsSync(usersDir)) {
22
+ const entries = fs.readdirSync(usersDir, { withFileTypes: true });
23
+ for (const entry of entries) {
24
+ if (entry.isDirectory() && !['Public', 'Default', 'All Users', 'Default User'].includes(entry.name)) {
25
+ const candidate = path.join(usersDir, entry.name);
26
+ if (fs.existsSync(path.join(candidate, '.claw-bridge', 'config.json'))) {
27
+ return candidate;
28
+ }
29
+ }
30
+ }
31
+ }
32
+ return homeDir;
33
+ }
34
+
35
+ function loadConfig() {
36
+ const homeDir = getRealHomeDir();
37
+
38
+ // Read from ~/.claw-bridge/config.json
39
+ const clawBridgePath = path.join(homeDir, '.claw-bridge', 'config.json');
40
+ let clawBridgeConfig = {};
41
+ if (fs.existsSync(clawBridgePath)) {
42
+ try {
43
+ clawBridgeConfig = JSON.parse(fs.readFileSync(clawBridgePath, 'utf-8'));
44
+ } catch (e) {
45
+ console.error('读取 claw-bridge 配置失败:', e);
46
+ }
47
+ }
48
+ if (!fs.existsSync(clawBridgePath)) {
49
+ console.warn(`[CONFIG] 未找到 ${clawBridgePath} (home=${homeDir})`);
50
+ }
51
+
52
+ // Read from local rongcloud-config.json
53
+ const localConfigPath = path.join(__dirname, '..', '..', 'rongcloud-config.json');
54
+ let localConfig = {};
55
+ if (fs.existsSync(localConfigPath)) {
56
+ try {
57
+ localConfig = JSON.parse(fs.readFileSync(localConfigPath, 'utf-8'));
58
+ } catch (e) {
59
+ console.error('读取本地配置失败:', e);
60
+ }
61
+ }
62
+
63
+ return {
64
+ appKey: process.env.DM_APP_KEY || localConfig.appKey || 'bmdehs6pbyyks',
65
+ token: localConfig.token || clawBridgeConfig.token,
66
+ accountId: localConfig.accountId || clawBridgeConfig.nodeId,
67
+ nodeName: clawBridgeConfig.nodeName || 'cli-client',
68
+ secretKey: localConfig.secretKey || 'secret_key',
69
+ nickname: localConfig.nickname || 'CLI客户端',
70
+ reconnectInterval: localConfig.reconnectInterval || 60,
71
+ heartbeatInterval: localConfig.heartbeatInterval || 20,
72
+ openclawPort: localConfig.openclawPort || 18789,
73
+ scriptTimeout: localConfig.scriptTimeout || 180,
74
+ successKeyword: localConfig.successKeyword || 'Success',
75
+ chatTimeout: localConfig.chatTimeout || 600
76
+ };
77
+ }
78
+
79
+ module.exports = { loadConfig, getRealHomeDir };
@@ -169,7 +169,8 @@ class ServiceManager {
169
169
  nodeOptions: ['--harmony', '--max_old_space_size=4096'],
170
170
  env: {
171
171
  USERPROFILE: userHome,
172
- HOME: userHome
172
+ HOME: userHome,
173
+ CLAW_SERVICE_HOME: userHome
173
174
  }
174
175
  });
175
176
 
package/service/worker.js CHANGED
@@ -93,10 +93,42 @@ const logTimestampValidation = (message) => {
93
93
 
94
94
  log.info(`[WORKER] 业务进程启动,PID: ${process.pid}`);
95
95
 
96
- const clawBridgeConfigPath = path.join(os.homedir(), '.claw-bridge', 'config.json');
97
96
  const localConfigPath = path.join(__dirname, '..', 'rongcloud-config.json');
98
97
  let rongcloudConfig = null;
99
98
 
99
+ /**
100
+ * 获取实际用户主目录
101
+ * Windows 服务以 SYSTEM 运行时 os.homedir() 返回 systemprofile,
102
+ * 优先使用 CLAW_SERVICE_HOME / USERPROFILE 环境变量,最后扫描 C:\Users
103
+ */
104
+ function getRealHomeDir() {
105
+ const envHome = process.env.CLAW_SERVICE_HOME || process.env.USERPROFILE || process.env.HOME;
106
+ if (envHome && !envHome.includes('systemprofile')) {
107
+ return envHome;
108
+ }
109
+ const homeDir = os.homedir();
110
+ if (!homeDir.includes('systemprofile')) {
111
+ return homeDir;
112
+ }
113
+ // SYSTEM 账户兜底:扫描 C:\Users 找包含 .claw-bridge 的实际用户目录
114
+ const usersDir = 'C:\\Users';
115
+ if (fs.existsSync(usersDir)) {
116
+ const entries = fs.readdirSync(usersDir, { withFileTypes: true });
117
+ for (const entry of entries) {
118
+ if (entry.isDirectory() && !['Public', 'Default', 'All Users', 'Default User'].includes(entry.name)) {
119
+ const candidate = path.join(usersDir, entry.name);
120
+ if (fs.existsSync(path.join(candidate, '.claw-bridge', 'config.json'))) {
121
+ return candidate;
122
+ }
123
+ }
124
+ }
125
+ }
126
+ return homeDir;
127
+ }
128
+
129
+ const homeDir = getRealHomeDir();
130
+ const clawBridgeConfigPath = path.join(homeDir, '.claw-bridge', 'config.json');
131
+
100
132
  function loadRongCloudConfig() {
101
133
  let config = {};
102
134
 
@@ -108,7 +140,7 @@ function loadRongCloudConfig() {
108
140
  config.nodeName = clawConfig.nodeName;
109
141
  log.info(`[WORKER] 从 claw-bridge 加载配置: nodeId=${clawConfig.nodeId}, nodeName=${clawConfig.nodeName}`);
110
142
  } else {
111
- log.warn('[WORKER] 未找到 ~/.claw-bridge/config.json');
143
+ log.warn(`[WORKER] 未找到 ${clawBridgeConfigPath}`);
112
144
  }
113
145
  } catch (err) {
114
146
  log.error(`[WORKER] 加载 claw-bridge 配置失败: ${err.message}`);
package/version.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "0.0.18",
2
+ "version": "0.0.20",
3
3
  "updatedAt": "2026-04-30T07:00:00.000Z"
4
4
  }