@ia-ccun/code-agent-claw 0.0.1 → 0.0.3
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/dist/public/index.html +6 -17
- package/package/.claude/plan/aicode-ui-npm.md +477 -0
- package/package/README.md +108 -0
- package/package/bin/cli.js +16 -0
- package/package/config/default.json +18 -0
- package/package/package.json +43 -0
- package/package/public/aicode.svg +1 -0
- package/package/public/index-v3.html +1757 -0
- package/package/public/index.html +1818 -0
- package/package/public/index_v3.html +1757 -0
- package/package/public/juejin.css +143 -0
- package/package/src/config.ts +239 -0
- package/package/src/index.ts +40 -0
- package/package/src/server/index.ts +103 -0
- package/package/src/server/routes.ts +342 -0
- package/package/src/server/websocket.ts +82 -0
- package/package/src/services/agent-rpc.ts +397 -0
- package/package/src/types/index.ts +60 -0
- package/package/src/utils/logger.ts +13 -0
- package/package/tsconfig.json +19 -0
- package/package.json +1 -1
- package/public/index.html +6 -17
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { Express, Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { AgentRpcService } from '../services/agent-rpc';
|
|
3
|
+
import { getAgentConfig, saveUserConfig, loadConfig, findAicodeCommand } from '../config';
|
|
4
|
+
import { AgentConfig, ConfigRequest, ApiResponse } from '../types';
|
|
5
|
+
import { logger } from '../utils/logger';
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import * as os from 'os';
|
|
9
|
+
import { execSync } from 'child_process';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 注册 API 路由
|
|
13
|
+
*/
|
|
14
|
+
export function registerRoutes(app: Express, agentService: AgentRpcService): void {
|
|
15
|
+
|
|
16
|
+
// ==================== 配置相关 ====================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 获取当前配置
|
|
20
|
+
*/
|
|
21
|
+
app.get('/api/aicode/config', (req: Request, res: Response) => {
|
|
22
|
+
logger.info('[API] GET /api/aicode/config');
|
|
23
|
+
const config = getAgentConfig();
|
|
24
|
+
const currentConfig = agentService.currentConfig;
|
|
25
|
+
|
|
26
|
+
// 读取 models.json
|
|
27
|
+
let models = null;
|
|
28
|
+
const modelsPath = path.join(os.homedir(), '.aicode-cli', 'agent', 'models.json');
|
|
29
|
+
if (fs.existsSync(modelsPath)) {
|
|
30
|
+
try {
|
|
31
|
+
models = JSON.parse(fs.readFileSync(modelsPath, 'utf-8'));
|
|
32
|
+
} catch (e) {
|
|
33
|
+
logger.warn('Failed to parse models.json:', e);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const response: ApiResponse<{
|
|
38
|
+
config: AgentConfig;
|
|
39
|
+
status: string;
|
|
40
|
+
models: any;
|
|
41
|
+
}> = {
|
|
42
|
+
success: true,
|
|
43
|
+
data: {
|
|
44
|
+
config: {
|
|
45
|
+
...config,
|
|
46
|
+
command: config.command
|
|
47
|
+
},
|
|
48
|
+
status: agentService.status,
|
|
49
|
+
models
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
res.json(response);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 保存配置并初始化 agent
|
|
58
|
+
*/
|
|
59
|
+
app.post('/api/aicode/config', async (req: Request, res: Response) => {
|
|
60
|
+
logger.info('[API] POST /api/aicode/config', req.body);
|
|
61
|
+
|
|
62
|
+
const body = req.body as ConfigRequest;
|
|
63
|
+
|
|
64
|
+
// command 字段可选,如果没有填则自动检测
|
|
65
|
+
|
|
66
|
+
// 自动检测 command 路径
|
|
67
|
+
let command = body.command;
|
|
68
|
+
if (!command || command === 'aicode') {
|
|
69
|
+
command = findAicodeCommand();
|
|
70
|
+
logger.info('[API] Auto-detected aicode command:', command);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const config: AgentConfig = {
|
|
74
|
+
enabled: true,
|
|
75
|
+
command: command,
|
|
76
|
+
provider: body.provider || 'minimax-custom',
|
|
77
|
+
model: body.model || 'MiniMax-M2.5',
|
|
78
|
+
noSession: body.noSession !== false,
|
|
79
|
+
sessionDir: body.sessionDir || '~/.aicode-cli/agent/sessions',
|
|
80
|
+
workingDir: body.workingDir || '~/.aicode-cli'
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// 先停止已有 agent
|
|
84
|
+
if (agentService.isRunning()) {
|
|
85
|
+
logger.info('[API] Stopping existing agent...');
|
|
86
|
+
agentService.stop();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 保存配置
|
|
90
|
+
saveUserConfig(config);
|
|
91
|
+
|
|
92
|
+
// 初始化并启动
|
|
93
|
+
const result = await agentService.initialize(config);
|
|
94
|
+
|
|
95
|
+
if (result.success) {
|
|
96
|
+
res.json({
|
|
97
|
+
success: true,
|
|
98
|
+
message: 'Agent initialized successfully'
|
|
99
|
+
} as ApiResponse);
|
|
100
|
+
} else {
|
|
101
|
+
res.status(500).json({
|
|
102
|
+
success: false,
|
|
103
|
+
message: result.message
|
|
104
|
+
} as ApiResponse);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// ==================== 状态相关 ====================
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 获取 agent 状态
|
|
112
|
+
*/
|
|
113
|
+
app.get('/api/aicode/status', (req: Request, res: Response) => {
|
|
114
|
+
logger.info('[API] GET /api/aicode/status');
|
|
115
|
+
|
|
116
|
+
res.json({
|
|
117
|
+
success: true,
|
|
118
|
+
data: {
|
|
119
|
+
status: agentService.status,
|
|
120
|
+
running: agentService.isRunning()
|
|
121
|
+
}
|
|
122
|
+
} as ApiResponse);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 健康检查
|
|
127
|
+
*/
|
|
128
|
+
app.get('/api/aicode/health', (req: Request, res: Response) => {
|
|
129
|
+
res.json({
|
|
130
|
+
success: true,
|
|
131
|
+
data: {
|
|
132
|
+
status: agentService.status,
|
|
133
|
+
running: agentService.isRunning()
|
|
134
|
+
}
|
|
135
|
+
} as ApiResponse);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 检测 aicode 命令路径
|
|
140
|
+
*/
|
|
141
|
+
app.get('/api/aicode/detect', (req: Request, res: Response) => {
|
|
142
|
+
logger.info('[API] GET /api/aicode/detect');
|
|
143
|
+
|
|
144
|
+
// 使用 findAicodeCommand 获取所有可能的路径
|
|
145
|
+
const detectedPaths: string[] = [];
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
// 1. 尝试 whereis
|
|
149
|
+
const whereisResult = execSync('whereis aicode', { encoding: 'utf-8' });
|
|
150
|
+
const match = whereisResult.match(/aicode:\s*(.+)$/);
|
|
151
|
+
if (match && match[1] && !match[1].includes('not found')) {
|
|
152
|
+
const paths = match[1].trim().split(/\s+/);
|
|
153
|
+
paths.forEach(p => {
|
|
154
|
+
if (p && fs.existsSync(p) && !detectedPaths.includes(p)) {
|
|
155
|
+
detectedPaths.push(p);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
} catch (e) {
|
|
160
|
+
// ignore
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 2. 尝试 which
|
|
164
|
+
try {
|
|
165
|
+
const whichResult = execSync('which -a aicode', { encoding: 'utf-8' }).trim();
|
|
166
|
+
if (whichResult) {
|
|
167
|
+
whichResult.split('\n').forEach(p => {
|
|
168
|
+
const trimmed = p.trim();
|
|
169
|
+
if (trimmed && fs.existsSync(trimmed) && !detectedPaths.includes(trimmed)) {
|
|
170
|
+
detectedPaths.push(trimmed);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
} catch (e) {
|
|
175
|
+
// ignore
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 3. 检查常见路径
|
|
179
|
+
const commonPaths = [
|
|
180
|
+
'/usr/local/bin/aicode',
|
|
181
|
+
'/usr/bin/aicode',
|
|
182
|
+
path.join(os.homedir(), '.local/bin/aicode'),
|
|
183
|
+
path.join(os.homedir(), 'bin/aicode'),
|
|
184
|
+
];
|
|
185
|
+
commonPaths.forEach(p => {
|
|
186
|
+
if (fs.existsSync(p) && !detectedPaths.includes(p)) {
|
|
187
|
+
detectedPaths.push(p);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
res.json({
|
|
192
|
+
success: true,
|
|
193
|
+
data: {
|
|
194
|
+
paths: detectedPaths,
|
|
195
|
+
recommended: detectedPaths.length > 0 ? detectedPaths[0] : 'aicode'
|
|
196
|
+
}
|
|
197
|
+
} as ApiResponse);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// ==================== Agent 操作 ====================
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 发送提示
|
|
204
|
+
*/
|
|
205
|
+
app.post('/api/aicode/prompt', async (req: Request, res: Response) => {
|
|
206
|
+
const { message } = req.body;
|
|
207
|
+
logger.info(`[API] POST /api/aicode/prompt: ${message?.substring(0, 50)}...`);
|
|
208
|
+
|
|
209
|
+
if (!agentService.isRunning()) {
|
|
210
|
+
return res.status(503).json({
|
|
211
|
+
success: false,
|
|
212
|
+
message: 'Agent is not running. Please initialize first.'
|
|
213
|
+
} as ApiResponse);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const result = await agentService.sendPrompt(message);
|
|
218
|
+
if (result.success) {
|
|
219
|
+
res.json({ success: true, data: result } as ApiResponse);
|
|
220
|
+
} else {
|
|
221
|
+
res.status(500).json({ success: false, message: result.message } as ApiResponse);
|
|
222
|
+
}
|
|
223
|
+
} catch (e: any) {
|
|
224
|
+
logger.error('[API] /prompt error:', e);
|
|
225
|
+
res.status(500).json({ success: false, message: e.message } as ApiResponse);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 发送引导消息
|
|
231
|
+
*/
|
|
232
|
+
app.post('/api/aicode/steer', async (req: Request, res: Response) => {
|
|
233
|
+
const { message } = req.body;
|
|
234
|
+
logger.info(`[API] POST /api/aicode/steer: ${message?.substring(0, 50)}...`);
|
|
235
|
+
|
|
236
|
+
if (!agentService.isRunning()) {
|
|
237
|
+
return res.status(503).json({
|
|
238
|
+
success: false,
|
|
239
|
+
message: 'Agent is not running'
|
|
240
|
+
} as ApiResponse);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const result = await agentService.sendSteer(message);
|
|
245
|
+
if (result.success) {
|
|
246
|
+
res.json({ success: true, data: result } as ApiResponse);
|
|
247
|
+
} else {
|
|
248
|
+
res.status(500).json({ success: false, message: result.message } as ApiResponse);
|
|
249
|
+
}
|
|
250
|
+
} catch (e: any) {
|
|
251
|
+
res.status(500).json({ success: false, message: e.message } as ApiResponse);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* 发送后续消息
|
|
257
|
+
*/
|
|
258
|
+
app.post('/api/aicode/follow-up', async (req: Request, res: Response) => {
|
|
259
|
+
const { message } = req.body;
|
|
260
|
+
logger.info(`[API] POST /api/aicode/follow-up`);
|
|
261
|
+
|
|
262
|
+
if (!agentService.isRunning()) {
|
|
263
|
+
return res.status(503).json({
|
|
264
|
+
success: false,
|
|
265
|
+
message: 'Agent is not running'
|
|
266
|
+
} as ApiResponse);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const result = await agentService.sendFollowUp(message);
|
|
271
|
+
if (result.success) {
|
|
272
|
+
res.json({ success: true, data: result } as ApiResponse);
|
|
273
|
+
} else {
|
|
274
|
+
res.status(500).json({ success: false, message: result.message } as ApiResponse);
|
|
275
|
+
}
|
|
276
|
+
} catch (e: any) {
|
|
277
|
+
res.status(500).json({ success: false, message: e.message } as ApiResponse);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 中止操作
|
|
283
|
+
*/
|
|
284
|
+
app.post('/api/aicode/abort', async (req: Request, res: Response) => {
|
|
285
|
+
logger.info('[API] POST /api/aicode/abort');
|
|
286
|
+
|
|
287
|
+
if (!agentService.isRunning()) {
|
|
288
|
+
return res.status(503).json({
|
|
289
|
+
success: false,
|
|
290
|
+
message: 'Agent is not running'
|
|
291
|
+
} as ApiResponse);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
const result = await agentService.sendAbort();
|
|
296
|
+
if (result.success) {
|
|
297
|
+
res.json({ success: true, data: result } as ApiResponse);
|
|
298
|
+
} else {
|
|
299
|
+
res.status(500).json({ success: false, message: result.message } as ApiResponse);
|
|
300
|
+
}
|
|
301
|
+
} catch (e: any) {
|
|
302
|
+
res.status(500).json({ success: false, message: e.message } as ApiResponse);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* 创建新会话
|
|
308
|
+
*/
|
|
309
|
+
app.post('/api/aicode/new-session', async (req: Request, res: Response) => {
|
|
310
|
+
const parentSession = req.query.parentSession as string | undefined;
|
|
311
|
+
logger.info(`[API] POST /api/aicode/new-session: parentSession=${parentSession}`);
|
|
312
|
+
|
|
313
|
+
if (!agentService.isRunning()) {
|
|
314
|
+
return res.status(503).json({
|
|
315
|
+
success: false,
|
|
316
|
+
message: 'Agent is not running'
|
|
317
|
+
} as ApiResponse);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
const result = await agentService.newSession(parentSession);
|
|
322
|
+
if (result.success) {
|
|
323
|
+
res.json({ success: true, data: result } as ApiResponse);
|
|
324
|
+
} else {
|
|
325
|
+
res.status(500).json({ success: false, message: result.message } as ApiResponse);
|
|
326
|
+
}
|
|
327
|
+
} catch (e: any) {
|
|
328
|
+
res.status(500).json({ success: false, message: e.message } as ApiResponse);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// 错误处理中间件
|
|
333
|
+
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
|
|
334
|
+
logger.error('[API] Error:', err);
|
|
335
|
+
res.status(500).json({
|
|
336
|
+
success: false,
|
|
337
|
+
message: err.message
|
|
338
|
+
} as ApiResponse);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
logger.info('[API] Routes registered');
|
|
342
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
2
|
+
import { AgentRpcService } from '../services/agent-rpc';
|
|
3
|
+
import { AgentEvent } from '../types';
|
|
4
|
+
import { logger } from '../utils/logger';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WebSocket 客户端集合
|
|
8
|
+
*/
|
|
9
|
+
const clients: Set<WebSocket> = new Set();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 设置 WebSocket 处理
|
|
13
|
+
*/
|
|
14
|
+
export function setupWebSocket(wss: WebSocketServer, agentService: AgentRpcService): void {
|
|
15
|
+
|
|
16
|
+
// 注册事件回调
|
|
17
|
+
agentService.registerCallback((event: AgentEvent) => {
|
|
18
|
+
broadcast(event);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
wss.on('connection', (ws: WebSocket) => {
|
|
22
|
+
logger.info('[WebSocket] Client connected');
|
|
23
|
+
clients.add(ws);
|
|
24
|
+
|
|
25
|
+
// 发送客户端数量
|
|
26
|
+
broadcastClientCount();
|
|
27
|
+
|
|
28
|
+
// 发送欢迎消息
|
|
29
|
+
ws.send(JSON.stringify({
|
|
30
|
+
type: 'welcome',
|
|
31
|
+
message: 'Connected to AICode Agent WebSocket'
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
ws.on('close', () => {
|
|
35
|
+
logger.info('[WebSocket] Client disconnected');
|
|
36
|
+
clients.delete(ws);
|
|
37
|
+
broadcastClientCount();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
ws.on('error', (error) => {
|
|
41
|
+
logger.error('[WebSocket] Error:', error);
|
|
42
|
+
clients.delete(ws);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
logger.info('[WebSocket] Server initialized');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 广播消息给所有客户端
|
|
51
|
+
*/
|
|
52
|
+
function broadcast(event: AgentEvent): void {
|
|
53
|
+
const message = JSON.stringify(event);
|
|
54
|
+
clients.forEach((client) => {
|
|
55
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
56
|
+
try {
|
|
57
|
+
client.send(message);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
logger.error('[WebSocket] Send error:', e);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 广播客户端数量
|
|
67
|
+
*/
|
|
68
|
+
function broadcastClientCount(): void {
|
|
69
|
+
const message = JSON.stringify({
|
|
70
|
+
type: 'client_count',
|
|
71
|
+
count: clients.size
|
|
72
|
+
});
|
|
73
|
+
clients.forEach((client) => {
|
|
74
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
75
|
+
try {
|
|
76
|
+
client.send(message);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
// 忽略
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|