openclaw-agent-dashboard 1.0.7 → 1.0.9
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.
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Windows 安装问题排查指南
|
|
2
|
+
|
|
3
|
+
本文档针对 `npx openclaw-agent-dashboard@1.0.8 --verbose` 在 Windows 下的安装问题进行说明和解决方案。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 一、问题概览
|
|
8
|
+
|
|
9
|
+
根据安装日志,主要有以下几类问题:
|
|
10
|
+
|
|
11
|
+
| 问题 | 严重程度 | 影响 |
|
|
12
|
+
|------|----------|------|
|
|
13
|
+
| Python 依赖安装失败 | 高 | Dashboard 后端无法启动 |
|
|
14
|
+
| 插件注册失败 | 中 | 需手动配置或重新安装 |
|
|
15
|
+
| Node.js DEP0190 警告 | 低 | 仅提示,不影响功能 |
|
|
16
|
+
| 安全模式警告 | 低 | 需配置 `plugins.allow` |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 二、Python 依赖安装失败(核心问题)
|
|
21
|
+
|
|
22
|
+
### 2.1 错误现象
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
[Errno 11001] getaddrinfo failed
|
|
26
|
+
Failed to establish a new connection
|
|
27
|
+
ERROR: Could not find a version that satisfies the requirement fastapi==0.109.0
|
|
28
|
+
ERROR: No matching distribution found for fastapi==0.109.0
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 2.2 根本原因
|
|
32
|
+
|
|
33
|
+
`[Errno 11001] getaddrinfo failed` 是 **Windows 网络/DNS 解析错误**,表示 pip 无法连接到 PyPI (pypi.org) 下载包。
|
|
34
|
+
|
|
35
|
+
常见原因:
|
|
36
|
+
|
|
37
|
+
1. **企业网络限制**:公司防火墙或代理阻止访问 pypi.org
|
|
38
|
+
2. **代理未配置**:使用代理上网但 pip 未设置代理
|
|
39
|
+
3. **DNS 解析失败**:无法解析 pypi.org 域名
|
|
40
|
+
4. **VPN 或网络策略**:限制外网访问
|
|
41
|
+
|
|
42
|
+
### 2.3 解决方案
|
|
43
|
+
|
|
44
|
+
#### 方案 A:配置 pip 代理(如有 HTTP/HTTPS 代理)
|
|
45
|
+
|
|
46
|
+
```powershell
|
|
47
|
+
# 临时设置(当前会话)
|
|
48
|
+
$env:HTTP_PROXY = "http://代理地址:端口"
|
|
49
|
+
$env:HTTPS_PROXY = "http://代理地址:端口"
|
|
50
|
+
|
|
51
|
+
# 或使用 pip 配置
|
|
52
|
+
python -m pip config set global.proxy "http://代理地址:端口"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### 方案 B:使用国内 PyPI 镜像
|
|
56
|
+
|
|
57
|
+
```powershell
|
|
58
|
+
# 使用清华镜像
|
|
59
|
+
python -m pip install -r C:\Users\h00427263\.openclaw\extensions\openclaw-agent-dashboard\dashboard\requirements.txt --user -i https://pypi.tuna.tsinghua.edu.cn/simple
|
|
60
|
+
|
|
61
|
+
# 或阿里云镜像
|
|
62
|
+
python -m pip install -r C:\Users\h00427263\.openclaw\extensions\openclaw-agent-dashboard\dashboard\requirements.txt --user -i https://mirrors.aliyun.com/pypi/simple/
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### 方案 C:离线安装(完全无外网时)
|
|
66
|
+
|
|
67
|
+
1. 在有网络的机器上下载依赖:
|
|
68
|
+
|
|
69
|
+
```powershell
|
|
70
|
+
pip download -r requirements.txt -d ./pip-packages
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
2. 将 `pip-packages` 目录和 `requirements.txt` 拷贝到目标机器
|
|
74
|
+
3. 在目标机器执行:
|
|
75
|
+
|
|
76
|
+
```powershell
|
|
77
|
+
pip install --no-index --find-links=./pip-packages -r requirements.txt --user
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
#### 方案 D:跳过 Python 依赖安装
|
|
81
|
+
|
|
82
|
+
若暂时无法解决网络问题,可先完成插件安装,后续再手动安装:
|
|
83
|
+
|
|
84
|
+
```powershell
|
|
85
|
+
npx openclaw-agent-dashboard@1.0.8 --verbose --skip-python
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
然后等网络恢复后手动执行:
|
|
89
|
+
|
|
90
|
+
```powershell
|
|
91
|
+
python -m pip install -r C:\Users\h00427263\.openclaw\extensions\openclaw-agent-dashboard\dashboard\requirements.txt --user
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
> **注意**:Windows 下若 `python3` 不可用,请使用 `python` 命令。
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 三、插件注册失败
|
|
99
|
+
|
|
100
|
+
### 3.1 错误现象
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
plugin already exists: C:\Users\h00427263\.openclaw\extensions\openclaw-agent-dashboard (delete it first)
|
|
104
|
+
⚠ 插件注册失败(Dashboard 可能需要在 openclaw 配置中手动添加)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 3.2 原因说明
|
|
108
|
+
|
|
109
|
+
安装脚本会先执行 `openclaw plugins uninstall`,再复制文件,最后执行 `openclaw plugins install`。
|
|
110
|
+
`uninstall` 可能只从配置中移除插件,**不会删除扩展目录**。当 `install` 发现目标目录已存在时,会报 "plugin already exists"。
|
|
111
|
+
|
|
112
|
+
### 3.3 解决方案
|
|
113
|
+
|
|
114
|
+
#### 方案 A:安装前手动删除旧目录
|
|
115
|
+
|
|
116
|
+
```powershell
|
|
117
|
+
# 删除旧插件目录
|
|
118
|
+
Remove-Item -Recurse -Force "C:\Users\h00427263\.openclaw\extensions\openclaw-agent-dashboard" -ErrorAction SilentlyContinue
|
|
119
|
+
|
|
120
|
+
# 重新安装
|
|
121
|
+
npx openclaw-agent-dashboard@1.0.8 --verbose
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### 方案 B:手动配置 openclaw
|
|
125
|
+
|
|
126
|
+
若插件文件已正确安装,可在 openclaw 配置中手动添加插件。配置文件通常位于:
|
|
127
|
+
|
|
128
|
+
- `C:\Users\h00427263\.openclaw\config.json` 或
|
|
129
|
+
- 环境变量 `OPENCLAW_STATE_DIR` 指向的目录
|
|
130
|
+
|
|
131
|
+
在 `plugins.allow` 中添加 `openclaw-agent-dashboard`,例如:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"plugins": {
|
|
136
|
+
"allow": ["openclaw-agent-dashboard"]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 四、其他警告说明
|
|
144
|
+
|
|
145
|
+
### 4.1 Node.js DEP0190 警告
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
(node:32244) [DEP0190] DeprecationWarning: Passing args to a child process with shell option true...
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- **含义**:在 `shell: true` 下传递参数时,Node.js 提示存在潜在安全风险。
|
|
152
|
+
- **影响**:当前仅为警告,不影响安装和运行。
|
|
153
|
+
- **处理**:可忽略,或等待后续版本改用更安全的调用方式。
|
|
154
|
+
|
|
155
|
+
### 4.2 安全模式警告
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
WARNING: Plugin "openclaw-agent-dashboard" contains dangerous code patterns: Shell command execution detected (child_process)
|
|
159
|
+
plugins.allow is empty; discovered non-bundled plugins may auto-load
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
- **含义**:插件使用了 `child_process` 执行命令,被识别为“危险模式”。
|
|
163
|
+
- **影响**:插件可能被当作未受信任代码,需要显式允许。
|
|
164
|
+
- **处理**:在 openclaw 配置中设置 `plugins.allow: ["openclaw-agent-dashboard"]`。
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 五、推荐安装流程(Windows + 企业网络)
|
|
169
|
+
|
|
170
|
+
1. **删除旧版本(若存在)**:
|
|
171
|
+
|
|
172
|
+
```powershell
|
|
173
|
+
Remove-Item -Recurse -Force "$env:USERPROFILE\.openclaw\extensions\openclaw-agent-dashboard" -ErrorAction SilentlyContinue
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
2. **使用国内镜像安装**:
|
|
177
|
+
|
|
178
|
+
```powershell
|
|
179
|
+
npx openclaw-agent-dashboard@1.0.8 --verbose --skip-python
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
3. **手动安装 Python 依赖(使用镜像)**:
|
|
183
|
+
|
|
184
|
+
```powershell
|
|
185
|
+
python -m pip install -r "$env:USERPROFILE\.openclaw\extensions\openclaw-agent-dashboard\dashboard\requirements.txt" --user -i https://pypi.tuna.tsinghua.edu.cn/simple
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
4. **配置 openclaw 信任插件**(如仍有警告):
|
|
189
|
+
|
|
190
|
+
编辑 `%USERPROFILE%\.openclaw\config.json`,添加:
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{
|
|
194
|
+
"plugins": {
|
|
195
|
+
"allow": ["openclaw-agent-dashboard"]
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
5. **验证**:执行 `openclaw tui`,Dashboard 应自动启动,访问 http://localhost:38271。
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 六、后续改进建议(项目维护者)
|
|
205
|
+
|
|
206
|
+
1. **安装脚本**:在 `openclaw plugins install` 前先 `rmrf(pluginPath)`,或改为传入 `extractedPluginDir` 让 openclaw 自行复制,避免 "plugin already exists"。
|
|
207
|
+
2. **Python 安装**:支持通过环境变量或参数指定 pip 镜像(如 `PIP_INDEX_URL`)。
|
|
208
|
+
3. **帮助信息**:在 Windows 下增加代理、镜像、离线安装的说明。
|
|
209
|
+
4. **runCommand**:考虑使用 `execSync(cmd, args, { shell: false })` 或 `spawn` 传参,消除 DEP0190 警告。
|
package/package.json
CHANGED
package/plugin/package.json
CHANGED
|
@@ -111,6 +111,23 @@ function getVenvPython(venvDir) {
|
|
|
111
111
|
return null;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// ============================================
|
|
115
|
+
// Pip 镜像
|
|
116
|
+
// ============================================
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 获取 pip 镜像参数
|
|
120
|
+
* 支持环境变量 PIP_INDEX_URL 或 PIP_MIRROR
|
|
121
|
+
* @returns {string[]}
|
|
122
|
+
*/
|
|
123
|
+
function getPipMirrorArgs() {
|
|
124
|
+
const mirror = process.env.PIP_INDEX_URL || process.env.PIP_MIRROR || '';
|
|
125
|
+
if (mirror) {
|
|
126
|
+
return ['-i', mirror, '--trusted-host', new URL(mirror).hostname];
|
|
127
|
+
}
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
|
|
114
131
|
// ============================================
|
|
115
132
|
// Python 依赖安装
|
|
116
133
|
// ============================================
|
|
@@ -150,17 +167,31 @@ function installWithVenv(reqFile, venvDir, silent) {
|
|
|
150
167
|
}
|
|
151
168
|
|
|
152
169
|
// 升级 pip(静默,失败不影响)
|
|
153
|
-
|
|
170
|
+
// 如果设了镜像,升级 pip 也用镜像
|
|
171
|
+
const pipMirrorArgs = getPipMirrorArgs();
|
|
172
|
+
runCommand(venvPython, ['-m', 'pip', 'install', '--upgrade', 'pip', '-q', ...pipMirrorArgs], { silent: true });
|
|
154
173
|
|
|
155
174
|
// 安装依赖
|
|
156
175
|
logInfo(' 安装依赖...');
|
|
157
176
|
const installResult = runCommand(
|
|
158
177
|
venvPython,
|
|
159
|
-
['-m', 'pip', 'install', '-r', reqFile, '-q'],
|
|
178
|
+
['-m', 'pip', 'install', '-r', reqFile, '-q', ...pipMirrorArgs],
|
|
160
179
|
{ silent, timeout: 180000 }
|
|
161
180
|
);
|
|
162
181
|
|
|
163
182
|
if (!installResult.success) {
|
|
183
|
+
// 默认源失败,尝试清华镜像
|
|
184
|
+
if (!pipMirrorArgs.length) {
|
|
185
|
+
logWarn(' 默认源失败,尝试清华镜像...');
|
|
186
|
+
const tsinghuaArgs = ['-i', 'https://pypi.tuna.tsinghua.edu.cn/simple', '--trusted-host', 'pypi.tuna.tsinghua.edu.cn'];
|
|
187
|
+
runCommand(venvPython, ['-m', 'pip', 'install', '--upgrade', 'pip', '-q', ...tsinghuaArgs], { silent: true, timeout: 60000 });
|
|
188
|
+
const retryResult = runCommand(
|
|
189
|
+
venvPython,
|
|
190
|
+
['-m', 'pip', 'install', '-r', reqFile, '-q', ...tsinghuaArgs],
|
|
191
|
+
{ silent, timeout: 180000 }
|
|
192
|
+
);
|
|
193
|
+
if (retryResult.success) return true;
|
|
194
|
+
}
|
|
164
195
|
logWarn(' venv 安装依赖失败');
|
|
165
196
|
if (!silent) {
|
|
166
197
|
console.log(' 错误:', installResult.output);
|
|
@@ -180,11 +211,12 @@ function installWithVenv(reqFile, venvDir, silent) {
|
|
|
180
211
|
function installWithPipUser(reqFile, silent) {
|
|
181
212
|
logInfo(' 尝试: pip --user(PEP 668 兜底)');
|
|
182
213
|
|
|
214
|
+
const pipMirrorArgs = getPipMirrorArgs();
|
|
215
|
+
const tsinghuaArgs = ['-i', 'https://pypi.tuna.tsinghua.edu.cn/simple', '--trusted-host', 'pypi.tuna.tsinghua.edu.cn'];
|
|
216
|
+
|
|
183
217
|
const pipCommands = [
|
|
184
|
-
{ cmd: getPythonCmd(), args: ['-m', 'pip', 'install', '-r', reqFile, '-q', '--user'], name: 'pip --user' },
|
|
185
|
-
{ cmd: getPythonCmd(), args: ['-m', 'pip', 'install', '-r', reqFile, '-q'], name: 'pip' },
|
|
186
|
-
{ cmd: 'pip', args: ['install', '-r', reqFile, '-q', '--user'], name: 'pip --user' },
|
|
187
|
-
{ cmd: 'pip3', args: ['install', '-r', reqFile, '-q', '--user'], name: 'pip3 --user' },
|
|
218
|
+
{ cmd: getPythonCmd(), args: ['-m', 'pip', 'install', '-r', reqFile, '-q', '--user', ...pipMirrorArgs], name: 'pip --user' },
|
|
219
|
+
{ cmd: getPythonCmd(), args: ['-m', 'pip', 'install', '-r', reqFile, '-q', ...pipMirrorArgs], name: 'pip' },
|
|
188
220
|
];
|
|
189
221
|
|
|
190
222
|
for (const { cmd, args, name } of pipCommands) {
|
|
@@ -196,6 +228,20 @@ function installWithPipUser(reqFile, silent) {
|
|
|
196
228
|
}
|
|
197
229
|
}
|
|
198
230
|
|
|
231
|
+
// 默认源失败,尝试清华镜像
|
|
232
|
+
if (!pipMirrorArgs.length) {
|
|
233
|
+
logWarn(' 默认源失败,尝试清华镜像...');
|
|
234
|
+
const retryCommands = [
|
|
235
|
+
{ cmd: getPythonCmd(), args: ['-m', 'pip', 'install', '-r', reqFile, '-q', '--user', ...tsinghuaArgs], name: 'pip --user (tsinghua)' },
|
|
236
|
+
{ cmd: getPythonCmd(), args: ['-m', 'pip', 'install', '-r', reqFile, '-q', ...tsinghuaArgs], name: 'pip (tsinghua)' },
|
|
237
|
+
];
|
|
238
|
+
for (const { cmd, args, name } of retryCommands) {
|
|
239
|
+
if (!commandExists(cmd)) continue;
|
|
240
|
+
const result = runCommand(cmd, args, { silent, timeout: 180000 });
|
|
241
|
+
if (result.success) return { success: true, method: name };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
199
245
|
return { success: false, method: null };
|
|
200
246
|
}
|
|
201
247
|
|
package/scripts/lib/common.js
CHANGED
|
@@ -191,10 +191,24 @@ function shellEscape(arg) {
|
|
|
191
191
|
*/
|
|
192
192
|
function runCommand(cmd, args = [], options = {}) {
|
|
193
193
|
const { cwd, silent = true, timeout = 120000 } = options;
|
|
194
|
+
const isWin = process.platform === 'win32';
|
|
194
195
|
|
|
195
196
|
try {
|
|
196
|
-
//
|
|
197
|
-
const
|
|
197
|
+
// Windows 用双引号,Unix 用单引号
|
|
198
|
+
const esc = (arg) => {
|
|
199
|
+
if (!arg) return '""';
|
|
200
|
+
if (isWin) {
|
|
201
|
+
// Windows: 用双引号包裹含空格/特殊字符的路径
|
|
202
|
+
if (/[^a-zA-Z0-9_\-./:=@]/.test(arg)) {
|
|
203
|
+
return '"' + arg.replace(/"/g, '""') + '"';
|
|
204
|
+
}
|
|
205
|
+
return arg;
|
|
206
|
+
} else {
|
|
207
|
+
return shellEscape(arg);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const cmdStr = [cmd, ...args.map(esc)].join(' ');
|
|
198
212
|
|
|
199
213
|
const result = execSync(
|
|
200
214
|
cmdStr,
|
|
@@ -203,7 +217,7 @@ function runCommand(cmd, args = [], options = {}) {
|
|
|
203
217
|
encoding: 'utf8',
|
|
204
218
|
timeout,
|
|
205
219
|
stdio: silent ? ['ignore', 'pipe', 'pipe'] : 'inherit',
|
|
206
|
-
shell:
|
|
220
|
+
shell: true,
|
|
207
221
|
}
|
|
208
222
|
);
|
|
209
223
|
return { success: true, code: 0, output: result || '' };
|