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 +1 -1
- package/bin/shizai-agent-mcp.js +101 -9
- package/package.json +1 -1
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.
|
|
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` 文件。安装器先校验、解压到同盘暂存目录,
|
package/bin/shizai-agent-mcp.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
if (fs.existsSync(backup) && fs.existsSync(destination))
|
|
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') {
|
|
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); }
|