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 = { type, cols, rows };
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) {
@@ -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)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "renote-server",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "WebSocket server for Renote - mobile remote development client with Claude Code integration",
5
5
  "main": "dist/index.js",
6
6
  "bin": {