openclaw-agent-dashboard 1.0.21 → 1.0.23

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.
Files changed (135) hide show
  1. package/README.md +55 -321
  2. package/frontend-dist/assets/index-B7XqKAxm.css +1 -0
  3. package/frontend-dist/assets/index-CxJaSYyo.js +24 -0
  4. package/{frontend → frontend-dist}/index.html +2 -1
  5. package/{plugin/openclaw.plugin.json → openclaw.plugin.json} +2 -2
  6. package/package.json +21 -13
  7. package/.github/workflows/release.yml +0 -56
  8. package/VERSION_DISPLAY_delivery.md +0 -242
  9. package/VERSION_DISPLAY_implementation_summary.md +0 -315
  10. package/design_manifest.md +0 -100
  11. package/docs/CHANGELOG_AGENT_MODIFICATIONS.md +0 -132
  12. package/docs/MAINTAINER_RELEASE_WORKFLOW.md +0 -211
  13. package/docs/Openclaw-Agent-Dashboard/345/217/221/345/270/203/344/270/216/346/233/264/346/226/260.md +0 -147
  14. package/docs/RELEASE-LATEST.md +0 -189
  15. package/docs/RELEASE-MODEL-CONFIG.md +0 -95
  16. package/docs/WINDOWS_INSTALL_TROUBLESHOOTING.md +0 -171
  17. package/docs/design/.gitkeep +0 -0
  18. package/docs/design/VERSION_DISPLAY_design.md +0 -1236
  19. package/docs/release-guide.md +0 -259
  20. package/docs/release-operations-manual.md +0 -167
  21. package/docs/reviews/.gitkeep +0 -0
  22. package/docs/reviews/approval_history.json +0 -14
  23. package/docs/reviews/cr_VERSION_DISPLAY.md +0 -397
  24. package/docs/reviews/traceability_manifest.json +0 -279
  25. package/docs/specs/VERSION_DISPLAY_spec.md +0 -371
  26. package/docs/specs/tr3-install-system.md +0 -580
  27. package/docs/windows-collaboration-model-paths-troubleshooting.md +0 -0
  28. package/frontend/package-lock.json +0 -1240
  29. package/frontend/package.json +0 -19
  30. package/frontend/src/App.vue +0 -355
  31. package/frontend/src/components/AgentCard.vue +0 -796
  32. package/frontend/src/components/AgentConfigPanel.vue +0 -539
  33. package/frontend/src/components/AgentDetailPanel.vue +0 -738
  34. package/frontend/src/components/ErrorAnalysisView.vue +0 -546
  35. package/frontend/src/components/ErrorCenterPanel.vue +0 -844
  36. package/frontend/src/components/PerformanceMonitor.vue +0 -515
  37. package/frontend/src/components/SettingsPanel.vue +0 -236
  38. package/frontend/src/components/TokenAnalysisPanel.vue +0 -683
  39. package/frontend/src/components/chain/ChainEdge.vue +0 -85
  40. package/frontend/src/components/chain/ChainNode.vue +0 -166
  41. package/frontend/src/components/chain/TaskChainView.vue +0 -425
  42. package/frontend/src/components/chain/index.ts +0 -3
  43. package/frontend/src/components/chain/types.ts +0 -70
  44. package/frontend/src/components/collaboration/CollaborationFlowSection.vue +0 -1032
  45. package/frontend/src/components/collaboration/CollaborationFlowWrapper.vue +0 -113
  46. package/frontend/src/components/common/VersionDisplay.vue +0 -187
  47. package/frontend/src/components/performance/PerformancePanel.vue +0 -119
  48. package/frontend/src/components/performance/PerformanceSection.vue +0 -1137
  49. package/frontend/src/components/tasks/TaskStatusSection.vue +0 -973
  50. package/frontend/src/components/timeline/TimelineConnector.vue +0 -31
  51. package/frontend/src/components/timeline/TimelineRound.vue +0 -135
  52. package/frontend/src/components/timeline/TimelineStep.vue +0 -691
  53. package/frontend/src/components/timeline/TimelineToolLink.vue +0 -109
  54. package/frontend/src/components/timeline/TimelineView.vue +0 -540
  55. package/frontend/src/components/timeline/index.ts +0 -5
  56. package/frontend/src/components/timeline/types.ts +0 -120
  57. package/frontend/src/composables/index.ts +0 -7
  58. package/frontend/src/composables/useDebounce.ts +0 -48
  59. package/frontend/src/composables/useRealtime.ts +0 -52
  60. package/frontend/src/composables/useState.ts +0 -52
  61. package/frontend/src/composables/useThrottle.ts +0 -46
  62. package/frontend/src/composables/useVirtualScroll.ts +0 -106
  63. package/frontend/src/main.ts +0 -4
  64. package/frontend/src/managers/EventDispatcher.ts +0 -127
  65. package/frontend/src/managers/RealtimeDataManager.ts +0 -302
  66. package/frontend/src/managers/StateManager.ts +0 -128
  67. package/frontend/src/managers/index.ts +0 -5
  68. package/frontend/src/types/collaboration.ts +0 -135
  69. package/frontend/src/types/index.ts +0 -20
  70. package/frontend/src/types/performance.ts +0 -105
  71. package/frontend/src/types/task.ts +0 -38
  72. package/frontend/vite.config.ts +0 -18
  73. package/legacy_code_anatomy.md +0 -518
  74. package/plugin/README.md +0 -99
  75. package/plugin/config.json.example +0 -1
  76. package/plugin/package.json +0 -26
  77. package/scripts/build-plugin.js +0 -81
  78. package/scripts/bundle.sh +0 -62
  79. package/scripts/install-plugin.sh +0 -162
  80. package/scripts/install-python-deps.sh +0 -226
  81. package/scripts/install.js +0 -684
  82. package/scripts/install.sh +0 -367
  83. package/scripts/lib/common.sh +0 -137
  84. package/scripts/release-pack.sh +0 -110
  85. package/scripts/start.js +0 -50
  86. package/scripts/test_available_models.py +0 -284
  87. package/scripts/test_version_display.sh +0 -128
  88. package/scripts/test_websocket_ping.py +0 -44
  89. package/session_registry.json +0 -58
  90. package/tests/.gitkeep +0 -0
  91. package/tests/qa_regression_report.md +0 -359
  92. package/tests/qa_version_display_report.md +0 -598
  93. /package/{src/backend → dashboard}/agents.py +0 -0
  94. /package/{src/backend → dashboard}/api/__init__.py +0 -0
  95. /package/{src/backend → dashboard}/api/agent_config_api.py +0 -0
  96. /package/{src/backend → dashboard}/api/agents.py +0 -0
  97. /package/{src/backend → dashboard}/api/agents_config.py +0 -0
  98. /package/{src/backend → dashboard}/api/chains.py +0 -0
  99. /package/{src/backend → dashboard}/api/collaboration.py +0 -0
  100. /package/{src/backend → dashboard}/api/debug_paths.py +0 -0
  101. /package/{src/backend → dashboard}/api/error_analysis.py +0 -0
  102. /package/{src/backend → dashboard}/api/errors.py +0 -0
  103. /package/{src/backend → dashboard}/api/performance.py +0 -0
  104. /package/{src/backend → dashboard}/api/subagents.py +0 -0
  105. /package/{src/backend → dashboard}/api/timeline.py +0 -0
  106. /package/{src/backend → dashboard}/api/version.py +0 -0
  107. /package/{src/backend → dashboard}/api/websocket.py +0 -0
  108. /package/{src/backend → dashboard}/collaboration.py +0 -0
  109. /package/{src/backend → dashboard}/data/__init__.py +0 -0
  110. /package/{src/backend → dashboard}/data/agent_config_manager.py +0 -0
  111. /package/{src/backend → dashboard}/data/chain_reader.py +0 -0
  112. /package/{src/backend → dashboard}/data/config_reader.py +0 -0
  113. /package/{src/backend → dashboard}/data/error_analyzer.py +0 -0
  114. /package/{src/backend → dashboard}/data/session_reader.py +0 -0
  115. /package/{src/backend → dashboard}/data/subagent_reader.py +0 -0
  116. /package/{src/backend → dashboard}/data/task_history.py +0 -0
  117. /package/{src/backend → dashboard}/data/timeline_reader.py +0 -0
  118. /package/{src/backend → dashboard}/data/version_info_reader.py +0 -0
  119. /package/{src/backend → dashboard}/errors.py +0 -0
  120. /package/{src/backend → dashboard}/main.py +0 -0
  121. /package/{src/backend → dashboard}/mechanism_reader.py +0 -0
  122. /package/{src/backend → dashboard}/mechanisms.py +0 -0
  123. /package/{src/backend → dashboard}/performance.py +0 -0
  124. /package/{src/backend → dashboard}/requirements.txt +0 -0
  125. /package/{src/backend → dashboard}/session_reader.py +0 -0
  126. /package/{src/backend → dashboard}/status/__init__.py +0 -0
  127. /package/{src/backend → dashboard}/status/change_tracker.py +0 -0
  128. /package/{src/backend → dashboard}/status/error_detector.py +0 -0
  129. /package/{src/backend → dashboard}/status/status_cache.py +0 -0
  130. /package/{src/backend → dashboard}/status/status_calculator.py +0 -0
  131. /package/{src/backend → dashboard}/status_calculator.py +0 -0
  132. /package/{src/backend → dashboard}/subagent_reader.py +0 -0
  133. /package/{src/backend → dashboard}/watchers/__init__.py +0 -0
  134. /package/{src/backend → dashboard}/watchers/file_watcher.py +0 -0
  135. /package/{plugin/index.js → index.js} +0 -0
