feat(ssa): SSA Agent mode MVP - prompt management + Phase 5A guardrails + UX enhancements
Backend: - Agent core prompts (Planner + Coder) now loaded from PromptService with 3-tier fallback (DB -> cache -> hardcoded) - Seed script (seed-ssa-agent-prompts.ts) for idempotent SSA_AGENT_PLANNER + SSA_AGENT_CODER setup - SSA fallback prompts added to prompt.fallbacks.ts - Phase 5A: XML tag extraction, defensive programming prompt, high-fidelity schema injection, AST pre-check - Default agent mode migration + session CRUD (rename/delete) APIs - R Docker: structured error handling (20+ patterns) + AST syntax pre-check Frontend: - Default agent mode (QPER toggle removed), view code fix, analysis result cards in chat - Session history sidebar with inline rename/delete, robust plan parsing from reviewResult - R code export wrapper for local reproducibility (package checks + data loader + polyfills) - SSA workspace CSS updates for sidebar actions and plan display Docs: - SSA module doc v4.2: Prompt inventory (2 Agent active / 11 QPER archived), dev progress updated - System overview doc v6.8: SSA Agent MVP milestone - Deployment checklist: DB-5 (seed script) + BE-10 (prompt management) Made-with: Cursor
This commit is contained in:
@@ -115,12 +115,8 @@ export default async function chatRoutes(app: FastifyInstance) {
|
||||
}
|
||||
// ── H1 结束 ──
|
||||
|
||||
// 3. 读取 session 的执行模式
|
||||
const session = await (prisma.ssaSession as any).findUnique({
|
||||
where: { id: sessionId },
|
||||
select: { executionMode: true },
|
||||
});
|
||||
const executionMode = (session?.executionMode as string) || 'qper';
|
||||
// 3. 执行模式:统一使用 Agent 通道(QPER 已废弃 UI 入口)
|
||||
const executionMode = 'agent';
|
||||
|
||||
// ── Agent 通道分流 ──
|
||||
if (executionMode === 'agent') {
|
||||
|
||||
@@ -50,7 +50,9 @@ export default async function sessionRoutes(app: FastifyInstance) {
|
||||
if (data) {
|
||||
const buffer = await data.toBuffer();
|
||||
const filename = data.filename;
|
||||
title = filename;
|
||||
const baseName = filename.replace(/\.(csv|xlsx?)$/i, '') || '数据';
|
||||
const now = new Date();
|
||||
title = `${baseName} ${now.getMonth() + 1}月${now.getDate()}日`;
|
||||
|
||||
// 生成存储 Key(遵循 OSS 目录结构规范)
|
||||
const uuid = crypto.randomUUID().replace(/-/g, '').substring(0, 16);
|
||||
@@ -113,20 +115,45 @@ export default async function sessionRoutes(app: FastifyInstance) {
|
||||
|
||||
const sessions = await prisma.ssaSession.findMany({
|
||||
where: { userId },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 20
|
||||
orderBy: { updatedAt: 'desc' },
|
||||
take: 30,
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
status: true,
|
||||
executionMode: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
return reply.send(sessions);
|
||||
return reply.send({ sessions });
|
||||
});
|
||||
|
||||
// 获取会话详情
|
||||
// 获取会话详情(含 Agent 执行历史)
|
||||
app.get('/:id', async (req, reply) => {
|
||||
const { id } = req.params as { id: string };
|
||||
|
||||
const session = await prisma.ssaSession.findUnique({
|
||||
where: { id },
|
||||
include: { messages: true }
|
||||
include: {
|
||||
agentExecutions: {
|
||||
orderBy: { createdAt: 'asc' },
|
||||
select: {
|
||||
id: true,
|
||||
query: true,
|
||||
planText: true,
|
||||
reviewResult: true,
|
||||
generatedCode: true,
|
||||
reportBlocks: true,
|
||||
retryCount: true,
|
||||
status: true,
|
||||
errorMessage: true,
|
||||
durationMs: true,
|
||||
createdAt: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
@@ -136,6 +163,60 @@ export default async function sessionRoutes(app: FastifyInstance) {
|
||||
return reply.send(session);
|
||||
});
|
||||
|
||||
/**
|
||||
* PATCH /sessions/:id
|
||||
* 更新会话(当前仅支持 title)
|
||||
*/
|
||||
app.patch('/:id', async (req, reply) => {
|
||||
const userId = getUserId(req);
|
||||
const { id } = req.params as { id: string };
|
||||
const body = req.body as { title?: string };
|
||||
|
||||
const session = await prisma.ssaSession.findUnique({ where: { id } });
|
||||
if (!session) {
|
||||
return reply.status(404).send({ error: 'Session not found' });
|
||||
}
|
||||
if (session.userId !== userId) {
|
||||
return reply.status(403).send({ error: 'Forbidden' });
|
||||
}
|
||||
|
||||
const data: { title?: string } = {};
|
||||
if (typeof body.title === 'string' && body.title.trim()) {
|
||||
data.title = body.title.trim();
|
||||
}
|
||||
if (Object.keys(data).length === 0) {
|
||||
return reply.send(session);
|
||||
}
|
||||
|
||||
const updated = await prisma.ssaSession.update({
|
||||
where: { id },
|
||||
data,
|
||||
});
|
||||
logger.info('[SSA:Session] Session updated', { sessionId: id, title: data.title });
|
||||
return reply.send(updated);
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /sessions/:id
|
||||
* 删除会话(级联删除消息、执行记录等)
|
||||
*/
|
||||
app.delete('/:id', async (req, reply) => {
|
||||
const userId = getUserId(req);
|
||||
const { id } = req.params as { id: string };
|
||||
|
||||
const session = await prisma.ssaSession.findUnique({ where: { id } });
|
||||
if (!session) {
|
||||
return reply.status(404).send({ error: 'Session not found' });
|
||||
}
|
||||
if (session.userId !== userId) {
|
||||
return reply.status(403).send({ error: 'Forbidden' });
|
||||
}
|
||||
|
||||
await prisma.ssaSession.delete({ where: { id } });
|
||||
logger.info('[SSA:Session] Session deleted', { sessionId: id });
|
||||
return reply.send({ success: true });
|
||||
});
|
||||
|
||||
/**
|
||||
* PATCH /sessions/:id/execution-mode
|
||||
* 切换双通道执行模式 (qper / agent)
|
||||
|
||||
Reference in New Issue
Block a user