refactor: 修复 3 项 P1 技术债(硬编码 URL / any 类型 / dead_code)

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<string, any> → Record<string, ChannelConfig>
  - api/config.ts: 新增 RestartResponse 类型,data: any → RestartResponse
  - ConfigPage.tsx: catch (e: any) ×2 → catch (e: unknown) + 类型守卫;
    as any → as SchedulerConfig['misfire_policy'];
    Record<string, any> → Partial<ChannelConfig>;(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
This commit is contained in:
oudecheng 2026-07-08 14:51:42 +08:00
parent 47a30e87d8
commit 46a1ca6853
12 changed files with 81 additions and 140 deletions

View File

@ -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<String> {
None
}
}
impl AgentLoop {
pub fn new(config: impl Into<AgentRuntimeConfig>) -> Result<Self, AgentError> {
let runtime_config = config.into();

View File

@ -60,27 +60,6 @@ impl HistoryUnit {
}
}
}
/// Expand this unit back into a flat list of ChatMessages.
#[allow(dead_code)]
fn into_messages(self) -> Vec<ChatMessage> {
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 { .. })
}
}
// ============================================================================

View File

@ -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;

View File

@ -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)?;

View File

@ -270,51 +270,6 @@ impl MemoryMaintenanceService {
)))
}
#[allow(dead_code)]
pub(crate) async fn run_for_scope(
&self,
scope_key: &str,
) -> Result<Option<MemoryMaintenanceScopeResult>, 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,

View File

@ -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) {

View File

@ -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

View File

@ -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<ShellSessionManager> {
self.shell_session_manager.clone()
}
pub(crate) fn build(&self) -> ToolRegistry {
let registry = ToolRegistry::new();

View File

@ -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<String> {
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

View File

@ -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<AppConfig>(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 }
}

View File

@ -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
<div className="flex items-center justify-between"><span className="text-sm text-[var(--text-secondary)]"></span><Toggle checked={config.scheduler.enabled} onChange={v => update('scheduler', { ...config.scheduler, enabled: v })} /></div>
<Field label="Tick 分辨率 (ms)"><input type="number" value={config.scheduler.tick_resolution_ms} onChange={e => update('scheduler', { ...config.scheduler, tick_resolution_ms: +e.target.value })} className={inputCls} /></Field>
<Field label="工作队列容量"><input type="number" value={config.scheduler.worker_queue_capacity} onChange={e => update('scheduler', { ...config.scheduler, worker_queue_capacity: +e.target.value })} className={inputCls} /></Field>
<Field label="Misfire 策略"><select value={config.scheduler.misfire_policy} onChange={e => update('scheduler', { ...config.scheduler, misfire_policy: e.target.value as any })} className={selectCls}><option value="skip"> (Skip)</option><option value="catch_up"> (Catch Up)</option></select></Field>
<Field label="Misfire 策略"><select value={config.scheduler.misfire_policy} onChange={e => update('scheduler', { ...config.scheduler, misfire_policy: e.target.value as SchedulerConfig['misfire_policy'] })} className={selectCls}><option value="skip"> (Skip)</option><option value="catch_up"> (Catch Up)</option></select></Field>
</SectionCard>
</div>
)
@ -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<string, any>) => update('channels', { ...config.channels, [name]: { ...config.channels[name], ...patch } })
const getChannelType = (ch: any): string => {
const updChannel = (name: string, patch: Partial<ChannelConfig>) => 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 (

View File

@ -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<string, ProviderConfig>
models: Record<string, ModelConfig>
@ -101,7 +146,7 @@ export interface AppConfig {
subagents: SubagentsConfig
experts: ExpertsConfig
client: ClientConfig
channels: Record<string, any>
channels: Record<string, ChannelConfig>
mcpServers: Record<string, McpServerConfig>
}