{"repo": "ccjjfdyqlhy/dsn-exp", "files": {"README_zh.md": {"1cfce308afc3f2333b59d64d39ffdb3b70bb210a": {"t": 1786068438.7303886, "n": 17545, "h": "83d25c88391445b2", "c": "\n# DSN-exp\n\n**开口即达。** 一个以语音为第一交互界面的 AI 伴侣,运行在你的机器上——你说话它就听,听完就做,做完就说,不等你敲键盘。\n\n[![Python 3.11+](https://img.shields.io/badge/python-3.11+-3776AB?style=for-the-badge&logo=python)](https://python.org)\n[![License](https://img.shields.io/badge/license-MIT-blue?style=for-the-badge)](LICENSE)\n[![GitHub](https://img.shields.io/badge/GitHub-ccjjfdyqlhy%2FDSN--exp-181717?style=for-the-badge&logo=github)](https://github.com/ccjjfdyqlhy/DSN-exp)\n\n[深入了解仓库复杂度](REPORT_zh.md) \n\n[English](README.md) | **简体中文**\n\n---\n\n```\n你走进来,它听见了。\n \"一小时后提醒我推送构建。\"\n你倒咖啡的功夫,它已经处理完了。\n \"设好了。对了,构建日志看起来没问题。\"\n你没让它检查。它知道什么值得说。\n```\n\n---\n\n## 为什么是语音优先?\n\n市面上所谓的\"AI 助手\",本质是套了聊天气泡的网页搜索。DSN-exp 从头到尾是反过来的:\n\n- **输入**:麦克风 → ASR (FunASR paraformer) → ASR 过滤 (1B 分类器滤除环境噪音) → AI\n- **输出**:AI → 流式 TTS (GPT-SoVITS),每句话边生成边播,不等整段合成完\n- **主动**:心跳每 2 秒轮询——提醒、闹钟、甚至摄像头看到场景变化,AI 都会主动开口\n- **感知通话模式**:连续对话,AI 切换为口语短句风格,沉默 = 还在听\n- **万物皆可说**:50+ 技能、100+ 工具、记忆检索、闹钟管理、音乐控制、系统操作——全用嘴完成\n\n**它不等着你打字。它跟你待在同一个房间里。不是云服务。不是 SaaS。一个在你硬盘上苏醒的智能体。**\n\n---\n\n## 🏗️ 系统架构\n\n```mermaid\ngraph TB\n subgraph VoiceClients[\"🎤 语音客户端\"]\n CLI[\"minimal.py
终端 · PvRecorder\"]\n WEB[\"psychoscope/server.py
Web · MediaRecorder\"]\n SEN[\"voice.js
感知模式 · LoudnessGate\"]\n AGT[\"agent_send.py
Agent 命令行\"]\n end\n\n subgraph VoicePipeline[\"🗣️ 语音管线\"]\n ASR[\"FunASR paraformer
16kHz · VAD · 标点恢复\"]\n FILT[\"ASR 过滤器
1B 模型 · FORWARD/HOLD\"]\n TTS[\"GPT-SoVITS
流式合成 · 多音色\"]\n TTS_PRE[\"TTS 预处理器
LLM 文本清理\"]\n end\n\n subgraph Engine[\"🧠 DSNEngine 核心引擎\"]\n PL[\"ChatPipeline · 5个Hook点\"]\n PM[\"PluginManager
插件管理器\"]\n PE[\"PromptEngine
提示词引擎\"]\n ME[\"MemorySystem
记忆系统\"]\n TM[\"TaskManager
任务管理器\"]\n WE[\"WorldEngine
世界引擎\"]\n SM[\"SkillManager
技能管理器\"]\n WS[\"Workspace
工作区\"]\n end\n\n subgraph Pipeline[\"管道执行流程\"]\n direction LR\n F[\"① PRE_FILTER
ASR过滤, 缓存检查\"]\n SP[\"assemble_prompt()
组装提示词\"]\n P[\"② PRE_PROCESS
视觉, 记忆注入
印象, 计划, 主动视觉\"]\n M[\"③ MODEL_INVOKE
ModelsPlugin\"]\n PO[\"④ POST_PROCESS
任务, 工具, 待办
记忆, 人格\"]\n T[\"⑤ POST_TTS
TTS合成\"]\n F --> SP --> P --> M --> PO --> T\n end\n\n subgraph Models[\"🤖 模型层\"]\n OA[\"☁️ OpenAIChat
云API\"]\n LS[\"🏠 LMStudioChat
本地模型\"]\n SCH[\"📊 ModelScheduler
VRAM调度\"]\n end\n\n subgraph Storage[\"💾 存储层\"]\n DB[\"🗄️ SQLite
聊天数据库\"]\n WK[\"📂 工作区文件\"]\n AC[\"🎵 音频缓存\"]\n end\n\n CLI & WEB & SEN & AGT --> ASR --> FILT --> Engine\n Engine --> TTS --> CLI & WEB\n M --> OA & LS\n OA & LS --> SCH\n ME --> DB\n WS --> WK\n\n subgraph Auth[\"🔐 认证系统\"]\n direction LR\n P0[\"配对码\"] --> S1[\"会话\"] --> W2[\"WebAuthn\"] --> T3[\"TOTP\"] --> K4[\"API Key\"]\n end\n\n subgraph Proactive[\"🔔 主动通知系统\"]\n HB[\"心跳 2s\"]\n RM[\"提醒 · 闹钟\"]\n CV[\"CameraWatcher · 视觉\"]\n end\n\n Proactive --> HB --> Engine --> TTS --> VoiceClients\n```\n\n---\n\n## 🎯 核心定位\n\n| 特性 | 说明 |\n|------|------|\n| **🎤 语音优先** | ASR → TTS 管线作为主要交互通道,感知免提模式实现无手对话 |\n| **🔒 完全私有** | 所有数据本地存储,SQLite加密,零云依赖 |\n| **🧠 长期记忆** | 对话自动摘要+向量检索,真正记得你说过的话 |\n| **🎭 多人格系统** | YAML角色卡+50维性格向量,支持人格蒸馏 |\n| **🌍 世界模拟** | 天气、时间、地点动态变化,AI有自己的\"生活\" |\n| **🛠️ 54+技能** | 搜索、文件、GitHub、音乐、文档、系统操作... |\n| **⚡ 异步任务** | 慢速工具后台执行,心跳轮询实时反馈 |\n| **🔐 多层认证** | 配对码/会话/WebAuthn/TOTP/API Key 五层防护 |\n| **🤖 Agent API** | 本地AI Agent专用接口,双向记忆同步 |\n\n---\n\n## ✨ 详细功能\n\n### 🎤 语音交互\n- **实时 ASR**:FunASR paraformer-zh,含 VAD 静音检测、噪音门控、标点恢复\n- **智能过滤**:1B 参数模型实时分类——是对话(FORWARD)还是噪音(HOLD),防止误触发\n- **流式 TTS**:GPT-SoVITS 逐句合成,AI 说一句播一句,没有沉默空档\n- **多音色切换**:运行中切换 TTS Profile,每套独立 GPT/SoVITS 权重\n- **感知通话模式**:连续对话——AI 自动去掉 Markdown,改用口语短句,沉默 = 还在听\n- **TTS 预处理**:LLM 清理文本,\"AI 3秒后处理 HTTP 请求\" → \"人工智能三秒后处理超文本传输协议请求\"——所有行在**一次批量调用**里完成(流式时首行仍走本地正则快路径),不再逐行串行调用 LLM\n- **语义音频缓存**:L1 静态 + L2 向量 + L3 槽位——重复查询直接命中缓存音频\n\n### 💬 智能对话\n- **双后端支持**:OpenAI兼容API (DeepSeek/智谱/OpenAI) + 本地LMStudio\n- **原生Tool Call**:OpenAI Function Calling,100+工具一键调用\n- **工具箱模式**:两阶段工具激活——首轮只发一个 `toolbox` 索引工具,激活后才附带具体工具 schema。约 100 个工具时单任务 prompt token 可省 ~69%(首轮 ~4.7k vs 全量 ~10.9k)。问\"你能做什么\"时直接按索引回答,无需激活任何工具\n- **历史裁剪**:`MODEL_MAX_HISTORY`(默认12)限制对话历史条数,保持 prompt 精简\n- **流式输出**:实时生成,边说边播\n- **语义缓存**:L1静态+L2向量+L3槽位,拦截重复请求\n\n### 🧠 记忆系统\n- **自动摘要**:LLM驱动压缩,AES-256-GCM加密\n- **向量检索**:768维语义搜索,支持模糊查询\n- **全局记忆**:跨聊天历史共享,真正的长期记忆\n- **记忆类型**:对话摘要(`exp`) + 手动备忘(`memo`)\n\n### 🎭 人格系统V3\n- **角色卡**:YAML格式,支持自然语言/语料库/经验条目\n- **蒸馏引擎**:从对话中提取性格特征,生成50维向量\n- **动态合成**:基于当前情绪和语境动态生成提示词\n- **性格维度**:社交、思维、情感、兴趣、行为、价值观...\n- **后台节流**:`fastcache` 模式下,情绪分析与记忆摘要作为延迟后台任务执行,带同用户冷却间隔,避免本地 GPU 推理与主回复争抢\n\n### 🌍 世界系统\n- **世界引擎**:天气、时间、地点自动变化\n- **叙事模型**:独立LLM实例,生成第三人称旁白\n- **动作叙述**:收集AI操作,生成人类可读描述\n- **世界预设**:默认世界 + 自定义世界配置\n\n### 🛠️ 技能系统\n- **54+内置技能**:\n - 🔍 `web_search` - 网络搜索\n - 📂 `file_manager` - 文件管理\n - 🐙 `github` - GitHub操作\n - 🎵 `ncm_music` - 网易云音乐\n - 📄 `document` - 文档处理 (扫描仪/打印机)\n - 💻 `system` - 系统操作\n - 🧠 `plan` - 计划管理\n - 🔧 `browser_use` - 浏览器自动化\n - 📝 `todo` - 待办事项\n - 🎭 `impression` - 印象系统\n - 🧪 `skillmgr` - 技能管理\n - ...\n- **自定义技能**:YAML配置,支持异步执行\n- **技能蒸馏**:从使用中学习,优化调用\n\n### ⏰ 主动通知系统\n- **心跳轮询**:客户端每 2 秒 polling,随时接收 AI 主动消息\n- **提醒系统**:倒计时、每日计划、Cron 周期、习惯打卡——到期自动 AI+TTS 播报\n- **闹钟系统**:完整增删改查,按周循环,语音 dismiss\n- **主动视觉**:CameraWatcher 后台抓帧 → VisionModel 分析场景变化 → AI 自主决定是否开口\n\n### ⚡ 任务系统\n- **复杂度分析**:自动判断是否需要异步执行\n- **异步任务**:慢速工具后台执行\n- **心跳轮询**:前端轮询获取异步结果\n- **实时反馈**:Agent循环每步骤实时TTS进度\n- **超步数汇报**:Agent 用完全部步数仍在执行工具时,追加一轮把工具结果总结给用户,而不是无声终止\n\n### 🖼️ 视觉系统\n- **VisionModel**:通用视觉模型 (GLM-4.6V/GPT-4V)\n- **OCRModel**:文档OCR,支持deepseek-ocr\n- **VISION_OVERRIDE**:接管OCR+2md管线,直接生成Markdown\n- **图片分析**:`describe_image`工具支持本地图片\n- **主动视觉**:后台 CameraWatcher 线程实现场景感知\n- **多摄像头并行**:`look_around` 并行描述所有摄像头(按逻辑名保序),多摄延迟减半\n- **按需帧缓存**:客户端缓存每台摄像头最近一帧,视觉请求零等待复用;心跳降到 2s,请求几乎即时送达\n- **VLM 预热**:启动时后台发一次 dummy 请求,消除首次推理冷启动(首次 VLM 推理可能 ~9s)\n- **look_around 去重**:短窗口内重复观察直接复用上次结果,不再重复抓帧+推理\n- **快速兜底**:客户端离线时 `look_around` 8s 内返回兜底(原 20s),不再拖住整条回复\n\n### 🔐 认证系统\n| 层级 | 方法 | 优先级 | 使用场景 |\n|------|------|--------|----------|\n| L4 | API Key | 1 | Agent API / 自动化 |\n| L1 | Session | 2 | 终端/Web UI |\n| L2 | WebAuthn | 3 | 通行密钥 |\n| L3 | TOTP | 4 | 双因素认证 |\n| L0 | 配对码 | 5 | 首次设备配对 |\n\n### 🧩 其他功能\n- **工作区系统**:用户隔离目录,AI笔记/扫描件/代码仓库\n- **文档系统**:扫描仪/打印机支持,`.hmd`格式\n- **维护系统**:自动内存压缩/人格蒸馏/日志清理\n- **语义缓存**:拦截重复请求,节省Token\n- **Agent API**:本地AI Agent专用接口\n\n---\n\n## 🚀 快速开始\n\n### 最小化配置 (3步启动)\n\n#### 1️⃣ 克隆并安装\n```bash\ngit clone https://github.com/ccjjfdyqlhy/DSN-exp\ncd DSN-exp\npip install -r requirements.txt\n```\n\n#### 2️⃣ 配置 (自动引导)\n```bash\npython main.py\n```\n\n首次运行会进入交互式引导,询问:\n- API选择:云API或本地模型\n- API Key和Base URL\n- 主模型选择\n- 是否启用核心功能\n\n引导完成后自动生成`.env`文件。\n\n#### 3️⃣ 连接客户端\n```bash\n# 终端语音客户端(按住 Enter 说话,松开发送)\npython psychoscope/minimal.py\n\n# Web界面(支持感知免提模式)\npython psychoscope/server.py\n# 然后访问 http://localhost:5000\n```\n\n**就这么简单!** 现在可以开始说话了。\n\n---\n\n### 常见配置场景\n\n#### 🌐 场景1:纯DeepSeek (最简单)\n```bash\n# .env (引导会自动生成)\nOPENAI_API_KEY=sk-your-deepseek-key\nOPENAI_API_BASE=https://api.deepseek.com/v1\nMAIN_MODEL_NAME=deepseek-v4-flash\n```\n\n#### 🏠 场景2:本地LMStudio\n```bash\n# .env\nMAIN_MODEL_TYPE=lmstudio\nMAIN_MODEL_NAME=llama-3-8b-instruct\nLMSTUDIO_BASE_URL=http://localhost:4501\n\n# 先启动LMStudio,加载模型\n# 然后启动DSN-exp\npython main.py\npython psychoscope/minimal.py\n```\n\n#### 🎯 场景3:智谱GLM\n```bash\n# .env\nOPENAI_API_KEY=your-zhipu-key\nOPENAI_API_BASE=https://open.bigmodel.cn/api/paas/v4\nMAIN_MODEL_NAME=glm-4.7\nVISION_API_KEY=your-zhipu-key\nVISION_MODEL_NAME=glm-4.6v\n```\n\n#### 🤖 场景4:Agent API\n```bash\n# 服务器控制台创建Agent\n/agent create MyAgent 1\n# → 输出API Key\n\n# 使用Agent发送消息\npython agent_send.py \"分析代码复杂度\"\n```\n\n---\n\n## 📂 项目结构\n\n```\nDSN-exp/\n├── 🚀 核心\n│ ├── main.py # 服务器入口 + 控制台\n│ ├── engine.py # DSNEngine 核心引擎\n│ ├── boot.py # Flask 应用初始化 + ASR/TTS 模型加载\n│ ├── config.py # 配置管理\n│ └── onboarding.py # 首次启动引导\n│\n├── 🔌 插件系统\n│ ├── plugins/\n│ │ ├── base.py # 插件基类 + HookPoint\n│ │ ├── manager.py # PluginManager\n│ │ ├── pipeline.py # ChatPipeline 对话管道\n│ │ └── builtin/ # 内置插件\n│ │ ├── models_plugin.py # 模型调用\n│ │ ├── memory_plugin.py # 记忆系统\n│ │ ├── vision_plugin.py # 视觉系统\n│ │ ├── task_plugin.py # 任务管理\n│ │ ├── tts_plugin.py # TTS 合成\n│ │ ├── tts_profile.py # TTS 音色配置\n│ │ ├── asr_filter_plugin.py # ASR 语音过滤器\n│ │ ├── active_vision_plugin.py # 摄像头观察器\n│ │ └── ...\n│ │ └── custom/ # 自定义插件\n│\n├── 🧠 记忆系统\n│ ├── memory/core.py # MemorySystem 核心类\n│ └── semantic_cache/ # 语义缓存 (L1/L2/L3)\n│\n├── 🎭 人格系统\n│ ├── prompt/personality_v3/ # PersonalitySystemV3\n│ │ ├── character_card.py # 角色卡定义\n│ │ ├── distillation_engine.py # 蒸馏引擎\n│ │ ├── dynamic_synthesizer.py # 动态合成器\n│ │ ├── traits.py # 50维性格向量\n│ │ └── ...\n│ ├── prompt/personality_v2/ # PersonalitySystemV2\n│ ├── prompt/library.py # PromptLibrary\n│ ├── prompt/engine.py # PromptEngine\n│ └── character_cards/ # YAML 角色卡\n│\n├── 🌍 世界系统\n│ ├── world/\n│ │ ├── engine.py # WorldEngine\n│ │ ├── state_manager.py # WorldStateManager\n│ │ ├── narrative_model.py # NarrativeModel\n│ │ ├── action_narrator.py # 动作叙述\n│ │ └── worlds/ # 世界配置\n│\n├── 🛠️ 技能系统\n│ ├── skills/\n│ │ ├── registry.py # SkillRegistry\n│ │ ├── manager.py # SkillManager\n│ │ ├── builtin/ # 内置技能 54+\n│ │ ├── custom/ # 自定义技能\n│ │ ├── distilled/ # 蒸馏技能\n│ │ └── system/ # 系统技能\n│\n├── ⚡ 任务系统\n│ ├── tasks.py # TaskManager + 复杂度分析\n│ ├── maintenance/ # 后台任务\n│ └── async_task_store.py # 异步任务存储\n│\n├── 🤖 模型客户端\n│ ├── models/\n│ │ ├── clients.py # OpenAIChat + LMStudioChat\n│ │ ├── scheduler.py # ModelScheduler\n│ │ ├── tts_process.py # TTS 预处理\n│ │ └── asr_filter.py # ASR 过滤 (1B 模型)\n│\n├── 🔐 认证系统\n│ ├── auth/\n│ │ ├── auth_manager.py # 认证管理器\n│ │ ├── api_key_manager.py # API Key\n│ │ ├── webauthn_manager.py # WebAuthn\n│ │ ├── totp_manager.py # TOTP\n│ │ ├── pairing.py # 配对码\n│ │ ├── session.py # 会话管理\n│ │ └── endpoints.py # 认证API\n│\n├── 💾 数据库\n│ ├── db/\n│ │ ├── chat.py # ChatDBManager\n│ │ ├── plan_store.py # 计划存储\n│ │ └── plan_engine.py # 计划引擎\n│\n├── 🖼️ 视觉系统\n│ ├── document/ # 文档处理 (.hmd)\n│ └── models/clients.py # VisionModel + OCRModel\n│\n├── 🎤 音频系统\n│ ├── audio/infer.py # VocalExp — GPT-SoVITS 客户端\n│ └── TTS_profiles/ # TTS 音色配置\n│\n├── 🗣️ 语音 API\n│ ├── api/app.py # ASR 识别 + passthrough 端点\n│ ├── api/heartbeat.py # 心跳通知轮询\n│ └── api/alarm.py # 闹钟 CRUD + dismiss\n│\n├── 🖥️ 前端客户端\n│ ├── psychoscope/\n│ │ ├── server.py # Web 服务器\n│ │ ├── minimal.py # 终端语音客户端\n│ │ └── static/\n│ │ ├── index.html\n│ │ ├── js/app.js # 主 Web 应用\n│ │ ├── js/voice.js # 浏览器感知模式\n│ │ └── js/typewriter.js\n│ └── agent_send.py # Agent CLI\n│\n├── 📊 配置文件\n│ ├── .env.example # 配置示例\n│ ├── model_profiles/ # 模型配置\n│ └── world/worlds/ # 世界配置\n│\n├── 🧪 测试与文档\n│ ├── tests/ # 测试\n│ ├── docs/ # 文档\n│ ├── REPORT.md # 技术报告\n│ └── GOALS.md # 开发目标\n│\n└── 📂 工作区与缓存\n ├── .dsn/workspace/ # 用户工作区\n ├── logs/ # 日志(含 ASR/TTS 调试日志)\n └── temp/ # 临时文件\n```\n\n---\n\n## 📦 核心模块介绍\n\n### 🎤 语音管线\n\n**三级语音处理:ASR → 过滤 → AI → TTS**\n\n```\n麦克风 → PCM 16kHz → FunASR (VAD + 识别) → ASR 过滤器 (FORWARD/HOLD)\n │\n ├── FORWARD → ChatPipeline → GPT-SoVITS TTS → 扬声器\n └── HOLD → 丢弃(记录到记忆系统作为环境音)\n```\n\n**组件:**\n- **FunASR**:paraformer-zh 模型,fsmn-vad 做静音检测,ct-punc-c 做标点恢复,支持 CUDA/CPU\n- **ASR 过滤器**:1B 参数 LMStudio 模型分类语音为对话或噪音,维护 20 轮对话历史防误判\n- **GPT-SoVITS TTS**:通过 REST API 流式合成,支持并行/串行架构,多音色切换\n- **TTS 预处理器**:两级清理——正则去 Markdown,LLM 转换数字/缩写到自然语音;多行合并为一次批量调用(流式首行走本地正则快路径)\n- **语义音频缓存**:L1 静态语素 + L2 向量语义 + L3 槽位注册\n\n### 🔄 ChatPipeline 对话管道\n**5个Hook点,完整的对话生命周期管理**\n\n| HookPoint | 触发时机 | 用途 |\n|-----------|----------|------|\n| PRE_FILTER | 管道起始 | ASR 过滤、语义缓存检查、HOLD 时短路 |\n| PRE_PROCESS | 模型调用前 | 记忆注入、视觉处理、主动视觉、印象、计划 |\n| MODEL_INVOKE | 模型调用 | OpenAI/LMStudio调用、工具传递、Agent 循环 |\n| POST_PROCESS | 输出后 | 任务执行、工具调用、记忆更新、人格更新 |\n| POST_TTS | 文本就绪后 | TTS 合成、缓存、音频投递 |\n\n### 🧠 MemorySystem 记忆系统\n**双层架构:摘要 + 向量检索**\n\n```\n对话 → LMSummaryModel → 压缩摘要 → AES-256加密 → SQLite\n ↓\n EmbeddingClient → 768维向量 → 语义检索\n```\n\n**特性:**\n- 自动摘要:每N轮自动压缩对话\n- 向量检索:支持语义模糊搜索\n- 全局记忆:跨聊天历史共享\n- 加密存储:AES-256-GCM保护隐私\n\n### 🎭 PersonalitySystemV3 人格系统\n**50维性格向量,真正的人格建模**\n\n**核心组件:**\n- **CharacterCard**:YAML角色卡,支持自然语言/语料库/经验条目\n- **DistillationEngine**:从对话中提取性格特征\n- **DynamicSynthesizer**:基于情绪和语境动态生成提示词\n- **PersonalityJudge**:50维性格向量:社交/思维/情感/兴趣/行为/价值观\n\n**工作流程:**\n```\n角色卡(YAML) → 蒸馏引擎 → 50维向量 → 动态合成器 → 提示词 → 模型\n```\n\n### 🌍 WorldSystem 世界系统\n**AI有自己的\"生活\"**\n\n**组件:**\n- **WorldEngine**:世界状态管理(天气/时间/地点)\n- **WorldStateManager**:状态更新和事件触发\n- **NarrativeModel**:独立LLM实例,生成旁白\n- **ActionNarrator**:收集AI操作,生成描述\n\n**示例:**\n```\n世界:default\n天气:晴天 ☀️\n时间:2026-07-03 14:30\n地点:书房 📚\n旁白:阳光透过窗户洒在书桌上,AI正在思考用户的请求...\n```\n\n### 🛠️ SkillSystem 技能系统\n**54+内置技能 + 自定义技能**\n\n**技能结构:**\n```yaml\nname: web_search\ndescription: 网络搜索\nversion: 1.0\ntools:\n - name: search\n description: 搜索网络\n parameters:\n query:\n type: string\n description: 搜索关键词\n```\n\n**内置技能列表:**\n- 🔍 `web_search` - 网络搜索\n- 📂 `file_manager` - 文件管理\n- 🐙 `github` - GitHub操作\n- 🎵 `ncm_music` - 网易云音乐\n- 📄 `document` - 文档处理\n- 💻 `system` - 系统操作\n- 🧠 `plan` - 计划管理\n- 🔧 `browser_use` - 浏览器自动化\n- 📝 `todo` - 待办事项\n- 🎭 `impression` - 印象系统\n- 🧪 `skillmgr` - 技能管理\n- ...\n\n### ⏰ 主动通知系统\n**心跳驱动的主动 AI 语音通知**\n\n```\n(时钟滴答) → TaskManager 触发 → task_notifications 表\n ↓\nHeartbeatPoller (2s) → GET /api/heartbeat → has_notification?\n ├── 是 → 构建提示词 → AI 回复 → TTS → 播报\n └── 否 → 继续休眠\n```\n\n**触发来源:**\n- **提醒**:倒计时、每日计划、Cron 周期、习惯打卡\n- **闹钟**:按周循环,支持 dismiss(8 天静音)\n- **主动视觉**:CameraWatcher 后台抓帧 → VisionModel 场景分析 → 变化检测\n\n### ⚡ TaskSystem 任务系统\n**智能异步,实时反馈**\n\n**特性:**\n- **复杂度分析**:自动判断是否需要异步\n- **异步任务**:慢速工具后台执行\n- **心跳轮询**:前端轮询获取结果\n- **实时反馈**:每步骤TTS进度推送\n- **超步数汇报**:步数用尽且工具未结束时不无声终止,追加一轮把结果总结给用户\n\n**工作流程:**\n```\n用户请求 → 复杂度分析 → 高复杂度? → 异步任务 → 心跳轮询 → 结果返回\n ↓\n 低复杂度 → 同步执行 → 直接返回\n```\n\n### 🔐 AuthSystem 认证系统\n**五层防护,安全可控**\n\n**认证层级:**\n1. **L4 API Key**:程序化访问,Agent API\n2. **L1 Session**:终端/Web UI登录\n3. **L2 WebAuthn**:通行密钥,硬件认证\n4. **L3 TOTP**:时间-based双因素认证\n5. **L0 配对码**:首次设备配对\n\n**流程示例:**\n```bash\n# 1. 生成配对码\n/newbind\n# → 配对码: 12345678 (8分钟有效)\n\n# 2. 客户端提交\nPOST /api/auth/pairing/verify\n{\"code\": \"12345678\"}\n\n# 3. 返回Session\n{\"session\": \"eyJhbGciOiJIUzI1NiIs...\"}\n```\n\n### 🖼️ VisionSystem 视觉系统\n**多模态感知**\n\n**组件:**\n- **VisionModel**:通用视觉模型 (GLM-4.6V/GPT-4V)\n- **OCRModel**:文档OCR (deepseek-ocr)\n- **VISION_OVERRIDE**:接管OCR+2md管线\n\n**视觉管线:**\n```\n图片 → VisionModel → 描述文本\n文档 → OCRModel → Markdown → .hmd\n```\n\n---\n\n## 🎮 使用技巧\n\n### 终端语音客户端快捷键\n```\n[Enter] 按住录音,松开发送\n[a 双击] 锁定/解锁面板\n[b 双击] 音乐播放器模式\n[p] 显示人格状态\n[s] 切换待机/唤醒\n[i] 系统信息\n[k] 跳过最新提醒\n[f] 静音闹钟 + 停止TTS\n[r] 手动触发心跳\n[t] 文字输入(同步)\n[=] 异步任务(长时间)\n[h] 显示帮助\n[q/Ctrl+C] 退出\n```\n\n### 服务器控制台命令\n```\n/newbind - 生成新配对码\n/users - 列出所有用户\n/status - 服务器状态\n/agent create - 创建Agent\n/agent bind - 绑定Agent到用户\n/memory query - 搜索记忆\n/memory reindex - 重建向量索引\n/hibernate sleep - 进入待机\n/config set - 动态修改配置\n/stop - 停止服务器\n```\n\n### API调用示例\n```bash\n# 发送消息\ncurl -X POST http://localhost:5000/api/chat/send \\\n -H \"Authorization: Bearer YOUR_SESSION\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"message\": \"你好\"}'\n\n# 语音直通(ASR → AI → TTS)\ncurl -X POST http://localhost:5000/api/asr/passthrough \\\n -H \"Authorization: Bearer YOUR_SESSION\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"audio_b64\": \"BASE64_WAV_DATA\"}'\n\n# Agent发送消息\npython agent_send.py \"分析代码复杂度\"\n\n# 搜索记忆\ncurl \"http://localhost:5000/api/memory/query?q=Python项目&limit=5\"\n```\n\n---\n\n## ⚙️ 核心配置\n\n### 最小配置 (必需)\n```bash\nOPENAI_API_KEY=sk-your-key # API密钥\nOPENAI_API_BASE=https://api.deepseek.com/v1 # API地址\nMAIN_MODEL_NAME=deepseek-v4-flash # 主模型\n```\n\n### 语音完整配置 (推荐)\n```bash\n# 语音\nASR_ENABLED=true\nASR_DEVICE=cuda\nTTS_BASE_URL=http://127.0.0.1:9880\nTTS_PROCESS_ENABLED=true\n\n# 主模型\nMAIN_MODEL_TYPE=openai\nMAIN_MODEL_NAME=deepseek-v4-flash\n\n# 记忆系统\nMEMORY_ENABLED=true\nMEMORY_EMBEDDING_ENABLED=true\n\n# 人格系统\nPERSONALITY_V3_ENABLED=true\n\n# 世界系统\nWORLD_ENABLED=true\nNARRATIVE_ENABLED=true\n\n# 语义缓存\nSEMANTIC_CACHE_ENABLED=true\n```\n\n### 性能与感知调优\n```bash\n# 工具箱:首轮只发 toolbox 索引,激活后才附带具体工具 schema\nTOOLBOX_ENABLED=true\n\n# 主模型历史条数上限(非 system 消息)\nMODEL_MAX_HISTORY=12\n\n# Agent 最大步数;步数用尽但任务未结束时会自动追加一轮向用户汇报\nAGENT_MAX_STEPS=15\n\n# 视觉:启动预热 VLM、look_around 短时去重窗口(秒)\nVISION_WARMUP=true\nVISION_LOOK_AROUND_DEDUP=10\n\n# 客户端侧 (minimal.py):心跳间隔(秒)、缓存帧新鲜度(秒)\n# DSN_HEARTBEAT_INTERVAL=2\n# DSN_FRAME_CACHE_MAX_AGE=3\n\n# 本地 GPU 后台任务节流(秒)\nHIBERNATE_PERSONALITY_COOLDOWN=30\nHIBERNATE_MEMORY_COOLDOWN=60\n```\n\n### 完整配置列表\n查看 [`.env.example`](.env.example) 获取所有配置项。\n\n---\n\n## 📄 许可证\n\n暂未考虑 :/\n\n---\n\n## 链接\n\n[LINUX DO](https://linux.do/) - 下一代技术交流社区\n\n---\n\n**[⬆ 回到顶部](#-dsn-exp)**\n\nMade with ❤️ by [Darkstar](https://github.com/ccjjfdyqlhy)\n"}}, "config.py": {"1cfce308afc3f2333b59d64d39ffdb3b70bb210a": {"t": 1786068483.4200728, "n": 18904, "h": "ea3409a37e55faf2", "c": "\n# DSN-exp/config.py\n# 所有配置均从环境变量或 .env 文件读取,切勿在此文件中硬编码密钥。\n\nimport os\nfrom pathlib import Path\n\n# 加载 .env 文件(如果存在)\ntry:\n from dotenv import load_dotenv\n _env_path = Path(__file__).parent / \".env\"\n if _env_path.exists():\n load_dotenv(_env_path)\nexcept ImportError:\n pass\n\n\ndef _env(key: str, default=None, required: bool = False):\n val = os.environ.get(key, default)\n if required and not val:\n raise EnvironmentError(\n f\"缺少必需的环境变量: {key}\\n请参考 .env.example 创建 .env 文件。\"\n )\n return val\n\n\ndef _env_bool(key: str, default: str = \"false\") -> bool:\n return str(_env(key, default)).lower() in (\"1\", \"true\", \"yes\", \"on\")\n\n\nclass Config:\n # ═══════════════════════════════════════════════════════════════════════\n # 第一层: 必需配置 — 不填系统无法运行\n # ═══════════════════════════════════════════════════════════════════════\n\n # 主对话模型\n MAIN_MODEL_TYPE = _env(\"MAIN_MODEL_TYPE\", \"openai\") # \"openai\" | \"lmstudio\"\n MAIN_MODEL_NAME = _env(\"MAIN_MODEL_NAME\", \"deepseek-v4-flash\")\n\n # OpenAI 兼容 API;主模型使用 openai 后端时 API Key 必填。\n OPENAI_API_KEY = _env(\n \"OPENAI_API_KEY\",\n \"\",\n required=MAIN_MODEL_TYPE == \"openai\",\n )\n OPENAI_API_BASE = _env(\"OPENAI_API_BASE\", \"https://api.deepseek.com/v1\")\n OPENAI_BACKUP_API_KEY = _env(\"OPENAI_BACKUP_API_KEY\", \"\")\n\n # 多账号 FailoverChat: 近期失败过的账号自动排后的窗口(秒)。\n # 该窗口内失败的账号会被自动放到回退链末尾(手动 promote/demote 仍保留,\n # 但\"刚失败过\"优先于提优,避免在坏账号上白等)。窗口过后恢复原优先级排序。\n FAILOVER_DOWN_WINDOW = int(_env(\"FAILOVER_DOWN_WINDOW\", \"3600\"))\n\n # ═══════════════════════════════════════════════════════════════════════\n # 第二层: 服务与存储\n # ═══════════════════════════════════════════════════════════════════════\n\n # ── 服务 ──\n SERVER_HOST = _env(\"SERVER_HOST\", \"0.0.0.0\")\n SERVER_PORT = int(_env(\"SERVER_PORT\", \"5000\"))\n SERVER_BASE_URL = _env(\"SERVER_BASE_URL\", \"\") # 对外访问地址,为空则自动检测\n LOCAL_CALLBACK_PORT = int(_env(\"LOCAL_CALLBACK_PORT\", \"5001\"))\n\n # ── 存储 ──\n DATABASE_PATH = _env(\"DATABASE_PATH\", \"DSN_usrdata.db\")\n LOG_DIR = _env(\"LOG_DIR\", \"logs\")\n LOG_MAX_BYTES = max(1024, int(_env(\"LOG_MAX_BYTES\", str(10 * 1024 * 1024))))\n LOG_BACKUP_COUNT = max(0, int(_env(\"LOG_BACKUP_COUNT\", \"30\")))\n LOG_BUFFER_SIZE = max(1, int(_env(\"LOG_BUFFER_SIZE\", \"200\")))\n LOG_LEVEL = _env(\"LOG_LEVEL\", \"INFO\")\n\n # ── 安全 ──\n JWT_SECRET = _env(\"JWT_SECRET\", \"dsn-exp-auto-secret\")\n\n # ═══════════════════════════════════════════════════════════════════════\n # 第三层: 模型基础设施\n # ═══════════════════════════════════════════════════════════════════════\n\n # ── 主模型参数(LMStudio 后端专用)──\n LMSTUDIO_ENABLED = _env(\"LMSTUDIO_ENABLED\", \"true\").lower() == \"true\"\n LMSTUDIO_BASE_URL = _env(\"LMSTUDIO_BASE_URL\", \"http://localhost:4501\")\n LMSTUDIO_TEMPERATURE = float(_env(\"LMSTUDIO_TEMPERATURE\", \"0.7\"))\n LMSTUDIO_MAX_TOKENS = int(_env(\"LMSTUDIO_MAX_TOKENS\", \"4096\"))\n LMSTUDIO_TIMEOUT = int(_env(\"LMSTUDIO_TIMEOUT\", \"300\"))\n\n # ── 推理模型 ──\n REASONER_ENABLED = _env(\"REASONER_ENABLED\", \"true\").lower() == \"true\"\n REASONER_MODEL = _env(\"REASONER_MODEL\", \"deepseek-v4-pro\")\n REASONER_TIMEOUT = int(_env(\"REASONER_TIMEOUT\", \"1200\"))\n\n # ── 模型共存管理 ──\n MAX_CONCURRENT_LM_MODELS = int(_env(\"MAX_CONCURRENT_LM_MODELS\", \"1\"))\n MODEL_LOAD_TIMEOUT = int(_env(\"MODEL_LOAD_TIMEOUT\", \"180\"))\n MODEL_REQUEST_TIMEOUT = int(_env(\"MODEL_REQUEST_TIMEOUT\", \"300\"))\n\n # ── Token 消耗定价 (USD / 1M tokens) ──\n TOKEN_PRICE_FLASH_INPUT = float(_env(\"TOKEN_PRICE_FLASH_INPUT\", \"0.14\"))\n TOKEN_PRICE_FLASH_OUTPUT = float(_env(\"TOKEN_PRICE_FLASH_OUTPUT\", \"0.28\"))\n TOKEN_PRICE_PRO_INPUT = float(_env(\"TOKEN_PRICE_PRO_INPUT\", \"0.435\"))\n TOKEN_PRICE_PRO_OUTPUT = float(_env(\"TOKEN_PRICE_PRO_OUTPUT\", \"0.87\"))\n DEEPSEEK_FLASH_INPUT_PRICE = TOKEN_PRICE_FLASH_INPUT\n DEEPSEEK_FLASH_OUTPUT_PRICE = TOKEN_PRICE_FLASH_OUTPUT\n DEEPSEEK_PRO_INPUT_PRICE = TOKEN_PRICE_PRO_INPUT\n DEEPSEEK_PRO_OUTPUT_PRICE = TOKEN_PRICE_PRO_OUTPUT\n\n # ═══════════════════════════════════════════════════════════════════════\n # 第四层: 子系统\n # ═══════════════════════════════════════════════════════════════════════\n\n # ── 记忆与摘要 ──\n MEMORY_ENABLED = _env(\"MEMORY_ENABLED\", \"true\").lower() == \"true\"\n MEMORY_SUMMARY_BACKEND = _env(\"MEMORY_SUMMARY_BACKEND\", \"openai\") # \"openai\" | \"lmstudio\"\n MEMORY_MODEL = _env(\"MEMORY_MODEL\", \"deepseek-v4-flash\")\n MEMORY_SUMMARY_LENGTH = int(_env(\"MEMORY_SUMMARY_LENGTH\", \"100\"))\n MEMORY_CONTEXT_WINDOW_SIZE = int(_env(\"MEMORY_CONTEXT_WINDOW_SIZE\", \"80\"))\n MEMORY_REPLACE_THRESHOLD_RATIO = float(_env(\"MEMORY_REPLACE_THRESHOLD_RATIO\", \"0.7\"))\n MEMORY_ASYNC_ENABLED = _env(\"MEMORY_ASYNC_ENABLED\", \"true\").lower() == \"true\"\n MEMORY_SEARCH_THRESHOLD = min(1.0, max(0.0, float(_env(\"MEMORY_SEARCH_THRESHOLD\", \"0.5\"))))\n MEMORY_DETAIL_CHARS_PER_ROUND = max(1, int(_env(\"MEMORY_DETAIL_CHARS_PER_ROUND\", \"4000\")))\n MEMORY_TOTAL_DETAIL_CHARS = max(1, int(_env(\"MEMORY_TOTAL_DETAIL_CHARS\", \"16000\")))\n MEMORY_QUERY_LIMIT = max(1, int(_env(\"MEMORY_QUERY_LIMIT\", \"50\")))\n\n # 摘要模型参数\n SUMMARY_RETRY_COUNT = max(0, int(_env(\"SUMMARY_RETRY_COUNT\", \"3\")))\n SUMMARY_RETRY_BACKOFF_SECONDS = max(0.0, float(_env(\"SUMMARY_RETRY_BACKOFF_SECONDS\", \"1.0\")))\n SUMMARY_CONTEXT_SIZE = max(0, int(_env(\"SUMMARY_CONTEXT_SIZE\", \"10\")))\n SUMMARY_TEMPERATURE = min(2.0, max(0.0, float(_env(\"SUMMARY_TEMPERATURE\", \"0.1\"))))\n SUMMARY_TIMEOUT = max(1, int(_env(\"SUMMARY_TIMEOUT\", \"60\")))\n\n # ── 向量嵌入检索 ──\n MEMORY_EMBEDDING_ENABLED = _env(\"MEMORY_EMBEDDING_ENABLED\", \"false\").lower() == \"true\"\n MEMORY_EMBEDDING_MODEL = _env(\"MEMORY_EMBEDDING_MODEL\", \"text-embedding-nomic-embed-text-v1.5@f16\")\n MEMORY_EMBEDDING_DIMS = int(_env(\"MEMORY_EMBEDDING_DIMS\", \"768\"))\n MEMORY_EMBEDDING_WEIGHT = float(_env(\"MEMORY_EMBEDDING_WEIGHT\", \"0.6\"))\n\n # ── 人格系统 V3 ──\n PERSONALITY_V3_ENABLED = _env(\"PERSONALITY_V3_ENABLED\", \"true\").lower() == \"true\"\n PERSONALITY_V3_OVERRIDE_V2 = _env(\"PERSONALITY_V3_OVERRIDE_V2\", \"true\").lower() == \"true\"\n PERSONALITY_MODEL_NAME = _env(\"PERSONALITY_MODEL_NAME\", \"google/gemma-3-4b\")\n PERSONALITY_MODEL_URL = _env(\"PERSONALITY_MODEL_URL\", None) or _env(\"LMSTUDIO_BASE_URL\", \"http://localhost:4501\")\n DISTILLATION_MODEL = _env(\"DISTILLATION_MODEL\", \"openai\")\n PERSONALITY_V3_DEFAULT_CARD = _env(\"PERSONALITY_V3_DEFAULT_CARD\", \"\")\n\n # ── 叙事世界模型 ──\n WORLD_ENABLED = _env(\"WORLD_ENABLED\", \"true\").lower() in (\"1\", \"true\", \"yes\")\n WORLD_PRESET = _env(\"WORLD_PRESET\", \"default\")\n WORLD_UPDATE_INTERVAL = int(_env(\"WORLD_UPDATE_INTERVAL\", \"60\"))\n\n NARRATIVE_ENABLED = _env(\"NARRATIVE_ENABLED\", \"true\").lower() in (\"1\", \"true\", \"yes\")\n NARRATIVE_MODEL_TYPE = _env(\"NARRATIVE_MODEL_TYPE\", \"lmstudio\")\n NARRATIVE_MODEL = _env(\"NARRATIVE_MODEL\", \"google/gemma-3-4b\")\n NARRATIVE_TEMPERATURE = float(_env(\"NARRATIVE_TEMPERATURE\", \"0.9\"))\n NARRATIVE_MAX_TOKENS = int(_env(\"NARRATIVE_MAX_TOKENS\", \"150\"))\n NARRATIVE_KEEP_HISTORY = _env(\"NARRATIVE_KEEP_HISTORY\", \"false\").lower() in (\"1\", \"true\", \"yes\")\n NARRATIVE_PRE_ENABLED = _env(\"NARRATIVE_PRE_ENABLED\", \"true\").lower() == \"true\"\n\n # ── 工具调用 ──\n TOOL_CALL_MODE = _env(\"TOOL_CALL_MODE\", \"native\") # \"native\" | \"xml\" | \"auto\"\n TOOL_CALL_MODEL = _env(\"TOOL_CALL_MODEL\", \"deepseek-v4-pro\")\n TOOLBOX_ENABLED = _env(\"TOOLBOX_ENABLED\", \"true\") == \"true\"\n # 主模型对话历史裁剪上限(非 system 消息保留条数);0 = 不裁剪\n MODEL_MAX_HISTORY = max(0, int(_env(\"MODEL_MAX_HISTORY\", \"12\")))\n # 客户端未携带 chat_id 时,自动续接该用户最近一个聊天,而不是新建聊天\n CHAT_RESUME_LATEST = _env(\"CHAT_RESUME_LATEST\", \"true\").lower() in (\"true\", \"1\", \"yes\", \"on\")\n\n # ── 文件操作限制 ──\n FILE_READ_MAX_SIZE_MB = max(1, int(_env(\"FILE_READ_MAX_SIZE_MB\", \"1\")))\n\n # ═══════════════════════════════════════════════════════════════════════\n # 第五层: 语音系统\n # ═══════════════════════════════════════════════════════════════════════\n\n # ── ASR ──\n ASR_ENABLED = _env(\"ASR_ENABLED\", \"false\").lower() == \"true\"\n ASR_DEVICE = _env(\"ASR_DEVICE\", \"cuda\")\n ASR_GPU_ID = _env(\"ASR_GPU_ID\", \"\")\n ASR_BATCH_SIZE_SECONDS = max(1, int(_env(\"ASR_BATCH_SIZE_SECONDS\", \"60\")))\n ASR_CONVERT_TIMEOUT = max(1, int(_env(\"ASR_CONVERT_TIMEOUT\", \"15\")))\n DEBUG_ASR = _env(\"DEBUG_ASR\", \"false\").lower() == \"true\"\n\n # ── ASR 过滤器 ──\n ASR_FILTER_ENABLED = _env(\"ASR_FILTER_ENABLED\", \"false\").lower() == \"true\"\n FILTER_MODEL = _env(\"FILTER_MODEL\", \"llama-3.2-1b-instruct\")\n\n # ── TTS ──\n TTS_ENABLED = _env(\"TTS_ENABLED\", \"true\").lower() == \"true\"\n TTS_BASE_URL = _env(\"TTS_BASE_URL\", \"http://127.0.0.1:9880\")\n TTS_PROBE_TIMEOUT = max(0.1, float(_env(\"TTS_PROBE_TIMEOUT\", \"3.0\")))\n TTS_PROBE_ATTEMPTS = max(1, int(_env(\"TTS_PROBE_ATTEMPTS\", _env(\"TTS_PROBE_RETRIES\", \"4\"))))\n TTS_PROBE_RETRIES = TTS_PROBE_ATTEMPTS\n TTS_PROBE_BACKOFF_BASE = max(0.0, float(_env(\"TTS_PROBE_BACKOFF_BASE\", \"2.0\")))\n TTS_PROBE_BACKOFF_MAX = max(TTS_PROBE_BACKOFF_BASE, float(_env(\"TTS_PROBE_BACKOFF_MAX\", \"8.0\")))\n TTS_FAST_FIRST_LINE = _env(\"TTS_FAST_FIRST_LINE\", \"true\").lower() == \"true\"\n\n # ── TTS 文本预处理 ──\n TTS_PROCESS_ENABLED = _env(\"TTS_PROCESS_ENABLED\", \"true\").lower() == \"true\"\n TTS_PROCESS_MODEL = _env(\"TTS_PROCESS_MODEL\", None) or _env(\"MEMORY_MODEL\", \"google/gemma-3-4b\")\n TTS_PROCESS_MAX_TOKENS = int(_env(\"TTS_PROCESS_MAX_TOKENS\", \"1024\"))\n TTS_PROCESS_TEMPERATURE = float(_env(\"TTS_PROCESS_TEMPERATURE\", \"0.2\"))\n TTS_PROCESS_TIMEOUT = int(_env(\"TTS_PROCESS_TIMEOUT\", \"30\"))\n\n # ═══════════════════════════════════════════════════════════════════════\n # 第六层: 视觉系统\n # ═══════════════════════════════════════════════════════════════════════\n\n # ── 视觉模型(GLM-4.6V / GPT-4V 等)──\n VISION_ENABLED = _env(\"VISION_ENABLED\", \"true\").lower() == \"true\"\n VISION_PROMPT = _env(\"VISION_PROMPT\", \"请详细描述这张图片的内容\")\n VISION_API_KEY = _env(\"VISION_API_KEY\", \"\")\n VISION_API_BASE = _env(\"VISION_API_BASE\", \"https://open.bigmodel.cn/api/paas/v4\")\n VISION_MODEL_NAME = _env(\"VISION_MODEL_NAME\", \"glm-4.6v\")\n VISION_OVERRIDE = _env(\"VISION_OVERRIDE\", \"false\").lower() == \"true\"\n # 启动时预热 VLM(后台发一次低 token dummy 请求,摊薄首次 look_around 冷启动)\n VISION_WARMUP = _env(\"VISION_WARMUP\", \"true\").lower() == \"true\"\n # look_around 短时去重窗口(秒):窗口内同 focus+camera 的重复观察直接复用上次结果\n VISION_LOOK_AROUND_DEDUP = float(_env(\"VISION_LOOK_AROUND_DEDUP\", \"10\"))\n\n # ── OCR ──\n OCR_MODEL = _env(\"OCR_MODEL\", \"deepseek-ocr\")\n OCR_BASE_URL = _env(\"OCR_BASE_URL\", \"http://localhost:4502\")\n OCR_UNLOAD_AFTER_USE = _env(\"OCR_UNLOAD_AFTER_USE\", \"true\").lower() == \"true\"\n\n # ── 文档解析 ──\n TWO_MD_API = _env(\"TWO_MD_API\", \"http://localhost:8000\")\n\n # ── 摄像头 ──\n CAMERA_ENABLED = _env(\"CAMERA_ENABLED\", \"true\").lower() == \"true\"\n CAMERA_DEVICE_ID = int(_env(\"CAMERA_DEVICE_ID\", \"0\"))\n\n # ── 主动视觉感知 ──\n ACTIVE_VISION_ENABLED = _env(\"ACTIVE_VISION_ENABLED\", \"false\").lower() == \"true\"\n ACTIVE_VISION_INTERVAL = int(_env(\"ACTIVE_VISION_INTERVAL\", \"300\"))\n ACTIVE_VISION_CAMERA = _env(\"ACTIVE_VISION_CAMERA\", \"\") # 周期观察用哪台摄像头(逻辑名), 空=主摄像头\n ACTIVE_VISION_PROACTIVE_COOLDOWN = int(_env(\"ACTIVE_VISION_PROACTIVE_COOLDOWN\", \"600\"))\n ACTIVE_VISION_PERIODIC_NOTIFY_MIN = int(_env(\"ACTIVE_VISION_PERIODIC_NOTIFY_MIN\", \"30\"))\n\n # ── 闲置时感知 (idle-time sensing) ──\n # 主开关:客户端在未按 Enter 录音时持续监听麦克风,感知到响动就捕捉并上报后端 ASR 存档。\n SENSING_ENABLED = _env_bool(\"SENSING_ENABLED\", \"false\")\n # 同一客户端两次上报之间的最小间隔(秒),服务端/客户端都会用它节流。\n SENSING_COOLDOWN = max(1, int(_env(\"SENSING_COOLDOWN\", \"60\")))\n # 单条感知片段的最大时长(秒)。\n SENSING_MAX_RECORD_SECS = max(1.0, float(_env(\"SENSING_MAX_RECORD_SECS\", \"6.0\")))\n # 允许 AI 通过工具查询闲置时感知记录(false 时工具返回未启用)。\n SENSING_AI_ACCESS_ENABLED = _env_bool(\"SENSING_AI_ACCESS_ENABLED\", \"false\")\n\n # ═══════════════════════════════════════════════════════════════════════\n # 第七层: 高级功能\n # ═══════════════════════════════════════════════════════════════════════\n\n # ── 认证 ──\n AUTH_SESSION_DAYS = int(_env(\"AUTH_SESSION_DAYS\", \"30\"))\n AUTH_PAIRING_DIGITS = int(_env(\"AUTH_PAIRING_DIGITS\", \"8\"))\n AUTH_PAIRING_TIMEOUT = int(_env(\"AUTH_PAIRING_TIMEOUT\", \"300\"))\n AUTH_WEBAUTHN_RP_NAME = _env(\"AUTH_WEBAUTHN_RP_NAME\", \"DSN-exp\")\n AUTH_TOTP_ISSUER = _env(\"AUTH_TOTP_ISSUER\", \"DSN-exp\")\n\n # ── 任务管理 ──\n TASK_MANAGER_ENABLED = _env(\"TASK_MANAGER_ENABLED\", \"true\").lower() == \"true\"\n TASK_MAX_WORKERS = int(_env(\"TASK_MAX_WORKERS\", \"5\"))\n TASK_COMPLEXITY_THRESHOLD = float(_env(\"TASK_COMPLEXITY_THRESHOLD\", \"0.4\"))\n REMINDER_CHECK_INTERVAL = int(_env(\"REMINDER_CHECK_INTERVAL\", \"60\"))\n REMINDER_LIST_LIMIT = max(1, int(_env(\"REMINDER_LIST_LIMIT\", \"50\")))\n TASK_NOTIFICATION_ENABLED = _env(\"TASK_NOTIFICATION_ENABLED\", \"true\").lower() == \"true\"\n ACTION_TIMEOUT = int(_env(\"ACTION_TIMEOUT\", \"300\"))\n\n # ── Agent 循环 ──\n AGENT_MAX_STEPS = max(1, int(_env(\"AGENT_MAX_STEPS\", \"10\")))\n AGENT_TOKEN_BUDGET = max(1, int(_env(\"AGENT_TOKEN_BUDGET\", \"1000000\")))\n AGENT_TIMEOUT_SECONDS = max(1.0, float(_env(\"AGENT_TIMEOUT_SECONDS\", \"120\")))\n\n # ── 双模协同 ──\n DUAL_ENABLED = _env_bool(\"DUAL_ENABLED\", \"false\")\n INSTANT_MODEL = _env(\"INSTANT_MODEL\", \"google/gemma-3-4b\")\n INSTANT_MODEL_URL = _env(\"INSTANT_MODEL_URL\", None) or _env(\"LMSTUDIO_BASE_URL\", \"http://localhost:4501\")\n INSTANT_TEMPERATURE = float(_env(\"INSTANT_TEMPERATURE\", \"0.6\"))\n INSTANT_MAX_TOKENS = int(_env(\"INSTANT_MAX_TOKENS\", \"512\"))\n INSTANT_TIMEOUT = max(1, int(_env(\"INSTANT_TIMEOUT\", \"15\")))\n DUAL_MAIN_WORKERS = max(1, int(_env(\"DUAL_MAIN_WORKERS\", \"3\")))\n INSTANT_CONTEXT_MAX_MESSAGES = max(4, int(_env(\"INSTANT_CONTEXT_MAX_MESSAGES\", \"30\")))\n INSTANT_CONTEXT_COMPRESS_THRESHOLD = max(6, int(_env(\"INSTANT_CONTEXT_COMPRESS_THRESHOLD\", \"40\")))\n\n # ── 售后维护 ──\n MAINTENANCE_ENABLED = _env(\"MAINTENANCE_ENABLED\", \"true\").lower() == \"true\"\n MAINTENANCE_IDLE_TIMEOUT_MINUTES = int(_env(\"MAINTENANCE_IDLE_TIMEOUT_MINUTES\", \"60\"))\n MAINTENANCE_SCHEDULE_STRATEGY = _env(\"MAINTENANCE_SCHEDULE_STRATEGY\", \"predictive\")\n MAINTENANCE_FIXED_HOUR = int(_env(\"MAINTENANCE_FIXED_HOUR\", \"4\"))\n MAINTENANCE_SCHEDULE_CHECK_INTERVAL = int(_env(\"MAINTENANCE_SCHEDULE_CHECK_INTERVAL\", \"60\"))\n HIBERNATE_MAX_QUEUE = int(_env(\"HIBERNATE_MAX_QUEUE\", \"100\"))\n MAINTENANCE_PREDICTIVE_MIN_FREE_HOURS = int(_env(\"MAINTENANCE_PREDICTIVE_MIN_FREE_HOURS\", \"3\"))\n MAINTENANCE_PREDICTIVE_MAX_HOUR = int(_env(\"MAINTENANCE_PREDICTIVE_MAX_HOUR\", \"8\"))\n MAINTENANCE_PREDICTIVE_IDLE_TRIGGER_MINUTES = int(_env(\"MAINTENANCE_PREDICTIVE_IDLE_TRIGGER_MINUTES\", \"60\"))\n MAINTENANCE_PREDICTIVE_MIN_DATA_SAMPLES = int(_env(\"MAINTENANCE_PREDICTIVE_MIN_DATA_SAMPLES\", \"50\"))\n MAINTENANCE_RETRY_ON_FAILURE = _env_bool(\"MAINTENANCE_RETRY_ON_FAILURE\", \"true\")\n MAINTENANCE_RETRY_DELAY_MINUTES = int(_env(\"MAINTENANCE_RETRY_DELAY_MINUTES\", \"30\"))\n MAINTENANCE_ESTIMATE_PER_TASK_SECONDS = int(_env(\"MAINTENANCE_ESTIMATE_PER_TASK_SECONDS\", \"300\"))\n MAINTENANCE_TASK_TIMEOUT_SECONDS = int(_env(\"MAINTENANCE_TASK_TIMEOUT_SECONDS\", \"600\"))\n\n # ── 驻守模型 ──\n STEWARD_ENABLED = _env(\"STEWARD_ENABLED\", \"true\").lower() == \"true\"\n STEWARD_MODEL_TYPE = _env(\"STEWARD_MODEL_TYPE\", \"openai\")\n STEWARD_MODEL_NAME = _env(\"STEWARD_MODEL_NAME\", \"google/gemma-3-4b\")\n STEWARD_TIMEOUT = int(_env(\"STEWARD_TIMEOUT\", \"300\"))\n STEWARD_TEMPERATURE = float(_env(\"STEWARD_TEMPERATURE\", \"0.5\"))\n STEWARD_MAX_TOKENS = int(_env(\"STEWARD_MAX_TOKENS\", \"2048\"))\n STEWARD_HISTORY_LIMIT = int(_env(\"STEWARD_HISTORY_LIMIT\", \"40\"))\n\n # ── 语义缓存 ──\n SEMANTIC_CACHE_ENABLED = _env(\"SEMANTIC_CACHE_ENABLED\", \"false\").lower() == \"true\"\n SEMANTIC_CACHE_DIR = _env(\"SEMANTIC_CACHE_DIR\", \".dsn/semantic_cache\")\n SEMANTIC_CACHE_SIMILARITY_THRESHOLD = float(_env(\"SEMANTIC_CACHE_SIMILARITY_THRESHOLD\", \"0.85\"))\n SEMANTIC_CACHE_MAX_ENTRIES = int(_env(\"SEMANTIC_CACHE_MAX_ENTRIES\", \"5000\"))\n\n # ── 性能模式 ──\n PERFORMANCE_MODE = _env(\"PERFORMANCE_MODE\", \"realtime\") # \"realtime\" | \"fastcache\"\n\n # ── 观察日记 ──\n NOTEBOOK_ENABLED = _env(\"NOTEBOOK_ENABLED\", \"true\").lower() == \"true\"\n NOTEBOOK_FREQUENCY = int(_env(\"NOTEBOOK_FREQUENCY\", \"10\"))\n\n # ── 工作区 ──\n WORKSPACE_DIR = _env(\"WORKSPACE_DIR\", \".dsn/workspace\")\n\n # ── 外部验证 ──\n LITTLESKIN_CLIENT_ID = _env(\"LITTLESKIN_CLIENT_ID\", \"\")\n LITTLESKIN_CLIENT_SECRET = _env(\"LITTLESKIN_CLIENT_SECRET\", \"\")\n\n # ── 调试 ──\n DEBUG_PLAY_AS_MODEL = _env(\"DEBUG_PLAY_AS_MODEL\", \"false\").lower() == \"true\"\n DEBUG_PLAY_AS_MODEL_PORT = int(_env(\"DEBUG_PLAY_AS_MODEL_PORT\", \"5050\"))\n\n # ── 可选客户端 / 管理端 ──\n WEB_ADMIN_ENABLED = _env_bool(\"WEB_ADMIN_ENABLED\", \"false\")\n WEB_ADMIN_HOST = _env(\"WEB_ADMIN_HOST\", \"127.0.0.1\")\n WEB_ADMIN_PORT = int(_env(\"WEB_ADMIN_PORT\", \"8008\"))\n WEB_ADMIN_PASSWORD = _env(\"WEB_ADMIN_PASSWORD\", \"\")\n DSN_BASE_URL = _env(\"DSN_BASE_URL\", \"http://127.0.0.1:5000\")\n DSN_AGENT_TIMEOUT = int(_env(\"DSN_AGENT_TIMEOUT\", \"300\"))\n DSN_HOST = _env(\"DSN_HOST\", \"127.0.0.1\")\n DSN_PORT = int(_env(\"DSN_PORT\", \"5000\"))\n PSYCHOSCOPE_HOST = _env(\"PSYCHOSCOPE_HOST\", \"127.0.0.1\")\n PSYCHOSCOPE_PORT = int(_env(\"PSYCHOSCOPE_PORT\", \"5000\"))\n PSYCHOSCOPE_DEBUG = _env_bool(\"PSYCHOSCOPE_DEBUG\", \"false\")\n\n @classmethod\n def load_skill_configs(cls):\n \"\"\"加载 skills/*/skill.env,让技能能声明自己的环境配置。\"\"\"\n try:\n from dotenv import load_dotenv as _load_dotenv\n except ImportError:\n return\n import logging\n\n logger = logging.getLogger(\"Config\")\n for base_key in (\"builtin\", \"custom\"):\n base_dir = Path(__file__).parent / \"skills\" / base_key\n if not base_dir.exists():\n continue\n for skill_dir in sorted(base_dir.iterdir()):\n env_file = skill_dir / \"skill.env\"\n if not env_file.exists():\n continue\n _load_dotenv(env_file)\n count = 0\n for line in env_file.read_text(encoding=\"utf-8\").splitlines():\n stripped = line.strip()\n if not stripped or stripped.startswith(\"#\") or \"=\" not in stripped:\n continue\n key, raw = stripped.split(\"=\", 1)\n key = key.strip()\n raw = raw.strip()\n value = os.environ.get(key, raw)\n try:\n if raw.lower() in (\"true\", \"false\"):\n value = value.lower() == \"true\"\n elif \".\" in raw:\n value = float(value)\n else:\n value = int(value)\n except (TypeError, ValueError):\n pass\n setattr(cls, key, value)\n count += 1\n logger.info(\"skill.env: %s (%d 项)\", skill_dir.name, count)\n"}}}}