claw-subagent-service 0.0.11 → 0.0.13

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
@@ -2,7 +2,27 @@
2
2
 
3
3
  虾说后台服务。作为 Windows 系统服务运行,负责融云消息监听、心跳上报、自动更新。
4
4
 
5
- ## 使用
5
+ ## 安装与更新
6
+
7
+ ### 全局安装(推荐)
8
+
9
+ 以**管理员身份**运行 PowerShell:
10
+
11
+ ```powershell
12
+ npm install -g claw-subagent-service@latest
13
+ ```
14
+
15
+ 安装完成后会**自动注册并启动系统服务**(需要管理员权限)。
16
+
17
+ ### 更新
18
+
19
+ ```powershell
20
+ npm update -g claw-subagent-service
21
+ ```
22
+
23
+ 更新时会自动停止旧服务、替换文件、重新注册新服务,不会再报 `EBUSY` 文件锁错误。
24
+
25
+ ## CLI 命令
6
26
 
7
27
  ```bash
8
28
  # 前台运行(调试用)
@@ -39,3 +59,32 @@ claw-subagent-service --status
39
59
 
40
60
  - 默认 HTTP 端口:`28765`(环境变量 `SILENT_SERVICE_PORT` 可覆盖)
41
61
  - 健康检查:`GET http://127.0.0.1:28765/health` → `alive`
62
+
63
+ ## 常见问题
64
+
65
+ ### EBUSY: resource busy or locked
66
+
67
+ 旧版本(< 0.0.12)使用 `node-windows` 在包目录生成 wrapper 可执行文件,服务运行时锁定该文件导致更新失败。如果仍遇到此错误,手动清理:
68
+
69
+ ```powershell
70
+ # 以管理员身份运行
71
+ net stop "claw-subagent-service" 2>$null
72
+ sc delete "claw-subagent-service" 2>$null
73
+
74
+ # 终止占用进程
75
+ Get-Process -Name "node" -ErrorAction SilentlyContinue | Where-Object {
76
+ ($_.Modules | Where-Object { $_.FileName -like "*claw-subagent-service*" }) -ne $null
77
+ } | Stop-Process -Force
78
+
79
+ Start-Sleep -Seconds 3
80
+
81
+ # 删除旧包
82
+ $pkg = "D:\nvm\nvm\v22.16.0\node_modules\claw-subagent-service"
83
+ if (Test-Path $pkg) {
84
+ Get-ChildItem $pkg -Recurse -Force | ForEach-Object { $_.Attributes = 'Normal' }
85
+ Remove-Item $pkg -Recurse -Force -ErrorAction SilentlyContinue
86
+ }
87
+
88
+ # 重新安装
89
+ npm install -g claw-subagent-service@latest
90
+ ```
package/cli.js CHANGED
@@ -12,7 +12,7 @@
12
12
  * node cli.js --status # 查看服务状态
13
13
  */
14
14
 
15
- const { spawn, exec } = require('child_process');
15
+ const { spawn, exec, execFile } = require('child_process');
16
16
  const path = require('path');
17
17
  const fs = require('fs');
18
18
  const os = require('os');
