未绑定执行器与回包失败的留痕加有界去重
- 未绑定宿主执行器的交互兜底按进程级 (method, outcome) 只写第一行,新增 distinct_outcomes - 适配器拒绝留痕抽出共用的 log_once,回包未送达同样按回合内原因只记一次 - 上限沿用 MAX_DENIED_REASON_LOGS = 64,模型重试不再逐次刷日志 - 同步更新 shared-memory 决策记录里的去重口径
This commit is contained in:
@@ -9,7 +9,7 @@ use sha2::{Digest, Sha256};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use std::sync::{Arc, Mutex, OnceLock, Weak};
|
||||
use std::time::Duration;
|
||||
use tokio::sync::{watch, Notify};
|
||||
|
||||
@@ -19,6 +19,11 @@ const MAX_REQUEST_CACHE: usize = 512;
|
||||
///
|
||||
/// 原因基本是固定的静态分类,上限只是防止宿主错误文本意外发散时无界增长。
|
||||
const MAX_DENIED_REASON_LOGS: usize = 64;
|
||||
/// 未绑定执行器路径的进程级去重集合。
|
||||
///
|
||||
/// 这条路径没有适配器实例可挂去重状态(那正是它存在的前提),只能用进程级集合;上限与
|
||||
/// [`MAX_DENIED_REASON_LOGS`] 同口径。
|
||||
static NO_ADAPTER_INTERACTION_LOGGED: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
|
||||
|
||||
/// 逐次审批协议的版本门禁:发行构建只接受捆绑侧车的固定版本;开发构建用宿主自带的 Codex
|
||||
/// (Linux 与未 stage 侧车时没有固定版本可用),按 profile 直接跳过该门禁。
|
||||
@@ -54,12 +59,29 @@ pub(super) fn denied_response(id: u64, method: &str) -> Value {
|
||||
"item/permissions/requestApproval" => "empty-permissions",
|
||||
_ => "unsupported-method",
|
||||
};
|
||||
app_log!(
|
||||
"agent.direct_codex.interaction.no_adapter method={method} outcome={outcome} request_id={id}"
|
||||
);
|
||||
log_no_adapter_interaction_once(method, outcome, id);
|
||||
denied(id, method)
|
||||
}
|
||||
|
||||
/// 未绑定执行器时的交互兜底留痕:进程级有界去重。
|
||||
///
|
||||
/// 模型被拒后常会反复重试同一个动作,逐次记录会把其它诊断刷掉;同一 `(method, outcome)` 只写
|
||||
/// 第一行,`distinct_outcomes` 保留"一共出现过几种"的信息。
|
||||
fn log_no_adapter_interaction_once(method: &str, outcome: &str, request_id: u64) {
|
||||
let logged = NO_ADAPTER_INTERACTION_LOGGED.get_or_init(|| Mutex::new(HashSet::new()));
|
||||
let Ok(mut logged) = logged.lock() else {
|
||||
return;
|
||||
};
|
||||
if logged.len() >= MAX_DENIED_REASON_LOGS || !logged.insert(format!("{method}\u{1}{outcome}")) {
|
||||
return;
|
||||
}
|
||||
let distinct = logged.len();
|
||||
drop(logged);
|
||||
app_log!(
|
||||
"agent.direct_codex.interaction.no_adapter method={method} outcome={outcome} request_id={request_id} distinct_outcomes={distinct}"
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) struct ExecutionBinding {
|
||||
inner: Weak<CodexAppServerInner>,
|
||||
pub(super) adapter: Arc<ExecutionAdapter>,
|
||||
@@ -348,7 +370,18 @@ impl ExecutionAdapter {
|
||||
/// 有用的诊断刷掉;"这一轮被拒过哪些原因"仍然完整。
|
||||
fn log_denied_reason(&self, method: &str, reason: &str) {
|
||||
let key = format!("{method}\u{1}{reason}");
|
||||
// 拒绝原因留痕不得反过来影响放行判定:锁不可用就放弃记录。
|
||||
let thread_id = &self.thread_id;
|
||||
self.log_once(key, |distinct| {
|
||||
format!(
|
||||
"agent.direct_codex.approval.denied thread_id={thread_id} method={method} reason={reason} distinct_reasons={distinct}"
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
/// 有界去重留痕:同一回合内同 `key` 只写第一行。
|
||||
///
|
||||
/// 共用一把独立于审批状态的锁;锁不可用就放弃记录,留痕不得反过来影响放行判定。
|
||||
fn log_once(&self, key: String, build_line: impl FnOnce(usize) -> String) {
|
||||
let Ok(mut logged) = self.denied_reasons_logged.lock() else {
|
||||
return;
|
||||
};
|
||||
@@ -357,10 +390,7 @@ impl ExecutionAdapter {
|
||||
}
|
||||
let distinct = logged.len();
|
||||
drop(logged);
|
||||
app_log!(
|
||||
"agent.direct_codex.approval.denied thread_id={} method={method} reason={reason} distinct_reasons={distinct}",
|
||||
self.thread_id
|
||||
);
|
||||
app_log!("{}", build_line(distinct));
|
||||
}
|
||||
|
||||
fn denied(&self, id: u64, method: &str, reason: &str) -> Value {
|
||||
@@ -761,9 +791,15 @@ impl ExecutionAdapter {
|
||||
|
||||
pub(super) fn response_write_failed(self: &Arc<Self>) {
|
||||
// 回包写不出去时 Codex 侧等不到 decision,表现为「审批没有回应」;与主动 decline 分开留痕。
|
||||
app_log!(
|
||||
"agent.direct_codex.approval.response_write_failed thread_id={}",
|
||||
self.thread_id
|
||||
// 同一回合内同一原因只留一行:这一步可能被重复触发。
|
||||
self.log_once(
|
||||
"host-interaction\u{1}response-write-failed".to_string(),
|
||||
|_| {
|
||||
format!(
|
||||
"agent.direct_codex.approval.response_write_failed thread_id={}",
|
||||
self.thread_id
|
||||
)
|
||||
},
|
||||
);
|
||||
let adapter = Arc::clone(self);
|
||||
tokio::spawn(async move {
|
||||
|
||||
Reference in New Issue
Block a user