fix(coding): close PI-100 review findings

This commit is contained in:
2026-08-23 20:55:49 +08:00
parent 22b4a9f9c4
commit 52b2467d5d
16 changed files with 679 additions and 122 deletions

View File

@@ -56,7 +56,7 @@ export async function handleCodingConversationRoutes(
sendJson(res, 503, {
success: false,
code: 'CODING_CORE_UNAVAILABLE',
error: 'Coding services are unavailable',
error: '本地编程服务暂时不可用。',
});
return true;
}

View File

@@ -2,33 +2,22 @@ import type { IncomingMessage, ServerResponse } from 'node:http';
import type { HostApiContext } from '../context';
import { CodingProductHostError } from '../coding-product-services';
import { sendJson } from '../route-utils';
import { sendFixedCodingError } from './coding-route-errors';
function unavailable(res: ServerResponse): void {
sendJson(res, 503, {
success: false,
code: 'CODING_PRODUCT_TOOLS_UNAVAILABLE',
error: 'Coding product tools are unavailable',
});
sendFixedCodingError(res, 503, 'CODING_PRODUCT_TOOLS_UNAVAILABLE');
}
function serviceError(res: ServerResponse, error: unknown): void {
if (error instanceof CodingProductHostError) {
sendJson(res, error.status, {
success: false,
code: error.code,
error: error.message,
});
sendFixedCodingError(res, error.status, error.code);
return;
}
const code = error && typeof error === 'object' && 'code' in error
? String(error.code)
: '';
if (code === 'ENOENT') {
sendJson(res, 404, {
success: false,
code: 'CODING_FILE_NOT_FOUND',
error: 'Project file does not exist',
});
sendFixedCodingError(res, 404, 'CODING_FILE_NOT_FOUND');
return;
}
const message = error instanceof Error ? error.message : '';
@@ -44,18 +33,10 @@ function serviceError(res: ServerResponse, error: unknown): void {
'Project file is not valid UTF-8 text',
]);
if (knownInputError.has(message)) {
sendJson(res, 400, {
success: false,
code: 'CODING_FILE_REQUEST_INVALID',
error: message,
});
sendFixedCodingError(res, 400, 'CODING_FILE_REQUEST_INVALID');
return;
}
sendJson(res, 500, {
success: false,
code: 'CODING_PRODUCT_TOOL_FAILED',
error: 'Coding product request failed',
});
sendFixedCodingError(res, 500, 'CODING_PRODUCT_TOOL_FAILED');
}
export async function handleCodingFileRoutes(

View File

@@ -1,5 +1,7 @@
import type { IncomingMessage, ServerResponse } from 'node:http';
import type { ProjectType } from '../../../shared/project-config';
import type { CodingProjectConfigSnapshot } from '../../coding-projects/project-service';
import type { CodingProject } from '../../coding-projects/project-store';
import type { HostApiContext } from '../context';
import { parseJsonBody, sendJson, sendNoContent } from '../route-utils';
import { sendCodingRouteError } from './coding-route-errors';
@@ -9,6 +11,19 @@ function isProjectRoute(pathname: string): boolean {
|| pathname.startsWith('/api/coding/projects/');
}
function publicProject(project: CodingProject | null): Omit<CodingProject, 'path'> | null {
if (!project) return null;
const { path: _path, ...safe } = project;
return safe;
}
function publicProjectSnapshot(snapshot: CodingProjectConfigSnapshot) {
return {
...snapshot,
project: publicProject(snapshot.project),
};
}
export async function handleCodingProjectRoutes(
req: IncomingMessage,
res: ServerResponse,
@@ -22,7 +37,7 @@ export async function handleCodingProjectRoutes(
sendJson(res, 503, {
success: false,
code: 'CODING_CORE_UNAVAILABLE',
error: 'Coding services are unavailable',
error: '本地编程服务暂时不可用。',
});
return true;
}
@@ -33,12 +48,15 @@ export async function handleCodingProjectRoutes(
projects.listProjects(),
projects.getActiveProject(),
]);
sendJson(res, 200, { projects: items, activeProjectId: activeProject?.id ?? null });
sendJson(res, 200, {
projects: items.map((project) => publicProject(project)),
activeProjectId: activeProject?.id ?? null,
});
return true;
}
if (url.pathname === '/api/coding/projects/open' && req.method === 'POST') {
const body = await parseJsonBody<{ projectPath?: string }>(req);
sendJson(res, 200, { project: await projects.openProject(body.projectPath ?? '') });
sendJson(res, 200, { project: publicProject(await projects.openProject(body.projectPath ?? '')) });
return true;
}
if (url.pathname === '/api/coding/projects/create' && req.method === 'POST') {
@@ -48,7 +66,7 @@ export async function handleCodingProjectRoutes(
projectName?: string;
projectType?: ProjectType;
}>(req);
sendJson(res, 201, { snapshot: await projects.createProject(body) });
sendJson(res, 201, { snapshot: publicProjectSnapshot(await projects.createProject(body)) });
return true;
}
if (url.pathname === '/api/coding/projects/remove' && req.method === 'POST') {
@@ -58,24 +76,26 @@ export async function handleCodingProjectRoutes(
return true;
}
if (url.pathname === '/api/coding/projects/active' && req.method === 'GET') {
sendJson(res, 200, { project: await projects.getActiveProject() });
sendJson(res, 200, { project: publicProject(await projects.getActiveProject()) });
return true;
}
if (url.pathname === '/api/coding/projects/active' && req.method === 'POST') {
const body = await parseJsonBody<{ projectId?: string }>(req);
sendJson(res, 200, { project: await projects.setActiveProject(body.projectId ?? '') });
sendJson(res, 200, { project: publicProject(await projects.setActiveProject(body.projectId ?? '')) });
return true;
}
if (url.pathname === '/api/coding/projects/config' && req.method === 'GET') {
sendJson(res, 200, {
snapshot: await projects.getConfig(url.searchParams.get('projectId')?.trim() || undefined),
snapshot: publicProjectSnapshot(
await projects.getConfig(url.searchParams.get('projectId')?.trim() || undefined),
),
});
return true;
}
if (url.pathname === '/api/coding/projects/config' && req.method === 'PUT') {
const body = await parseJsonBody<{ projectId?: string; config?: unknown }>(req);
sendJson(res, 200, {
snapshot: await projects.saveConfig(body.projectId ?? '', body.config),
snapshot: publicProjectSnapshot(await projects.saveConfig(body.projectId ?? '', body.config)),
});
return true;
}

View File

@@ -3,27 +3,72 @@ import { CodingProjectServiceError } from '../../coding-projects/project-service
import { CodingConversationServiceError } from '../../coding-runtime/conversation-service';
import { sendJson } from '../route-utils';
const FIXED_CODING_ERROR_MESSAGES: Readonly<Record<string, string>> = {
CODING_ACTIVE_PROJECT_REQUIRED: '请先选择一个编程项目。',
CODING_AGENT_NOT_FOUND: '当前伙伴不可用,请重新选择。',
CODING_AGENT_ID_IMMUTABLE: '已有伙伴标识不能修改。',
CODING_CONVERSATION_NOT_FOUND: '指定的对话不存在。',
CODING_CONVERSATION_REQUEST_INVALID: '对话请求无效,请检查输入。',
CODING_FILE_NOT_FOUND: '指定的项目文件不存在。',
CODING_FILE_REQUEST_INVALID: '文件请求无效,请检查输入。',
CODING_INTERACTION_REQUEST_INVALID: '交互响应无效,请重试。',
CODING_KNOWLEDGE_ALREADY_EXISTS: '同名知识文件已存在。',
CODING_KNOWLEDGE_REQUEST_INVALID: '知识文件无效,请检查后重试。',
CODING_MIGRATION_MODEL_REQUIRED: '请先为该对话选择一个可用模型。',
CODING_MODEL_UNAVAILABLE: '所选模型当前不可用,请重新选择。',
CODING_PROJECT_ALREADY_EXISTS: '该编程项目已经存在。',
CODING_PROJECT_CONFIG_INVALID: '项目配置无效,请检查后重试。',
CODING_PROJECT_IDENTITY_IMMUTABLE: '项目创建标识不能修改。',
CODING_PROJECT_NOT_FOUND: '指定的编程项目不存在。',
CODING_PROJECT_REQUEST_INVALID: '项目请求无效,请检查输入。',
CODING_PROJECT_TYPE_IMMUTABLE: '项目类型创建后不能修改。',
CODING_PRODUCT_TOOL_FAILED: '项目工具执行失败,请重试。',
CODING_PRODUCT_TOOLS_UNAVAILABLE: '项目工具暂时不可用。',
CODING_PROVIDER_AUTH_REQUIRED: 'Provider 凭证不可用,请修复账号后重试。',
CODING_REQUEST_CAPACITY_EXCEEDED: '本地请求队列已满,请稍后重试。',
CODING_REQUEST_ID_CONFLICT: '该请求标识已用于不同内容,请使用新的标识。',
CODING_REQUEST_UNCERTAIN: '请求状态无法确认,请先核对对话后再决定是否重试。',
CODING_RUNTIME_PROTOCOL_ERROR: '本地 Agent 通信异常,请执行恢复。',
CODING_RUNTIME_READY_TIMEOUT: '本地 Agent 启动超时,请执行恢复。',
CODING_RUNTIME_START_FAILED: '本地 Agent 启动失败,请执行恢复。',
CODING_RUNTIME_UNAVAILABLE: '本地编程运行时暂时不可用。',
CODING_SESSION_UNREADABLE: '对话会话无法读取,原文件已保留。',
CODING_STORAGE_WRITE_FAILED: '本地数据写入失败,请检查存储后重试。',
};
function fixedMessage(code: string): string {
return FIXED_CODING_ERROR_MESSAGES[code] ?? '编程操作失败,请重试。';
}
export function sendFixedCodingError(
res: ServerResponse,
status: number,
code: string,
): void {
sendJson(res, status, {
success: false,
code,
error: fixedMessage(code),
});
}
export function sendCodingRouteError(res: ServerResponse, error: unknown): void {
if (error instanceof CodingProjectServiceError || error instanceof CodingConversationServiceError) {
sendJson(res, error.status, {
success: false,
code: error.code,
error: error.message,
});
sendFixedCodingError(res, error.status, error.code);
return;
}
if (error instanceof SyntaxError) {
sendJson(res, 400, {
success: false,
code: 'CODING_REQUEST_INVALID',
error: 'Request JSON is invalid',
error: '请求内容无效,请检查后重试。',
});
return;
}
sendJson(res, 500, {
success: false,
code: 'CODING_REQUEST_FAILED',
error: 'Coding request failed',
error: '编程操作失败,请重试。',
});
}