feat(ssa): Implement dual-channel architecture Phase 1-3 (QPER + LLM Agent pipeline)

Completed:
- Phase 1: DB schema (execution_mode + ssa_agent_executions), ModeToggle component, Session PATCH API
- Phase 2: AgentPlannerService + AgentCoderService (streaming) + CodeRunnerService + R Docker /execute-code endpoint
- Phase 3: AgentCodePanel (3-step confirmation UI), SSE event handling (7 agent events), streaming code display
- Three-step confirmation pipeline: plan -> user confirm -> stream code -> user confirm -> execute R code -> results
- R Docker sandbox /execute-code endpoint with 120s timeout + block_helpers preloaded
- E2E dual-channel test script (8 tests)
- Updated R engine architecture doc (v1.5) and SSA module status doc (v4.0)

Technical details:
- AgentCoderService uses LLM streaming (chatStream) for real-time code generation feedback
- ReviewerAgent temporarily disabled, prioritizing Plan -> Code -> Execute flow
- CodeRunnerService wraps user code with auto data loading (df variable injection)
- Frontend handles agent_planning, agent_plan_ready, code_generating, code_generated, code_executing, code_result events
- ask_user mechanism used for plan and code confirmation steps

Files: 24 files (4 new services, 2 new components, 1 migration, 1 E2E test, 16 modified)
Made-with: Cursor
This commit is contained in:
2026-03-02 22:23:54 +08:00
parent 71d32d11ee
commit aadceb5cde
24 changed files with 2694 additions and 56 deletions

View File

@@ -257,6 +257,88 @@ export function useSSAChat(): UseSSAChatReturn {
continue;
}
// ── Agent 通道 SSE 事件 ──
if (parsed.type === 'agent_planning') {
const { setAgentExecution, setWorkspaceOpen, setActivePane } = useSSAStore.getState();
setAgentExecution({
id: parsed.executionId || crypto.randomUUID(),
sessionId: '',
query: content,
retryCount: 0,
status: 'planning',
});
setActivePane('execution');
setWorkspaceOpen(true);
continue;
}
if (parsed.type === 'agent_plan_ready') {
const { updateAgentExecution } = useSSAStore.getState();
updateAgentExecution({
planText: parsed.planText,
planSteps: parsed.plan?.steps,
status: 'plan_pending',
});
continue;
}
if (parsed.type === 'code_generating') {
const { updateAgentExecution } = useSSAStore.getState();
updateAgentExecution({
partialCode: parsed.partialCode,
status: 'coding',
});
continue;
}
if (parsed.type === 'code_generated') {
const { updateAgentExecution } = useSSAStore.getState();
updateAgentExecution({
generatedCode: parsed.code,
partialCode: undefined,
status: parsed.code ? 'code_pending' : 'coding',
});
continue;
}
if (parsed.type === 'code_executing') {
const { updateAgentExecution } = useSSAStore.getState();
updateAgentExecution({ status: 'executing' });
continue;
}
if (parsed.type === 'code_result') {
const { updateAgentExecution } = useSSAStore.getState();
updateAgentExecution({
reportBlocks: parsed.reportBlocks,
generatedCode: parsed.code || useSSAStore.getState().agentExecution?.generatedCode,
status: 'completed',
durationMs: parsed.durationMs,
});
continue;
}
if (parsed.type === 'code_error') {
const { updateAgentExecution } = useSSAStore.getState();
updateAgentExecution({
status: 'error',
errorMessage: parsed.message,
});
continue;
}
if (parsed.type === 'code_retry') {
const { updateAgentExecution } = useSSAStore.getState();
updateAgentExecution({
status: 'coding',
retryCount: parsed.retryCount || 0,
generatedCode: parsed.code,
errorMessage: undefined,
});
continue;
}
// 错误事件
if (parsed.type === 'error') {
const errMsg = parsed.message || '处理消息时发生错误';