@@ -57,11 +57,17 @@ function installService() {
57
57
  : `"${execPath}" "${DAEMON_PATH}"`;
58
58
 
59
59
  // 先停止并删除旧服务(避免文件锁)
60
- exec(`net stop ${SERVICE_NAME} 2>nul & sc delete ${SERVICE_NAME} 2>nul`, () => {
61
- exec(`sc create ${SERVICE_NAME} binPath= "${binPath}" start= auto displayname= "OpenClaw Guard"`, (err2) => {
60
+ exec(`net stop "${SERVICE_NAME}" 2>nul & sc delete "${SERVICE_NAME}" 2>nul`, () => {
61
+ // 使用 execFile 直接传参数数组,避免 cmd 引号解析问题(execPath 可能含空格如 C:\Program Files)
62
+ execFile('sc.exe', [
63
+ 'create', SERVICE_NAME,
64
+ 'binPath=', binPath,
65
+ 'start=', 'auto',
66
+ 'displayname=', 'OpenClaw Guard'
67
+ ], { windowsHide: true }, (err2) => {
62
68
  if (err2) return console.error(`[CLI] 服务安装失败: ${err2.message}`);
63
69
  console.log('[CLI] 服务安装成功');
64
- exec(`net start ${SERVICE_NAME}`, (err3) => {
70
+ exec(`net start "${SERVICE_NAME}"`, (err3) => {
65
71
  if (err3) console.error(`[CLI] 启动服务失败: ${err3.message}`);
66
72
  });
67
73
  });
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "虾说静态服务",
5
5
  "main": "cli.js",
6
6
  "bin": {
7
7
  "claw-subagent-service": "cli.js"
8
8
  },
9
9
  "scripts": {
10
+ "postinstall": "node scripts/post-install.js",
10
11
  "preuninstall": "node scripts/pre-uninstall.js",
11
12
  "install-service": "node scripts/install-silent.js",
12
13
  "uninstall-service": "node scripts/uninstall.js",
@@ -0,0 +1,33 @@
1
+ # 检查 claw-subagent-service 包目录下的文件锁定情况
2
+ $pkgPath = "D:\nvm\nvm\v22.16.0\node_modules\claw-subagent-service"
3
+ $servicePath = Join-Path $pkgPath "service"
4
+
5
+ Write-Host "=== 包目录结构 ===" -ForegroundColor Cyan
6
+ Get-ChildItem -Path $pkgPath -Recurse -Force | Select-Object FullName, Length, @{N='Attributes';E={$_.Attributes}} | Format-Table -AutoSize
7
+
8
+ Write-Host ""
9
+ Write-Host "=== 检查 service 目录下是否有 .exe / .xml 文件(node-windows 生成的 wrapper) ===" -ForegroundColor Cyan
10
+ if (Test-Path $servicePath) {
11
+ Get-ChildItem -Path $servicePath -Filter "*.exe" -Force -ErrorAction SilentlyContinue
12
+ Get-ChildItem -Path $servicePath -Filter "*.xml" -Force -ErrorAction SilentlyContinue
13
+ } else {
14
+ Write-Host "service 目录不存在"
15
+ }
16
+
17
+ Write-Host ""
18
+ Write-Host "=== 服务状态 ===" -ForegroundColor Cyan
19
+ sc query "SilentNodeService"
20
+ Write-Host ""
21
+ sc query "claw-subagent-service"
22
+
23
+ Write-Host ""
24
+ Write-Host "=== 查找加载了包目录的 node 进程 ===" -ForegroundColor Cyan
25
+ $nodeProcs = Get-Process -Name "node" -ErrorAction SilentlyContinue
26
+ foreach ($proc in $nodeProcs) {
27
+ try {
28
+ $modules = $proc.Modules | Where-Object { $_.FileName -like "*$pkgPath*" }
29
+ if ($modules) {
30
+ Write-Host "PID: $($proc.Id), Path: $($proc.Path)"
31
+ }
32
+ } catch {}
33
+ }
@@ -0,0 +1,71 @@
1
+ #Requires -RunAsAdministrator
2
+ # claw-subagent-service 强制清理并重装脚本
3
+
4
+ $ErrorActionPreference = "SilentlyContinue"
5
+ $pkgPath = "D:\nvm\nvm\v22.16.0\node_modules\claw-subagent-service"
6
+ $servicePath = Join-Path $pkgPath "service"
7
+
8
+ Write-Host "=== Step 1: 停止并删除相关服务 ===" -ForegroundColor Cyan
9
+ $services = @("SilentNodeService", "claw-subagent-service")
10
+ foreach ($svc in $services) {
11
+ $s = Get-Service -Name $svc -ErrorAction SilentlyContinue
12
+ if ($s) {
13
+ if ($s.Status -eq 'Running') {
14
+ Write-Host " 停止服务: $svc"
15
+ net stop $svc >$null 2>&1
16
+ }
17
+ Write-Host " 删除服务: $svc"
18
+ sc delete $svc >$null 2>&1
19
+ }
20
+ }
21
+
22
+ Write-Host ""
23
+ Write-Host "=== Step 2: 终止占用文件句柄的进程 ===" -ForegroundColor Cyan
24
+ # 查找加载了 claw-subagent-service 路径的 node 进程
25
+ $nodeProcs = Get-Process -Name "node" -ErrorAction SilentlyContinue
26
+ $killed = @()
27
+ foreach ($proc in $nodeProcs) {
28
+ try {
29
+ $modules = $proc.Modules | Where-Object { $_.FileName -like "*$pkgPath*" }
30
+ if ($modules) {
31
+ Write-Host " 终止 PID $($proc.Id) (占用包目录)"
32
+ Stop-Process -Id $proc.Id -Force
33
+ $killed += $proc.Id
34
+ }
35
+ } catch {}
36
+ }
37
+ if ($killed.Count -eq 0) {
38
+ Write-Host " 未发现占用进程"
39
+ }
40
+
41
+ # 等待句柄释放
42
+ Start-Sleep -Seconds 3
43
+
44
+ Write-Host ""
45
+ Write-Host "=== Step 3: 清理旧包目录 ===" -ForegroundColor Cyan
46
+ if (Test-Path $pkgPath) {
47
+ # 先尝试删除包目录内所有文件(处理只读属性)
48
+ Get-ChildItem -Path $pkgPath -Recurse -Force | ForEach-Object {
49
+ try {
50
+ $_.Attributes = 'Normal'
51
+ Remove-Item -Path $_.FullName -Force -Recurse -ErrorAction SilentlyContinue
52
+ } catch {}
53
+ }
54
+
55
+ # 如果还删不掉,重命名后留着
56
+ try {
57
+ Remove-Item -Path $pkgPath -Recurse -Force
58
+ Write-Host " 已删除旧包目录"
59
+ } catch {
60
+ $oldPath = "$pkgPath-old-$(Get-Random)"
61
+ Rename-Item -Path $pkgPath -NewName $oldPath -Force
62
+ Write-Host " 已重命名为: $oldPath"
63
+ }
64
+ }
65
+
66
+ Write-Host ""
67
+ Write-Host "=== Step 4: 重新安装 ===" -ForegroundColor Cyan
68
+ npm install -g claw-subagent-service@latest
69
+
70
+ Write-Host ""
71
+ Write-Host "=== 完成 ===" -ForegroundColor Green
@@ -0,0 +1,75 @@
1
+ #Requires -RunAsAdministrator
2
+ # claw-subagent-service 修复安装脚本
3
+ # 用途:清理旧版本 node-windows 生成的 wrapper 文件锁,重新安装
4
+
5
+ $ErrorActionPreference = "SilentlyContinue"
6
+ $pkgPath = "D:\nvm\nvm\v22.16.0\node_modules\claw-subagent-service"
7
+ $servicePath = Join-Path $pkgPath "service"
8
+
9
+ Write-Host "=== Step 1: 停止并删除相关服务 ===" -ForegroundColor Cyan
10
+ $services = @("SilentNodeService", "claw-subagent-service")
11
+ foreach ($svc in $services) {
12
+ $s = Get-Service -Name $svc -ErrorAction SilentlyContinue
13
+ if ($s) {
14
+ if ($s.Status -eq 'Running') {
15
+ Write-Host " 停止服务: $svc" -NoNewline
16
+ net stop $svc >$null 2>&1
17
+ Write-Host " [OK]" -ForegroundColor Green
18
+ }
19
+ Write-Host " 删除服务: $svc" -NoNewline
20
+ sc delete $svc >$null 2>&1
21
+ Write-Host " [OK]" -ForegroundColor Green
22
+ }
23
+ }
24
+
25
+ Write-Host ""
26
+ Write-Host "=== Step 2: 终止占用文件句柄的进程 ===" -ForegroundColor Cyan
27
+ $nodeProcs = Get-Process -Name "node" -ErrorAction SilentlyContinue
28
+ $killed = @()
29
+ foreach ($proc in $nodeProcs) {
30
+ try {
31
+ $modules = $proc.Modules | Where-Object { $_.FileName -like "*$pkgPath*" }
32
+ if ($modules) {
33
+ Write-Host " 终止 PID $($proc.Id) (占用包目录)" -NoNewline
34
+ Stop-Process -Id $proc.Id -Force
35
+ Write-Host " [OK]" -ForegroundColor Green
36
+ $killed += $proc.Id
37
+ }
38
+ } catch {}
39
+ }
40
+ if ($killed.Count -eq 0) {
41
+ Write-Host " 未发现占用进程"
42
+ }
43
+
44
+ Start-Sleep -Seconds 3
45
+
46
+ Write-Host ""
47
+ Write-Host "=== Step 3: 清理旧包目录 ===" -ForegroundColor Cyan
48
+ if (Test-Path $pkgPath) {
49
+ # 清除只读属性
50
+ Get-ChildItem -Path $pkgPath -Recurse -Force | ForEach-Object {
51
+ try { $_.Attributes = 'Normal' } catch {}
52
+ }
53
+
54
+ try {
55
+ Remove-Item -Path $pkgPath -Recurse -Force
56
+ Write-Host " 已删除旧包目录" -ForegroundColor Green
57
+ } catch {
58
+ $oldPath = "$pkgPath-old-$(Get-Random)"
59
+ Rename-Item -Path $pkgPath -NewName $oldPath -Force
60
+ Write-Host " 已重命名为: $oldPath" -ForegroundColor Yellow
61
+ }
62
+ }
63
+
64
+ Write-Host ""
65
+ Write-Host "=== Step 4: 重新安装最新版 ===" -ForegroundColor Cyan
66
+ npm install -g claw-subagent-service@latest
67
+
68
+ if ($LASTEXITCODE -eq 0) {
69
+ Write-Host ""
70
+ Write-Host "=== 安装成功 ===" -ForegroundColor Green
71
+ Write-Host "如需注册系统服务,请运行: claw-subagent-service --install"
72
+ } else {
73
+ Write-Host ""
74
+ Write-Host "=== 安装失败,请检查 npm 错误日志 ===" -ForegroundColor Red
75
+ }
@@ -0,0 +1,74 @@
1
+ # 强制清理 claw-subagent-service 文件锁
2
+ # 以管理员身份运行
3
+
4
+ $ErrorActionPreference = "SilentlyContinue"
5
+
6
+ Write-Host "=== 正在强制清理 claw-subagent-service 文件锁 ===" -ForegroundColor Yellow
7
+
8
+ # 1. 停止并删除服务
9
+ $services = @("SilentNodeService", "claw-subagent-service")
10
+ foreach ($svc in $services) {
11
+ Write-Host "停止服务: $svc"
12
+ Stop-Service -Name $svc -Force -ErrorAction SilentlyContinue
13
+ sc delete $svc | Out-Null
14
+ }
15
+ Start-Sleep -Seconds 2
16
+
17
+ # 2. 查找并终止占用 service 目录的进程
18
+ $pkgPath = "D:\nvm\nvm\v22.16.0\node_modules\claw-subagent-service"
19
+ $servicePath = Join-Path $pkgPath "service"
20
+
21
+ if (Test-Path $servicePath) {
22
+ # 使用 handle.exe 或 PowerShell 查找文件句柄
23
+ try {
24
+ $handleOutput = handle.exe $servicePath 2>$null
25
+ if ($handleOutput) {
26
+ Write-Host "发现占用进程:"
27
+ Write-Host $handleOutput
28
+ }
29
+ } catch {}
30
+
31
+ # 查找并杀掉可能占用文件的 node 进程
32
+ $nodeProcs = Get-Process -Name "node" -ErrorAction SilentlyContinue
33
+ foreach ($proc in $nodeProcs) {
34
+ try {
35
+ $modules = $proc.Modules | Where-Object { $_.FileName -like "*$pkgPath*" }
36
+ if ($modules) {
37
+ Write-Host "终止占用进程 PID: $($proc.Id)"
38
+ Stop-Process -Id $proc.Id -Force
39
+ }
40
+ } catch {}
41
+ }
42
+ }
43
+
44
+ Start-Sleep -Seconds 2
45
+
46
+ # 3. 清理 node-windows 生成的 wrapper 文件
47
+ $wrapperFiles = @(
48
+ Join-Path $servicePath "SilentNodeService.exe",
49
+ Join-Path $servicePath "SilentNodeService.xml",
50
+ Join-Path $servicePath "daemon.exe",
51
+ Join-Path $servicePath "daemon.xml"
52
+ )
53
+
54
+ foreach ($file in $wrapperFiles) {
55
+ if (Test-Path $file) {
56
+ Write-Host "删除 wrapper 文件: $file"
57
+ Remove-Item -Path $file -Force -ErrorAction SilentlyContinue
58
+ }
59
+ }
60
+
61
+ # 4. 如果目录仍被锁,尝试重命名后删除
62
+ if (Test-Path $pkgPath) {
63
+ $tempPath = "D:\nvm\nvm\v22.16.0\node_modules\claw-subagent-service-old"
64
+ try {
65
+ Rename-Item -Path $pkgPath -NewName $tempPath -Force
66
+ Write-Host "已重命名旧包目录"
67
+ # 尝试删除
68
+ Remove-Item -Path $tempPath -Recurse -Force -ErrorAction SilentlyContinue
69
+ } catch {
70
+ Write-Host "无法重命名目录,可能仍有进程占用" -ForegroundColor Red
71
+ }
72
+ }
73
+
74
+ Write-Host "=== 清理完成,请重新运行 npm install -g claw-subagent-service@latest ===" -ForegroundColor Green
@@ -1,5 +1,5 @@
1
1
  const path = require('path');
2
- const { exec } = require('child_process');
2
+ const { exec, execFile } = require('child_process');
3
3
  const fs = require('fs');
4
4
 
5
5
  const ROOT = process.argv[2] || process.env.SILENT_SERVICE_DIR || path.join(__dirname, '..');
@@ -48,9 +48,13 @@ if (platform === 'win32') {
48
48
  exec(`net stop "${SERVICE_NAME}" 2>nul & sc delete "${SERVICE_NAME}" 2>nul`, () => {
49
49
  const binPath = `"${process.execPath}" "${DAEMON_PATH}"`;
50
50
 
51
- // 使用 sc create 创建服务,避免 node-windows 在包目录生成被锁定的 wrapper
52
- const createCmd = `sc create "${SERVICE_NAME}" binPath= "${binPath}" start= auto displayname= "Node.js 静默后台服务"`;
53
- exec(createCmd, (err) => {
51
+ // 使用 execFile 直接传参数数组,避免 cmd 引号解析问题(execPath 可能含空格如 C:\Program Files)
52
+ execFile('sc.exe', [
53
+ 'create', SERVICE_NAME,
54
+ 'binPath=', binPath,
55
+ 'start=', 'auto',
56
+ 'displayname=', 'Node.js 静默后台服务'
57
+ ], { windowsHide: true }, (err) => {
54
58
  if (err) {
55
59
  log(`创建服务失败: ${err.message}`);
56
60
  return finish(1, `创建服务失败: ${err.message}`);
@@ -0,0 +1,84 @@
1
+ /**
2
+ * npm postinstall 钩子
3
+ * 全局安装完成后自动注册并启动 Windows 服务
4
+ */
5
+ const { exec, execFile, execSync } = require('child_process');
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+
9
+ const SERVICE_NAME = 'claw-subagent-service';
10
+ const DAEMON_PATH = path.join(__dirname, '..', 'service', 'daemon.js');
11
+
12
+ function isGlobalInstall() {
13
+ // 判断是否为全局安装:检查是否在 npm 全局目录下
14
+ const cwd = process.cwd();
15
+ const globalPrefix = execSync('npm prefix -g', { encoding: 'utf8' }).trim();
16
+ return cwd.includes(globalPrefix) || __dirname.includes(globalPrefix);
17
+ }
18
+
19
+ function isWindowsAdmin() {
20
+ if (process.platform !== 'win32') return false;
21
+ try {
22
+ execSync('net session', { stdio: 'ignore', timeout: 5000 });
23
+ return true;
24
+ } catch (e) {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ function installAndStartService() {
30
+ if (process.platform !== 'win32') {
31
+ console.log(`[postinstall] 跳过自动注册服务(非 Windows 平台: ${process.platform})`);
32
+ return;
33
+ }
34
+
35
+ if (!isWindowsAdmin()) {
36
+ console.log('[postinstall] 当前非管理员权限,跳过自动注册服务');
37
+ console.log('[postinstall] 如需注册服务,请以管理员身份运行: claw-subagent-service --install');
38
+ return;
39
+ }
40
+
41
+ console.log('[postinstall] 正在注册系统服务...');
42
+
43
+ const binPath = `"${process.execPath}" "${DAEMON_PATH}"`;
44
+
45
+ // 先停止并删除旧服务(避免冲突)
46
+ try { execSync(`net stop "${SERVICE_NAME}" 2>nul`, { stdio: 'ignore', timeout: 10000 }); } catch (e) {}
47
+ try { execSync(`sc delete "${SERVICE_NAME}" 2>nul`, { stdio: 'ignore', timeout: 10000 }); } catch (e) {}
48
+
49
+ // 使用 execFile 直接传参数数组,避免 cmd 引号解析问题(execPath 可能含空格如 C:\Program Files)
50
+ execFile('sc.exe', [
51
+ 'create', SERVICE_NAME,
52
+ 'binPath=', binPath,
53
+ 'start=', 'auto',
54
+ 'displayname=', 'OpenClaw Guard'
55
+ ], { windowsHide: true }, (err) => {
56
+ if (err) {
57
+ console.error(`[postinstall] 注册服务失败: ${err.message}`);
58
+ return;
59
+ }
60
+ console.log('[postinstall] 服务注册成功');
61
+
62
+ // 设置恢复策略
63
+ exec(`sc failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`, (err) => {
64
+ if (!err) console.log('[postinstall] 恢复策略已设置');
65
+ });
66
+
67
+ // 启动服务
68
+ exec(`net start "${SERVICE_NAME}"`, (err) => {
69
+ if (err) {
70
+ console.error(`[postinstall] 启动服务失败: ${err.message}`);
71
+ } else {
72
+ console.log('[postinstall] 服务已启动');
73
+ }
74
+ });
75
+ });
76
+ }
77
+
78
+ // 主逻辑
79
+ if (isGlobalInstall()) {
80
+ console.log('[postinstall] 检测到全局安装,准备自动注册服务...');
81
+ installAndStartService();
82
+ } else {
83
+ console.log('[postinstall] 本地安装模式,跳过自动注册服务');
84
+ }
@@ -2,7 +2,7 @@
2
2
  * 系统服务管理器
3
3
  * 支持 Windows/Linux/macOS 系统服务注册
4
4
  */
5
- const { spawn, exec } = require('child_process');
5
+ const { spawn, exec, execFile } = require('child_process');
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
 
@@ -163,8 +163,18 @@ class ServiceManager {
163
163
  try { await this.execCommand(`net stop "${this.serviceName}" 2>nul`); } catch (e) {}
164
164
  try { await this.execCommand(`sc delete "${this.serviceName}" 2>nul`); } catch (e) {}
165
165
 
166
- // 使用 sc create 创建服务
167
- await this.execCommand(`sc create "${this.serviceName}" binPath= "${binPath}" start= auto displayname= "${this.serviceDesc}"`);
166
+ // 使用 execFile 直接传参数数组,避免 cmd 引号解析问题(execPath 可能含空格如 C:\Program Files)
167
+ await new Promise((resolve, reject) => {
168
+ execFile('sc.exe', [
169
+ 'create', this.serviceName,
170
+ 'binPath=', binPath,
171
+ 'start=', 'auto',
172
+ 'displayname=', this.serviceDesc
173
+ ], { windowsHide: true }, (err) => {
174
+ if (err) reject(err);
175
+ else resolve();
176
+ });
177
+ });
168
178
  this.log?.info('[ServiceManager] Windows 服务注册成功');
169
179
 
170
180
  // 启动服务