renote-server 1.0.2 → 1.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.
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.claudeChatService = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const logger_1 = require("../utils/logger");
|
|
6
|
+
class ClaudeChatService {
|
|
7
|
+
constructor() {
|
|
8
|
+
// 存储正在运行的进程,按 sessionId 索引
|
|
9
|
+
this.runningProcesses = new Map();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 发送消息给 Claude CLI
|
|
13
|
+
* 使用 -p (print) 模式,消息通过 stdin 传入,响应通过 jsonl 文件监听获取
|
|
14
|
+
*/
|
|
15
|
+
async sendMessage(options) {
|
|
16
|
+
const { workspaceDirName, sessionId, newSessionId, message, cwd, allowedTools } = options;
|
|
17
|
+
// 确定实际使用的 sessionId
|
|
18
|
+
const effectiveSessionId = sessionId || newSessionId;
|
|
19
|
+
// 检查该会话是否已有进程在运行
|
|
20
|
+
if (effectiveSessionId && this.runningProcesses.has(effectiveSessionId)) {
|
|
21
|
+
return {
|
|
22
|
+
success: false,
|
|
23
|
+
error: 'A message is already being processed for this session',
|
|
24
|
+
sessionId: effectiveSessionId,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const args = ['-p'];
|
|
28
|
+
// 恢复现有会话
|
|
29
|
+
if (sessionId) {
|
|
30
|
+
args.push('--resume', sessionId);
|
|
31
|
+
}
|
|
32
|
+
// 新建会话并指定 ID
|
|
33
|
+
else if (newSessionId) {
|
|
34
|
+
args.push('--session-id', newSessionId);
|
|
35
|
+
}
|
|
36
|
+
// 添加允许的工具参数
|
|
37
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
38
|
+
args.push('--allowedTools', allowedTools.join(','));
|
|
39
|
+
}
|
|
40
|
+
logger_1.logger.info(`[ChatService] Sending message to Claude CLI`);
|
|
41
|
+
logger_1.logger.info(`[ChatService] Args: ${args.join(' ')}`);
|
|
42
|
+
logger_1.logger.info(`[ChatService] CWD: ${cwd || 'default'}`);
|
|
43
|
+
logger_1.logger.info(`[ChatService] Message length: ${message.length}`);
|
|
44
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
45
|
+
logger_1.logger.info(`[ChatService] Allowed tools: ${allowedTools.join(', ')}`);
|
|
46
|
+
}
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
const proc = (0, child_process_1.spawn)('claude', args, {
|
|
49
|
+
cwd: cwd || process.cwd(),
|
|
50
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
51
|
+
env: {
|
|
52
|
+
...process.env,
|
|
53
|
+
// 确保 Claude CLI 不会等待用户输入
|
|
54
|
+
TERM: 'dumb',
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
// 记录进程
|
|
58
|
+
if (effectiveSessionId) {
|
|
59
|
+
this.runningProcesses.set(effectiveSessionId, proc);
|
|
60
|
+
}
|
|
61
|
+
// 写入消息到 stdin
|
|
62
|
+
proc.stdin.write(message);
|
|
63
|
+
proc.stdin.end();
|
|
64
|
+
// 收集输出(主要用于错误检测)
|
|
65
|
+
let stdout = '';
|
|
66
|
+
let stderr = '';
|
|
67
|
+
proc.stdout.on('data', (data) => {
|
|
68
|
+
stdout += data.toString();
|
|
69
|
+
// 可以选择性地记录 stdout,但主要响应通过 jsonl 监听获取
|
|
70
|
+
logger_1.logger.debug(`[ChatService] stdout: ${data.toString().substring(0, 200)}...`);
|
|
71
|
+
});
|
|
72
|
+
proc.stderr.on('data', (data) => {
|
|
73
|
+
stderr += data.toString();
|
|
74
|
+
logger_1.logger.warn(`[ChatService] stderr: ${data.toString()}`);
|
|
75
|
+
});
|
|
76
|
+
proc.on('close', (code) => {
|
|
77
|
+
// 移除进程记录
|
|
78
|
+
if (effectiveSessionId) {
|
|
79
|
+
this.runningProcesses.delete(effectiveSessionId);
|
|
80
|
+
}
|
|
81
|
+
if (code === 0) {
|
|
82
|
+
logger_1.logger.info(`[ChatService] Claude CLI completed successfully`);
|
|
83
|
+
resolve({
|
|
84
|
+
success: true,
|
|
85
|
+
sessionId: effectiveSessionId,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
logger_1.logger.error(`[ChatService] Claude CLI exited with code ${code}`);
|
|
90
|
+
resolve({
|
|
91
|
+
success: false,
|
|
92
|
+
error: stderr || `Exit code: ${code}`,
|
|
93
|
+
sessionId: effectiveSessionId,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
proc.on('error', (err) => {
|
|
98
|
+
// 移除进程记录
|
|
99
|
+
if (effectiveSessionId) {
|
|
100
|
+
this.runningProcesses.delete(effectiveSessionId);
|
|
101
|
+
}
|
|
102
|
+
logger_1.logger.error(`[ChatService] Failed to spawn Claude CLI:`, err);
|
|
103
|
+
resolve({
|
|
104
|
+
success: false,
|
|
105
|
+
error: err.message,
|
|
106
|
+
sessionId: effectiveSessionId,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 检查指定会话是否有正在进行的消息处理
|
|
113
|
+
*/
|
|
114
|
+
isProcessing(sessionId) {
|
|
115
|
+
return this.runningProcesses.has(sessionId);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 取消正在进行的消息处理
|
|
119
|
+
*/
|
|
120
|
+
cancelMessage(sessionId) {
|
|
121
|
+
const proc = this.runningProcesses.get(sessionId);
|
|
122
|
+
if (proc) {
|
|
123
|
+
proc.kill('SIGTERM');
|
|
124
|
+
this.runningProcesses.delete(sessionId);
|
|
125
|
+
logger_1.logger.info(`[ChatService] Cancelled message processing for session ${sessionId}`);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.claudeChatService = new ClaudeChatService();
|
|
@@ -38,6 +38,8 @@ class TerminalWebSocketHandler {
|
|
|
38
38
|
const type = (url.searchParams.get('type') || 'shell');
|
|
39
39
|
const cols = parseInt(url.searchParams.get('cols') || '80', 10);
|
|
40
40
|
const rows = parseInt(url.searchParams.get('rows') || '24', 10);
|
|
41
|
+
const claudeArgsParam = url.searchParams.get('claudeArgs');
|
|
42
|
+
const cwd = url.searchParams.get('cwd') || undefined;
|
|
41
43
|
// Validate token
|
|
42
44
|
if (!this.authManager.validateToken(token)) {
|
|
43
45
|
logger_1.logger.warn('Terminal WebSocket: invalid token');
|
|
@@ -54,7 +56,13 @@ class TerminalWebSocketHandler {
|
|
|
54
56
|
this.wsToSession.set(ws, { clientId, sessionId });
|
|
55
57
|
logger_1.logger.info(`Terminal WebSocket connected: clientId=${clientId}, sessionId=${sessionId}, type=${type}, ${cols}x${rows}`);
|
|
56
58
|
const connection = localTerminalManager_1.localTerminalManager.getOrCreateConnection(clientId);
|
|
57
|
-
const options = {
|
|
59
|
+
const options = {
|
|
60
|
+
type,
|
|
61
|
+
cols,
|
|
62
|
+
rows,
|
|
63
|
+
cwd,
|
|
64
|
+
claudeArgs: claudeArgsParam ? JSON.parse(decodeURIComponent(claudeArgsParam)) : undefined,
|
|
65
|
+
};
|
|
58
66
|
const success = connection.startTerminal(sessionId, (data) => {
|
|
59
67
|
// Send terminal output as text frame
|
|
60
68
|
if (ws.readyState === ws_1.default.OPEN) {
|
package/dist/websocket/server.js
CHANGED
|
@@ -16,6 +16,7 @@ const sessionBrowser_1 = require("../claude/sessionBrowser");
|
|
|
16
16
|
const terminal_1 = require("../terminal");
|
|
17
17
|
const git_1 = require("../git");
|
|
18
18
|
const terminalWebSocket_1 = require("../terminal/terminalWebSocket");
|
|
19
|
+
const chatService_1 = require("../claude/chatService");
|
|
19
20
|
class WebSocketServer {
|
|
20
21
|
constructor() {
|
|
21
22
|
this.clients = new Map();
|
|
@@ -135,6 +136,9 @@ class WebSocketServer {
|
|
|
135
136
|
case 'get_session_folder_info':
|
|
136
137
|
await this.handleGetSessionFolderInfo(ws, message);
|
|
137
138
|
break;
|
|
139
|
+
case 'send_claude_message':
|
|
140
|
+
await this.handleSendClaudeMessage(ws, message);
|
|
141
|
+
break;
|
|
138
142
|
default:
|
|
139
143
|
const clientId = this.getClientId(ws);
|
|
140
144
|
if (!clientId) {
|
|
@@ -474,6 +478,56 @@ class WebSocketServer {
|
|
|
474
478
|
this.sendError(ws, `Failed to get session folder info: ${error}`);
|
|
475
479
|
}
|
|
476
480
|
}
|
|
481
|
+
async handleSendClaudeMessage(ws, message) {
|
|
482
|
+
const clientId = this.getClientId(ws);
|
|
483
|
+
if (!clientId) {
|
|
484
|
+
this.sendError(ws, 'Not authenticated');
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
try {
|
|
488
|
+
const { data } = message;
|
|
489
|
+
if (!data || !data.workspaceDirName || !data.message) {
|
|
490
|
+
this.sendError(ws, 'workspaceDirName and message are required');
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
const { workspaceDirName, sessionId, newSessionId, message: userMessage, allowedTools } = data;
|
|
494
|
+
// 解码 workspaceDirName 为实际路径
|
|
495
|
+
const cwd = '/' + workspaceDirName.replace(/-/g, '/');
|
|
496
|
+
logger_1.logger.info(`[SendClaudeMessage] Sending message to Claude CLI`);
|
|
497
|
+
logger_1.logger.info(`[SendClaudeMessage] CWD: ${cwd}`);
|
|
498
|
+
logger_1.logger.info(`[SendClaudeMessage] SessionId: ${sessionId || 'none'}`);
|
|
499
|
+
logger_1.logger.info(`[SendClaudeMessage] NewSessionId: ${newSessionId || 'none'}`);
|
|
500
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
501
|
+
logger_1.logger.info(`[SendClaudeMessage] AllowedTools: ${allowedTools.join(', ')}`);
|
|
502
|
+
}
|
|
503
|
+
// 调用 chatService 发送消息
|
|
504
|
+
const result = await chatService_1.claudeChatService.sendMessage({
|
|
505
|
+
workspaceDirName,
|
|
506
|
+
sessionId,
|
|
507
|
+
newSessionId,
|
|
508
|
+
message: userMessage,
|
|
509
|
+
cwd,
|
|
510
|
+
allowedTools,
|
|
511
|
+
});
|
|
512
|
+
// 响应客户端
|
|
513
|
+
this.send(ws, {
|
|
514
|
+
type: 'send_claude_message_response',
|
|
515
|
+
data: {
|
|
516
|
+
success: result.success,
|
|
517
|
+
error: result.error,
|
|
518
|
+
sessionId: result.sessionId,
|
|
519
|
+
},
|
|
520
|
+
});
|
|
521
|
+
// 如果成功,响应会通过现有的 watchSession 机制推送
|
|
522
|
+
if (result.success) {
|
|
523
|
+
logger_1.logger.info(`[SendClaudeMessage] Message sent successfully, responses will be pushed via watchSession`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
catch (error) {
|
|
527
|
+
logger_1.logger.error('Error sending Claude message:', error);
|
|
528
|
+
this.sendError(ws, `Failed to send message: ${error}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
477
531
|
async handleAuth(ws, token) {
|
|
478
532
|
logger_1.logger.info(`[Auth] Validating token: "${token ? '***' : '(empty)'}"`);
|
|
479
533
|
if (this.authManager.validateToken(token)) {
|