shizai-agent-mcp 0.2.5 → 0.2.6

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
@@ -24,7 +24,7 @@ npx -y shizai-agent-mcp path
24
24
  npx -y shizai-agent-mcp uninstall
25
25
  ```
26
26
 
27
- 当前 npm launcher 版本为 `0.2.5`,默认下载最新的 `v0.6.5` Windows x64 资产:
27
+ 当前 npm launcher 版本为 `0.2.6`,默认下载最新的 `v0.6.5` Windows x64 资产:
28
28
  `shizai-agent-v0.6.5-windows-x64.zip`。npm 包不附带浏览器自动化依赖,也不要求用户安装额外扩展。
29
29
 
30
30
  发布 Release 时必须同时上传同名 `.sha256` 文件。安装器先校验、解压到同盘暂存目录,
@@ -29,9 +29,75 @@ function validateInstallRoot() {
29
29
  return resolved;
30
30
  }
31
31
  function psQuote(value) { return `'${String(value).replaceAll("'", "''")}'`; }
32
- function runPowerShell(script, args = []) {
33
- return execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script, ...args], { encoding: 'utf8' });
34
- }
32
+ function runPowerShell(script, args = []) {
33
+ return execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script, ...args], { encoding: 'utf8' });
34
+ }
35
+ function sleepSync(milliseconds) {
36
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
37
+ }
38
+ function removeDirectoryWithRetries(directory, attempts = 10) {
39
+ if (!fs.existsSync(directory)) return;
40
+ let lastError;
41
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
42
+ try {
43
+ fs.rmSync(directory, { recursive: true, force: true });
44
+ return;
45
+ } catch (error) {
46
+ lastError = error;
47
+ sleepSync(200 * (attempt + 1));
48
+ }
49
+ }
50
+ throw lastError;
51
+ }
52
+ function stopInstalledProcesses(destination) {
53
+ if (process.platform !== 'win32') return [];
54
+ const executables = [
55
+ path.join(destination, 'shizai-agent-mcp.exe'),
56
+ path.join(destination, 'shizai-agent.exe')
57
+ ];
58
+ const targetList = executables.map(psQuote).join(', ');
59
+ const script = String.raw`
60
+ $ErrorActionPreference = 'Stop'
61
+ $targets = @(${targetList} | ForEach-Object { [IO.Path]::GetFullPath([string]$_).ToLowerInvariant() })
62
+ $matches = @(Get-CimInstance Win32_Process | Where-Object {
63
+ if (-not $_.ExecutablePath) { return $false }
64
+ try { $targets -contains [IO.Path]::GetFullPath([string]$_.ExecutablePath).ToLowerInvariant() } catch { $false }
65
+ })
66
+ $matchedIds = @($matches | ForEach-Object { [int]$_.ProcessId })
67
+ foreach ($process in $matches) {
68
+ Stop-Process -Id $process.ProcessId -Force -ErrorAction Stop
69
+ }
70
+ $deadline = [DateTime]::UtcNow.AddSeconds(5)
71
+ do {
72
+ $remainingIds = @()
73
+ foreach ($targetId in $matchedIds) {
74
+ if ($null -ne (Get-Process -Id $targetId -ErrorAction SilentlyContinue)) {
75
+ $remainingIds += $targetId
76
+ }
77
+ }
78
+ if ($remainingIds.Count -eq 0) { break }
79
+ Start-Sleep -Milliseconds 100
80
+ } while ([DateTime]::UtcNow -lt $deadline)
81
+ if ($remainingIds.Count -gt 0) {
82
+ throw ('安装前无法停止实在 Agent 进程:' + (($remainingIds | Sort-Object) -join ', '))
83
+ }
84
+ @($matchedIds) | ConvertTo-Json -Compress
85
+ `;
86
+ const output = runPowerShell(script).trim();
87
+ if (!output) return [];
88
+ const parsed = JSON.parse(output);
89
+ return Array.isArray(parsed) ? parsed : [parsed];
90
+ }
91
+ function cleanupInstallArtifacts(parent, excluded = []) {
92
+ const excludedPaths = new Set(excluded.map(item => path.resolve(item).toLowerCase()));
93
+ for (const entry of fs.readdirSync(parent, { withFileTypes: true })) {
94
+ if (!entry.isDirectory()) continue;
95
+ if (!entry.name.startsWith('.shizai-agent-stage-') && !entry.name.startsWith('.shizai-agent-backup-')) continue;
96
+ const candidate = path.join(parent, entry.name);
97
+ if (excludedPaths.has(path.resolve(candidate).toLowerCase())) continue;
98
+ removeDirectoryWithRetries(candidate);
99
+ }
100
+ }
35
101
  function assetName(version) {
36
102
  if (process.platform !== 'win32' || process.arch !== 'x64') {
37
103
  throw new Error(`当前 npm 安装器暂时只提供 Windows x64,检测到 ${process.platform}/${process.arch}`);
@@ -62,6 +128,7 @@ function install(version) {
62
128
  const stage = path.join(parent, `.shizai-agent-stage-${nonce}`);
63
129
  const backup = path.join(parent, `.shizai-agent-backup-${nonce}`);
64
130
  let verified = false;
131
+ const stoppedProcesses = new Set();
65
132
  try {
66
133
  console.log(`下载 ${url}`);
67
134
  download(url, archive);
@@ -89,25 +156,44 @@ function install(version) {
89
156
  source = nested;
90
157
  }
91
158
  fs.writeFileSync(path.join(source, 'version.json'), JSON.stringify({ version, asset, installedAt: new Date().toISOString() }, null, 2));
159
+ cleanupInstallArtifacts(parent, [stage, backup]);
160
+ for (const processId of stopInstalledProcesses(destination)) stoppedProcesses.add(processId);
92
161
  let previousMoved = false;
93
162
  try {
94
163
  if (fs.existsSync(destination)) {
95
164
  fs.renameSync(destination, backup);
96
165
  previousMoved = true;
166
+ for (const processId of stopInstalledProcesses(destination)) stoppedProcesses.add(processId);
97
167
  }
98
168
  fs.renameSync(source, destination);
99
- if (previousMoved) fs.rmSync(backup, { recursive: true, force: true });
100
169
  } catch (error) {
101
170
  if (!fs.existsSync(destination) && previousMoved && fs.existsSync(backup)) {
102
171
  fs.renameSync(backup, destination);
103
172
  }
104
173
  throw error;
105
174
  }
106
- json({ installed: true, version, directory: destination, command: binaryPath(), verified });
175
+ if (previousMoved) {
176
+ try {
177
+ removeDirectoryWithRetries(backup);
178
+ } catch (error) {
179
+ for (const processId of stopInstalledProcesses(destination)) stoppedProcesses.add(processId);
180
+ try {
181
+ removeDirectoryWithRetries(backup);
182
+ } catch (retryError) {
183
+ const failedInstall = path.join(temp, 'failed-install');
184
+ fs.renameSync(destination, failedInstall);
185
+ fs.renameSync(backup, destination);
186
+ removeDirectoryWithRetries(failedInstall);
187
+ throw new Error(`旧版本清理失败,已恢复原安装:${retryError.message}`);
188
+ }
189
+ }
190
+ }
191
+ cleanupInstallArtifacts(parent, [stage, backup]);
192
+ json({ installed: true, version, directory: destination, command: binaryPath(), verified, stopped_processes: [...stoppedProcesses].sort((left, right) => left - right) });
107
193
  } finally {
108
- fs.rmSync(temp, { recursive: true, force: true });
109
- fs.rmSync(stage, { recursive: true, force: true });
110
- if (fs.existsSync(backup) && fs.existsSync(destination)) fs.rmSync(backup, { recursive: true, force: true });
194
+ removeDirectoryWithRetries(temp);
195
+ removeDirectoryWithRetries(stage);
196
+ if (fs.existsSync(backup) && fs.existsSync(destination)) removeDirectoryWithRetries(backup);
111
197
  }
112
198
  }
113
199
  function replaceTomlSection(content, section, replacement) {
@@ -165,7 +251,13 @@ function main() {
165
251
  }
166
252
  if (command === 'status') return json({ installed: fs.existsSync(binaryPath()), directory: installRoot, command: binaryPath(), versionFile: path.join(installRoot, 'version.json') });
167
253
  if (command === 'path') return console.log(binaryPath());
168
- if (command === 'uninstall') { if (fs.existsSync(installRoot)) fs.rmSync(installRoot, { recursive: true, force: true }); return json({ uninstalled: true, directory: installRoot }); }
254
+ if (command === 'uninstall') {
255
+ const destination = validateInstallRoot();
256
+ const stoppedProcesses = stopInstalledProcesses(destination);
257
+ removeDirectoryWithRetries(destination);
258
+ if (fs.existsSync(path.dirname(destination))) cleanupInstallArtifacts(path.dirname(destination));
259
+ return json({ uninstalled: true, directory: destination, stopped_processes: stoppedProcesses });
260
+ }
169
261
  throw new Error(`未知命令:${command}`);
170
262
  }
171
263
  try { main(); } catch (error) { fail(error.message); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shizai-agent-mcp",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Installer and launcher for the Shizai Agent local MCP server",
5
5
  "bin": {
6
6
  "shizai-agent-mcp": "bin/shizai-agent-mcp.js"