feat: refine codex live chat context flows
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
getAppConfig,
|
||||
getChatContextSettingsConfig,
|
||||
getAppConfigSnapshot,
|
||||
getChatTypesConfig,
|
||||
normalizeAppConfigSnapshot,
|
||||
upsertAppConfig,
|
||||
@@ -52,20 +52,20 @@ function getRequestAppDomain(request: { headers: Record<string, string | string[
|
||||
export async function registerAppConfigRoutes(app: FastifyInstance) {
|
||||
app.get('/api/app-config', async (request) => {
|
||||
const appOrigin = getRequestAppOrigin(request);
|
||||
const config = await getAppConfig(appOrigin);
|
||||
const config = await getAppConfigSnapshot(appOrigin);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
config: normalizeAppConfigSnapshot(config),
|
||||
config,
|
||||
};
|
||||
});
|
||||
|
||||
app.get('/api/chat-types', async (request) => {
|
||||
const chatTypes = await getChatTypesConfig(getRequestAppOrigin(request));
|
||||
const chatTypeConfig = await getChatTypesConfig(getRequestAppOrigin(request));
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
chatTypes,
|
||||
...chatTypeConfig,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -108,17 +108,21 @@ export async function registerAppConfigRoutes(app: FastifyInstance) {
|
||||
}
|
||||
}
|
||||
|
||||
const parsed = z.object({
|
||||
chatTypes: z.array(z.unknown()),
|
||||
}).parse(payload ?? {});
|
||||
const parsed = z
|
||||
.object({
|
||||
chatTypes: z.array(z.unknown()).optional(),
|
||||
customChatTypes: z.array(z.unknown()).optional(),
|
||||
})
|
||||
.parse(payload ?? {});
|
||||
|
||||
const appOrigin = getRequestAppOrigin(request);
|
||||
const appDomain = getRequestAppDomain(request);
|
||||
const savedChatTypes = await upsertChatTypesConfig(parsed.chatTypes, appOrigin, appDomain);
|
||||
const targetChatTypes = parsed.customChatTypes ?? parsed.chatTypes ?? [];
|
||||
const savedChatTypeConfig = await upsertChatTypesConfig(targetChatTypes, appOrigin, appDomain);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
chatTypes: savedChatTypes,
|
||||
...savedChatTypeConfig,
|
||||
};
|
||||
} catch (error) {
|
||||
return reply.code(409).send({
|
||||
|
||||
13
etc/servers/work-server/src/routes/chat.test.ts
Normal file
13
etc/servers/work-server/src/routes/chat.test.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { resolveStaticContentType } from './chat.js';
|
||||
|
||||
test('resolveStaticContentType returns html content type for chat resource html files', () => {
|
||||
assert.equal(resolveStaticContentType('/tmp/sample.html'), 'text/html; charset=utf-8');
|
||||
assert.equal(resolveStaticContentType('/tmp/sample.htm'), 'text/html; charset=utf-8');
|
||||
});
|
||||
|
||||
test('resolveStaticContentType keeps plain text content type for code resources', () => {
|
||||
assert.equal(resolveStaticContentType('/tmp/sample.ts'), 'text/plain; charset=utf-8');
|
||||
assert.equal(resolveStaticContentType('/tmp/sample.diff'), 'text/plain; charset=utf-8');
|
||||
});
|
||||
@@ -10,6 +10,7 @@ import { ensureChatSessionResourceDirectories, getActiveChatService, getChatRunt
|
||||
import { rollbackChatRuntimeRequest } from '../services/chat-runtime-rollback-service.js';
|
||||
import {
|
||||
CHAT_CONTEXT_DESCRIPTION_MAX_LENGTH,
|
||||
clearChatConversationData,
|
||||
createChatConversation,
|
||||
deleteUnansweredChatConversationRequest,
|
||||
deleteChatConversation,
|
||||
@@ -22,13 +23,14 @@ import {
|
||||
updateChatConversationContext,
|
||||
} from '../services/chat-room-service.js';
|
||||
import { chatRuntimeService } from '../services/chat-runtime-service.js';
|
||||
import { resolveMainProjectRoot } from '../services/main-project-root-service.js';
|
||||
|
||||
const CHAT_ATTACHMENT_ROUTE_BODY_LIMIT = 20 * 1024 * 1024;
|
||||
const CHAT_ATTACHMENT_FILE_SIZE_LIMIT = 10 * 1024 * 1024;
|
||||
const CHAT_PUBLIC_ROUTE_PREFIX = '/.codex_chat/';
|
||||
const CHAT_API_RESOURCE_ROUTE_PREFIX = '/api/chat/resources';
|
||||
|
||||
function resolveStaticContentType(filePath: string) {
|
||||
export function resolveStaticContentType(filePath: string) {
|
||||
const extension = path.extname(filePath).toLowerCase();
|
||||
|
||||
switch (extension) {
|
||||
@@ -40,10 +42,12 @@ function resolveStaticContentType(filePath: string) {
|
||||
case '.cjs':
|
||||
case '.json':
|
||||
case '.css':
|
||||
case '.html':
|
||||
case '.txt':
|
||||
case '.diff':
|
||||
return 'text/plain; charset=utf-8';
|
||||
case '.html':
|
||||
case '.htm':
|
||||
return 'text/html; charset=utf-8';
|
||||
case '.md':
|
||||
case '.markdown':
|
||||
return 'text/markdown; charset=utf-8';
|
||||
@@ -139,7 +143,7 @@ function sanitizeChatAttachmentFileName(fileName: string) {
|
||||
}
|
||||
|
||||
function resolveChatAttachmentRepoPath() {
|
||||
return path.resolve(env.SERVER_COMMAND_MAIN_PROJECT_ROOT || env.PLAN_MAIN_PROJECT_REPO_PATH || env.PLAN_GIT_REPO_PATH);
|
||||
return resolveMainProjectRoot();
|
||||
}
|
||||
|
||||
function getClientIdHeader(request: { headers: Record<string, unknown> }) {
|
||||
@@ -421,7 +425,7 @@ export async function registerChatRoutes(app: FastifyInstance) {
|
||||
});
|
||||
}
|
||||
|
||||
const messageLimit = query.limit ?? 6;
|
||||
const messageLimit = query.limit ?? 8;
|
||||
const detailPage = await listChatConversationDetailPage(params.sessionId, {
|
||||
limit: messageLimit,
|
||||
beforeMessageId: query.beforeMessageId ?? null,
|
||||
@@ -562,4 +566,34 @@ export async function registerChatRoutes(app: FastifyInstance) {
|
||||
sessionId: params.sessionId,
|
||||
};
|
||||
});
|
||||
|
||||
app.post('/api/chat/conversations/:sessionId/clear', async (request, reply) => {
|
||||
const params = z.object({
|
||||
sessionId: z.string().trim().min(1).max(120),
|
||||
}).parse(request.params ?? {});
|
||||
|
||||
const clientId = canViewAllConversations(request) ? null : getClientIdHeader(request);
|
||||
const current = await getChatConversation(params.sessionId, clientId || null);
|
||||
|
||||
if (!current) {
|
||||
return reply.code(404).send({
|
||||
message: '초기화할 채팅방을 찾을 수 없습니다.',
|
||||
});
|
||||
}
|
||||
|
||||
getActiveChatService()?.resetSessionData(params.sessionId);
|
||||
chatRuntimeService.clearSession(params.sessionId);
|
||||
const item = await clearChatConversationData(params.sessionId, clientId || null);
|
||||
|
||||
if (!item) {
|
||||
return reply.code(404).send({
|
||||
message: '채팅방 데이터 초기화 후 다시 불러오지 못했습니다.',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
item,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
cancelServerRestartReservation,
|
||||
confirmServerRestartReservation,
|
||||
getRestartReservationWorkloadSummary,
|
||||
requestImmediateRestartRecovery,
|
||||
getServerRestartReservation,
|
||||
scheduleServerRestartReservation,
|
||||
} from '../services/server-restart-reservation-service.js';
|
||||
@@ -90,14 +91,40 @@ export async function registerServerCommandRoutes(app: FastifyInstance) {
|
||||
}
|
||||
}
|
||||
|
||||
const result = await restartServerCommand(key);
|
||||
try {
|
||||
const result = await restartServerCommand(key);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
item: result.server,
|
||||
commandOutput: result.commandOutput,
|
||||
restartState: result.restartState,
|
||||
};
|
||||
return {
|
||||
ok: true,
|
||||
item: result.server,
|
||||
commandOutput: result.commandOutput,
|
||||
restartState: result.restartState,
|
||||
};
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : '서버 재기동에 실패했습니다.';
|
||||
|
||||
if (key !== 'test' && key !== 'work-server') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!/(build|vite|tsc|typescript|npm run build|pnpm build|rollup|esbuild|compilation|compile|exit:\d+)/i.test(message)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
await requestImmediateRestartRecovery(app.log, key, message);
|
||||
const server = (await listServerCommands()).find((item) => item.key === key);
|
||||
|
||||
if (!server) {
|
||||
throw new Error(`${key} 서버 상태를 다시 읽지 못했습니다.`);
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
item: server,
|
||||
commandOutput: `${message}\n\n빌드 실패를 감지해 Codex 자동 개선과 재기동 재시도를 시작했습니다.`,
|
||||
restartState: 'accepted' as const,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/server-commands/restart-reservation', async (request, reply) => {
|
||||
|
||||
Reference in New Issue
Block a user