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:
@@ -192,28 +192,39 @@ function(req) {
|
||||
|
||||
message(glue::glue("[ExecuteCode] session={session_id}, code_length={nchar(code)}, timeout={timeout_sec}s"))
|
||||
|
||||
# 在隔离环境中执行,预加载 block_helpers 和 data_loader
|
||||
sandbox_env <- new.env(parent = globalenv())
|
||||
|
||||
# 如果有 session_id,尝试预设数据路径变量
|
||||
if (!is.null(session_id) && nchar(session_id) > 0) {
|
||||
sandbox_env$SESSION_ID <- session_id
|
||||
}
|
||||
|
||||
start_time <- proc.time()
|
||||
|
||||
# 捕获输出和结果
|
||||
collected_warnings <- list()
|
||||
collected_messages <- character(0)
|
||||
|
||||
output_capture <- tryCatch(
|
||||
withTimeout(
|
||||
{
|
||||
# 捕获打印输出
|
||||
captured_output <- utils::capture.output({
|
||||
result <- eval(parse(text = code), envir = sandbox_env)
|
||||
result <- withCallingHandlers(
|
||||
eval(parse(text = code), envir = sandbox_env),
|
||||
warning = function(w) {
|
||||
collected_warnings[[length(collected_warnings) + 1]] <<- w$message
|
||||
invokeRestart("muffleWarning")
|
||||
},
|
||||
message = function(m) {
|
||||
collected_messages <<- c(collected_messages, conditionMessage(m))
|
||||
invokeRestart("muffleMessage")
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
list(
|
||||
result = result,
|
||||
output = captured_output,
|
||||
warnings = collected_warnings,
|
||||
messages = collected_messages,
|
||||
error = NULL
|
||||
)
|
||||
},
|
||||
@@ -221,10 +232,14 @@ function(req) {
|
||||
onTimeout = "error"
|
||||
),
|
||||
error = function(e) {
|
||||
error_info <- format_agent_error(e, code, collected_warnings, collected_messages)
|
||||
list(
|
||||
result = NULL,
|
||||
output = NULL,
|
||||
error = e$message
|
||||
warnings = collected_warnings,
|
||||
messages = collected_messages,
|
||||
error = error_info$message,
|
||||
error_detail = error_info
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -232,39 +247,54 @@ function(req) {
|
||||
elapsed_ms <- round((proc.time() - start_time)["elapsed"] * 1000)
|
||||
|
||||
if (!is.null(output_capture$error)) {
|
||||
detail <- output_capture$error_detail
|
||||
message(glue::glue("[ExecuteCode] ERROR after {elapsed_ms}ms: {output_capture$error}"))
|
||||
|
||||
console_lines <- c(
|
||||
output_capture$output,
|
||||
if (length(output_capture$warnings) > 0) paste0("Warning: ", output_capture$warnings),
|
||||
if (length(output_capture$messages) > 0) output_capture$messages
|
||||
)
|
||||
|
||||
return(list(
|
||||
status = "error",
|
||||
error_code = "E_EXEC",
|
||||
error_code = if (!is.null(detail)) detail$error_code else "E_EXEC",
|
||||
error_type = if (!is.null(detail)) detail$error_type else "runtime",
|
||||
message = output_capture$error,
|
||||
user_hint = paste0("R 代码执行出错 (", elapsed_ms, "ms): ", output_capture$error),
|
||||
user_hint = if (!is.null(detail)) detail$user_hint else output_capture$error,
|
||||
error_line = if (!is.null(detail)) detail$error_line else NULL,
|
||||
error_context = if (!is.null(detail)) detail$error_context else NULL,
|
||||
console_output = console_lines,
|
||||
duration_ms = elapsed_ms
|
||||
))
|
||||
}
|
||||
|
||||
message(glue::glue("[ExecuteCode] SUCCESS in {elapsed_ms}ms"))
|
||||
|
||||
# 将结果标准化
|
||||
final_result <- output_capture$result
|
||||
|
||||
# 如果结果是 list 且包含 report_blocks,直接返回
|
||||
console_lines <- c(
|
||||
output_capture$output,
|
||||
if (length(output_capture$warnings) > 0) paste0("Warning: ", output_capture$warnings),
|
||||
if (length(output_capture$messages) > 0) output_capture$messages
|
||||
)
|
||||
|
||||
if (is.list(final_result) && !is.null(final_result$report_blocks)) {
|
||||
return(list(
|
||||
status = "success",
|
||||
result = final_result,
|
||||
console_output = output_capture$output,
|
||||
console_output = console_lines,
|
||||
duration_ms = elapsed_ms
|
||||
))
|
||||
}
|
||||
|
||||
# 否则包装为通用结果
|
||||
return(list(
|
||||
status = "success",
|
||||
result = list(
|
||||
data = final_result,
|
||||
report_blocks = list()
|
||||
),
|
||||
console_output = output_capture$output,
|
||||
console_output = console_lines,
|
||||
duration_ms = elapsed_ms
|
||||
))
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user