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:
2026-03-08 15:23:09 +08:00
parent c681155de2
commit ac724266c1
24 changed files with 1598 additions and 140 deletions

View File

@@ -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') {

View File

@@ -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)