@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.
@@ -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
- });
@@ -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
- }