@agent-webui/ai-desk-daemon 1.0.30-beta1 → 1.0.30-beta2
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 +22 -4
- package/bin/cli.js +78 -5
- package/lib/cli-anything-manager.js +218 -0
- package/lib/config.js +66 -10
- package/lib/daemon-manager.js +1 -2
- package/lib/harness-manager.js +178 -0
- package/lib/platform.js +71 -2
- package/lib/runtime-manager.js +320 -0
- package/lib/workspace-packages.js +172 -0
- package/package.json +20 -6
- package/python-runtime/cli_anything/__init__.py +3 -0
- package/python-runtime/cli_anything/__main__.py +5 -0
- package/python-runtime/cli_anything/_compat.py +195 -0
- package/python-runtime/cli_anything/analyze.py +10 -0
- package/python-runtime/cli_anything/design.py +10 -0
- package/python-runtime/cli_anything/generate.py +10 -0
- package/python-runtime/cli_anything/map.py +10 -0
- package/python-runtime/cli_anything/model.py +10 -0
- package/python-runtime/setup.py +14 -0
- package/scripts/postinstall.js +23 -4
- package/dist/darwin-arm64/ai-desk-daemon +0 -0
- package/dist/darwin-x64/ai-desk-daemon +0 -0
- package/dist/linux-arm64/ai-desk-daemon +0 -0
- package/dist/linux-x64/ai-desk-daemon +0 -0
- package/dist/win32-x64/ai-desk-daemon.exe +0 -0
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## ✨ 特性
|
|
10
10
|
|
|
11
|
-
- 🎯
|
|
11
|
+
- 🎯 **智能启动** - Web 访问时自动检测并尝试启动守护进程
|
|
12
12
|
- 🖥️ **图形化管理** - 系统托盘 + 控制面板,便捷管理
|
|
13
13
|
- 🔄 **实时监控** - 查看守护进程状态、日志和统计信息
|
|
14
14
|
- 🚀 **智能启动器** - 多种方式自动唤醒守护进程
|
|
@@ -24,11 +24,17 @@
|
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
# 全局安装
|
|
27
|
-
npm install -g agent-webui/ai-desk
|
|
27
|
+
npm install -g @agent-webui/ai-desk
|
|
28
28
|
|
|
29
|
-
# 启动 daemon
|
|
29
|
+
# 启动 daemon(默认使用已保存模式)
|
|
30
30
|
aidesk start
|
|
31
31
|
|
|
32
|
+
# 切到 Native mode
|
|
33
|
+
aidesk start --mode native
|
|
34
|
+
|
|
35
|
+
# 切到 Bundled CLI-Anything runtime mode
|
|
36
|
+
aidesk start --mode cli-anything
|
|
37
|
+
|
|
32
38
|
# 查看状态
|
|
33
39
|
aidesk status
|
|
34
40
|
```
|
|
@@ -36,6 +42,9 @@ aidesk status
|
|
|
36
42
|
**包含内容**:
|
|
37
43
|
- ✅ AI Desk Daemon 后台服务
|
|
38
44
|
- ✅ CLI 命令行管理工具
|
|
45
|
+
- ✅ 默认 harness 运行时与安装脚本
|
|
46
|
+
- ✅ bundled CLI-Anything runtime(随包安装到 AI Desk runtime)
|
|
47
|
+
- ✅ 按平台分发的内置 Python runtime
|
|
39
48
|
- ✅ HTTP API (http://localhost:9527)
|
|
40
49
|
|
|
41
50
|
**不包含**:
|
|
@@ -43,14 +52,21 @@ aidesk status
|
|
|
43
52
|
- ❌ 桌面 GUI 应用
|
|
44
53
|
|
|
45
54
|
**可用命令**:
|
|
46
|
-
- `aidesk start` -
|
|
55
|
+
- `aidesk start` - 启动守护进程(后台运行,沿用当前已保存 mode)
|
|
56
|
+
- `aidesk start --mode native` - 切到 Native mode 并启动
|
|
57
|
+
- `aidesk start --mode cli-anything` - 切到 Bundled CLI-Anything runtime mode 并启动
|
|
47
58
|
- `aidesk start --log` - 启动守护进程(前台运行,跟随日志)
|
|
48
59
|
- `aidesk stop` - 停止守护进程
|
|
49
60
|
- `aidesk restart` - 重启守护进程
|
|
61
|
+
- `aidesk restart --mode cli-anything` - 切到 Bundled CLI-Anything runtime mode 并重启
|
|
50
62
|
- `aidesk status` - 查看状态
|
|
51
63
|
- `aidesk logs` - 查看日志
|
|
52
64
|
- `aidesk logs -f` - 实时查看日志(不会停止守护进程)
|
|
53
65
|
|
|
66
|
+
**Mode 说明**:
|
|
67
|
+
- `Native mode`:检测、模型查询、命令执行走 daemon 原生实现。
|
|
68
|
+
- `Bundled CLI-Anything runtime mode`:使用 npm 安装时随包准备好的 Python runtime 和 `cli_anything` 入口;如果某个功能当前 runtime 不支持,daemon 会按配置回退到 native。
|
|
69
|
+
|
|
54
70
|
📖 详细使用说明:[NPM_CLI.md](NPM_CLI.md)
|
|
55
71
|
|
|
56
72
|
---
|
|
@@ -69,6 +85,8 @@ curl -fsSL https://github.com/your-repo/ai-desk-desktop/releases/latest/download
|
|
|
69
85
|
./scripts/install-macos.sh
|
|
70
86
|
```
|
|
71
87
|
|
|
88
|
+
安装脚本会为当前会话尝试启动 daemon,但不会再通过 LaunchAgent/Plist 保活。
|
|
89
|
+
|
|
72
90
|
#### Linux
|
|
73
91
|
|
|
74
92
|
```bash
|
package/bin/cli.js
CHANGED
|
@@ -7,12 +7,67 @@
|
|
|
7
7
|
const { program } = require('commander');
|
|
8
8
|
const chalk = require('chalk');
|
|
9
9
|
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
10
11
|
const { start, stop, restart, status } = require('../lib/daemon-manager');
|
|
11
12
|
const { getLogPath } = require('../lib/platform');
|
|
12
13
|
const { VERSION } = require('../lib/platform');
|
|
13
14
|
|
|
15
|
+
function resolveRequestedMode(options) {
|
|
16
|
+
const hasExplicitMode = typeof options.mode === 'string' && options.mode.trim() !== '';
|
|
17
|
+
|
|
18
|
+
if (options.cliAnything && options.native) {
|
|
19
|
+
throw new Error('Use either --cli-anything or --native, not both');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (hasExplicitMode && (options.cliAnything || options.native)) {
|
|
23
|
+
throw new Error('Use either --mode or the mode shortcut flags, not both');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (options.cliAnything) {
|
|
27
|
+
return 'cli-anything';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (options.native) {
|
|
31
|
+
return 'native';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!hasExplicitMode) {
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const normalizedMode = options.mode.trim().toLowerCase();
|
|
39
|
+
if (normalizedMode !== 'native' && normalizedMode !== 'cli-anything') {
|
|
40
|
+
throw new Error(`Unsupported mode: ${options.mode}. Use "native" or "cli-anything".`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return normalizedMode;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function configureRequestedMode(mode) {
|
|
47
|
+
const packageRoot = path.join(__dirname, '..');
|
|
48
|
+
const { loadConfig } = require('../lib/config');
|
|
49
|
+
const { syncCLIAnythingConfig } = require('../lib/cli-anything-manager');
|
|
50
|
+
|
|
51
|
+
if (!mode) {
|
|
52
|
+
const config = loadConfig();
|
|
53
|
+
if (config.cli_anything?.enabled) {
|
|
54
|
+
return syncCLIAnythingConfig(packageRoot, { enabled: true });
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (mode === 'cli-anything') {
|
|
60
|
+
return syncCLIAnythingConfig(packageRoot, { enabled: true });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return syncCLIAnythingConfig(packageRoot, {
|
|
64
|
+
enabled: false,
|
|
65
|
+
ensureInstalled: false,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
14
69
|
program
|
|
15
|
-
.name('
|
|
70
|
+
.name('aidesk')
|
|
16
71
|
.description('AI Desk Daemon - CLI tool for managing the AI Desk daemon service')
|
|
17
72
|
.version(VERSION);
|
|
18
73
|
|
|
@@ -20,11 +75,20 @@ program
|
|
|
20
75
|
program
|
|
21
76
|
.command('start')
|
|
22
77
|
.description('Start the daemon')
|
|
78
|
+
.option('-m, --mode <mode>', 'Implementation mode: native or cli-anything')
|
|
79
|
+
.option('--cli-anything', 'Enable CLI-Anything mode before starting')
|
|
80
|
+
.option('--native', 'Force native mode before starting')
|
|
23
81
|
.option('--log', 'Follow daemon logs in foreground (Ctrl+C to stop daemon)')
|
|
24
82
|
.action(async (options) => {
|
|
25
83
|
try {
|
|
26
|
-
const
|
|
27
|
-
const
|
|
84
|
+
const mode = resolveRequestedMode(options) || 'native';
|
|
85
|
+
const modeResult = configureRequestedMode(mode);
|
|
86
|
+
start();
|
|
87
|
+
if (mode === 'cli-anything' && modeResult?.runtimeInfo?.cliAnythingPath) {
|
|
88
|
+
console.log(chalk.cyan(`CLI-Anything mode configured: ${modeResult.runtimeInfo.cliAnythingPath}`));
|
|
89
|
+
} else if (mode === 'native') {
|
|
90
|
+
console.log(chalk.cyan('Native mode configured'));
|
|
91
|
+
}
|
|
28
92
|
console.log(chalk.green('✓ Daemon started successfully'));
|
|
29
93
|
|
|
30
94
|
// Only follow logs if --log is specified
|
|
@@ -138,9 +202,19 @@ program
|
|
|
138
202
|
program
|
|
139
203
|
.command('restart')
|
|
140
204
|
.description('Restart the daemon')
|
|
141
|
-
.
|
|
205
|
+
.option('-m, --mode <mode>', 'Implementation mode: native or cli-anything')
|
|
206
|
+
.option('--cli-anything', 'Enable CLI-Anything mode before restarting')
|
|
207
|
+
.option('--native', 'Force native mode before restarting')
|
|
208
|
+
.action(async (options) => {
|
|
142
209
|
try {
|
|
210
|
+
const mode = resolveRequestedMode(options);
|
|
211
|
+
const modeResult = configureRequestedMode(mode);
|
|
143
212
|
restart();
|
|
213
|
+
if (mode === 'cli-anything' && modeResult?.runtimeInfo?.cliAnythingPath) {
|
|
214
|
+
console.log(chalk.cyan(`CLI-Anything mode configured: ${modeResult.runtimeInfo.cliAnythingPath}`));
|
|
215
|
+
} else if (mode === 'native') {
|
|
216
|
+
console.log(chalk.cyan('Native mode configured'));
|
|
217
|
+
}
|
|
144
218
|
console.log(chalk.green('✓ Daemon restarted successfully'));
|
|
145
219
|
} catch (error) {
|
|
146
220
|
console.error(chalk.red('✗ Failed to restart daemon:'), error.message);
|
|
@@ -228,4 +302,3 @@ program.parse(process.argv);
|
|
|
228
302
|
if (!process.argv.slice(2).length) {
|
|
229
303
|
program.outputHelp();
|
|
230
304
|
}
|
|
231
|
-
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { ensureRuntime, readJSON, run, writeJSON } = require('./runtime-manager');
|
|
4
|
+
const { getConfigPath } = require('./platform');
|
|
5
|
+
|
|
6
|
+
const CLI_ANYTHING_MODULE = 'cli_anything';
|
|
7
|
+
|
|
8
|
+
function resolveCommandPath(binDir, command) {
|
|
9
|
+
const candidates = process.platform === 'win32'
|
|
10
|
+
? [
|
|
11
|
+
path.join(binDir, `${command}.exe`),
|
|
12
|
+
path.join(binDir, `${command}.cmd`),
|
|
13
|
+
path.join(binDir, `${command}.bat`),
|
|
14
|
+
path.join(binDir, command),
|
|
15
|
+
]
|
|
16
|
+
: [path.join(binDir, command)];
|
|
17
|
+
|
|
18
|
+
for (const candidate of candidates) {
|
|
19
|
+
if (fs.existsSync(candidate)) {
|
|
20
|
+
return candidate;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return candidates[0];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveCLIAnythingRuntimeSource(packageRoot = '') {
|
|
28
|
+
const candidates = [];
|
|
29
|
+
|
|
30
|
+
if (process.env.AI_DESK_CLI_ANYTHING_RUNTIME_SOURCE) {
|
|
31
|
+
candidates.push(process.env.AI_DESK_CLI_ANYTHING_RUNTIME_SOURCE);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (packageRoot) {
|
|
35
|
+
candidates.push(path.join(packageRoot, 'python-runtime'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
candidates.push(path.join(__dirname, '..', 'python-runtime'));
|
|
39
|
+
|
|
40
|
+
for (const candidate of candidates) {
|
|
41
|
+
if (!candidate) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const setupPyPath = path.join(candidate, 'setup.py');
|
|
46
|
+
if (fs.existsSync(setupPyPath)) {
|
|
47
|
+
return candidate;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return '';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function cliAnythingRuntimeLooksHealthy(runtimeInfo, pythonModule = CLI_ANYTHING_MODULE) {
|
|
55
|
+
let importable = false;
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const importResult = run(runtimeInfo.pythonPath, ['-c', `import ${pythonModule}`]);
|
|
59
|
+
importable = importResult.status === 0;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
importable = false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const cliAnythingPath = resolveCommandPath(runtimeInfo.binDir, 'cli-anything');
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
importable,
|
|
68
|
+
cliAnythingPath,
|
|
69
|
+
healthy: importable && fs.existsSync(cliAnythingPath),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function installCLIAnythingRuntime(runtimeInfo, packageRoot = '', options = {}) {
|
|
74
|
+
const pythonModule = options.pythonModule || CLI_ANYTHING_MODULE;
|
|
75
|
+
const runtimeSource = resolveCLIAnythingRuntimeSource(packageRoot);
|
|
76
|
+
|
|
77
|
+
if (!runtimeSource) {
|
|
78
|
+
throw new Error('CLI-Anything runtime source not found in the installed AI Desk package');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const args = ['-m', 'pip', 'install', '--upgrade'];
|
|
82
|
+
if (options.forceReinstall) {
|
|
83
|
+
args.push('--force-reinstall');
|
|
84
|
+
}
|
|
85
|
+
args.push(runtimeSource);
|
|
86
|
+
|
|
87
|
+
const installResult = run(runtimeInfo.pythonPath, args);
|
|
88
|
+
if (installResult.status !== 0) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
installResult.stderr ||
|
|
91
|
+
installResult.stdout ||
|
|
92
|
+
`Failed to install CLI-Anything runtime from ${runtimeSource}`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const health = cliAnythingRuntimeLooksHealthy(runtimeInfo, pythonModule);
|
|
97
|
+
if (!health.importable) {
|
|
98
|
+
throw new Error(`CLI-Anything Python module "${pythonModule}" is not importable after installation`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!fs.existsSync(health.cliAnythingPath)) {
|
|
102
|
+
throw new Error(`CLI-Anything executable not found after installation: ${health.cliAnythingPath}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
...runtimeInfo,
|
|
107
|
+
cliAnythingPath: health.cliAnythingPath,
|
|
108
|
+
pythonModule,
|
|
109
|
+
runtimeSource,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function ensureCLIAnythingRuntime(packageRoot = '', options = {}) {
|
|
114
|
+
const pythonModule = options.pythonModule || CLI_ANYTHING_MODULE;
|
|
115
|
+
const runtimeInfo = ensureRuntime();
|
|
116
|
+
const health = cliAnythingRuntimeLooksHealthy(runtimeInfo, pythonModule);
|
|
117
|
+
|
|
118
|
+
if (health.healthy && !options.forceReinstall) {
|
|
119
|
+
return {
|
|
120
|
+
...runtimeInfo,
|
|
121
|
+
cliAnythingPath: health.cliAnythingPath,
|
|
122
|
+
pythonModule,
|
|
123
|
+
runtimeSource: resolveCLIAnythingRuntimeSource(packageRoot),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return installCLIAnythingRuntime(runtimeInfo, packageRoot, options);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function syncCLIAnythingConfig(packageRoot = '', options = {}) {
|
|
131
|
+
const configPath = options.configPath || getConfigPath();
|
|
132
|
+
const config = readJSON(configPath);
|
|
133
|
+
const cliAnything = config.cli_anything || {};
|
|
134
|
+
const runtime = cliAnything.runtime || {};
|
|
135
|
+
const features = cliAnything.features || {};
|
|
136
|
+
|
|
137
|
+
let runtimeInfo = null;
|
|
138
|
+
if (options.ensureInstalled !== false) {
|
|
139
|
+
runtimeInfo = ensureCLIAnythingRuntime(packageRoot, options);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (typeof options.enabled === 'boolean') {
|
|
143
|
+
cliAnything.enabled = options.enabled;
|
|
144
|
+
} else if (typeof cliAnything.enabled !== 'boolean') {
|
|
145
|
+
cliAnything.enabled = false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (typeof options.fallbackToNative === 'boolean') {
|
|
149
|
+
cliAnything.fallback_to_native = options.fallbackToNative;
|
|
150
|
+
} else if (typeof cliAnything.fallback_to_native !== 'boolean') {
|
|
151
|
+
cliAnything.fallback_to_native = true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (runtimeInfo) {
|
|
155
|
+
cliAnything.cli_anything_path = runtimeInfo.cliAnythingPath;
|
|
156
|
+
runtime.python_path = runtimeInfo.pythonPath;
|
|
157
|
+
runtime.python_module = runtimeInfo.pythonModule;
|
|
158
|
+
} else {
|
|
159
|
+
cliAnything.cli_anything_path = cliAnything.cli_anything_path || '';
|
|
160
|
+
runtime.python_path = runtime.python_path || '';
|
|
161
|
+
runtime.python_module = runtime.python_module || CLI_ANYTHING_MODULE;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (typeof options.timeout === 'number') {
|
|
165
|
+
cliAnything.timeout = options.timeout;
|
|
166
|
+
} else if (typeof cliAnything.timeout !== 'number') {
|
|
167
|
+
cliAnything.timeout = 30;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (typeof options.maxRetries === 'number') {
|
|
171
|
+
cliAnything.max_retries = options.maxRetries;
|
|
172
|
+
} else if (typeof cliAnything.max_retries !== 'number') {
|
|
173
|
+
cliAnything.max_retries = 3;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (typeof options.generationTimeout === 'number') {
|
|
177
|
+
runtime.generation_timeout = options.generationTimeout;
|
|
178
|
+
} else if (typeof runtime.generation_timeout !== 'number') {
|
|
179
|
+
runtime.generation_timeout = 300;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const booleanFeatureDefaults = {
|
|
183
|
+
ai_cli_detection: true,
|
|
184
|
+
model_listing: true,
|
|
185
|
+
command_execution: true,
|
|
186
|
+
task_generation: true,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
for (const [featureKey, defaultValue] of Object.entries(booleanFeatureDefaults)) {
|
|
190
|
+
if (featureKey === 'task_generation' && typeof options.taskGeneration === 'boolean') {
|
|
191
|
+
features[featureKey] = options.taskGeneration;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (typeof features[featureKey] !== 'boolean') {
|
|
196
|
+
features[featureKey] = defaultValue;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
cliAnything.runtime = runtime;
|
|
201
|
+
cliAnything.features = features;
|
|
202
|
+
config.cli_anything = cliAnything;
|
|
203
|
+
writeJSON(configPath, config);
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
configPath,
|
|
207
|
+
config,
|
|
208
|
+
runtimeInfo,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports = {
|
|
213
|
+
CLI_ANYTHING_MODULE,
|
|
214
|
+
ensureCLIAnythingRuntime,
|
|
215
|
+
resolveCLIAnythingRuntimeSource,
|
|
216
|
+
resolveCommandPath,
|
|
217
|
+
syncCLIAnythingConfig,
|
|
218
|
+
};
|
package/lib/config.js
CHANGED
|
@@ -7,6 +7,65 @@ const path = require('path');
|
|
|
7
7
|
const { getConfigDir, getConfigPath } = require('./platform');
|
|
8
8
|
|
|
9
9
|
const DEFAULT_PORT = 9527;
|
|
10
|
+
const DEFAULT_CONFIG = {
|
|
11
|
+
port: DEFAULT_PORT,
|
|
12
|
+
logLevel: 'info',
|
|
13
|
+
autoStart: true,
|
|
14
|
+
cli_anything: {
|
|
15
|
+
enabled: false,
|
|
16
|
+
fallback_to_native: true,
|
|
17
|
+
cli_anything_path: '',
|
|
18
|
+
timeout: 30,
|
|
19
|
+
max_retries: 3,
|
|
20
|
+
features: {
|
|
21
|
+
ai_cli_detection: true,
|
|
22
|
+
model_listing: true,
|
|
23
|
+
command_execution: true,
|
|
24
|
+
task_generation: true,
|
|
25
|
+
},
|
|
26
|
+
runtime: {
|
|
27
|
+
python_path: '',
|
|
28
|
+
python_module: 'cli_anything',
|
|
29
|
+
generation_timeout: 300,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
harness_runtime: {
|
|
33
|
+
venv_path: '',
|
|
34
|
+
python_path: '',
|
|
35
|
+
registry_path: '',
|
|
36
|
+
source: '',
|
|
37
|
+
runtime_home: '',
|
|
38
|
+
package_name: '',
|
|
39
|
+
package_version: '',
|
|
40
|
+
archive_sha256: '',
|
|
41
|
+
},
|
|
42
|
+
harnesses: {},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function cloneDefaultConfig() {
|
|
46
|
+
return JSON.parse(JSON.stringify(DEFAULT_CONFIG));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function mergeConfig(base, override) {
|
|
50
|
+
if (!override || typeof override !== 'object' || Array.isArray(override)) {
|
|
51
|
+
return base;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const merged = Array.isArray(base) ? [...base] : { ...base };
|
|
55
|
+
for (const [key, value] of Object.entries(override)) {
|
|
56
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
57
|
+
const nestedBase = merged[key] && typeof merged[key] === 'object' && !Array.isArray(merged[key])
|
|
58
|
+
? merged[key]
|
|
59
|
+
: {};
|
|
60
|
+
merged[key] = mergeConfig(nestedBase, value);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
merged[key] = value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return merged;
|
|
68
|
+
}
|
|
10
69
|
|
|
11
70
|
/**
|
|
12
71
|
* Ensure config directory exists
|
|
@@ -33,22 +92,17 @@ function loadConfig() {
|
|
|
33
92
|
const configPath = getConfigPath();
|
|
34
93
|
|
|
35
94
|
if (!fs.existsSync(configPath)) {
|
|
36
|
-
|
|
37
|
-
const defaultConfig = {
|
|
38
|
-
port: DEFAULT_PORT,
|
|
39
|
-
logLevel: 'info',
|
|
40
|
-
autoStart: true
|
|
41
|
-
};
|
|
95
|
+
const defaultConfig = cloneDefaultConfig();
|
|
42
96
|
saveConfig(defaultConfig);
|
|
43
97
|
return defaultConfig;
|
|
44
98
|
}
|
|
45
99
|
|
|
46
100
|
try {
|
|
47
101
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
48
|
-
return JSON.parse(content);
|
|
102
|
+
return mergeConfig(cloneDefaultConfig(), JSON.parse(content));
|
|
49
103
|
} catch (error) {
|
|
50
104
|
console.error('Failed to load config:', error.message);
|
|
51
|
-
return
|
|
105
|
+
return cloneDefaultConfig();
|
|
52
106
|
}
|
|
53
107
|
}
|
|
54
108
|
|
|
@@ -59,7 +113,8 @@ function saveConfig(config) {
|
|
|
59
113
|
ensureConfigDir();
|
|
60
114
|
|
|
61
115
|
const configPath = getConfigPath();
|
|
62
|
-
|
|
116
|
+
const mergedConfig = mergeConfig(cloneDefaultConfig(), config);
|
|
117
|
+
fs.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2), 'utf8');
|
|
63
118
|
}
|
|
64
119
|
|
|
65
120
|
/**
|
|
@@ -97,7 +152,8 @@ module.exports = {
|
|
|
97
152
|
saveConfig,
|
|
98
153
|
getPort,
|
|
99
154
|
setPort,
|
|
155
|
+
cloneDefaultConfig,
|
|
156
|
+
mergeConfig,
|
|
100
157
|
getConfigPath, // Re-export from platform.js
|
|
101
158
|
DEFAULT_PORT
|
|
102
159
|
};
|
|
103
|
-
|
package/lib/daemon-manager.js
CHANGED
|
@@ -80,7 +80,7 @@ function start() {
|
|
|
80
80
|
throw new Error(
|
|
81
81
|
`Daemon binary not found at: ${binaryPath}\n` +
|
|
82
82
|
`This might be a corrupted installation. Try reinstalling:\n` +
|
|
83
|
-
` npm install -g @
|
|
83
|
+
` npm install -g @agent-webui/ai-desk --force`
|
|
84
84
|
);
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -186,4 +186,3 @@ module.exports = {
|
|
|
186
186
|
restart,
|
|
187
187
|
status
|
|
188
188
|
};
|
|
189
|
-
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { ensureRuntime, readJSON, run, syncHarnessConfig, writeJSON } = require('./runtime-manager');
|
|
4
|
+
|
|
5
|
+
function listDependencyNames(packageRoot) {
|
|
6
|
+
const packageJsonPath = path.join(packageRoot, 'package.json');
|
|
7
|
+
const packageJson = readJSON(packageJsonPath);
|
|
8
|
+
|
|
9
|
+
const allDeps = {
|
|
10
|
+
...(packageJson.dependencies || {}),
|
|
11
|
+
...(packageJson.optionalDependencies || {}),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return Object.keys(allDeps).filter((name) => name.startsWith('@agent-webui/ai-desk-harness-'));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function findWorkspaceSiblingPackage(packageRoot, packageName) {
|
|
18
|
+
const packagesRoot = path.resolve(packageRoot, '..');
|
|
19
|
+
if (!fs.existsSync(packagesRoot)) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const children = fs.readdirSync(packagesRoot, { withFileTypes: true });
|
|
24
|
+
for (const child of children) {
|
|
25
|
+
if (!child.isDirectory()) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const packageJsonPath = path.join(packagesRoot, child.name, 'package.json');
|
|
30
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const pkg = readJSON(packageJsonPath);
|
|
35
|
+
if (pkg.name === packageName) {
|
|
36
|
+
return path.dirname(packageJsonPath);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function resolveHarnessPackageDir(packageRoot, packageName) {
|
|
44
|
+
try {
|
|
45
|
+
const packageJsonPath = require.resolve(`${packageName}/package.json`, {
|
|
46
|
+
paths: [packageRoot],
|
|
47
|
+
});
|
|
48
|
+
return path.dirname(packageJsonPath);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
const workspaceMatch = findWorkspaceSiblingPackage(packageRoot, packageName);
|
|
51
|
+
if (workspaceMatch) {
|
|
52
|
+
return workspaceMatch;
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`Failed to resolve harness package ${packageName} from ${packageRoot}: ${error.message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveHarnesses(packageRoot) {
|
|
59
|
+
const harnessNames = listDependencyNames(packageRoot);
|
|
60
|
+
|
|
61
|
+
return harnessNames.map((packageName) => {
|
|
62
|
+
const packageDir = resolveHarnessPackageDir(packageRoot, packageName);
|
|
63
|
+
const packageJson = readJSON(path.join(packageDir, 'package.json'));
|
|
64
|
+
const manifestPath = path.join(packageDir, 'manifest.json');
|
|
65
|
+
const manifest = readJSON(manifestPath);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
packageDir,
|
|
69
|
+
packageJson,
|
|
70
|
+
packageName,
|
|
71
|
+
manifest,
|
|
72
|
+
manifestPath,
|
|
73
|
+
sourcePath: path.resolve(packageDir, manifest.source_path),
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolveCommandPath(binDir, command) {
|
|
79
|
+
const candidates = process.platform === 'win32'
|
|
80
|
+
? [
|
|
81
|
+
path.join(binDir, `${command}.exe`),
|
|
82
|
+
path.join(binDir, `${command}.cmd`),
|
|
83
|
+
path.join(binDir, `${command}.bat`),
|
|
84
|
+
path.join(binDir, command),
|
|
85
|
+
]
|
|
86
|
+
: [path.join(binDir, command)];
|
|
87
|
+
|
|
88
|
+
for (const candidate of candidates) {
|
|
89
|
+
if (fs.existsSync(candidate)) {
|
|
90
|
+
return candidate;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return candidates[0];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function installHarnessPackage(runtimeInfo, harness) {
|
|
98
|
+
if (!fs.existsSync(harness.sourcePath)) {
|
|
99
|
+
throw new Error(`Harness source not found: ${harness.sourcePath}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const installResult = run(runtimeInfo.pythonPath, ['-m', 'pip', 'install', '--upgrade', harness.sourcePath]);
|
|
103
|
+
if (installResult.status !== 0) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
installResult.stderr ||
|
|
106
|
+
installResult.stdout ||
|
|
107
|
+
`Failed to install harness ${harness.packageName} from ${harness.sourcePath}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const requirement of harness.manifest.python_requirements || []) {
|
|
112
|
+
const requirementResult = run(runtimeInfo.pythonPath, ['-m', 'pip', 'install', '--upgrade', requirement]);
|
|
113
|
+
if (requirementResult.status !== 0) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
requirementResult.stderr ||
|
|
116
|
+
requirementResult.stdout ||
|
|
117
|
+
`Failed to install Python requirement ${requirement} for ${harness.packageName}`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
name: harness.manifest.name,
|
|
124
|
+
displayName: harness.manifest.display_name || harness.manifest.name,
|
|
125
|
+
module: harness.manifest.module,
|
|
126
|
+
commands: harness.manifest.commands || [],
|
|
127
|
+
capabilities: harness.manifest.capabilities || [],
|
|
128
|
+
commandPath: resolveCommandPath(runtimeInfo.binDir, (harness.manifest.commands || [])[0] || ''),
|
|
129
|
+
commandPaths: Object.fromEntries(
|
|
130
|
+
(harness.manifest.commands || []).map((command) => [command, resolveCommandPath(runtimeInfo.binDir, command)])
|
|
131
|
+
),
|
|
132
|
+
packageName: harness.packageName,
|
|
133
|
+
packageVersion: harness.packageJson.version || '',
|
|
134
|
+
version: harness.manifest.version || harness.packageJson.version || '',
|
|
135
|
+
sourcePath: harness.sourcePath,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function writeHarnessRegistry(runtimeInfo, harnesses) {
|
|
140
|
+
const registry = {
|
|
141
|
+
version: 1,
|
|
142
|
+
generated_at: new Date().toISOString(),
|
|
143
|
+
runtime: {
|
|
144
|
+
python_path: runtimeInfo.pythonPath,
|
|
145
|
+
venv_path: runtimeInfo.venvDir,
|
|
146
|
+
registry_path: runtimeInfo.registryPath,
|
|
147
|
+
source: runtimeInfo.source || '',
|
|
148
|
+
runtime_home: runtimeInfo.runtimeHomeDir || '',
|
|
149
|
+
package_name: runtimeInfo.packageName || '',
|
|
150
|
+
package_version: runtimeInfo.packageVersion || '',
|
|
151
|
+
archive_sha256: runtimeInfo.archiveSHA256 || '',
|
|
152
|
+
},
|
|
153
|
+
harnesses,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
writeJSON(runtimeInfo.registryPath, registry);
|
|
157
|
+
syncHarnessConfig(runtimeInfo, harnesses);
|
|
158
|
+
return registry;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function installHarnessesForPackageRoot(packageRoot) {
|
|
162
|
+
const runtimeInfo = ensureRuntime();
|
|
163
|
+
const harnessPackages = resolveHarnesses(packageRoot);
|
|
164
|
+
const installedHarnesses = harnessPackages.map((harness) => installHarnessPackage(runtimeInfo, harness));
|
|
165
|
+
const registry = writeHarnessRegistry(runtimeInfo, installedHarnesses);
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
harnessCount: installedHarnesses.length,
|
|
169
|
+
harnesses: installedHarnesses,
|
|
170
|
+
registry,
|
|
171
|
+
runtimeInfo,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = {
|
|
176
|
+
installHarnessesForPackageRoot,
|
|
177
|
+
resolveHarnesses,
|
|
178
|
+
};
|