策划panic时把代码位置和负载写入design_debug
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
运行段经tokio task-local携带项目根,多线程runtime跨worker迁移也能归因 首次运行安装panic hook(take_hook包装原hook,update_hook在1.96仍unstable) hook仅在策划运行段内生效,其它任务直接透传原hook design_panic_error只返回固定公开文案,debug记录由hook统一写入 回归测试断言panic记录落盘且包含design_runtime.rs位置与注入payload
This commit is contained in:
@@ -15,6 +15,43 @@ const DESIGN_ACTIVE_LOCK: &str = ".agent/design-agent/active.lock";
|
||||
const DESIGN_PANIC_PUBLIC_ERROR: &str =
|
||||
"策划运行发生内部错误,本轮已中断,可直接重试;若反复出现请反馈。";
|
||||
|
||||
// 运行段经 task-local 携带项目根,panic hook 据此把位置和负载写进私有 design_debug。
|
||||
// task-local 而非 thread-local:多线程 runtime 下 future 会跨 worker 迁移。
|
||||
tokio::task_local! {
|
||||
static DESIGN_PANIC_ROOT: Option<PathBuf>;
|
||||
}
|
||||
|
||||
fn ensure_design_panic_hook() {
|
||||
static ONCE: std::sync::Once = std::sync::Once::new();
|
||||
ONCE.call_once(|| {
|
||||
let previous = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
if let Ok(Some(root)) = DESIGN_PANIC_ROOT.try_with(Clone::clone) {
|
||||
let location = info
|
||||
.location()
|
||||
.map(|location| {
|
||||
format!(
|
||||
"{}:{}:{}",
|
||||
location.file(),
|
||||
location.line(),
|
||||
location.column()
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| "未知位置".to_string());
|
||||
design_debug(
|
||||
&root,
|
||||
"panic",
|
||||
json!({
|
||||
"location": location,
|
||||
"error": info.payload_as_str().unwrap_or("未知 panic 负载"),
|
||||
}),
|
||||
);
|
||||
}
|
||||
previous(info);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(
|
||||
tag = "type",
|
||||
@@ -982,13 +1019,15 @@ async fn finish_design_command(
|
||||
// panic 边界:运行期 panic 转成普通失败,交给既有错误分支恢复(重读检查点、
|
||||
// 写 last_error、发最终 view)。否则 unwind 会杀死 command task,IPC 永不
|
||||
// 返回(前端停在工作态),会话停在无错误的 pending,用户只能看到无声的重试。
|
||||
let outcome =
|
||||
std::panic::AssertUnwindSafe(run_design_loop(root, resources, &mut session, &mut emit))
|
||||
.catch_unwind()
|
||||
.await;
|
||||
ensure_design_panic_hook();
|
||||
let run = DESIGN_PANIC_ROOT.scope(
|
||||
Some(root.to_path_buf()),
|
||||
run_design_loop(root, resources, &mut session, &mut emit),
|
||||
);
|
||||
let outcome = std::panic::AssertUnwindSafe(run).catch_unwind().await;
|
||||
let result = match outcome {
|
||||
Ok(result) => result,
|
||||
Err(payload) => Err(design_panic_error(root, payload)),
|
||||
Err(payload) => Err(design_panic_error(payload)),
|
||||
};
|
||||
if let Err(error) = result {
|
||||
// 从最后一个持久检查点恢复,防止写后未记结果被误认为已完成。
|
||||
@@ -1010,14 +1049,9 @@ async fn finish_design_command(
|
||||
Ok(view)
|
||||
}
|
||||
|
||||
// panic 负载可能包含路径或内容片段,公开文案固定;细节只进私有 design_debug。
|
||||
fn design_panic_error(root: &Path, payload: Box<dyn std::any::Any + Send>) -> String {
|
||||
let detail = payload
|
||||
.downcast_ref::<&str>()
|
||||
.map(|value| (*value).to_string())
|
||||
.or_else(|| payload.downcast_ref::<String>().cloned())
|
||||
.unwrap_or_else(|| "未知 panic 负载".to_string());
|
||||
design_debug(root, "panic", json!({"error": detail}));
|
||||
// panic 负载可能包含路径或内容片段,公开文案固定;位置和负载由 panic hook 写进私有
|
||||
// design_debug(task-local 提供项目根),不进入用户可见消息。
|
||||
fn design_panic_error(_payload: Box<dyn std::any::Any + Send>) -> String {
|
||||
DESIGN_PANIC_PUBLIC_ERROR.to_string()
|
||||
}
|
||||
|
||||
@@ -1665,6 +1699,7 @@ mod tests {
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn design_tool_panic_becomes_visible_retryable_error() {
|
||||
let (_temp, root, resources) = init_design_project();
|
||||
std::env::set_var("GENARRATIVE_AGC_DESIGN_DEBUG", "1");
|
||||
let panic_call = platform_llm::LlmToolCall {
|
||||
id: "call-panic".into(),
|
||||
name: "design_test__panic".into(),
|
||||
@@ -1705,6 +1740,30 @@ mod tests {
|
||||
"panic 不得写半个工具输出"
|
||||
);
|
||||
|
||||
// design_debug 经独立线程落盘,轮询等待 panic 记录出现。
|
||||
let debug_dir = root.join(".debug/design-agent");
|
||||
let mut panic_record = None;
|
||||
for _ in 0..100 {
|
||||
panic_record = fs::read_dir(&debug_dir).ok().and_then(|entries| {
|
||||
entries.flatten().find_map(|entry| {
|
||||
let path = entry.path();
|
||||
let name = path.file_name()?.to_string_lossy().into_owned();
|
||||
if name.ends_with("-panic.json") {
|
||||
fs::read_to_string(path).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
});
|
||||
if panic_record.is_some() {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||
}
|
||||
let panic_record = panic_record.expect("panic hook 必须把位置和负载写入 design_debug");
|
||||
assert!(panic_record.contains("design_runtime.rs"));
|
||||
assert!(panic_record.contains("注入的策划工具 panic"));
|
||||
|
||||
let view =
|
||||
continue_design_agent_at(&root, &resources, "turn-retry", DesignInput::Retry, |_| {})
|
||||
.await
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
`finish_design_command` 的运行段包在 `catch_unwind` 边界内:任何运行期 panic 被转成一次普通失败,由既有错误分支从最后一个持久检查点恢复,向会话写入固定公开文案"策划运行发生内部错误,本轮已中断,可直接重试;若反复出现请反馈。",并以 `running=false、可重试、lastError 有值` 的视图正常返回。panic 负载只写入私有 design_debug,不进入用户可见消息;continue、recover_uncertain 与 decide 三个入口共享同一边界。边界不改变工具错误、Provider 瞬态重试和批次不确定恢复的既有语义。
|
||||
|
||||
运行段通过 task-local 携带项目根;首次运行时安装的 panic hook 在 panic 瞬间把代码位置(file:line:column)和负载追加写入私有 design_debug,随后交给原 hook 维持既有 stderr 输出。hook 只在策划运行段内生效(其它任务无 task-local 上下文时直接透传),多线程 runtime 下 future 跨 worker 迁移也能正确归因。
|
||||
|
||||
## 资源画布交互与工作台状态同步
|
||||
|
||||
- 工作台向窗口标题栏发布正在运行的项目时,输入未变化不得形成重复发布与清理的渲染循环;打开项目动作始终使用当前工作台处理逻辑,退出工作台后清除其标题栏状态。
|
||||
|
||||
Reference in New Issue
Block a user