feat(ssa): Agent channel UX optimization (Solution B) + Plan-and-Execute architecture design

SSA Agent channel improvements (12 code files, +931/-203 lines):
- Solution B: left/right separation of concerns (gaze guiding + state mutex + time-travel)
- JWT token refresh mechanism (ensureFreshToken) to fix HTTP 401 during pipeline
- Code truncation fix: LLM maxTokens 4000->8000 + CSS max-height 60vh
- Retry streaming code generation with generateCodeStream()
- R Docker structured errors: 20+ pattern matching + format_agent_error + line extraction
- Prompt iron rules: strict output format in CoderAgent System Prompt
- parseCode robustness: XML/Markdown/inference 3-tier matching + length validation
- consoleOutput type defense: handle both array and scalar from R Docker unboxedJSON
- Agent progress bar sync: derive phase from agentExecution.status
- Export report / view code buttons restored for Agent mode
- ExecutingProgress component: real-time timer + dynamic tips + step pulse animation

Architecture design (3 review reports):
- Plan-and-Execute step-by-step execution architecture approved
- Code accumulation strategy (R Docker stays stateless)
- 5 engineering guardrails: XML tags, AST pre-check, defensive prompts, high-fidelity schema, error classification circuit breaker

Docs: update SSA module status v4.1, system status v6.7, deployment changelist
Made-with: Cursor
This commit is contained in:
2026-03-07 22:32:32 +08:00
parent 87655ea7e6
commit 52989cd03f
18 changed files with 1334 additions and 230 deletions

View File

@@ -67,7 +67,25 @@ ERROR_CODES <- list(
R_ERROR_MAPPING <- list(
"system is computationally singular" = "E005_SINGULAR_MATRIX",
"did not converge" = "E006_CONVERGENCE_FAILED",
"constant" = "E007_VARIANCE_ZERO"
"constant" = "E007_VARIANCE_ZERO",
"object '.*' not found" = "E001_COLUMN_NOT_FOUND",
"undefined columns" = "E001_COLUMN_NOT_FOUND",
"subscript out of bounds" = "E100_INTERNAL_ERROR",
"cannot coerce" = "E002_TYPE_MISMATCH",
"non-numeric argument" = "E002_TYPE_MISMATCH",
"not meaningful for factors" = "E002_TYPE_MISMATCH",
"missing value where TRUE/FALSE needed" = "E100_INTERNAL_ERROR",
"replacement has" = "E100_INTERNAL_ERROR",
"could not find function" = "E101_PACKAGE_MISSING",
"there is no package called" = "E101_PACKAGE_MISSING",
"cannot open the connection" = "E100_INTERNAL_ERROR",
"singular gradient" = "E005_SINGULAR_MATRIX",
"rank deficien" = "E005_SINGULAR_MATRIX",
"contrasts can be applied only to factors" = "E002_TYPE_MISMATCH",
"need at least 2 observations" = "E004_SAMPLE_TOO_SMALL",
"not enough observations" = "E004_SAMPLE_TOO_SMALL",
"sample size must be" = "E004_SAMPLE_TOO_SMALL",
"groups with fewer than" = "E004_SAMPLE_TOO_SMALL"
)
# 构造错误响应(含用户友好提示)
@@ -86,6 +104,64 @@ make_error <- function(error_def, ...) {
))
}
#' Agent 通道专用:结构化错误格式
#' 提取行号、错误分类、修复建议,方便 LLM 理解并修复
format_agent_error <- function(e, code, warnings = list(), messages = character(0)) {
raw_msg <- conditionMessage(e)
error_line <- NULL
line_match <- regmatches(raw_msg, regexpr("(line \\d+|at \\d+:\\d+|:( *)\\d+)", raw_msg))
if (length(line_match) > 0 && nchar(line_match[1]) > 0) {
nums <- regmatches(line_match[1], gregexpr("\\d+", line_match[1]))[[1]]
error_line <- as.integer(nums[1])
}
error_context <- NULL
if (!is.null(error_line) && !is.null(code)) {
code_lines <- strsplit(code, "\n")[[1]]
start <- max(1, error_line - 3)
end <- min(length(code_lines), error_line + 3)
context_lines <- paste0(
ifelse(start:end == error_line, ">>> ", " "),
sprintf("%3d| ", start:end),
code_lines[start:end]
)
error_context <- paste(context_lines, collapse = "\n")
}
error_code <- "E_EXEC"
error_type <- "runtime"
user_hint <- raw_msg
for (pattern in names(R_ERROR_MAPPING)) {
if (grepl(pattern, raw_msg, ignore.case = TRUE)) {
key <- R_ERROR_MAPPING[[pattern]]
info <- ERROR_CODES[[key]]
error_code <- info$code
error_type <- info$type
user_hint <- paste0(info$user_hint, " | ", raw_msg)
break
}
}
friendly_msg <- paste0(
"[", error_code, "] ", raw_msg,
if (!is.null(error_line)) paste0(" (约第 ", error_line, " 行)") else "",
if (length(warnings) > 0) paste0("\n[Warnings] ", paste(utils::head(warnings, 5), collapse = "; ")) else ""
)
list(
message = friendly_msg,
raw_error = raw_msg,
error_code = error_code,
error_type = error_type,
error_line = error_line,
error_context = error_context,
user_hint = user_hint,
warnings = if (length(warnings) > 0) warnings else NULL
)
}
# 尝试将 R 原始错误映射为友好错误码
map_r_error <- function(raw_error_msg) {
for (pattern in names(R_ERROR_MAPPING)) {