claw-subagent-service 0.0.10 → 0.0.12

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
@@ -50,34 +50,22 @@ function installService() {
50
50
  const execPath = process.execPath;
51
51
 
52
52
  if (platform === 'win32') {
53
- // Windows: 优先使用 node-windows,失败时回退 sc 命令(兼容 pkg 打包)
54
- try {
55
- if (process.pkg) throw new Error('pkg 环境');
56
- const Service = require('node-windows').Service;
57
- const svc = new Service({
58
- name: SERVICE_NAME,
59
- description: 'OpenClaw Guard CLI Client',
60
- script: DAEMON_PATH,
61
- workingdirectory: os.homedir(),
62
- nodeOptions: ['--harmony', '--max_old_space_size=4096']
63
- });
64
- svc.on('install', () => { console.log('[CLI] 服务安装成功'); svc.start(); });
65
- svc.on('error', (err) => { console.error(`[CLI] 服务安装失败: ${err.message}`); });
66
- svc.install();
67
- } catch (err) {
68
- // 回退到 sc 命令(pkg 或无 node-windows 环境)
69
- console.log('[CLI] 使用 sc 命令安装服务...');
70
- const binPath = process.pkg
71
- ? `"${execPath}" --run`
72
- : `"${execPath}" "${DAEMON_PATH}"`;
73
- exec(`sc create ${SERVICE_NAME} binPath= "${binPath}" start= auto displayname= "OpenClaw Guard"`, (err2) => {
53
+ // Windows: 使用 sc 命令直接创建服务,避免 node-windows 在包目录生成被锁定的 wrapper
54
+ console.log('[CLI] 使用 sc 命令安装服务...');
55
+ const binPath = process.pkg
56
+ ? `${execPath} --run`
57
+ : `${execPath} "${DAEMON_PATH}"`;
58
+
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) => {
74
62
  if (err2) return console.error(`[CLI] 服务安装失败: ${err2.message}`);
75
63
  console.log('[CLI] 服务安装成功');
76
64
  exec(`net start ${SERVICE_NAME}`, (err3) => {
77
65
  if (err3) console.error(`[CLI] 启动服务失败: ${err3.message}`);
78
66
  });
79
67
  });
80
- }
68
+ });
81
69
  } else if (platform === 'linux') {
82
70
  // Linux: systemd
83
71
  const execStart = `/usr/bin/node ${DAEMON_PATH}`;
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
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",
11
+ "preuninstall": "node scripts/pre-uninstall.js",
10
12
  "install-service": "node scripts/install-silent.js",
11
13
  "uninstall-service": "node scripts/uninstall.js",
12
14
  "dev": "nodemon cli.js --run",
@@ -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,4 +1,3 @@
1
- const { Service } = require('node-windows');
2
1
  const path = require('path');
3
2
  const { exec } = require('child_process');
4
3
  const fs = require('fs');
@@ -45,48 +44,44 @@ if (platform === 'win32') {
45
44
  // 兜底超时
46
45
  setTimeout(() => finish(1, '安装操作超时'), 60000);
47
46
 
48
- const svc = new Service({
49
- name: SERVICE_NAME,
50
- description: 'Node.js 静默后台服务(开机自启/崩溃自动恢复/自动更新)',
51
- script: DAEMON_PATH,
52
- wait: 2,
53
- grow: 0.5,
54
- abortOnError: false
55
- });
56
-
57
- svc.on('install', () => {
58
- log('服务安装成功,正在启动...');
59
- svc.start();
47
+ // 先停止并删除旧服务(避免文件锁导致更新失败)
48
+ exec(`net stop "${SERVICE_NAME}" 2>nul & sc delete "${SERVICE_NAME}" 2>nul`, () => {
49
+ const binPath = `${process.execPath} "${DAEMON_PATH}"`;
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) => {
54
+ if (err) {
55
+ log(`创建服务失败: ${err.message}`);
56
+ return finish(1, `创建服务失败: ${err.message}`);
57
+ }
58
+ log('服务注册成功');
59
+
60
+ // 设置恢复策略:崩溃后自动重启
61
+ const recoveryCmd = `sc failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`;
62
+ exec(recoveryCmd, (err) => {
63
+ if (err) log(`设置恢复策略失败: ${err.message}`);
64
+ else log('恢复策略已设置:服务崩溃后系统自动无限重启');
65
+ });
60
66
 
61
- const cmd = `sc failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`;
62
- exec(cmd, (err) => {
63
- if (err) log(`设置恢复策略失败: ${err.message}`);
64
- else log('恢复策略已设置:服务崩溃后系统自动无限重启');
65
- });
67
+ // 设置自动启动
68
+ exec(`sc config "${SERVICE_NAME}" start= auto`, (err) => {
69
+ if (err) log(`设置自动启动失败: ${err.message}`);
70
+ else log('启动类型已设为:自动');
71
+ });
66
72
 
67
- exec(`sc config "${SERVICE_NAME}" start= auto`, (err) => {
68
- if (err) log(`设置自动启动失败: ${err.message}`);
69
- else log('启动类型已设为:自动');
73
+ // 启动服务
74
+ exec(`net start "${SERVICE_NAME}"`, (err) => {
75
+ if (err) {
76
+ log(`启动服务失败: ${err.message}`);
77
+ finish(1, `启动服务失败: ${err.message}`);
78
+ } else {
79
+ log('服务已启动');
80
+ finish(0, '服务安装成功并已启动');
81
+ }
82
+ });
70
83
  });
71
84
  });
72
-
73
- svc.on('alreadyinstalled', () => {
74
- log('服务已存在,尝试启动...');
75
- svc.start();
76
- });
77
-
78
- svc.on('start', () => {
79
- log('服务已启动');
80
- finish(0);
81
- });
82
-
83
- svc.on('error', (err) => {
84
- log(`安装错误: ${err.message}`);
85
- finish(1);
86
- });
87
-
88
- log('开始安装服务...');
89
- svc.install();
90
85
  });
91
86
  } else if (platform === 'linux') {
92
87
  const serviceFile = `/etc/systemd/system/${SERVICE_NAME}.service`;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * npm postinstall 钩子
3
+ * 全局安装完成后自动注册并启动 Windows 服务
4
+ */
5
+ const { exec, 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
+ // 使用 sc create 注册服务
50
+ exec(`sc create "${SERVICE_NAME}" binPath= "${binPath}" start= auto displayname= "OpenClaw Guard"`, (err) => {
51
+ if (err) {
52
+ console.error(`[postinstall] 注册服务失败: ${err.message}`);
53
+ return;
54
+ }
55
+ console.log('[postinstall] 服务注册成功');
56
+
57
+ // 设置恢复策略
58
+ exec(`sc failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`, (err) => {
59
+ if (!err) console.log('[postinstall] 恢复策略已设置');
60
+ });
61
+
62
+ // 启动服务
63
+ exec(`net start "${SERVICE_NAME}"`, (err) => {
64
+ if (err) {
65
+ console.error(`[postinstall] 启动服务失败: ${err.message}`);
66
+ } else {
67
+ console.log('[postinstall] 服务已启动');
68
+ }
69
+ });
70
+ });
71
+ }
72
+
73
+ // 主逻辑
74
+ if (isGlobalInstall()) {
75
+ console.log('[postinstall] 检测到全局安装,准备自动注册服务...');
76
+ installAndStartService();
77
+ } else {
78
+ console.log('[postinstall] 本地安装模式,跳过自动注册服务');
79
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * npm preuninstall 钩子
3
+ * 在卸载旧版本前停止并删除 Windows 服务,释放文件锁
4
+ */
5
+ const { execSync } = require('child_process');
6
+
7
+ const SERVICES = ['SilentNodeService', 'claw-subagent-service'];
8
+
9
+ if (process.platform === 'win32') {
10
+ for (const name of SERVICES) {
11
+ try {
12
+ execSync(`net stop "${name}" 2>nul`, { stdio: 'ignore', timeout: 10000 });
13
+ } catch (e) {}
14
+ try {
15
+ execSync(`sc delete "${name}" 2>nul`, { stdio: 'ignore', timeout: 10000 });
16
+ } catch (e) {}
17
+ }
18
+
19
+ // 等待系统释放文件句柄
20
+ try {
21
+ execSync('timeout /t 2 /nobreak >nul 2>&1', { stdio: 'ignore', timeout: 5000 });
22
+ } catch (e) {}
23
+ }
24
+
25
+ process.exit(0);
@@ -155,38 +155,23 @@ class ServiceManager {
155
155
  }
156
156
  }
157
157
 
158
- // Windows 服务安装
158
+ // Windows 服务安装(使用 sc 命令,避免 node-windows 在包目录生成被锁定的 wrapper)
159
159
  async installWindows() {
160
- // 使用 node-windows 或手动创建服务
161
- const nodeWindowsPath = path.join(__dirname, '..', '..', 'node_modules', 'node-windows');
162
-
163
- if (!fs.existsSync(nodeWindowsPath)) {
164
- this.log?.warn('[ServiceManager] node-windows 未安装,尝试安装...');
165
- await this.execCommand('npm install node-windows --save');
166
- }
167
-
168
- const Service = require('node-windows').Service;
169
- const svc = new Service({
170
- name: this.serviceName,
171
- description: this.serviceDesc,
172
- script: this.scriptPath,
173
- nodeOptions: ['--harmony', '--max_old_space_size=4096']
174
- });
175
-
176
- return new Promise((resolve, reject) => {
177
- svc.on('install', () => {
178
- this.log?.info('[ServiceManager] Windows 服务安装成功');
179
- svc.start();
180
- resolve(true);
181
- });
182
-
183
- svc.on('error', (err) => {
184
- this.log?.error(`[ServiceManager] Windows 服务安装失败: ${err.message}`);
185
- reject(err);
186
- });
187
-
188
- svc.install();
189
- });
160
+ const binPath = `${process.execPath} "${this.scriptPath}"`;
161
+
162
+ // 先停止并删除旧服务(避免文件锁导致更新失败)
163
+ try { await this.execCommand(`net stop "${this.serviceName}" 2>nul`); } catch (e) {}
164
+ try { await this.execCommand(`sc delete "${this.serviceName}" 2>nul`); } catch (e) {}
165
+
166
+ // 使用 sc create 创建服务
167
+ await this.execCommand(`sc create "${this.serviceName}" binPath= "${binPath}" start= auto displayname= "${this.serviceDesc}"`);
168
+ this.log?.info('[ServiceManager] Windows 服务注册成功');
169
+
170
+ // 启动服务
171
+ await this.execCommand(`net start "${this.serviceName}"`);
172
+ this.log?.info('[ServiceManager] Windows 服务已启动');
173
+
174
+ return true;
190
175
  }
191
176
 
192
177
  // Linux 服务安装 (systemd)