use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use crate::cm_internal::memory::long_term_memory::LongTermMemoryRuntime;
use crate::cm_internal::readonly_tool_ttl_cache::ReadonlyToolTtlCache;
use crate::cm_internal::tool_registry::HandlerLookupTable;
use crate::cm_internal::tool_sandbox::SyncDefaultSandboxBackend;
use crate::cm_internal::tool_stats::ToolOutcomeRecorder;
use crate::cm_internal::workspace::changelist::WorkspaceChangelistRegistry;
use crate::cm_internal::workspace::tasks_side::{TasksData, WorkspaceTasksByPath};
#[derive(Clone)]
pub struct TurnProcessHandles {
pub workspace_changelist_registry: Arc<WorkspaceChangelistRegistry>,
pub tool_outcome_recorder: Arc<ToolOutcomeRecorder>,
pub handler_lookup: HandlerLookupTable,
pub sync_default_sandbox_backend: Arc<dyn SyncDefaultSandboxBackend>,
pub readonly_tool_ttl_cache: Arc<ReadonlyToolTtlCache>,
}
impl TurnProcessHandles {
pub fn default_arc() -> Arc<Self> {
ProcessHandles::default_arc_process_handles().turn_handles_arc()
}
}
pub struct ProcessHandles {
pub workspace_changelist_registry: Arc<WorkspaceChangelistRegistry>,
pub tool_outcome_recorder: Arc<ToolOutcomeRecorder>,
pub handler_lookup: HandlerLookupTable,
pub sync_default_sandbox_backend: Arc<dyn SyncDefaultSandboxBackend>,
pub readonly_tool_ttl_cache: Arc<ReadonlyToolTtlCache>,
pub workspace_tasks_by_path: WorkspaceTasksByPath,
cli_long_term_memory: Mutex<Option<(PathBuf, Arc<LongTermMemoryRuntime>)>>,
}
impl ProcessHandles {
pub fn new(
workspace_changelist_registry: Arc<WorkspaceChangelistRegistry>,
tool_outcome_recorder: Arc<ToolOutcomeRecorder>,
handler_lookup: HandlerLookupTable,
sync_default_sandbox_backend: Arc<dyn SyncDefaultSandboxBackend>,
) -> Self {
Self {
workspace_changelist_registry,
tool_outcome_recorder,
handler_lookup,
sync_default_sandbox_backend,
readonly_tool_ttl_cache: Arc::new(ReadonlyToolTtlCache::new()),
workspace_tasks_by_path: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
cli_long_term_memory: Mutex::new(None),
}
}
pub fn new_arc(
workspace_changelist_registry: Arc<WorkspaceChangelistRegistry>,
tool_outcome_recorder: Arc<ToolOutcomeRecorder>,
handler_lookup: HandlerLookupTable,
sync_default_sandbox_backend: Arc<dyn SyncDefaultSandboxBackend>,
) -> Arc<Self> {
Arc::new(Self::new(
workspace_changelist_registry,
tool_outcome_recorder,
handler_lookup,
sync_default_sandbox_backend,
))
}
pub fn turn_handles(&self) -> TurnProcessHandles {
TurnProcessHandles {
workspace_changelist_registry: Arc::clone(&self.workspace_changelist_registry),
tool_outcome_recorder: Arc::clone(&self.tool_outcome_recorder),
handler_lookup: self.handler_lookup.clone(),
sync_default_sandbox_backend: Arc::clone(&self.sync_default_sandbox_backend),
readonly_tool_ttl_cache: Arc::clone(&self.readonly_tool_ttl_cache),
}
}
pub fn turn_handles_arc(&self) -> Arc<TurnProcessHandles> {
Arc::new(self.turn_handles())
}
pub fn default_arc_process_handles() -> Arc<Self> {
ProcessHandles::new_arc(
Arc::new(WorkspaceChangelistRegistry::default()),
Arc::new(ToolOutcomeRecorder::new()),
HandlerLookupTable::default_dispatch(),
crate::cm_internal::tool_sandbox::default_sync_default_sandbox_backend(),
)
}
pub fn cli_long_term_memory_handles_with_stderr_notice(
self: &Arc<Self>,
cfg: &crate::cm_config::AgentConfig,
failure_notified: &std::sync::atomic::AtomicBool,
) -> (Option<Arc<LongTermMemoryRuntime>>, Option<String>) {
Self::cli_long_term_memory_handles_inner(self, cfg, Some(failure_notified))
}
fn cli_long_term_memory_handles_inner(
self: &Arc<Self>,
cfg: &crate::cm_config::AgentConfig,
failure_notified: Option<&std::sync::atomic::AtomicBool>,
) -> (Option<Arc<LongTermMemoryRuntime>>, Option<String>) {
if !cfg.long_term_memory.long_term_memory_enabled {
return (None, None);
}
let path = {
let p = cfg
.long_term_memory
.long_term_memory_store_sqlite_path
.trim();
if p.is_empty() {
std::path::Path::new(&cfg.command_exec.run_command_working_dir)
.join(".crabmate")
.join("long_term_memory.db")
} else {
std::path::PathBuf::from(p)
}
};
let mut guard = self
.cli_long_term_memory
.lock()
.unwrap_or_else(|e| e.into_inner());
if let Some((stored, rt)) = guard.as_ref()
&& stored == &path
{
return (Some(Arc::clone(rt)), Some("cli".to_string()));
}
match LongTermMemoryRuntime::open(&path) {
Ok(r) => {
let a = Arc::clone(&r);
*guard = Some((path, r));
(Some(a), Some("cli".to_string()))
}
Err(e) => {
log::warn!(
target: "crabmate",
"CLI 长期记忆库打开失败 path={} error={}",
path.display(),
e
);
if let Some(flag) = failure_notified
&& !flag.swap(true, std::sync::atomic::Ordering::SeqCst)
{
let detail = e.to_string();
let max = 240usize;
let (head, tail) = if detail.chars().count() > max {
let head: String = detail.chars().take(max).collect();
(head, "…")
} else {
(detail, "")
};
eprintln!(
"crabmate: 警告:配置中已启用长期记忆 (long_term_memory_enabled),但本进程无法打开 SQLite;长期记忆在本进程中已禁用。\n\
路径: {}\n\
错误: {}{}\n\
请检查目录权限、磁盘空间或向量后端依赖(如 fastembed / ONNX);若暂不需要可设 long_term_memory_enabled = false。详情见日志 (target=crabmate)。",
path.display(),
head,
tail
);
}
(None, None)
}
}
}
pub async fn tasks_data_for_workspace_path(self: &Arc<Self>, workspace_key: &str) -> TasksData {
let g = self.workspace_tasks_by_path.read().await;
g.get(workspace_key).cloned().unwrap_or_default()
}
pub fn workspace_changelog_markdown_for_scope(
self: &Arc<Self>,
cfg: &crate::cm_config::AgentConfig,
scope: &str,
) -> Result<String, &'static str> {
if !cfg
.session_workspace_changelist
.session_workspace_changelist_enabled
{
return Err("会话工作区变更集已在配置中关闭(session_workspace_changelist_enabled)");
}
let max_chars = cfg
.session_workspace_changelist
.session_workspace_changelist_max_chars;
let cl = self
.workspace_changelist_registry
.changelist_for_scope(scope);
let (_rev, body) = cl.snapshot_markdown(max_chars);
Ok(body.unwrap_or_default())
}
}