feat: refresh shared chat and server workflows

This commit is contained in:
2026-05-26 12:26:33 +09:00
parent 51e0099bea
commit c1d0f4c1db
82 changed files with 18604 additions and 12461 deletions

View File

@@ -45,6 +45,7 @@ import {
} from './notification-message-service.js';
import { extractChatMessageParts, type ChatMessagePart } from './chat-message-parts.js';
import { resolveMainProjectRoot } from './main-project-root-service.js';
import { isRuntimeDraining, trackWebSocketConnectionClosed, trackWebSocketConnectionOpened } from './runtime-drain-service.js';
import {
findLatestPlanItem,
findPlanItemByPreviewUrl,
@@ -322,6 +323,14 @@ export function getActiveChatService() {
return activeChatService;
}
type ChatServiceRuntimeSnapshot = {
activeRequestCount: number;
queuedRequestCount: number;
connectedSessionCount: number;
activeSocketCount: number;
canAcceptNewRequests: boolean;
};
function getSessionSocketReadyState(session: ChatSessionState) {
for (const socket of session.sockets) {
if (socket.readyState === SOCKET_READY_STATE_OPEN) {
@@ -4362,6 +4371,38 @@ export class ChatService {
};
}
getRuntimeSnapshot(): ChatServiceRuntimeSnapshot {
let activeRequestCount = 0;
let queuedRequestCount = 0;
let activeSocketCount = 0;
let connectedSessionCount = 0;
for (const session of this.sessions.values()) {
activeRequestCount += session.activeRequestCount;
queuedRequestCount += session.queue.length;
let sessionHasOpenSocket = false;
for (const socket of session.sockets) {
if (socket.readyState === SOCKET_READY_STATE_OPEN) {
activeSocketCount += 1;
sessionHasOpenSocket = true;
}
}
if (sessionHasOpenSocket) {
connectedSessionCount += 1;
}
}
return {
activeRequestCount,
queuedRequestCount,
connectedSessionCount,
activeSocketCount,
canAcceptNewRequests: !isRuntimeDraining(),
};
}
close() {
activeRuntimeController = null;
if (activeChatService === this) {
@@ -5267,6 +5308,7 @@ export class ChatService {
session.sockets.add(socket);
session.lastSeenAt = Date.now();
this.clientStates.set(socket, session);
trackWebSocketConnectionOpened();
socket.on('message', (raw: RawData) => {
this.handleMessage(socket, raw);
@@ -5275,12 +5317,14 @@ export class ChatService {
socket.on('close', () => {
this.clientStates.delete(socket);
session.sockets.delete(socket);
trackWebSocketConnectionClosed();
});
socket.on('error', (error: Error) => {
this.logger.error(error, 'chat websocket error');
this.clientStates.delete(socket);
session.sockets.delete(socket);
trackWebSocketConnectionClosed();
});
await this.initializeSession(session);
@@ -5640,6 +5684,16 @@ export class ChatService {
return null;
}
if (isRuntimeDraining()) {
this.sendToSession(state, {
type: 'chat:error',
payload: {
message: '현재 서버가 배포 전환 중이라 새 AI 요청을 받지 않습니다. 잠시 후 다시 시도해 주세요.',
},
});
return null;
}
if (contextOverride) {
const mergedContext = {
...(state.context ?? {