Update: 将子项目从 submodule 转为完整内容

- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
This commit is contained in:
freedak
2026-07-04 19:20:46 +08:00
parent 54d6465fa7
commit f7a720204a
3360 changed files with 802660 additions and 3 deletions
@@ -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);
}
}