Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
use crate::manager::acp::AcpAgentManager;
|
||||
|
||||
use crate::manager::acp::mode_normalize::normalize_requested_mode;
|
||||
use crate::protocol::error::AcpError;
|
||||
use crate::shared_kernel::{ConfigKey, ConfigValue, ModeId, ModelId};
|
||||
use agent_client_protocol::schema::{
|
||||
SessionId, SetSessionConfigOptionRequest, SetSessionModeRequest, SetSessionModelRequest,
|
||||
};
|
||||
use nomifun_common::AppError;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
/// Actions the session driver must execute to align CLI state with user intent.
|
||||
///
|
||||
/// Produced by `AcpSession::plan_reconcile` — a pure function that compares
|
||||
/// desired vs observed and returns a list of idempotent, order-independent ops.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ReconcileAction {
|
||||
SetMode { mode: ModeId },
|
||||
SetModel { model: ModelId },
|
||||
SetConfigOption { key: ConfigKey, value: ConfigValue },
|
||||
}
|
||||
|
||||
impl AcpAgentManager {
|
||||
/// Execute reconcile actions produced by `AcpSession::plan_reconcile`.
|
||||
///
|
||||
/// Compares the aggregate's desired state against what the CLI has
|
||||
/// reported as current, then issues the minimal set of SDK calls
|
||||
/// (set_mode, set_model, set_config_option) to bring the CLI into
|
||||
/// alignment.
|
||||
///
|
||||
/// Failure handling:
|
||||
/// - `SessionNotFound`: returned as `AppError::NotFound` so callers
|
||||
/// (e.g. `open_session_resume`) can drop the stale sid and rebuild
|
||||
/// the session. ELECTRON-1HQ regressed because we silently swallowed
|
||||
/// this case during warmup, leaving downstream `session/prompt` to
|
||||
/// surface the same error to the user every turn.
|
||||
/// - Any other error: logged and skipped (best-effort), so a failed
|
||||
/// `set_config_option` doesn't block a successful `set_mode`.
|
||||
pub(super) async fn reconcile_session(&self, session_id: &str) -> Result<(), AppError> {
|
||||
use crate::manager::acp::ReconcileAction;
|
||||
|
||||
let (invalid_model, actions) = {
|
||||
let mut session = self.session.write().await;
|
||||
let invalid_model = session.clear_invalid_desired_model();
|
||||
let actions = session.plan_reconcile();
|
||||
(invalid_model, actions)
|
||||
};
|
||||
if let Some(model) = invalid_model {
|
||||
warn!(
|
||||
conversation_id = %self.params.conversation_id,
|
||||
model_id = %model,
|
||||
"reconcile_session: dropped unavailable desired model"
|
||||
);
|
||||
}
|
||||
for action in actions {
|
||||
match action {
|
||||
ReconcileAction::SetMode { mode } => {
|
||||
let normalized = normalize_requested_mode(&self.params.metadata, mode.as_str());
|
||||
if normalized.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Err(e) = self
|
||||
.protocol
|
||||
.set_mode(SetSessionModeRequest::new(
|
||||
SessionId::new(session_id),
|
||||
normalized.clone(),
|
||||
))
|
||||
.await
|
||||
{
|
||||
if matches!(e, AcpError::SessionNotFound { .. }) {
|
||||
warn!(
|
||||
conversation_id = %self.params.conversation_id,
|
||||
mode_id = %normalized,
|
||||
error = %e,
|
||||
"reconcile_session: set_mode hit SessionNotFound; aborting reconcile"
|
||||
);
|
||||
return Err(AppError::from(e));
|
||||
}
|
||||
error!(
|
||||
conversation_id = %self.params.conversation_id,
|
||||
mode_id = %normalized,
|
||||
error = %e,
|
||||
"reconcile_session: set_mode failed"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// SDK does not push a notification after a successful
|
||||
// set_mode — sync observed/advertised ourselves so the
|
||||
// next plan_reconcile is a no-op.
|
||||
let mut session = self.session.write().await;
|
||||
session.apply_observed_mode(ModeId::new(normalized));
|
||||
self.commit_session_changes(&mut session).await;
|
||||
}
|
||||
|
||||
ReconcileAction::SetModel { model } => {
|
||||
if let Err(e) = self
|
||||
.protocol
|
||||
.set_model(SetSessionModelRequest::new(
|
||||
SessionId::new(session_id),
|
||||
model.as_str().to_owned(),
|
||||
))
|
||||
.await
|
||||
{
|
||||
if matches!(e, AcpError::SessionNotFound { .. }) {
|
||||
warn!(
|
||||
conversation_id = %self.params.conversation_id,
|
||||
model_id = %model,
|
||||
error = %e,
|
||||
"reconcile_session: set_model hit SessionNotFound; aborting reconcile"
|
||||
);
|
||||
return Err(AppError::from(e));
|
||||
}
|
||||
error!(
|
||||
conversation_id = %self.params.conversation_id,
|
||||
model_id = %model,
|
||||
error = %e,
|
||||
"reconcile_session: set_model failed"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// SDK does not push a CurrentModelUpdate notification —
|
||||
// sync observed/advertised ourselves.
|
||||
let mut session = self.session.write().await;
|
||||
let model_for_notice = model.clone();
|
||||
session.apply_observed_model(model);
|
||||
if self.params.metadata.behavior_policy.self_identity_sticky {
|
||||
session.set_pending_model_notice(model_for_notice);
|
||||
}
|
||||
self.commit_session_changes(&mut session).await;
|
||||
}
|
||||
|
||||
ReconcileAction::SetConfigOption { key, value } => {
|
||||
if let Err(err) = self
|
||||
.protocol
|
||||
.set_config_option(SetSessionConfigOptionRequest::new(
|
||||
SessionId::new(session_id),
|
||||
key.as_str().to_owned(),
|
||||
value.as_str().to_owned(),
|
||||
))
|
||||
.await
|
||||
{
|
||||
if matches!(err, AcpError::SessionNotFound { .. }) {
|
||||
warn!(
|
||||
conversation_id = %self.params.conversation_id,
|
||||
config_id = %key,
|
||||
desired = %value,
|
||||
error = %err,
|
||||
"reconcile_session: set_config_option hit SessionNotFound; aborting reconcile"
|
||||
);
|
||||
return Err(AppError::from(err));
|
||||
}
|
||||
info!(
|
||||
conversation_id = %self.params.conversation_id,
|
||||
config_id = %key,
|
||||
desired = %value,
|
||||
error = %err,
|
||||
"reconcile_session: set_config_option failed; skipping"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// Sync observed ourselves so the next plan_reconcile
|
||||
// does not replay this action. CLI does not push a
|
||||
// config-update notification after set_config_option.
|
||||
let mut session = self.session.write().await;
|
||||
session.apply_observed_config(key, value);
|
||||
self.commit_session_changes(&mut session).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn reconcile_action_equality() {
|
||||
let a = ReconcileAction::SetMode {
|
||||
mode: ModeId::new("plan"),
|
||||
};
|
||||
let b = ReconcileAction::SetMode {
|
||||
mode: ModeId::new("plan"),
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user