@ia-ccun/code-agent-claw 0.0.3 → 0.0.5
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/bin/cli.js +36 -1
- package/dist/public/index.html +42 -21
- package/ia-ccun-code-agent-claw-0.0.4.tgz +0 -0
- package/package.json +1 -1
- package/public/index.html +55 -22
- package/package/.claude/plan/aicode-ui-npm.md +0 -477
- package/package/README.md +0 -108
- package/package/bin/cli.js +0 -16
- package/package/config/default.json +0 -18
- package/package/package.json +0 -43
- package/package/public/aicode.svg +0 -1
- package/package/public/index-v3.html +0 -1757
- package/package/public/index.html +0 -1818
- package/package/public/index_v3.html +0 -1757
- package/package/public/juejin.css +0 -143
- package/package/src/config.ts +0 -239
- package/package/src/index.ts +0 -40
- package/package/src/server/index.ts +0 -103
- package/package/src/server/routes.ts +0 -342
- package/package/src/server/websocket.ts +0 -82
- package/package/src/services/agent-rpc.ts +0 -397
- package/package/src/types/index.ts +0 -60
- package/package/src/utils/logger.ts +0 -13
- package/package/tsconfig.json +0 -19
|
@@ -1,397 +0,0 @@
|
|
|
1
|
-
import { spawn, ChildProcess } from 'child_process';
|
|
2
|
-
import * as readline from 'readline';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
|
-
import * as path from 'path';
|
|
5
|
-
import { AgentConfig, CommandResult, AgentEvent, EventCallback, AgentStatus } from '../types';
|
|
6
|
-
import { logger } from '../utils/logger';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* aicode RPC 服务
|
|
10
|
-
* 负责管理 aicode 子进程,并通过 JSONL 协议进行通信
|
|
11
|
-
*/
|
|
12
|
-
export class AgentRpcService {
|
|
13
|
-
private process: ChildProcess | null = null;
|
|
14
|
-
private rl: readline.Interface | null = null;
|
|
15
|
-
private pendingCommands: Map<string, {
|
|
16
|
-
resolve: (r: CommandResult) => void;
|
|
17
|
-
reject: (e: Error) => void;
|
|
18
|
-
}> = new Map();
|
|
19
|
-
private callbacks: Set<EventCallback> = new Set();
|
|
20
|
-
private config: AgentConfig | null = null;
|
|
21
|
-
private _status: AgentStatus = 'uninitialized';
|
|
22
|
-
private healthCheckInterval: NodeJS.Timeout | null = null;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 获取当前状态
|
|
26
|
-
*/
|
|
27
|
-
get status(): AgentStatus {
|
|
28
|
-
return this._status;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 获取当前配置
|
|
33
|
-
*/
|
|
34
|
-
get currentConfig(): AgentConfig | null {
|
|
35
|
-
return this.config;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 初始化 agent(保存配置并启动)
|
|
40
|
-
*/
|
|
41
|
-
async initialize(config: AgentConfig): Promise<CommandResult> {
|
|
42
|
-
logger.info('[AgentRpc] Initializing with config:', config);
|
|
43
|
-
|
|
44
|
-
// 验证命令是否存在
|
|
45
|
-
const commandPath = config.command;
|
|
46
|
-
if (!fs.existsSync(commandPath)) {
|
|
47
|
-
const error = `aicode command not found: ${commandPath}`;
|
|
48
|
-
logger.error('[AgentRpc]', error);
|
|
49
|
-
return { success: false, message: error };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// 确保目录存在
|
|
53
|
-
this.ensureDir(config.sessionDir);
|
|
54
|
-
this.ensureDir(config.workingDir);
|
|
55
|
-
|
|
56
|
-
// 保存配置
|
|
57
|
-
this.config = config;
|
|
58
|
-
|
|
59
|
-
// 启动进程
|
|
60
|
-
return this.start();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* 启动 aicode 进程
|
|
65
|
-
*/
|
|
66
|
-
async start(): Promise<CommandResult> {
|
|
67
|
-
if (!this.config) {
|
|
68
|
-
return { success: false, message: 'Config not set' };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (this._status === 'running') {
|
|
72
|
-
return { success: true, message: 'Already running' };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
this._status = 'starting';
|
|
76
|
-
logger.info('[AgentRpc] Starting aicode process...');
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
// 构建命令参数
|
|
80
|
-
const args: string[] = [
|
|
81
|
-
'--mode', 'rpc',
|
|
82
|
-
'--provider', this.config.provider,
|
|
83
|
-
'--model', this.config.model,
|
|
84
|
-
'--session-dir', this.config.sessionDir
|
|
85
|
-
];
|
|
86
|
-
|
|
87
|
-
// noSession 模式下不传 session 参数或使用特殊处理
|
|
88
|
-
if (!this.config.noSession) {
|
|
89
|
-
// 如果需要会话模式,可以在这里添加
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
logger.info(`[AgentRpc] Command: ${this.config.command} ${args.join(' ')}`);
|
|
93
|
-
logger.info(`[AgentRpc] Working dir: ${this.config.workingDir}`);
|
|
94
|
-
|
|
95
|
-
// 启动子进程
|
|
96
|
-
this.process = spawn(this.config.command, args, {
|
|
97
|
-
cwd: this.config.workingDir,
|
|
98
|
-
env: { ...process.env },
|
|
99
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// 创建 readline 接口监听 stdout
|
|
103
|
-
this.rl = readline.createInterface({
|
|
104
|
-
input: this.process.stdout!,
|
|
105
|
-
crlfDelay: Infinity
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// 监听 stdout 事件
|
|
109
|
-
this.rl.on('line', (line: string) => {
|
|
110
|
-
this.handleEvent(line);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// 监听 stderr
|
|
114
|
-
this.process.stderr?.on('data', (data: Buffer) => {
|
|
115
|
-
const line = data.toString().replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '');
|
|
116
|
-
if (line.trim()) {
|
|
117
|
-
logger.warn(`[aicode-stderr] ${line}`);
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// 监听进程退出
|
|
122
|
-
this.process.on('exit', (code, signal) => {
|
|
123
|
-
logger.info(`[AgentRpc] Process exited with code ${code}, signal ${signal}`);
|
|
124
|
-
this._status = 'stopped';
|
|
125
|
-
this.stopHealthCheck();
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
this.process.on('error', (err) => {
|
|
129
|
-
logger.error('[AgentRpc] Process error:', err);
|
|
130
|
-
this._status = 'error';
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// 启动健康检查
|
|
134
|
-
this.startHealthCheck();
|
|
135
|
-
|
|
136
|
-
this._status = 'running';
|
|
137
|
-
logger.info('[AgentRpc] aicode process started successfully');
|
|
138
|
-
|
|
139
|
-
return { success: true, message: 'Agent started' };
|
|
140
|
-
} catch (error: any) {
|
|
141
|
-
this._status = 'error';
|
|
142
|
-
logger.error('[AgentRpc] Failed to start:', error);
|
|
143
|
-
return { success: false, message: error.message };
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* 处理事件
|
|
149
|
-
*/
|
|
150
|
-
private handleEvent(jsonLine: string): void {
|
|
151
|
-
try {
|
|
152
|
-
const trimmed = jsonLine.trim();
|
|
153
|
-
if (!trimmed || !trimmed.startsWith('{')) {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const event = JSON.parse(jsonLine);
|
|
158
|
-
const type = event.type;
|
|
159
|
-
|
|
160
|
-
logger.debug(`[AgentRpc] <<< type=${type}`);
|
|
161
|
-
|
|
162
|
-
// 处理命令响应 - RPC 模式特有
|
|
163
|
-
if (type === 'response') {
|
|
164
|
-
this.handleCommandResponse(event);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// 构建 AgentEvent
|
|
169
|
-
const agentEvent: AgentEvent = {
|
|
170
|
-
type,
|
|
171
|
-
data: event
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
if (type === 'message_update') {
|
|
175
|
-
const msgEvent = event.assistantMessageEvent;
|
|
176
|
-
if (msgEvent) {
|
|
177
|
-
agentEvent.messageType = msgEvent.type;
|
|
178
|
-
agentEvent.delta = msgEvent.delta;
|
|
179
|
-
}
|
|
180
|
-
} else if (type === 'agent_end') {
|
|
181
|
-
agentEvent.success = true;
|
|
182
|
-
agentEvent.usage = event.usage;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// 通知回调
|
|
186
|
-
this.callbacks.forEach(cb => {
|
|
187
|
-
try {
|
|
188
|
-
cb(agentEvent);
|
|
189
|
-
} catch (e) {
|
|
190
|
-
logger.error('[AgentRpc] Event callback error:', e);
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
} catch (e) {
|
|
194
|
-
// 忽略解析错误
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* 处理命令响应
|
|
200
|
-
*/
|
|
201
|
-
private handleCommandResponse(event: any): void {
|
|
202
|
-
const commandId = event.id;
|
|
203
|
-
const commandType = event.command;
|
|
204
|
-
const success = event.success;
|
|
205
|
-
|
|
206
|
-
logger.info(`[AgentRpc] Command response: id=${commandId}, command=${commandType}, success=${success}`);
|
|
207
|
-
|
|
208
|
-
const pending = this.pendingCommands.get(commandId);
|
|
209
|
-
if (pending) {
|
|
210
|
-
this.pendingCommands.delete(commandId);
|
|
211
|
-
if (success) {
|
|
212
|
-
pending.resolve({ success: true, message: commandType });
|
|
213
|
-
} else {
|
|
214
|
-
pending.resolve({ success: false, message: event.error || 'Command failed' });
|
|
215
|
-
}
|
|
216
|
-
} else {
|
|
217
|
-
logger.warn(`[AgentRpc] No pending command for id: ${commandId}`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* 发送提示
|
|
223
|
-
*/
|
|
224
|
-
async sendPrompt(message: string): Promise<CommandResult> {
|
|
225
|
-
return this.sendCommand('prompt', message);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* 发送引导消息
|
|
230
|
-
*/
|
|
231
|
-
async sendSteer(message: string): Promise<CommandResult> {
|
|
232
|
-
return this.sendCommand('steer', message);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* 发送后续消息
|
|
237
|
-
*/
|
|
238
|
-
async sendFollowUp(message: string): Promise<CommandResult> {
|
|
239
|
-
return this.sendCommand('follow_up', message);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* 中止操作
|
|
244
|
-
*/
|
|
245
|
-
async sendAbort(): Promise<CommandResult> {
|
|
246
|
-
return this.sendCommand('abort');
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* 创建新会话
|
|
251
|
-
*/
|
|
252
|
-
async newSession(parentSession?: string): Promise<CommandResult> {
|
|
253
|
-
return this.sendCommand('new_session', undefined, parentSession);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* 发送命令
|
|
258
|
-
*/
|
|
259
|
-
private sendCommand(type: string, message?: string, extra?: string): Promise<CommandResult> {
|
|
260
|
-
return new Promise((resolve, reject) => {
|
|
261
|
-
if (!this.isRunning() || !this.process || !this.process.stdin) {
|
|
262
|
-
return resolve({ success: false, message: 'Agent is not running' });
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const command: any = { type };
|
|
266
|
-
if (message) command.message = message;
|
|
267
|
-
if (extra) command.parentSession = extra;
|
|
268
|
-
|
|
269
|
-
// 生成命令ID
|
|
270
|
-
const commandId = Date.now() + '-' + Math.floor(Math.random() * 10000);
|
|
271
|
-
command.id = commandId;
|
|
272
|
-
|
|
273
|
-
this.pendingCommands.set(commandId, { resolve, reject });
|
|
274
|
-
|
|
275
|
-
try {
|
|
276
|
-
const jsonLine = JSON.stringify(command) + '\n';
|
|
277
|
-
logger.debug(`[AgentRpc] >>> ${jsonLine.trim()}`);
|
|
278
|
-
this.process.stdin.write(jsonLine);
|
|
279
|
-
} catch (e: any) {
|
|
280
|
-
this.pendingCommands.delete(commandId);
|
|
281
|
-
reject(e);
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* 检查是否运行中
|
|
288
|
-
*/
|
|
289
|
-
isRunning(): boolean {
|
|
290
|
-
return this._status === 'running' && this.process?.pid !== undefined;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* 停止 agent
|
|
295
|
-
*/
|
|
296
|
-
stop(): void {
|
|
297
|
-
logger.info('[AgentRpc] Stopping agent...');
|
|
298
|
-
this._status = 'stopped';
|
|
299
|
-
this.stopHealthCheck();
|
|
300
|
-
|
|
301
|
-
try {
|
|
302
|
-
if (this.process?.stdin) {
|
|
303
|
-
this.process.stdin.end();
|
|
304
|
-
}
|
|
305
|
-
if (this.process) {
|
|
306
|
-
this.process.kill('SIGTERM');
|
|
307
|
-
setTimeout(() => {
|
|
308
|
-
if (this.process && !this.process.killed) {
|
|
309
|
-
this.process.kill('SIGKILL');
|
|
310
|
-
}
|
|
311
|
-
}, 3000);
|
|
312
|
-
}
|
|
313
|
-
} catch (e) {
|
|
314
|
-
logger.error('[AgentRpc] Error stopping:', e);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
this.process = null;
|
|
318
|
-
this.rl = null;
|
|
319
|
-
logger.info('[AgentRpc] Agent stopped');
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* 注册事件回调
|
|
324
|
-
*/
|
|
325
|
-
registerCallback(callback: EventCallback): void {
|
|
326
|
-
this.callbacks.add(callback);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* 移除事件回调
|
|
331
|
-
*/
|
|
332
|
-
removeCallback(callback: EventCallback): void {
|
|
333
|
-
this.callbacks.delete(callback);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* 启动健康检查
|
|
338
|
-
*/
|
|
339
|
-
private startHealthCheck(): void {
|
|
340
|
-
this.stopHealthCheck();
|
|
341
|
-
|
|
342
|
-
this.healthCheckInterval = setInterval(async () => {
|
|
343
|
-
if (!this.isRunning() && this._status === 'running') {
|
|
344
|
-
logger.warn('[AgentRpc] Process died, auto-restarting...');
|
|
345
|
-
this._status = 'restarting';
|
|
346
|
-
|
|
347
|
-
// 自动重启
|
|
348
|
-
if (this.config) {
|
|
349
|
-
try {
|
|
350
|
-
await this.start();
|
|
351
|
-
logger.info('[AgentRpc] Auto-restart successful');
|
|
352
|
-
} catch (e) {
|
|
353
|
-
logger.error('[AgentRpc] Auto-restart failed:', e);
|
|
354
|
-
this._status = 'error';
|
|
355
|
-
}
|
|
356
|
-
} else {
|
|
357
|
-
this._status = 'stopped';
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}, 5000);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* 停止健康检查
|
|
365
|
-
*/
|
|
366
|
-
private stopHealthCheck(): void {
|
|
367
|
-
if (this.healthCheckInterval) {
|
|
368
|
-
clearInterval(this.healthCheckInterval);
|
|
369
|
-
this.healthCheckInterval = null;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* 确保目录存在
|
|
375
|
-
*/
|
|
376
|
-
private ensureDir(dirPath: string): void {
|
|
377
|
-
if (!fs.existsSync(dirPath)) {
|
|
378
|
-
logger.info(`[AgentRpc] Creating directory: ${dirPath}`);
|
|
379
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* 单例实例
|
|
386
|
-
*/
|
|
387
|
-
let agentRpcService: AgentRpcService | null = null;
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* 获取 AgentRpcService 单例
|
|
391
|
-
*/
|
|
392
|
-
export function getAgentRpcService(): AgentRpcService {
|
|
393
|
-
if (!agentRpcService) {
|
|
394
|
-
agentRpcService = new AgentRpcService();
|
|
395
|
-
}
|
|
396
|
-
return agentRpcService;
|
|
397
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
export interface ServerConfig {
|
|
2
|
-
port: number;
|
|
3
|
-
host: string;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface AgentConfig {
|
|
7
|
-
enabled: boolean;
|
|
8
|
-
command: string;
|
|
9
|
-
provider: string;
|
|
10
|
-
model: string;
|
|
11
|
-
noSession: boolean;
|
|
12
|
-
sessionDir: string;
|
|
13
|
-
workingDir: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface LoggingConfig {
|
|
17
|
-
level: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface Config {
|
|
21
|
-
server: ServerConfig;
|
|
22
|
-
agent: AgentConfig;
|
|
23
|
-
logging: LoggingConfig;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface CommandResult {
|
|
27
|
-
success: boolean;
|
|
28
|
-
message: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface AgentEvent {
|
|
32
|
-
type: string;
|
|
33
|
-
data?: any;
|
|
34
|
-
messageType?: string;
|
|
35
|
-
delta?: string;
|
|
36
|
-
success?: boolean;
|
|
37
|
-
usage?: {
|
|
38
|
-
completionTokens?: number;
|
|
39
|
-
promptTokens?: number;
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type EventCallback = (event: AgentEvent) => void;
|
|
44
|
-
|
|
45
|
-
export type AgentStatus = 'uninitialized' | 'starting' | 'running' | 'stopped' | 'error' | 'restarting';
|
|
46
|
-
|
|
47
|
-
export interface ConfigRequest {
|
|
48
|
-
command?: string;
|
|
49
|
-
provider?: string;
|
|
50
|
-
model?: string;
|
|
51
|
-
noSession?: boolean;
|
|
52
|
-
sessionDir?: string;
|
|
53
|
-
workingDir?: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface ApiResponse<T = any> {
|
|
57
|
-
success: boolean;
|
|
58
|
-
message?: string;
|
|
59
|
-
data?: T;
|
|
60
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import pino from 'pino';
|
|
2
|
-
|
|
3
|
-
export const logger = pino({
|
|
4
|
-
level: process.env.LOG_LEVEL || 'info',
|
|
5
|
-
transport: process.env.NODE_ENV !== 'production' ? {
|
|
6
|
-
target: 'pino-pretty',
|
|
7
|
-
options: {
|
|
8
|
-
colorize: true,
|
|
9
|
-
translateTime: 'HH:MM:ss',
|
|
10
|
-
ignore: 'pid,hostname'
|
|
11
|
-
}
|
|
12
|
-
} : undefined
|
|
13
|
-
});
|
package/package/tsconfig.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "commonjs",
|
|
5
|
-
"lib": ["ES2020"],
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true,
|
|
12
|
-
"resolveJsonModule": true,
|
|
13
|
-
"declaration": true,
|
|
14
|
-
"declarationMap": true,
|
|
15
|
-
"sourceMap": true
|
|
16
|
-
},
|
|
17
|
-
"include": ["src/**/*"],
|
|
18
|
-
"exclude": ["node_modules", "dist"]
|
|
19
|
-
}
|