@@ -1,684 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * OpenClaw Agent Dashboard - 插件安装脚本(跨平台)
4
- *
5
- * 两种模式:
6
- * 1. 本地模式:从本地源码安装(npm run deploy 流程)
7
- * 2. 远程模式:从 GitHub Release 下载预构建包安装(npx 流程)
8
- *
9
- * 用法:
10
- * node scripts/install.js # 本地模式
11
- * npx openclaw-agent-dashboard # 远程模式(发布 npm 后)
12
- * node scripts/install.js --verbose # 显示详细输出
13
- * node scripts/install.js --dry-run # 预览
14
- * node scripts/install.js --skip-python # 跳过 Python 依赖
15
- * node scripts/install.js --version 1.0.4 # 指定版本(远程模式)
16
- */
17
-
18
- const fs = require('fs');
19
- const path = require('path');
20
- const {
21
- logInfo,
22
- logStep,
23
- logOk,
24
- logWarn,
25
- logError,
26
- detectOS,
27
- commandExists,
28
- resolveOpenClawConfigDir,
29
- getPluginPath,
30
- parseJsonVersion,
31
- runCommand,
32
- runCommandAsync,
33
- rmrf,
34
- copyDir,
35
- backupDir,
36
- restoreBackup,
37
- cleanupBackup,
38
- cleanupOldBackups,
39
- downloadFile,
40
- formatBytes,
41
- } = require('./lib/common');
42
-
43
- // ============================================
44
- // 配置
45
- // ============================================
46
-
47
- const REPO_OWNER = 'Umarchen';
48
- const REPO_NAME = 'openclaw-agent-dashboard';
49
- const PLUGIN_ID = 'openclaw-agent-dashboard';
50
-
51
- // ============================================
52
- // 参数解析
53
- // ============================================
54
-
55
- function parseArgs() {
56
- const args = process.argv.slice(2);
57
- const opts = {
58
- verbose: args.includes('--verbose') || args.includes('-v') || process.env.VERBOSE === '1',
59
- dryRun: args.includes('--dry-run'),
60
- skipPython: args.includes('--skip-python'),
61
- version: null,
62
- };
63
-
64
- for (let i = 0; i < args.length; i++) {
65
- if (args[i] === '--version' && args[i + 1]) {
66
- opts.version = args[++i];
67
- }
68
- }
69
-
70
- return opts;
71
- }
72
-
73
- // ============================================
74
- // 判断安装模式
75
- // ============================================
76
-
77
- /**
78
- * 检测是否为远程模式(npx 触发,无本地源码)
79
- * @returns {boolean}
80
- */
81
- function isRemoteMode() {
82
- // 如果本地 plugin 目录已打包好,优先用本地模式
83
- const pluginDir = path.join(__dirname, '..', 'plugin');
84
- const dashboardMain = path.join(pluginDir, 'dashboard', 'main.py');
85
-
86
- if (fs.existsSync(dashboardMain)) {
87
- return false;
88
- }
89
-
90
- // npx 模式下,__dirname 会在 npx 缓存目录中
91
- // 检查是否在 node_modules/.cache/npx 或类似路径中
92
- const execDir = path.dirname(__dirname);
93
- const inNpxCache = execDir.includes('npx') ||
94
- execDir.includes('_npx') ||
95
- execDir.includes('npm/_npx');
96
-
97
- return inNpxCache;
98
- }
99
-
100
- // ============================================
101
- // 检查函数
102
- // ============================================
103
-
104
- function checkPrerequisites(remoteMode) {
105
- const os = detectOS();
106
- const checks = [{ cmd: 'node', hint: 'https://nodejs.org' }];
107
-
108
- if (!remoteMode) {
109
- checks.push({ cmd: 'openclaw', hint: 'npm install -g openclaw' });
110
- }
111
-
112
- // Python 检查
113
- if (!commandExists('python3') && !commandExists('python')) {
114
- logWarn('未找到 Python 3,Dashboard 后端可能无法运行');
115
- logInfo('安装指南: https://www.python.org');
116
- }
117
-
118
- let allPassed = true;
119
- for (const { cmd, hint } of checks) {
120
- if (!commandExists(cmd)) {
121
- logError(`未找到 ${cmd},请先安装: ${hint}`);
122
- allPassed = false;
123
- }
124
- }
125
-
126
- return allPassed;
127
- }
128
-
129
- // ============================================
130
- // 远程模式:tgz 解压(纯 Node.js,跨平台)
131
- // ============================================
132
-
133
- /**
134
- * 解析 tar 文件路径和大小
135
- * @param {Buffer} buffer - tar 数据
136
- * @returns {{name: string, size: number, type: string, offset: number}[]}
137
- */
138
- function parseTarEntries(buffer) {
139
- const entries = [];
140
- let offset = 0;
141
-
142
- while (offset < buffer.length - 512) {
143
- // 读取文件名(0-99)
144
- let name = buffer.toString('utf8', offset, offset + 100).replace(/\0.*$/, '');
145
-
146
- // 跳过空块
147
- if (name === '') {
148
- offset += 512;
149
- continue;
150
- }
151
-
152
- // 文件大小(124-135,八进制)
153
- const sizeStr = buffer.toString('utf8', offset + 124, offset + 135).replace(/\0.*$/, '').trim();
154
- const size = parseInt(sizeStr, 8) || 0;
155
-
156
- // 文件类型(156)
157
- const type = buffer.toString('utf8', offset + 156, offset + 157);
158
-
159
- entries.push({ name, size, type, offset });
160
-
161
- // 跳到下一个条目(512 字节头 + 数据,数据按 512 对齐)
162
- const dataBlocks = Math.ceil(size / 512);
163
- offset += 512 + dataBlocks * 512;
164
- }
165
-
166
- return entries;
167
- }
168
-
169
- /**
170
- * 解压 tgz 文件到指定目录
171
- * @param {string} tgzFile - tgz 文件路径
172
- * @param {string} destDir - 目标目录
173
- * @param {boolean} verbose - 显示详细信息
174
- * @returns {Promise<boolean>}
175
- */
176
- async function extractTgz(tgzFile, destDir, verbose) {
177
- const zlib = require('zlib');
178
- const fsPromises = require('fs').promises;
179
-
180
- return new Promise((resolve) => {
181
- const fileStream = fs.createReadStream(tgzFile);
182
- const gunzip = zlib.createGunzip();
183
- const chunks = [];
184
-
185
- gunzip.on('data', (chunk) => chunks.push(chunk));
186
- gunzip.on('end', () => {
187
- try {
188
- const tarData = Buffer.concat(chunks);
189
- const entries = parseTarEntries(tarData);
190
-
191
- for (const entry of entries) {
192
- // 跳过目录类型
193
- if (entry.type === '5') continue;
194
- // 跳过空文件名或 pax header
195
- if (!entry.name || entry.name.startsWith('PaxHeader')) continue;
196
-
197
- const fullPath = path.join(destDir, entry.name);
198
- fs.mkdirSync(path.dirname(fullPath), { recursive: true });
199
-
200
- // 写入文件数据
201
- const dataOffset = entry.offset + 512;
202
- const fileData = tarData.slice(dataOffset, dataOffset + entry.size);
203
- fs.writeFileSync(fullPath, fileData);
204
-
205
- if (verbose) {
206
- logInfo(` ${entry.name} (${formatBytes(entry.size)})`);
207
- }
208
- }
209
-
210
- resolve(true);
211
- } catch (err) {
212
- logError(`解压处理失败: ${err.message}`);
213
- resolve(false);
214
- }
215
- });
216
-
217
- gunzip.on('error', (err) => {
218
- logError(`gzip 解压失败: ${err.message}`);
219
- resolve(false);
220
- });
221
-
222
- fileStream.on('error', (err) => {
223
- logError(`读取文件失败: ${err.message}`);
224
- resolve(false);
225
- });
226
-
227
- fileStream.pipe(gunzip);
228
- });
229
- }
230
-
231
- // ============================================
232
- // 远程模式:版本解析
233
- // ============================================
234
-
235
- function resolveVersion(requested) {
236
- if (requested && requested !== 'latest') {
237
- return requested;
238
- }
239
-
240
- // 优先从本地 package.json 读取版本(npm/npx 安装时自带)
241
- try {
242
- const pkgPath = path.join(__dirname, '..', 'package.json');
243
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
244
- if (pkg.version) return pkg.version;
245
- } catch {
246
- // ignore
247
- }
248
-
249
- // fallback: 从 GitHub API 获取最新 release 版本
250
- try {
251
- const https = require('https');
252
- const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`;
253
-
254
- return new Promise((resolve) => {
255
- https.get(url, {
256
- headers: { 'User-Agent': 'openclaw-agent-dashboard-installer' },
257
- }, (res) => {
258
- let data = '';
259
- res.on('data', (chunk) => { data += chunk; });
260
- res.on('end', () => {
261
- try {
262
- const json = JSON.parse(data);
263
- const tag = json.tag_name || '';
264
- resolve(tag.replace(/^v/, '') || '1.0.0');
265
- } catch {
266
- logWarn('无法解析版本信息');
267
- resolve('1.0.0');
268
- }
269
- });
270
- }).on('error', () => {
271
- logWarn('无法连接 GitHub API');
272
- resolve('1.0.0');
273
- });
274
- });
275
- } catch {
276
- return Promise.resolve('1.0.0');
277
- }
278
- }
279
-
280
- // ============================================
281
- // 远程模式:从 npm 包自带的 plugin 目录安装(无需下载 tgz)
282
- // ============================================
283
-
284
- async function localPluginInstall(bundledPluginDir, pluginPath, options) {
285
- const newVersion = parseJsonVersion(path.join(bundledPluginDir, 'openclaw.plugin.json'));
286
- const oldVersion = fs.existsSync(path.join(pluginPath, 'openclaw.plugin.json'))
287
- ? parseJsonVersion(path.join(pluginPath, 'openclaw.plugin.json'))
288
- : null;
289
-
290
- logInfo(`版本: ${newVersion}(npm 包自带)`);
291
- if (oldVersion) {
292
- logInfo(` ${oldVersion} → ${newVersion}`);
293
- }
294
-
295
- // 备份旧版本
296
- if (fs.existsSync(pluginPath)) {
297
- logStep('备份旧版本...');
298
- backupDir(pluginPath);
299
- const extDir = path.dirname(pluginPath);
300
- cleanupOldBackups(extDir, `${PLUGIN_ID}.backup-`, 2);
301
- logOk('备份完成');
302
- }
303
-
304
- // 复制到插件目录
305
- logStep('安装插件...');
306
- if (fs.existsSync(pluginPath)) {
307
- rmrf(pluginPath);
308
- }
309
- copyDir(bundledPluginDir, pluginPath);
310
- logOk('插件已安装');
311
-
312
- // 安装 Python 依赖
313
- if (!options.skipPython && fs.existsSync(path.join(pluginPath, 'dashboard', 'requirements.txt'))) {
314
- logStep('安装 Python 依赖...');
315
- const scriptPath = path.join(__dirname, 'install-python-deps.js');
316
- const args = [scriptPath, pluginPath];
317
- if (options.verbose) args.push('--verbose');
318
- const result = runCommand('node', args, { silent: !options.verbose });
319
- if (!result.success) {
320
- logWarn('Python 依赖安装失败,Dashboard 启动时可能需要手动安装');
321
- }
322
- }
323
-
324
- // 清理旧备份
325
- logStep('清理旧备份...');
326
- const extDir = path.dirname(pluginPath);
327
- cleanupOldBackups(extDir, `${PLUGIN_ID}.backup-`, 1);
328
-
329
- return true;
330
- }
331
-
332
- // ============================================
333
- // 远程模式:下载安装
334
- // ============================================
335
-
336
- /**
337
- * 获取 tgz 缓存目录
338
- * @returns {string}
339
- */
340
- function getCacheDir() {
341
- if (process.platform === 'win32') {
342
- // Windows: %LOCALAPPDATA%\openclaw-agent-dashboard\cache
343
- const localAppData = process.env.LOCALAPPDATA || process.env.USERPROFILE || require('os').homedir();
344
- return path.join(localAppData, 'openclaw-agent-dashboard', 'cache');
345
- }
346
- // Linux/macOS: ~/.cache/openclaw-agent-dashboard
347
- const home = process.env.HOME || require('os').homedir();
348
- return path.join(home, '.cache', 'openclaw-agent-dashboard');
349
- }
350
-
351
- async function remoteInstall(pluginPath, options) {
352
- const tmpDir = path.join(require('os').tmpdir(), `oc-dashboard-install-${Date.now()}`);
353
- fs.mkdirSync(tmpDir, { recursive: true });
354
-
355
- // 优先使用 npm 包自带的 plugin 目录(npx 模式下 plugin 就在包里)
356
- const bundledPluginDir = path.join(__dirname, '..', 'plugin');
357
- const bundledPluginJson = path.join(bundledPluginDir, 'openclaw.plugin.json');
358
- const bundledDashboardMain = path.join(bundledPluginDir, 'dashboard', 'main.py');
359
-
360
- // 仅当 npm 包内带完整 plugin(含 dashboard)时才直接拷贝;否则走 GitHub Release tgz,避免残缺 plugin 覆盖用户目录
361
- if (fs.existsSync(bundledPluginJson) && fs.existsSync(bundledDashboardMain)) {
362
- return await localPluginInstall(bundledPluginDir, pluginPath, options);
363
- }
364
-
365
- try {
366
- // 1. 解析版本
367
- const version = await resolveVersion(options.version);
368
- logInfo(`版本: ${version}`);
369
-
370
- const downloadUrl = `https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download/v${version}/${PLUGIN_ID}-v${version}.tgz`;
371
- const tgzFileName = `${PLUGIN_ID}-v${version}.tgz`;
372
- const cacheDir = getCacheDir();
373
- const cachedTgz = path.join(cacheDir, tgzFileName);
374
- const tgzFile = path.join(tmpDir, `${PLUGIN_ID}.tgz`);
375
-
376
- // 2. 检查缓存
377
- if (fs.existsSync(cachedTgz)) {
378
- logStep('使用本地缓存...');
379
- logInfo(` 缓存: ${cachedTgz}`);
380
- fs.copyFileSync(cachedTgz, tgzFile);
381
- logOk('缓存命中,跳过下载');
382
- } else {
383
- // 下载
384
- logStep('下载预构建包...');
385
- if (!await downloadFile(downloadUrl, tgzFile, { verbose: options.verbose })) {
386
- logError('下载失败');
387
- logInfo('');
388
- logInfo('可能原因:');
389
- logInfo(' 1. 网络连接问题');
390
- logInfo(' 2. 版本不存在: v' + version);
391
- logInfo(' 3. GitHub 访问受限');
392
- logInfo('');
393
- logInfo('替代方案:');
394
- logInfo(' git clone https://github.com/' + REPO_OWNER + '/' + REPO_NAME + '.git');
395
- logInfo(' cd ' + REPO_NAME);
396
- logInfo(' npm install && npm run deploy');
397
- return false;
398
- }
399
- // 保存到缓存
400
- try {
401
- fs.mkdirSync(cacheDir, { recursive: true });
402
- fs.copyFileSync(tgzFile, cachedTgz);
403
- logInfo(` 已缓存到: ${cachedTgz}`);
404
- } catch (e) {
405
- // 缓存写入失败不影响安装
406
- if (options.verbose) logWarn(`缓存写入失败: ${e.message}`);
407
- }
408
- logOk('下载完成');
409
- }
410
-
411
- // 3. 解压 tgz(纯 Node.js 实现,不依赖系统 tar)
412
- logStep('解压安装包...');
413
- const extractDir = path.join(tmpDir, 'extract');
414
- fs.mkdirSync(extractDir, { recursive: true });
415
-
416
- let extractOk = false;
417
- try {
418
- extractOk = await extractTgz(tgzFile, extractDir, options.verbose);
419
- } catch (err) {
420
- logError(`解压失败: ${err.message}`);
421
- }
422
-
423
- if (!extractOk) {
424
- logError('解压失败');
425
- return false;
426
- }
427
- logOk('解压完成');
428
-
429
- // 找到解压后的 plugin 目录(tgz 内可能有一层 package 目录)
430
- let extractedPluginDir = extractDir;
431
- const entries = fs.readdirSync(extractDir);
432
- if (entries.length === 1) {
433
- const single = path.join(extractDir, entries[0]);
434
- if (fs.statSync(single).isDirectory() && fs.existsSync(path.join(single, 'openclaw.plugin.json'))) {
435
- extractedPluginDir = single;
436
- }
437
- }
438
-
439
- // 4. 获取新旧版本
440
- const newVersion = parseJsonVersion(path.join(extractedPluginDir, 'openclaw.plugin.json'));
441
- const oldVersion = fs.existsSync(path.join(pluginPath, 'openclaw.plugin.json'))
442
- ? parseJsonVersion(path.join(pluginPath, 'openclaw.plugin.json'))
443
- : null;
444
-
445
- if (oldVersion) {
446
- logInfo(`${oldVersion} → ${newVersion}`);
447
- }
448
-
449
- // 5. 备份旧版本
450
- if (fs.existsSync(pluginPath)) {
451
- logStep('备份旧版本...');
452
- const backupPath = backupDir(pluginPath);
453
- // 清理旧备份(保留最近2个)
454
- const extDir = path.dirname(pluginPath);
455
- cleanupOldBackups(extDir, `${PLUGIN_ID}.backup-`, 2);
456
- logOk('备份完成');
457
- }
458
-
459
- // 6. 安装
460
- logStep('安装插件...');
461
-
462
- // 复制到 extensions 目录,openclaw 会自动发现并加载,无需 plugins install
463
- copyDir(extractedPluginDir, pluginPath);
464
- logOk('插件已安装');
465
-
466
- if (!commandExists('openclaw')) {
467
- logWarn('未找到 openclaw 命令,请先安装: npm install -g openclaw');
468
- }
469
-
470
- // 7. 安装 Python 依赖
471
- if (!options.skipPython && fs.existsSync(path.join(pluginPath, 'dashboard', 'requirements.txt'))) {
472
- logStep('安装 Python 依赖...');
473
- const scriptPath = path.join(__dirname, 'install-python-deps.js');
474
- const args = [scriptPath, pluginPath];
475
- if (options.verbose) args.push('--verbose');
476
- const result = runCommand('node', args, { silent: !options.verbose });
477
- if (!result.success) {
478
- logWarn('Python 依赖安装失败,Dashboard 启动时可能需要手动安装');
479
- }
480
- }
481
-
482
- // 8. 清理旧备份(升级成功后)
483
- logStep('清理旧备份...');
484
- const extDir = path.dirname(pluginPath);
485
- cleanupOldBackups(extDir, `${PLUGIN_ID}.backup-`, 1);
486
-
487
- return true;
488
- } finally {
489
- // 清理临时目录
490
- rmrf(tmpDir);
491
- }
492
- }
493
-
494
- // ============================================
495
- // 本地模式:原有流程(不变)
496
- // ============================================
497
-
498
- function isPluginPacked() {
499
- const pluginDir = path.join(__dirname, '..', 'plugin');
500
- const dashboardMain = path.join(pluginDir, 'dashboard', 'main.py');
501
- return fs.existsSync(dashboardMain);
502
- }
503
-
504
- async function uninstallOld(pluginPath, verbose) {
505
- if (!fs.existsSync(pluginPath)) {
506
- logOk('无旧版本');
507
- return true;
508
- }
509
-
510
- logInfo(' 执行: openclaw plugins uninstall openclaw-agent-dashboard --force');
511
- const result = await runCommandAsync('openclaw', ['plugins', 'uninstall', 'openclaw-agent-dashboard', '--force']);
512
-
513
- if (result.success) {
514
- logOk(' 已卸载(配置记录)');
515
- } else {
516
- logWarn(' uninstall 失败(可能未注册)');
517
- }
518
-
519
- if (fs.existsSync(pluginPath)) {
520
- rmrf(pluginPath);
521
- logOk(' 已删除旧目录');
522
- }
523
-
524
- return true;
525
- }
526
-
527
- async function installPlugin(verbose) {
528
- logInfo(' 执行: openclaw plugins install ./plugin');
529
- const result = await runCommandAsync('openclaw', ['plugins', 'install', './plugin']);
530
-
531
- if (!result.success) {
532
- logError('插件安装失败');
533
- return false;
534
- }
535
-
536
- logOk('插件已安装');
537
- return true;
538
- }
539
-
540
- function installPythonDeps(pluginPath, verbose) {
541
- const reqFile = path.join(pluginPath, 'dashboard', 'requirements.txt');
542
-
543
- if (!fs.existsSync(reqFile)) {
544
- logWarn('插件未正确安装(缺少 requirements.txt)');
545
- return false;
546
- }
547
-
548
- const scriptPath = path.join(__dirname, 'install-python-deps.js');
549
- const args = [scriptPath, pluginPath];
550
- if (verbose) args.push('--verbose');
551
-
552
- const result = runCommand('node', args, { silent: !verbose });
553
- return result.success;
554
- }
555
-
556
- // ============================================
557
- // 主流程
558
- // ============================================
559
-
560
- async function main() {
561
- const options = parseArgs();
562
- const remoteMode = isRemoteMode();
563
-
564
- logInfo(`系统: ${detectOS()}`);
565
- logInfo(`配置目录: ${resolveOpenClawConfigDir()}`);
566
-
567
- const pluginPath = getPluginPath();
568
- logInfo(`插件路径: ${pluginPath}`);
569
- console.log('');
570
-
571
- // dry-run
572
- if (options.dryRun) {
573
- const newVersion = remoteMode
574
- ? await resolveVersion(options.version)
575
- : parseJsonVersion(path.join(__dirname, '..', 'plugin', 'openclaw.plugin.json'));
576
-
577
- console.log('');
578
- logInfo(`[DRY-RUN] 模式: ${remoteMode ? '远程安装' : '本地安装'}`);
579
- logInfo(`[DRY-RUN] 版本: ${newVersion}`);
580
- logInfo(`[DRY-RUN] 安装到: ${pluginPath}`);
581
- if (!options.skipPython) {
582
- logInfo('[DRY-RUN] 安装 Python 依赖到 venv');
583
- }
584
- logOk('预览完成,未执行实际安装');
585
- process.exit(0);
586
- }
587
-
588
- if (remoteMode) {
589
- // ============ 远程模式 ============
590
- logInfo('=== OpenClaw Agent Dashboard 远程安装 ===');
591
- console.log('');
592
-
593
- if (!checkPrerequisites(true)) {
594
- process.exit(1);
595
- }
596
-
597
- const success = await remoteInstall(pluginPath, options);
598
-
599
- console.log('');
600
- if (success) {
601
- logOk('=== 安装完成 ===');
602
- } else {
603
- logError('安装失败');
604
- process.exit(1);
605
- }
606
- } else {
607
- // ============ 本地模式(原有流程) ============
608
- const rootDir = path.join(__dirname, '..');
609
- const newVersion = parseJsonVersion(path.join(rootDir, 'plugin', 'openclaw.plugin.json'));
610
- const oldVersion = fs.existsSync(path.join(pluginPath, 'openclaw.plugin.json'))
611
- ? parseJsonVersion(path.join(pluginPath, 'openclaw.plugin.json'))
612
- : null;
613
-
614
- if (oldVersion) {
615
- logInfo('=== OpenClaw Agent Dashboard 插件升级 ===');
616
- console.log('');
617
- logInfo(` ${oldVersion} → ${newVersion}`);
618
- } else {
619
- logInfo('=== OpenClaw Agent Dashboard 插件安装 ===');
620
- console.log('');
621
- logInfo(` 版本: ${newVersion}`);
622
- }
623
-
624
- // 1. 检查前置条件
625
- logStep('1/4 检查前置条件...');
626
- if (!checkPrerequisites(false)) {
627
- process.exit(1);
628
- }
629
- logOk('前置条件检查通过');
630
-
631
- // 2. 检查插件是否已打包
632
- logStep('2/4 检查插件打包...');
633
- if (!isPluginPacked()) {
634
- logError('插件未打包,请先执行: npm run pack');
635
- process.exit(1);
636
- }
637
- logOk('插件已打包');
638
-
639
- // 3. 安装插件
640
- if (fs.existsSync(pluginPath)) {
641
- logStep('3/4 移除旧版本后安装...');
642
- } else {
643
- logStep('3/4 安装插件...');
644
- }
645
-
646
- await uninstallOld(pluginPath, options.verbose);
647
-
648
- if (!await installPlugin(options.verbose)) {
649
- process.exit(1);
650
- }
651
-
652
- // 4. 安装 Python 依赖
653
- if (!options.skipPython) {
654
- logStep('4/4 安装 Python 依赖...');
655
- installPythonDeps(pluginPath, options.verbose);
656
- } else {
657
- logStep('4/4 跳过 Python 依赖安装');
658
- }
659
-
660
- console.log('');
661
- if (oldVersion) {
662
- logOk(`=== 升级完成 (${oldVersion} → ${newVersion}) ===`);
663
- } else {
664
- logOk(`=== 安装完成 (v${newVersion}) ===`);
665
- }
666
- }
667
-
668
- // 公共完成提示
669
- console.log('');
670
- logInfo('执行任意 openclaw 命令(如 openclaw tui)时,Dashboard 会自动启动。');
671
- logInfo('访问地址: http://localhost:38271');
672
- console.log('');
673
- logInfo('若端口被占用,可创建 ~/.openclaw-agent-dashboard/config.json 设置端口:');
674
- logInfo(' {"port": 38271}');
675
- console.log('');
676
- }
677
-
678
- main().catch((err) => {
679
- logError(`安装失败: ${err.message}`);
680
- if (process.env.VERBOSE === '1') {
681
- console.error(err);
682
- }
683
- process.exit(1);
684
- });