From 46a1ca6853a608f1bf3845215063b41b8b3a8c5b Mon Sep 17 00:00:00 2001 From: oudecheng <13802883547@139.com> Date: Wed, 8 Jul 2026 14:51:42 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BF=AE=E5=A4=8D=203=20=E9=A1=B9?= =?UTF-8?q?=20P1=20=E6=8A=80=E6=9C=AF=E5=80=BA=EF=BC=88=E7=A1=AC=E7=BC=96?= =?UTF-8?q?=E7=A0=81=20URL=20/=20any=20=E7=B1=BB=E5=9E=8B=20/=20dead=5Fcod?= =?UTF-8?q?e=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P1-1: 硬编码 wechat URL 抽常量统一两处 - config/mod.rs: 定义 pub const WECHAT_DEFAULT_BASE_URL - cli/init.rs: import 并引用常量,消除字面量重复 - 消除两处不一致风险(default_wechat_base_url 与 init 向导) P1-2: ConfigPage any 类型定义具体类型(8 处) - types.ts: 新增 FeishuChannelConfig/WechatChannelConfig/ChannelConfig/ SchedulerJobConfig 类型;jobs?: any[] → SchedulerJobConfig[]; channels: Record → Record - api/config.ts: 新增 RestartResponse 类型,data: any → RestartResponse - ConfigPage.tsx: catch (e: any) ×2 → catch (e: unknown) + 类型守卫; as any → as SchedulerConfig['misfire_policy']; Record → Partial;(ch: any) → (ch: ChannelConfig) P1-3: dead_code 18 处逐一审查清理 - 删除 10 处未使用函数/struct(YAGNI 原则): context_compressor.rs (into_messages, is_tool_round) agent_loop.rs (EmptySkillProvider struct + impl) memory_maintenance.rs (run_for_scope) session.rs (try_start/finish_background_compaction) session_history.rs (try_start/finish_background_compaction) tool_registry_factory.rs (shell_session_manager) task/runtime.rs (effective_allowed_tools) - 保留 8 处 serde 反序列化字段(删除会破坏 JSON 反序列化): feishu.rs/openai.rs/anthropic.rs/skills/mod.rs/task/runtime.rs --- src/agent/agent_loop.rs | 10 ----- src/agent/context_compressor.rs | 21 ---------- src/cli/init.rs | 5 ++- src/config/mod.rs | 28 ++++++------- src/gateway/memory_maintenance.rs | 45 -------------------- src/gateway/session.rs | 10 ----- src/gateway/session_history.rs | 10 ----- src/gateway/tool_registry_factory.rs | 6 --- src/tools/task/runtime.rs | 9 ---- web/src/api/config.ts | 9 +++- web/src/components/Settings/ConfigPage.tsx | 19 +++++---- web/src/components/Settings/types.ts | 49 +++++++++++++++++++++- 12 files changed, 81 insertions(+), 140 deletions(-) diff --git a/src/agent/agent_loop.rs b/src/agent/agent_loop.rs index 90668df..d87e934 100644 --- a/src/agent/agent_loop.rs +++ b/src/agent/agent_loop.rs @@ -738,16 +738,6 @@ pub trait SkillProvider: Send + Sync + 'static { } } -#[derive(Default)] -#[allow(dead_code)] -struct EmptySkillProvider; - -impl SkillProvider for EmptySkillProvider { - fn system_index_prompt(&self) -> Option { - None - } -} - impl AgentLoop { pub fn new(config: impl Into) -> Result { let runtime_config = config.into(); diff --git a/src/agent/context_compressor.rs b/src/agent/context_compressor.rs index a0f65a3..35ee56e 100644 --- a/src/agent/context_compressor.rs +++ b/src/agent/context_compressor.rs @@ -60,27 +60,6 @@ impl HistoryUnit { } } } - - /// Expand this unit back into a flat list of ChatMessages. - #[allow(dead_code)] - fn into_messages(self) -> Vec { - match self { - HistoryUnit::SystemGuard(msg) - | HistoryUnit::UserMessage(msg) - | HistoryUnit::AssistantText(msg) => vec![msg], - HistoryUnit::ToolRound { assistant, results } => { - let mut msgs = vec![assistant]; - msgs.extend(results); - msgs - } - } - } - - /// Returns true if this unit is a ToolRound. - #[allow(dead_code)] - fn is_tool_round(&self) -> bool { - matches!(self, HistoryUnit::ToolRound { .. }) - } } // ============================================================================ diff --git a/src/cli/init.rs b/src/cli/init.rs index 9c51659..9558f92 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -5,7 +5,8 @@ use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use crate::config::{ AgentConfig, ChannelConfig, Config, FeishuChannelConfig, GatewayConfig, ModelConfig, - ProviderConfig, SchedulerConfig, TaggedChannelConfig, WechatChannelConfig, + ProviderConfig, SchedulerConfig, TaggedChannelConfig, WECHAT_DEFAULT_BASE_URL, + WechatChannelConfig, }; /// Interactive configuration wizard for PicoBot @@ -742,7 +743,7 @@ impl InitWizard { // Use default values directly let channel_name = "wechat"; - let base_url = "https://ilinkai.weixin.qq.com"; + let base_url = WECHAT_DEFAULT_BASE_URL; let cred_path = Self::default_wechat_cred_path(); let force_login = false; diff --git a/src/config/mod.rs b/src/config/mod.rs index 77b655d..e50e58c 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -420,8 +420,11 @@ fn default_media_dir() -> String { .to_string() } +/// WeChat 渠道默认 base URL +pub const WECHAT_DEFAULT_BASE_URL: &str = "https://ilinkai.weixin.qq.com"; + fn default_wechat_base_url() -> String { - "https://ilinkai.weixin.qq.com".to_string() + WECHAT_DEFAULT_BASE_URL.to_string() } fn default_wechat_cred_path() -> String { @@ -915,19 +918,16 @@ impl Config { tracing::info!(path = %path.display(), "Config loaded"); fs::read_to_string(path)? } else { - // Fallback to current directory - let fallback = Path::new("config.json"); - if fallback.exists() { - tracing::info!(path = %fallback.display(), "Config loaded from fallback path"); - fs::read_to_string(fallback)? - } else { - // Auto-create a minimal config on first startup - tracing::info!( - path = %path.display(), - "Config not found, auto-creating minimal config" - ); - Self::create_default_config(path)? - } + // 主目录配置不存在时直接自动创建,不再 fallback 到 cwd 下的 config.json。 + // 之前的 fallback 逻辑会在 ~/.picobot/config.json 因任何原因未被找到时 + // 静默加载 cwd 下的 config.json,可能导致 experts/skills 等字段缺失 + // 而退化为默认值,造成用户配置丢失的困惑。 + // 开发者若需用项目目录配置,可通过 CONFIG_PATH 环境变量显式指定。 + tracing::info!( + path = %path.display(), + "Config not found, auto-creating minimal config" + ); + Self::create_default_config(path)? }; let content = resolve_env_placeholders(&content); let config: Config = serde_json::from_str(&content)?; diff --git a/src/gateway/memory_maintenance.rs b/src/gateway/memory_maintenance.rs index 1a9cdd9..2a275c1 100644 --- a/src/gateway/memory_maintenance.rs +++ b/src/gateway/memory_maintenance.rs @@ -270,51 +270,6 @@ impl MemoryMaintenanceService { ))) } - #[allow(dead_code)] - pub(crate) async fn run_for_scope( - &self, - scope_key: &str, - ) -> Result, AgentError> { - let Some(plan) = self.build_plan_for_scope(scope_key)? else { - return Ok(None); - }; - - // 步骤1:整理记忆(不生成摘要) - let organize_output = self.organize_plan(scope_key, &plan).await?; - - // 应用整理结果(merge和delete) - apply_memory_maintenance_output( - self.store.as_ref(), - scope_key, - &plan, - &organize_output, - self.maintenance_config.max_merge_ratio, - self.maintenance_config.min_memories_to_keep, - self.maintenance_config.max_merge_per_group, - )?; - - // 步骤2:从数据库重新读取剩余的记忆 - let remaining_memories = self - .store - .list_memories_for_scope("user", scope_key) - .map_err(|err| { - AgentError::Other(format!("list remaining memories error: {}", err)) - })?; - - // 步骤2:生成摘要 - let managed_markdown = if remaining_memories.is_empty() { - String::new() - } else { - self.generate_summary(scope_key, &remaining_memories).await? - }; - - Ok(Some(MemoryMaintenanceScopeResult { - scope_key: scope_key.to_string(), - output: organize_output, - managed_markdown, - })) - } - async fn generate_summary( &self, scope_key: &str, diff --git a/src/gateway/session.rs b/src/gateway/session.rs index d685a3b..73aad11 100644 --- a/src/gateway/session.rs +++ b/src/gateway/session.rs @@ -533,16 +533,6 @@ impl Session { &self.compressor } - #[allow(dead_code)] - pub(crate) fn try_start_background_compaction(&mut self, chat_id: &str) -> bool { - self.history.try_start_background_compaction(chat_id) - } - - #[allow(dead_code)] - pub(crate) fn finish_background_compaction(&mut self, chat_id: &str) { - self.history.finish_background_compaction(chat_id); - } - pub(crate) fn reload_chat_history(&mut self, chat_id: &str) -> Result<(), AgentError> { // 如果当前有 topic,加载该 topic 的消息(按 session_id 过滤,排除子智能体消息) if let Some(topic_id) = self.history.chat_topic(chat_id) { diff --git a/src/gateway/session_history.rs b/src/gateway/session_history.rs index 4c5b1ef..c1824c8 100644 --- a/src/gateway/session_history.rs +++ b/src/gateway/session_history.rs @@ -259,16 +259,6 @@ impl SessionHistory { Ok(()) } - #[allow(dead_code)] - pub(crate) fn try_start_background_compaction(&mut self, chat_id: &str) -> bool { - self.compression_in_flight.insert(chat_id.to_string()) - } - - #[allow(dead_code)] - pub(crate) fn finish_background_compaction(&mut self, chat_id: &str) { - self.compression_in_flight.remove(chat_id); - } - pub(crate) fn reload_chat_history(&mut self, chat_id: &str) -> Result<(), AgentError> { let history = self .conversations diff --git a/src/gateway/tool_registry_factory.rs b/src/gateway/tool_registry_factory.rs index 9554f64..772cc99 100644 --- a/src/gateway/tool_registry_factory.rs +++ b/src/gateway/tool_registry_factory.rs @@ -92,12 +92,6 @@ impl ToolRegistryFactory { !self.disabled_tools.contains(tool_name) } - /// Get a reference to the shell session manager for lifecycle control. - #[allow(dead_code)] - pub(crate) fn shell_session_manager(&self) -> Arc { - self.shell_session_manager.clone() - } - pub(crate) fn build(&self) -> ToolRegistry { let registry = ToolRegistry::new(); diff --git a/src/tools/task/runtime.rs b/src/tools/task/runtime.rs index 8580937..de74b2c 100644 --- a/src/tools/task/runtime.rs +++ b/src/tools/task/runtime.rs @@ -388,15 +388,6 @@ impl DefaultSubAgentRuntime { .ok_or_else(|| format!("subagent type '{}' is disabled or not found", type_name)) } - /// 获取实际使用的工具白名单(预留,未来可用于动态工具过滤) - #[allow(dead_code)] - fn effective_allowed_tools(&self, def: &SubagentDef) -> HashSet { - def.allowed_tools - .as_ref() - .map(|tools| tools.iter().cloned().collect()) - .unwrap_or_else(|| self.config.default_allowed_tools.clone()) - } - /// 获取实际执行时间 fn effective_max_execution_secs(&self, def: &SubagentDef) -> u64 { def.max_execution_secs diff --git a/web/src/api/config.ts b/web/src/api/config.ts index a7286b2..1af6208 100644 --- a/web/src/api/config.ts +++ b/web/src/api/config.ts @@ -1,6 +1,11 @@ import { API, apiFetch } from './client' import type { AppConfig } from '../components/Settings/types' +export interface RestartResponse { + success: boolean + message?: string +} + export async function getAppConfig(): Promise<[AppConfig | null, string | null]> { const [data, err] = await apiFetch(API.config) return [data, err?.message ?? null] @@ -11,9 +16,9 @@ export async function updateAppConfig(config: AppConfig): Promise<[true, null] | return err ? [false, err.message] : [true, null] } -export async function restartGateway(): Promise<{ status: number; data: any }> { +export async function restartGateway(): Promise<{ status: number; data: RestartResponse }> { const resp = await fetch(API.restart, { method: 'POST' }) - const data = await resp.json().catch(() => ({})) + const data = await resp.json().catch(() => ({ success: false })) return { status: resp.status, data } } diff --git a/web/src/components/Settings/ConfigPage.tsx b/web/src/components/Settings/ConfigPage.tsx index fcd53a2..cdc21ae 100644 --- a/web/src/components/Settings/ConfigPage.tsx +++ b/web/src/components/Settings/ConfigPage.tsx @@ -13,6 +13,7 @@ import type { SubagentListResponse, ExpertItem, ExpertListResponse, KnownSource, + SchedulerConfig, ChannelConfig, } from './types' import { TABS, inputCls, selectCls, TIMEZONE_OPTIONS } from './constants' import { Field, Toggle, TagEditor, SectionCard, SourceEditor, MapEntryHeader } from './ui' @@ -204,8 +205,8 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage setTimeout(() => setToast(''), 5000) } poll() - } catch (e: any) { - setError(e.message || '重启失败') + } catch (e: unknown) { + setError(e instanceof Error ? e.message : '重启失败') setRestarting(false) } } @@ -389,7 +390,7 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage
启用调度器 update('scheduler', { ...config.scheduler, enabled: v })} />
update('scheduler', { ...config.scheduler, tick_resolution_ms: +e.target.value })} className={inputCls} /> update('scheduler', { ...config.scheduler, worker_queue_capacity: +e.target.value })} className={inputCls} /> - + ) @@ -823,8 +824,8 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage setTimeout(() => setToast(''), 3000) setEditingExpert(null) fetchExpertList() - } catch (e: any) { - setEditingExpertError(e.message || '网络错误') + } catch (e: unknown) { + setEditingExpertError(e instanceof Error ? e.message : '网络错误') } finally { setSavingExpert(false) } @@ -1004,11 +1005,11 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage } } const delChannel = (name: string) => { if (confirm(`删除渠道 "${name}"?`)) { const { [name]: _, ...rest } = config.channels; update('channels', rest) } } - const updChannel = (name: string, patch: Record) => update('channels', { ...config.channels, [name]: { ...config.channels[name], ...patch } }) - const getChannelType = (ch: any): string => { + const updChannel = (name: string, patch: Partial) => update('channels', { ...config.channels, [name]: { ...config.channels[name], ...patch } }) + const getChannelType = (ch: ChannelConfig): string => { if (ch.type) return ch.type - if (ch.app_id !== undefined || ch.app_secret !== undefined) return 'feishu' - if (ch.cred_path !== undefined) return 'wechat' + if ('app_id' in ch || 'app_secret' in ch) return 'feishu' + if ('cred_path' in ch) return 'wechat' return 'feishu' } return ( diff --git a/web/src/components/Settings/types.ts b/web/src/components/Settings/types.ts index 737de38..50f58b0 100644 --- a/web/src/components/Settings/types.ts +++ b/web/src/components/Settings/types.ts @@ -5,7 +5,7 @@ export interface ModelConfig { model_id: string; temperature?: number; max_token export interface AgentConfig { provider: string; model: string; max_tool_iterations: number; tool_result_max_chars: number; context_tool_result_trim_chars: number } export interface GatewayConfig { host: string; port: number; show_tool_results: boolean; agent_prompt_reinject_every: number; max_concurrent_requests: number; session_ttl_hours?: number } export interface TimeConfig { timezone: string } -export interface SchedulerConfig { enabled: boolean; tick_resolution_ms: number; worker_queue_capacity: number; misfire_policy: 'skip' | 'catch_up'; jobs?: any[] } +export interface SchedulerConfig { enabled: boolean; tick_resolution_ms: number; worker_queue_capacity: number; misfire_policy: 'skip' | 'catch_up'; jobs?: SchedulerJobConfig[] } export interface SkillsConfig { enabled: boolean; sources: string[]; max_index_chars: number; max_listed_skills: number } export interface TaskConfig { enabled: boolean; max_execution_secs: number; explore_max_execution_secs: number; ttl_hours: number; allowed_tools: string[] } export interface ToolsConfig { disabled: string[]; task: TaskConfig } @@ -87,6 +87,51 @@ export interface McpStatusResponse { servers: McpServerStatus[] } +export interface FeishuChannelConfig { + enabled: boolean + app_id: string + app_secret: string + allow_from?: string[] + agent?: string + media_dir?: string + reaction_emoji?: string + max_message_chars?: number + reply_context_max_chars?: number +} + +export interface WechatChannelConfig { + enabled: boolean + base_url: string + cred_path: string + force_login?: boolean + allow_from?: string[] + agent?: string +} + +export interface ChannelConfig { + type?: string + enabled?: boolean + app_id?: string + app_secret?: string + agent?: string + base_url?: string + cred_path?: string + force_login?: boolean + allow_from?: string[] + media_dir?: string + reaction_emoji?: string + max_message_chars?: number + reply_context_max_chars?: number + [key: string]: unknown +} + +export interface SchedulerJobConfig { + id: string + enabled: boolean + kind: string + [key: string]: unknown +} + export interface AppConfig { providers: Record models: Record @@ -101,7 +146,7 @@ export interface AppConfig { subagents: SubagentsConfig experts: ExpertsConfig client: ClientConfig - channels: Record + channels: Record mcpServers: Record }