diff --git a/.gitignore b/.gitignore index 0ba05ce8f..5cc7a28bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ +# CodeGraph index (generated, not tracked) +.codegraph/ + +# Generated architecture docs +docs/codegraph-architecture.md + # Workflow docs (local only) workflow create_issue diff --git a/app/src/lib/i18n/chunks/zh-CN-3.ts b/app/src/lib/i18n/chunks/zh-CN-3.ts index 1846dbc5b..9a5827678 100644 --- a/app/src/lib/i18n/chunks/zh-CN-3.ts +++ b/app/src/lib/i18n/chunks/zh-CN-3.ts @@ -33,14 +33,13 @@ const zhCN3: TranslationMap = { 'workspace.building': '构建中...', 'workspace.buildSummaryTrees': '构建摘要树', 'workspace.viewVault': '查看存储库', - 'workspace.openingVaultTitle': 'Opening vault in Obsidian', + 'workspace.openingVaultTitle': '在 Obsidian 中打开存储库', 'workspace.openingVaultMessage': - "If Obsidian doesn't open, install it from obsidian.md or use Reveal Folder. Vault path:", - 'workspace.openVaultFailedTitle': "Couldn't open vault in Obsidian", - 'workspace.openVaultFailedMessage': - 'Use Reveal Folder to open the vault directory directly. Vault path:', - 'workspace.revealVaultFailed': "Couldn't reveal vault folder", - 'workspace.revealFolder': 'Reveal Folder', + '如果 Obsidian 没有打开,请从 obsidian.md 安装或使用"显示文件夹"。存储库路径:', + 'workspace.openVaultFailedTitle': '无法在 Obsidian 中打开存储库', + 'workspace.openVaultFailedMessage': '使用"显示文件夹"直接打开存储库目录。存储库路径:', + 'workspace.revealVaultFailed': '无法显示存储库文件夹', + 'workspace.revealFolder': '显示文件夹', 'workspace.graphLoadFailed': '无法加载记忆图谱', 'workspace.loadingGraph': '正在加载记忆图谱...', 'workspace.graphViewMode': '记忆图谱视图模式', diff --git a/app/src/lib/i18n/chunks/zh-CN-5.ts b/app/src/lib/i18n/chunks/zh-CN-5.ts index ff6312d73..8c6a4f068 100644 --- a/app/src/lib/i18n/chunks/zh-CN-5.ts +++ b/app/src/lib/i18n/chunks/zh-CN-5.ts @@ -443,39 +443,38 @@ const zhCN5: TranslationMap = { 'settings.appearance.modeSystem': '跟随系统', 'settings.appearance.modeSystemDesc': '跟随操作系统外观设置。', 'settings.appearance.helperText': - 'Dark mode switches the entire app — chat, settings, panels — to a dim palette. "Match system" follows your OS appearance and updates live.', - 'settings.mascot.characterPreview': 'Preview', - 'settings.mascot.characterStates': 'states', - 'settings.mascot.characterVisemes': 'visemes', - 'settings.mascot.colorAria': 'OpenHuman color', - 'settings.mascot.colorBlack': 'Black', - 'settings.mascot.colorBurgundy': 'Burgundy', - 'settings.mascot.colorGreen': 'Green', - 'settings.mascot.colorNavy': 'Navy', - 'settings.mascot.colorYellow': 'Yellow', - 'settings.mascot.libraryUnavailable': 'OpenHuman library unavailable', + '深色模式会将整个应用——聊天、设置、面板——切换为暗色调。"跟随系统"会同步你的操作系统外观并实时更新。', + 'settings.mascot.characterPreview': '预览', + 'settings.mascot.characterStates': '状态', + 'settings.mascot.characterVisemes': '视素', + 'settings.mascot.colorAria': 'OpenHuman 颜色', + 'settings.mascot.colorBlack': '黑色', + 'settings.mascot.colorBurgundy': '酒红色', + 'settings.mascot.colorGreen': '绿色', + 'settings.mascot.colorNavy': '深蓝色', + 'settings.mascot.colorYellow': '黄色', + 'settings.mascot.libraryUnavailable': 'OpenHuman 资源库不可用', 'settings.mascot.title': 'OpenHuman', - 'settings.developerMenu.mcpServer.title': 'MCP Server', - 'settings.developerMenu.mcpServer.desc': 'Configure external MCP clients to connect to OpenHuman', - 'settings.mcpServer.title': 'MCP Server', - 'settings.mcpServer.toolsSectionTitle': 'Available Tools', + 'settings.developerMenu.mcpServer.title': 'MCP 服务器', + 'settings.developerMenu.mcpServer.desc': '配置外部 MCP 客户端以连接到 OpenHuman', + 'settings.mcpServer.title': 'MCP 服务器', + 'settings.mcpServer.toolsSectionTitle': '可用工具', 'settings.mcpServer.toolsSectionDesc': - 'Tools exposed via the MCP stdio server when running openhuman-core mcp', - 'settings.mcpServer.configSectionTitle': 'Client Configuration', - 'settings.mcpServer.configSectionDesc': - 'Select your MCP client to generate the correct configuration snippet', - 'settings.mcpServer.copySnippet': 'Copy to Clipboard', - 'settings.mcpServer.copied': 'Copied!', - 'settings.mcpServer.openConfigFile': 'Open Config File', + '运行 openhuman-core mcp 时通过 MCP stdio 服务器暴露的工具', + 'settings.mcpServer.configSectionTitle': '客户端配置', + 'settings.mcpServer.configSectionDesc': '选择你的 MCP 客户端以生成对应的配置代码片段', + 'settings.mcpServer.copySnippet': '复制到剪贴板', + 'settings.mcpServer.copied': '已复制!', + 'settings.mcpServer.openConfigFile': '打开配置文件', 'settings.mcpServer.binaryPathNotFound': - 'OpenHuman binary not found. If running from source, build with: cargo build --bin openhuman-core', - 'settings.mcpServer.openConfigError': 'Failed to open config file', + '未找到 OpenHuman 二进制文件。如果使用源码运行,请执行:cargo build --bin openhuman-core', + 'settings.mcpServer.openConfigError': '打开配置文件失败', 'settings.mcpServer.clientClaudeDesktop': 'Claude Desktop', 'settings.mcpServer.clientCursor': 'Cursor', 'settings.mcpServer.clientCodex': 'Codex', 'settings.mcpServer.clientZed': 'Zed', - 'settings.mcpServer.configFilePath': 'Config file', - 'settings.mcpServer.clientSelectorAriaLabel': 'MCP client selector', + 'settings.mcpServer.configFilePath': '配置文件', + 'settings.mcpServer.clientSelectorAriaLabel': 'MCP 客户端选择器', }; export default zhCN5; diff --git a/docs/SECURITY_AUDIT.md b/docs/SECURITY_AUDIT.md new file mode 100644 index 000000000..8db0453a3 --- /dev/null +++ b/docs/SECURITY_AUDIT.md @@ -0,0 +1,211 @@ +# OpenHuman Security Audit — Architecture & Data Flow Analysis + +> Date: 2026-05-21 +> Author: JAYcodr (fork analysis, not an official audit) +> Scope: Architecture overview, trust boundaries, credential flow, attack surface + +--- + +## 1. System Overview + +OpenHuman is a desktop AI assistant with a **Rust core** running in-process inside a Tauri desktop host, and a **React/TypeScript frontend**. Communication between frontend and core happens via two channels: + +| Channel | Protocol | Auth | +|---|---|---| +| Primary | Socket.IO (bidirectional streaming) | Session-baked connection auth | +| Secondary | HTTP JSON-RPC | Basic Auth (`WWW-Authenticate` realm) | + +**No sidecar binary** — core runs as a tokio task inside the Tauri process (`core_process.rs`). + +--- + +## 2. Module Map + +### Core (`src/openhuman/`) — 66 domains + +| Category | Domains | +|---|---| +| Agent | `agent`, `agent_experience`, `agent_tool_policy` | +| Memory | `memory` (stm_recall, docs), `embeddings`, `learning`, `workspace` | +| Skills | `skills` (metadata-only), `mcp_client`, `mcp_clients`, `mcp_server`, `composio` | +| Channels | `channels` (dispatch), `telegram`, `discord`, `whatsapp_data`, `webview_accounts` | +| Infrastructure | `http_host`, `socket` (Socket.IO server), `runtime_node`, `runtime_python` | +| Business Logic | `billing`, `credentials`, `vault`, `encryption`, `notifications`, `webhooks`, `approval`, `cron`, `meet`, `meet_agent`, `team`, `threads`, `todos` | +| UI-adjacent | `accessibility`, `autocomplete`, `screen_intelligence`, `voice` | +| Other | `config`, `health`, `heartbeat`, `doctor`, `migration`, `update`, `security`, `prompt_injection` | + +### Transport (`src/core/`) + +| File | Role | +|---|---| +| `src/core/jsonrpc.rs` | JSON-RPC over HTTP, method dispatch | +| `src/core/socketio.rs` | Socket.IO server, `WebChannelEvent` struct for streaming | +| `src/core/auth.rs` | HTTP Basic Auth handler | +| `src/openhuman/http_host/rpc.rs` | JSON-RPC endpoint (`list()` function) | +| `src/openhuman/http_host/auth.rs` | `WWW-Authenticate` header, `unauthorized_response()` | + +### Event Bus (`src/core/event_bus/`) + +Typed pub/sub + in-process typed request/response: + +```text +publish_global(DomainEvent) → fire-and-forget broadcast +register_native_global(method, handler) → one-to-one typed dispatch +request_native_global(method, req) → call and wait for response +``` + +**Domain events:** `agent`, `memory`, `channel`, `skill`, `tool`, `webhook`, `mcp_client`, `system`, `approval`, `cron`, `triage` + +--- + +## 3. Credential & Token Flows + +### Core RPC Auth + +- HTTP JSON-RPC protected by **HTTP Basic Auth** +- Realm: `"OpenHuman Hosted Directory"` +- Per-launch bearer token stored in `OPENHUMAN_CORE_TOKEN` env var +- Frontend obtains bearer via `invoke('core_rpc_token')` Tauri command + +### Stored Credentials + +- `credentials` domain manages credential storage +- `encryption` domain handles at-rest encryption +- `auth-profiles.json` — auth data referenced by `settings.ai.apiKeysEncrypted` i18n key + +### MCP Server Auth + +- Composio API key stored via `settings.composio.apiKeyStoredPlaceholder` +- MCP client config (Claude Desktop, Cursor, Codex, Zed) generated in settings panel + +--- + +## 4. Trust Boundaries & Attack Surface + +### Boundary 1: External Channels (Telegram, Discord, WhatsApp, etc.) + +- Inbound messages from third-party messaging platforms flow through `channels/runtime/dispatch.rs` +- Each provider scanner runs as native CDP/scraping — **no JS injection** in migrated providers +- `ChannelInboundMessage` event published to event bus + +**Risk:** Third-party message content is untrusted. Prompt injection possible if message content is rendered or echoed without sanitization. The `prompt_injection` domain exists as a guard. + +### Boundary 2: MCP Tool Bridge (`mcp_client/`, `mcp_clients/`) + +- External MCP servers connect via stdio or HTTP +- Tools exposed through `tool_registry` +- `McpClientToolExecuted` events published + +**Risk:** MCP tools are external services. Tool output flows back into agent context. No obvious output sanitization in the tool execution path. + +### Boundary 3: Skill Runtime (Removed) + +- QuickJS / `rquickjs` runtime was **removed** (PR #1061) +- `src/openhuman/skills/` is now metadata-only +- No dynamic code execution from skill packages + +**Risk:** Significantly reduced vs. prior architecture. + +### Boundary 4: Local File System Access + +- `workspace`, `vault`, `webview_accounts` domains have file system access +- `screen_intelligence`, `accessibility` domains capture screen content +- Memory stored via `memory` domain + +**Risk:** Screen capture and file access are high-privilege operations. Controlled by macOS permissions (Accessibility, Screen Recording). + +### Boundary 5: MCP Server Config File + +- Settings panel generates `~/.config/openhuman/mcp.json` for external MCP clients +- Config written via `settings.mcpServer.openConfigFile` / `writeFile` +- Path exposed via `settings.mcpServer.configFilePath` + +**Risk:** If `mcp.json` is world-readable, token theft possible. Worth auditing file permissions on the config directory. + +--- + +## 5. Data Flows + +### Agent Turn (primary AI interaction) + +```text +External message → channels/runtime/dispatch.rs + → request_native_global("agent.run_turn", AgentTurnRequest) + → agent/bus.rs: run_tool_call_loop() + → tool_registry → SkillExecution events + → on_delta mpsc channel → WebChannelEvent (Socket.IO) + → frontend (SocketIOMCPTransportImpl) +``` + +### Memory Recall + +```text +Tool call: memory.recall → memory/stm_recall/recall.rs: stm_recall() + → MemoryRecalled event on event bus + → consumed by skill/mcp_client subscribers +``` + +### Credential Setup + +```text +Frontend settings → core RPC (JSON-RPC over HTTP + Basic Auth) + → credentials domain → encryption domain + → stored to auth-profiles.json +``` + +--- + +## 6. Security Observations (Not Exhaustive) + +### Areas Worth Auditing + +1. **Prompt injection from channel messages** — `prompt_injection` domain exists; need to verify it's applied to all channel inbound paths and not just chat UI +2. **MCP tool output sanitization** — external MCP tool output flows into agent context without obvious filtering +3. **Config directory permissions** — `~/.config/openhuman/` and `mcp.json` permission model not reviewed +4. **Credential encryption** — `encryption` domain used for at-rest encryption; key management model unclear +5. **WebView CSP** — embedded webviews (Telegram, Discord, etc.) loaded under CEF — need to verify CSP headers and iframe restrictions +6. **`OPENHUMAN_CORE_TOKEN` in process env** — bearer token in env var; visible via `/proc/self/environ` on Linux or process inspection on macOS +7. **No rate limiting observed** on HTTP JSON-RPC endpoint + +### Positive Signals + +- QuickJS skill runtime removed — large attack surface eliminated +- CEF webviews for migrated providers have **zero injected JS** — good isolation +- MCP server stdio transport provides sandboxing for external tools +- `security` domain exists — may contain hardening measures not reviewed here + +--- + +## 7. Recommended Next Steps (for Maintainers) + +- [ ] Audit `prompt_injection` domain coverage — is it applied to all channel inbound paths? +- [ ] Document `encryption` domain key management +- [ ] Check file permissions on `~/.config/openhuman/` +- [ ] Add rate limiting to HTTP JSON-RPC endpoint +- [ ] Document MCP tool output handling expectations +- [ ] Review `OPENHUMAN_CORE_TOKEN` lifetime and exposure scope + +--- + +## 8. RPC Method Reference + +JSON-RPC methods follow `domain_operation` pattern: + +```text +memory_recall_memories +memory_recall_context +thread_turn_state_lifecycle +wallet_setup_round_trips_status +tool_registry_lists_and_gets_entries +``` + +Native (event bus) methods: + +```text +agent.run_turn → agent/bus.rs +memory.sync → memory/bus.rs +``` + +--- + +*This document is an independent analysis, not an official security assessment.* \ No newline at end of file diff --git a/gitbooks/features/native-tools/README.zh-CN.md b/gitbooks/features/native-tools/README.zh-CN.md new file mode 100644 index 000000000..9f448e63d --- /dev/null +++ b/gitbooks/features/native-tools/README.zh-CN.md @@ -0,0 +1,42 @@ +--- +description: >- + OpenHuman 智能体开箱即用的完整工具集——研究、编码、 + 控制你的机器、安排任务、回复你,以及调用 118+ 第三方服务。 +icon: toolbox +--- + +# 原生工具 + +OpenHuman 的智能体并非空载交付。智能体背后的每个模型在安装瞬间就有一套精选工具可用——无需插件市场、无需接入 API 密钥、无需注册 MCP 服务器。整个工具带都在盒子里。 + +本页是索引。每个子页面覆盖一个工具族。 + +## 为什么原生提供这些工具 + +纯插件模式意味着工具跑在不同进程里,通过 RPC 交互,各自维护认证和打包逻辑。这对于开放式扩展性没问题,但对于每个智能体都需要的**核心**工具(读文件、搜索网页、编辑代码、设提醒、加入会议),以内置方式提供意味着: + +* 一致的错误处理。 +* 零安装门槛。 +* 所有输出自动经过[智能 Token 压缩](../token-compression.zh-CN.md)。 +* 可预测的安全边界——文件系统工具遵守工作区作用域,网络工具通过 OpenHuman 代理。 + +## 工具带 + +| 类别 | 包含内容 | +| ------ | -------------- | +| [网络搜索](web-search.zh-CN.md) | 无需自带 API key 搜索实时网页。 | +| [网页抓取](web-scraper.zh-CN.md) | 从任意 URL 拉取干净文本——文章、文档、README。 | +| [编码器](coder.md) | 读/写/编辑/补丁文件,glob,grep,git,lint,test。 | +| [浏览器与计算机控制](browser-and-computer.zh-CN.md) | 打开 URL、截图、点击、输入、移动鼠标。 | +| [定时任务与调度](cron.md) | 循环任务、一次性提醒、定时智能体运行。 | +| [语音](voice.md) | 语音转文字输入、文字转语音输出、实时 Google Meet 智能体。 | +| [记忆工具](memory-tools.md) | 在[记忆树](../obsidian-wiki/memory-tree.zh-CN.md)中召回、存储、遗忘和搜索。 | +| [第三方集成](../integrations/README.md) | 智能体视角中的 [118+ 已连接服务](../integrations/README.md)。 | +| [智能体协作](agent-coordination.md) | 生成子智能体、委托给技能、规划、询问用户。 | +| [系统与工具](system-and-utilities.md) | Shell、node、SQL、当前时间、推送通知、LSP。 | + +## 另见 + +* [智能 Token 压缩](../token-compression.zh-CN.md) —— 保持工具输出成本有界的机制。 +* [第三方集成](../integrations/README.md) —— 118+ 目录的面向用户介绍和 OAuth 流程。 +* [隐私与安全](../privacy-and-security.md) —— 每个工具运行所在的安全边界。 diff --git a/gitbooks/features/native-tools/browser-and-computer.zh-CN.md b/gitbooks/features/native-tools/browser-and-computer.zh-CN.md new file mode 100644 index 000000000..50cfe5238 --- /dev/null +++ b/gitbooks/features/native-tools/browser-and-computer.zh-CN.md @@ -0,0 +1,33 @@ +--- +description: 原生打开 URL、截图、点击、输入、移动鼠标。 +icon: display +--- + +# 浏览器与计算机控制 + +当智能体需要像人一样*使用*你的机器时——打开页面、截图、点击按钮、输入短语——这些工具就是它做这些事的方式。 + +## 浏览器 + +* **打开**一个 URL,进入智能体可以回读的嵌入式 webview。 +* **截图**当前页面。 +* **检查**图像输出和元数据,以便智能体描述它看到的内容。 + +浏览器界面通过 CEF(Chromium Embedded Framework)运行,并包含一个安全层,限制页面能做什么。参见 [Chromium Embedded Framework](../../developing/cef.md) 了解平台详情。 + +## 计算机(鼠标 + 键盘) + +* **鼠标**——移动、点击、拖拽。 +* **键盘**——输入文本、发送快捷键。 +* **类人路径**——移动和点击遵循类人轨迹,而非瞬移,因此不会触发简单的机器人检测。 + +## 适用于 + +* 驱动没有 API 或没有[原生集成](../integrations/README.md)的网站。 +* 单次截图不够的多步骤 UI 流程。 +* 在聊天中自动化本地应用。 + +## 另见 + +* [网页抓取](web-scraper.zh-CN.md) —— 当你只需要文章而非整个页面时。 +* [Chromium Embedded Framework](../../developing/cef.md) —— 运行时浏览器层。 diff --git a/gitbooks/features/native-tools/web-scraper.zh-CN.md b/gitbooks/features/native-tools/web-scraper.zh-CN.md new file mode 100644 index 000000000..932b7469c --- /dev/null +++ b/gitbooks/features/native-tools/web-scraper.zh-CN.md @@ -0,0 +1,31 @@ +--- +description: 一个专门的"获取并阅读"工具,返回干净的文本而非原始 HTML。 +icon: globe +--- + +# 网页抓取 + +一个专门构建的获取工具,区别于通用的 `http_request` / `curl`。它的存在是因为智能体不需要原始 HTML——它需要的是*文章*。 + +## 功能 + +* 获取一个 URL。 +* 剥离 Boilerplate(导航、广告、页脚、脚本)。 +* 返回智能体可以推理的干净文本。 + +## 护栏 + +* 响应上限 1 MB——大页面被截断,而非静默丢弃。 +* 20 秒超时——慢速服务器不会阻塞对话。 +* 遵守与其他网络工具相同的代理和 URL 防护规则。 + +## 适用于 + +* 阅读文章、博客文章、文档页面、GitHub README,去除噪音。 +* 跟进[网络搜索](web-search.zh-CN.md)的结果。 +* 按需摘要单个页面。 + +## 另见 + +* [网络搜索](web-search.zh-CN.md) —— 找到要输入抓取器的 URL。 +* [智能 Token 压缩](../token-compression.zh-CN.md) —— 在长页面到达模型之前对其进行修剪。 diff --git a/gitbooks/features/native-tools/web-search.zh-CN.md b/gitbooks/features/native-tools/web-search.zh-CN.md new file mode 100644 index 000000000..1237f07c5 --- /dev/null +++ b/gitbooks/features/native-tools/web-search.zh-CN.md @@ -0,0 +1,23 @@ +--- +description: 智能体可直接调用的原生搜索工具——无需 API key。 +icon: magnifying-glass +--- + +# 网络搜索 + +智能体可以自行搜索实时网页。由服务器端代理(Parallel)支持,所以你无需携带搜索 API key,该工具返回标题、摘要片段和 URL,供后续跟进。 + +## 适用于 + +* 研究——"X 的最新动态是什么"。 +* 引用追踪——"为我找到 Y 的三个来源"。 +* 回答前的事实核查——如果智能体不够自信,会快速搜索。 + +## 与通用 HTTP 的区别 + +一个纯粹的 `http_request` 工具可以获取 URL 但无法*找到* URL。网络搜索是发现层:它为智能体挑选正确的 URL,然后交给[网页抓取](web-scraper.zh-CN.md)进行实际阅读。 + +## 另见 + +* [网页抓取](web-scraper.zh-CN.md) —— 获取并清理特定 URL。 +* [智能 Token 压缩](../token-compression.zh-CN.md) —— 搜索摘要片段在进入模型之前被压缩。 diff --git a/gitbooks/features/obsidian-wiki/README.zh-CN.md b/gitbooks/features/obsidian-wiki/README.zh-CN.md new file mode 100644 index 000000000..b3280d242 --- /dev/null +++ b/gitbooks/features/obsidian-wiki/README.zh-CN.md @@ -0,0 +1,53 @@ +--- +description: >- + 每个记忆块也作为 Markdown 文件存在于与你 Obsidian 兼容的存储库中, + 你可以打开和编辑。灵感来自 Karpathy 的 obsidian-wiki 工作流。 +icon: book-open +--- + +# Obsidian 风格的记忆 + +

OpenHuman 记忆在 Obsidian 中的预览。来自各种来源(GMail、Slack、Whatsapp 等)的数据被组织成一棵记忆树。

+ +OpenHuman 的记忆不是一个黑箱。智能体在其上推理的相同块作为普通的 `.md` 文件写入你工作区内的存储库中。你可以在 [Obsidian](https://obsidian.md) 中打开它,浏览、编辑、手动链接笔记,智能体都会看到你的改动。 + +设计直接灵感来自 [Andrej Karpathy 的 obsidian-wiki 工作流](https://x.com/karpathy/status/2039805659525644595):一个个人 wiki,你生活中每个有趣的事物最终都成为一个可链接的笔记。 + +## 存储库在哪里 + +```text +/ +└── wiki/ + ├── summaries/ # 自动生成的源 / 主题 / 全局摘要 + ├── notes/ # 你的手写笔记(自由格式) + └── … # 每个已连接工具包的文件夹 +```text + +`summaries/` 文件夹按层级布局:全局树按日期,源树按源,主题树按实体。每个文件的前置元数据携带来源(源 id、时间范围、作用域),以便智能体可以将任何声明追溯到产生它的块。 + +## 打开存储库 + +在桌面 app 中,**记忆**标签页有一个**"在 Obsidian 中查看存储库"**按钮。它使用 `obsidian://open?path=...` 深度链接,所以你需要已安装 Obsidian。 + +你也可以在任何编辑器中打开该文件夹,它其实就是 Markdown。文件之间的链接使用标准的 `[[wiki-link]]` 语法,因此 Obsidian 的图谱视图、反向链接和标签浏览器开箱即用。 + +## 手动编辑笔记 + +`wiki/notes/` 中的任何内容都会被纳入摄取范围。处理 Gmail 和 Slack 的相同流水线会获取你的手写笔记,对它们进行分块、评分,并与其他所有内容一起折叠到主题树和全局树中。 + +这意味着你可以: + +* 将会议笔记放入 `wiki/notes/2026-05-08-board-call.md`,智能体明天就会知道背景。 +* 按项目、人物、股票代码维护一个文件,主题树将你的手动笔记视为另一个数据源。 +* 批量导入现有 Obsidian 存储库:将 `.md` 文件放入并触发摄入。 + +## 为什么这很重要 + +你无法信任你无法读取的记忆。大多数"AI 记忆"系统将状态隐藏在不透明的嵌入中;OpenHuman 的存储库则相反,智能体的记忆**确确实实**就是一个你拥有的 Markdown 文件夹。如果智能体弄错了什么,你可以找到文件,修复它,下一次检索就是正确的。 + +这也是最干净的导出方式:即使明天不再使用 OpenHuman,你仍然保留一个完整的个人 wiki。 + +## 另见 + +* [记忆树](memory-tree.zh-CN.md)。产生存储库的流水线。 +* [从集成自动拉取](auto-fetch.zh-CN.md)。存储库如何自行增长。 diff --git a/gitbooks/features/obsidian-wiki/agentmemory-backend.zh-CN.md b/gitbooks/features/obsidian-wiki/agentmemory-backend.zh-CN.md new file mode 100644 index 000000000..155f1831d --- /dev/null +++ b/gitbooks/features/obsidian-wiki/agentmemory-backend.zh-CN.md @@ -0,0 +1,166 @@ +--- +description: >- + 可选的 `Memory` trait 后端,委托给本地运行的 agentmemory REST 服务器, + 适用于在 Claude Code、Cursor、Codex、OpenCode 和 OpenHuman 间 + 自托管 agentmemory 的用户。 +icon: database +--- + +# agentmemory 后端 + +OpenHuman 默认的 `Memory` trait 后端是 `sqlite`——即 [记忆树](memory-tree.zh-CN.md) 中记录的统一存储。对于已经在本地运行 [agentmemory](https://github.com/rohitg00/agentmemory) 的用户——通常是因为他们希望在 Claude Code、Cursor、Codex、OpenCode 和 OpenHuman 之间共享单一持久化存储——OpenHuman 暴露了一个可选后端,将每个 trait 调用代理到 agentmemory 的 REST 层面。 + +选择 `backend = "agentmemory"` 会跳过 OpenHuman 的 SQLite + 嵌入器路径。agentmemory 拥有存储、嵌入和检索层。OpenHuman 成为一个精简的 REST 客户端。 + +## 何时使用 + +在以下情况下使用 agentmemory 后端: + +- 你已经为一个或多个编码智能体运行 `npx -y @agentmemory/agentmemory`,并希望 OpenHuman 共享相同的持久化存储。 +- 你希望混合 BM25 + 向量 + 图检索,而无需在 OpenHuman 端配置单独的嵌入器。 +- 你偏好 agentmemory 的生命周期(整合、保留评分、自动遗忘、图提取)而不是 OpenHuman 的统一存储。 + +在以下情况下保持默认的 `sqlite` 后端: + +- 你想要完全自包含的单进程操作,无外部守护进程依赖。 +- 你依赖 OpenHuman 特定的记忆树功能(分块、密封、摘要树),这些功能在 SQLite 存储之上运行。记忆树流水线不受 trait 后端影响——它在主机的文档存储上操作,正交——但 agentmemory 后端在你已经在其他智能体上标准化使用 agentmemory 时最有价值。 + +## 快速开始 + +1. **安装 + 启动 agentmemory**(一个终端): + + ```bash + npx -y @agentmemory/agentmemory + ``` + + 默认为 `http://localhost:3111`(REST)+ `ws://localhost:49134`(引擎)。首次启动在 `~/.agentmemory/.hmac` 生成 HMAC 密钥并打印一次。 + +2. **在 `config.toml` 中将 OpenHuman 指向它**: + + ```toml + [memory] + backend = "agentmemory" + # 以下为默认值——仅在覆盖时设置。 + # agentmemory_url = "http://localhost:3111" + # agentmemory_secret = "" # HMAC bearer token,可选 + # agentmemory_timeout_ms = 5000 + ``` + +3. **重启 OpenHuman**。Factory 会跳过 SQLite 路径并记录 `[memory::factory] using agentmemory backend at `。 + +就这样。现有的 OpenHuman 调用点(`store`、`recall`、`get`、`list`、`forget`、`namespace_summaries`、`count`、`health_check`)保持不变。 + +## 配置 keys + +| 字段 | 默认值 | 用途 | +| --- | --- | --- | +| `agentmemory_url` | `http://localhost:3111` | agentmemory REST 服务器的基础 URL | +| `agentmemory_secret` | 无 | 可选的 HMAC bearer token。作为 `Authorization: Bearer ` 发送 | +| `agentmemory_timeout_ms` | `5000` | 每个请求的 reqwest 超时 | + +当 `backend == "agentmemory"` 时,以下现有 `MemoryConfig` 字段被**忽略**——agentmemory 通过 `~/.agentmemory/.env` 管理自己的嵌入堆栈: + +- `embedding_provider` +- `embedding_model` +- `embedding_dimensions` +- `sqlite_open_timeout_secs` + +在此路径上设置它们是空操作。本地 AI Ollama 健康检查也不在此路径上运行——agentmemory 的守护进程管理自己的嵌入器生命周期。 + +## 字段映射 + +OpenHuman 的 `MemoryEntry` ↔ agentmemory 传输行: + +| OpenHuman 字段 | agentmemory 字段 | 备注 | +| --- | --- | --- | +| `namespace` | `project` | 空时默认为 `"default"` | +| `key` | `title` | | +| `content` | `content` | | +| `id` | `id` | agentmemory 生成的(`mem_`) | +| `category: Core` | `type: "fact"` | | +| `category: Daily` | `type: "conversation"` | | +| `category: Conversation` | `type: "conversation"` | | +| `category: Custom(s)` | `type: "fact"` + `concepts: [s]` | 自定义标签滚入 concepts 数组以保持可查询性 | +| `session_id` | `sessionIds: [...]` | OpenHuman 暴露单个 id;agentmemory 持久化一个数组 | +| `timestamp` | `updatedAt`(RFC3339) | 如果 `updatedAt` 缺失则回退到 `createdAt` | +| `score`(仅召回命中) | smart-search `score` | 在 `recall` 响应中填充,`get` / `list` 时为 `None` | + +agentmemory 携带额外字段——`concepts`(自动提取)、`files`(路径标签)、`strength`(保留评分)、`version`、`supersedes`(生命周期链)——此后端保留为默认值。它们是 agentmemory 生命周期层的内部字段,不需要通过 OpenHuman 的 trait 进行往返。 + +## Trait 方法 → 端点 + +| `Memory` 方法 | agentmemory REST | 备注 | +| --- | --- | --- | +| `store` | `POST /agentmemory/remember` | `{project, title, content, type, concepts, sessionIds}` | +| `recall` | `POST /agentmemory/smart-search` | 混合 BM25 + 向量 + 图 | +| `get` | `POST /agentmemory/smart-search` | + 客户端精确 title 过滤 | +| `list` | `GET /agentmemory/memories?latest=true&project=` | | +| `forget` | `get(ns, key)` → `POST /agentmemory/forget` | 两步:先解析 id 再 forget | +| `namespace_summaries` | `GET /agentmemory/projects` | 返回 `[{name, count, lastUpdated}]` | +| `count` | `GET /agentmemory/health` | 读取 `memories` 字段 | +| `health_check` | `GET /agentmemory/livez` | | + +`RecallOpts.category`、`RecallOpts.session_id` 和 `RecallOpts.min_score` 作为**客户端过滤**应用于 smart-search 响应。agentmemory 的 REST 面今天不将它们作为服务器端过滤器暴露。对于非常大的召回窗口(limit > 100),建议发出更严格的查询字符串以减少服务器端工作,而不是依赖客户端后过滤。 + +## 安全性 + +当 `agentmemory_secret` 被设置时,客户端遵守 agentmemory 的 v0.9.12 明文 Bearer 守卫约定: + +- **环回主机**(`localhost`、`127.0.0.1`、`::1`)上的 `http://` —— 允许。本地开发路径。 +- **`https://`** 到任何主机 —— 允许。 +- **到非环回主机的明文 HTTP** —— 在构造时发出一次性 stderr 警告。Bearer 在线路上是可观察的。 +- **`AGENTMEMORY_REQUIRE_HTTPS=1`**(进程环境,ASCII 大小写不敏感,匹配 `1` 或 `true`)—— 将警告升级为构造时的硬性拒绝。后端启动失败而不是静默泄露 bearer。 + +生产部署应设置 `AGENTMEMORY_REQUIRE_HTTPS=1`,这样配置错误的 TLS 终结器会明显报错,而不是静默泄露。 + +明文 bearer guard 镜像了 agentmemory [PR #315](https://github.com/rohitg00/agentmemory/pull/315) 中的集成插件 guard,因此在 Hermes / OpenClaw / pi 上看到过相同警告的操作员会在 OpenHuman 上认出它。 + +## 故障模式 + +| 故障 | 后端行为 | +| --- | --- | +| 启动时守护进程不可达 | `from_config` 成功(URL 解析),但首次调用时 `health_check()` 返回 false。Trait 方法向上冒泡 `reqwest` 传输错误 | +| 网络超时 | 按 trait 约定返回 `anyhow::Error`;浮出到调用者 | +| 4xx / 5xx 响应 | 带状态 + body 片段的 `anyhow::Error` | +| Bearer 通过明文非环回(无环境变量) | 一次性 stderr 警告,请求继续 | +| Bearer 通过明文非环回 + `AGENTMEMORY_REQUIRE_HTTPS=1` | 构造时硬性拒绝 | +| 空的 `agentmemory_url` | 构造时硬性拒绝并提示留空以使用默认值 | +| 无效的 URL 语法 | 构造时硬性拒绝并附带解析器错误 | + +**不会自动回退到 SQLite。** 如果守护进程在启动时未运行,后端会明显抛出传输错误。操作员在 `config.toml` 中切回 `backend = "sqlite"` 以恢复。理由:静默的 SQLite 回退会隐藏配置错误的守护进程——"私密、简单、可预测"胜过"神奇容忍"。 + +## 性能说明 + +后端是一个精简的 REST 代理——每个 trait 调用增加一个 HTTP 往返。实际影响: + +- `store` 和 `forget` 是单 RTT。 +- `recall`、`get`、`list` 是单 RTT。 +- 对未知 key 的 `forget` 是两个 RTT(隐式 `get` 查找 + 一个空操作确认)。调用者可以通过检查先前 `list` 的返回值来短路这个。 +- agentmemory 的 REST 默认是 `127.0.0.1` —— 同主机延迟低于一毫秒。通过 HTTPS 终结的管理部署,预期每个 RTT 约 10–30ms。 +- 默认每请求超时为 5 秒。如果在 iii 引擎冷启动时看到间歇性超时,增加 `agentmemory_timeout_ms`;agentmemory 长时间空闲后的第一次请求延迟可达 3–5 秒,取决于持久化状态。 + +## 迁移:从 SQLite 到 agentmemory + +目前没有原地迁移。建议路径: + +1. 通过 OpenHuman 现有的导出 RPC(或直接 SQL)从 SQLite 存储导出你现有的记忆。 +2. 遍历导出,将每一行 POST 到 `/agentmemory/remember`,使用相同的 `project` + `title` + `content`。agentmemory 将分配新 id;OpenHuman 端在首次 `list` 时获取它们。 +3. 设置 `backend = "agentmemory"` 并重启。 + +专门的批量导入路径作为后续跟进。 + +## 实现参考 + +仓库内文件: + +- [`store/agentmemory/mod.rs`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/memory/store/agentmemory/mod.rs) —— 模块表面 +- [`store/agentmemory/backend.rs`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/memory/store/agentmemory/backend.rs) —— `impl Memory for AgentMemoryBackend` +- [`store/agentmemory/client.rs`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/memory/store/agentmemory/client.rs) —— reqwest 包装器 + 明文 bearer guard +- [`store/agentmemory/mapping.rs`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/memory/store/agentmemory/mapping.rs) —— `MemoryEntry` ↔ agentmemory JSON +- [`tests/agentmemory_backend.rs`](https://github.com/tinyhumansai/openhuman/tree/main/tests/agentmemory_backend.rs) —— 12 个 axum-mock 集成测试 + +相关的上游: + +- agentmemory 仓库 —— +- agentmemory REST 约定 —— `~/.agentmemory/.env` keys + 端点列表在 agentmemory README 中 +- v0.9.12 明文 bearer guard —— agentmemory PR #315 diff --git a/gitbooks/features/obsidian-wiki/auto-fetch.zh-CN.md b/gitbooks/features/obsidian-wiki/auto-fetch.zh-CN.md new file mode 100644 index 000000000..994ab611b --- /dev/null +++ b/gitbooks/features/obsidian-wiki/auto-fetch.zh-CN.md @@ -0,0 +1,60 @@ +--- +description: >- + 每隔二十分钟,OpenHuman 遍历每个活跃集成,将新数据整合进你的记忆树。 + 无需提示词,无需编写轮询循环。 +icon: arrows-rotate +--- + +# 自动拉取集成 + +大多数"AI 助手"是被动的:你提问,它们思考,它们回答。OpenHuman 则相反。它持续从你的技术栈中拉取数据,所以当你问"昨晚我的收件箱收到了什么?"时,答案已经在[记忆树](memory-tree.zh-CN.md)里了。 + +## 工作原理 + +一个单一的周期性调度器每二十分钟触发一次。每次触发时,它遍历每个活跃的[集成](../integrations/README.md),查找匹配的原生 provider,如果该连接的距上次同步的时间足够长,就调用 `provider.sync(ctx, SyncReason::Periodic)`。 + +```text +每 20 分钟 + | + v +遍历每个活跃连接(Gmail、Notion、GitHub……) + | + +--> 检查 sync_state(toolkit, connection_id) + | - 上次同步时间戳 + | - 每日预算 + | - 去重集合 + | - 游标 + | + +--> 如果间隔已过 -> provider.sync() + | + +--> 成功 -> record_sync_success(ts) +```text + +这里有几个关键点: + +* **一个全局触发,而不是每个连接一个任务。** 每个用户的连接数很少;一个 20 分钟的触发周期足够了,而且 bookkeeping 很简单。 +* **状态按 `(toolkit, connection_id)` 划分。** 每个连接有自己的游标、上次同步时间戳、去重集合和每日预算。重启时从中重建;即使重启后错过了一次周期性同步也无害,因为下一个触发周期会重新拾取。 +* **原生同步与事件驱动路径共享。** 当 webhook 或 `on_connection_created` 事件触发非周期性同步时,它们在同一个 sync_state 上盖戳,所以调度器不会冗余地重新触发。 +* **错误被记录并静默处理。** 调度器绝不能在其循环中 panic,否则周期性同步会在进程剩余生命周期内静默停止。 + +## 什么进入记忆树 + +每个 provider 负责定义自己的摄入逻辑。例如 Gmail provider 获取一页新消息,运行邮件规范化器,通过相同的手动 UI 摄入路径传输结果,块进入 SQLite,摘要 bucket 被填充,任何被触及的实体都会将主题树标记为脏。 + +其他 providers(GitHub、Slack、Notion……)遵循相同的形状:从游标后获取新项目 → 规范化 → 摄入到[记忆树](memory-tree.zh-CN.md)。 + +## 为什么是 20 分钟触发周期 + +最初设计每 60 秒运行一次。当连接了多个 provider 时,这意味着持续不断的 HTTP 获取和数据库写入,在笔记本上明显繁忙。二十分钟用一点延迟换取明显更少的前台负载。每个 provider 的 `sync_interval_secs` 仍然限制实际同步之间的**最小**延迟;全局触发周期只放宽上限。 + +## 调优和可见性 + +* **每个 provider 的间隔。** 每个原生 provider 声明自己的 `sync_interval_secs`,所以高流量工具包(Gmail)可以比低流量工具包(Stripe)更频繁地同步。 +* **每日预算。** 每个连接有每日请求预算,以保持 API 成本和速率限制合理。 +* **日志。** 同步活动以 debug 级别记录在 core 日志中。 + +## 另见 + +* [第三方集成](../integrations/README.md)。自动拉取运行的连接器层。 +* [记忆树](memory-tree.zh-CN.md)。一切最终到达的地方。 +* [智能 Token 压缩](../token-compression.zh-CN.md)。使"获取一切"保持低成本的原因。 diff --git a/gitbooks/features/obsidian-wiki/memory-tree.zh-CN.md b/gitbooks/features/obsidian-wiki/memory-tree.zh-CN.md new file mode 100644 index 000000000..aa1cffa7e --- /dev/null +++ b/gitbooks/features/obsidian-wiki/memory-tree.zh-CN.md @@ -0,0 +1,172 @@ +--- +description: >- + OpenHuman 的本地优先存储库。从工具中摄入数据,规范化为 Markdown, + 分块,评分,并折叠为层级化的摘要树。 +icon: tree +--- + +# 记忆树 + +

记忆树。所有文档的高度压缩视图。

+ +记忆树是 OpenHuman 的存储库。它不是一个披着"记忆"外衣的向量数据库,而是一个确定性的、bucket-sealed(桶密封)处理流水线,将你一天中杂乱的数据流——聊天、邮件、文档、集成同步结果——转化为你机器上结构化的、可查询的、带摘要支撑的 Markdown。 + +## 它做什么 + +每个连接的源都走同样的流水线: + +```text +源适配器(聊天 / 邮件 / 文档) + | + v +规范化 规范化的 Markdown + 来源元数据 + | + v +分块器 确定性的 ID,≤3k token 的有界片段 + | + v +内容存储 原子 .md 文件(正文 + 标签) + | + v +存储 持久化(块、评分、摘要、任务、热度) + | + v +评分 信号 + 向量 + 实体提取 + | + v +源 / 主题 / 全局树 按作用域的摘要树 + | + v +检索 搜索 / 深入 / 主题 / 全局 / 获取 +```text + +热路径(规范化 → 分块 → 快速评分 → 持久化 → 入队后续工作)很快。重型工作——向量生成、实体提取、密封摘要 bucket、每日摘要——在后台 workers 中运行,UI 永远不会阻塞。 + +如果你开启了[本地 AI](../model-routing/local-ai.md),嵌入向量和摘要树的构建可以在**设备上通过 Ollama** 运行;否则它们像其他模型调用一样通过 OpenHuman 后端处理。 + +## 三棵树,三个作用域 + +* **源树**,每个源一个滚动缓冲区(L0),填满后密封为 L1 → L2 → …。每个 Gmail 标签、每个 Slack 频道、每个上传的文档各一棵。 +* **主题树**,按实体懒加载的摘要,由**热度**驱动。某个实体(人、项目、股票代码、仓库)出现得越频繁,其主题树就越积极地被构建和刷新。 +* **全局树**,一个跨当天摄入的所有内容的每日全局摘要。 + +检索可以针对任何作用域:搜索单个源,深入某个主题,或拉取全局摘要。 + +## 它在磁盘上的位置 + +位于你的工作区内(默认 `~/.openhuman`,或 `OPENHUMAN_WORKSPACE` 指向的路径): + +| 路径 | 内容 | +| ------------------------- | ---------------------------------------------- | +| `memory_tree/chunks.db` | 块、评分、摘要、实体索引、任务、热度 | +| `wiki/` | Markdown 存储库 —— 见 [Obsidian Wiki](./) | + +一切都是本地的。除非你明确发送包含原始数据的聊天消息,否则你的原始数据不会离开你的机器。 + +## 为什么是树,而不是向量存储 + +向量存储回答"与这个查询相似的是什么?"记忆需要回答更多: + +* **今天发生了什么?**(全局摘要) +* **这个人的最新情况是什么?**(主题树,热度驱动) +* **上周二下午 3 点 Stripe webhook 说了什么?**(源树 + 来源追溯) + +树给你压缩**和**导航。嵌入向量仍然存在于内部,所以语义搜索继续工作,但上面的结构才是让记忆感觉像大脑而不是一堆碎片的原因。 + +## 流水线如何工作? + +用户看到的功能很简单:连接一个源,智能体就获得了对其的持久记忆。实现这一功能的流水线横跨一条 HTTP 触发的摄入路径、一个持久化的任务队列、一组后台 workers、三个独立的摘要树,以及一个每日 UTC 调度器。 + +### 1. 摄入 + +新的聊天 / 邮件 / 文档到达。热路径将其规范化为 Markdown,用确定性 ID 分块,运行廉价的快速评分,在单个事务中持久化所有内容,将每个块标记为 `pending_extraction`,并为 workers 入队后续工作。 + +这里有三个重要属性: + +* **确定性的。** 块 ID 是内容寻址的,所以对相同输入重新运行摄入永远不会产生重复。 +* **快速的。** 这条路径中没有 LLM 调用——只有廉价的启发式方法。 +* **写入有界。** 所有操作在一个事务中完成,所以部分摄入不会留下悬空的行。 + +### 2. 队列 + +后续工作进入持久化的任务队列(与块在同一个磁盘存储中)。每个任务携带一种类型、一个 payload、一个去重 key、重试记录和一个调度窗口。类型如下: + +| 类型 | 功能 | +| --------------- | -------------------------------------------------------------------------------------- | +| `extract_chunk` | 深度评分 + 实体提取。决定 `admitted` 还是 `dropped`。 | +| `append_buffer` | 将一个 admitted 的叶子添加到源的(或主题的)树的 L0 缓冲区。可能触发密封。 | +| `seal` | 将 L0 缓冲区压缩为 L1 摘要;如果父缓冲区已满,则向上级联。 | +| `topic_route` | 将叶子路由到每个实体的主题树,由热度检查控制。 | +| `digest_daily` | 构建全局每日摘要节点。 | +| `flush_stale` | 强制密封停留太久的缓冲区。 | + +### 3. Workers + +一个小型的后台 workers 池(默认 3 个)从队列中取出任务并运行。池被摄入路径立即唤醒,有一个短轮询后备方案,所以错过的唤醒不会搁置工作。共享信号量限制并发 LLM 调用,这样新源的突发不会意外地扇出到数十个并发嵌入向量。 + +启动时,任何 worker 租约已过期的任务(因为崩溃或 kill)会被返还到队列。崩溃不会丢失已 admitted 但尚未密封的工作。 + +### 4. 树状态 + +三棵独立的树从同一个叶子流构建。 + +* **源树** —— 每个源一个。新叶子进入 L0 缓冲区;当缓冲区填满(或 stale-flush 触发),一个 `seal` 写入 L1 摘要,级联继续向上。 +* **主题树** —— 每个高热度实体一个。路由器检查实体是否足够热以值得拥有自己的树,如果是,则追加到其缓冲区。 +* **全局树** —— 一棵树,每天增长一个节点,随着天数累积向上行走。 + +### 5. 调度器 + +调度器循环独立于摄入路径运行。每天 00:00 UTC 它为昨天入队一个全局每日摘要,并为今天入队一个 stale-flush。调度器**不自己运行**摘要器——一切通过队列,所以重试、去重和 stale-lock 恢复保持集中。 + +### 6. 叶子生命周期 + +每个块经历一个小型状态机: + +```text +pending_extraction --> admitted --> buffered --> sealed + \ + --> dropped +``` + +* 提取根据深度评分决定 `admitted` 还是 `dropped`。 +* Admitted 的叶子移入缓冲区(`buffered`)。 +* 当缓冲区密封时,里面的每个叶子被标记为 `sealed`。 +* `dropped` 的叶子停在这里。它们的块行保留用于来源追溯,但没有缓冲区或摘要引用它们。 + +这就是为什么检索可以显示来源追溯而无需重新运行流水线:块行及其终端生命周期状态就够了。 + +## 触发摄入 + +* **自动的** —— 每个活跃的集成每 20 分钟自动拉取一次;见 [自动拉取](auto-fetch.zh-CN.md)。 +* **手动的** —— 桌面 app 的"记忆"标签页暴露了每个源的"运行摄入"触发器。 +* **RPC** —— `openhuman.memory_tree_ingest`,用于高级工作流。 + +## 在桌面 app 中 —— 智能标签页 + +从底部导航栏打开。 + +**系统状态。** 页面顶部显示当前状态(空闲、摄入中、摘要中)和一个**运行摄入**按钮,用于手动触发对任何连接源的同步。 + +**记忆指标:** + +| 指标 | 显示内容 | +| ---------------------- | ------------------------------------------------------------------------------------------ | +| **存储** | `/memory_tree/chunks.db` 和 Obsidian 存储库总大小。 | +| **源** | 已摄入的不同源数量(每个 Gmail 标签、Slack 频道、文档等各算一个)。 | +| **块** | 存储中 ≤3k token 的块总数。 | +| **主题** | 目前已实例化的主题树数量(从"热"实体构建的每个实体摘要)。 | +| **最早 / 最新记忆** | 最旧和最新块的时间戳。 | + +**记忆图谱。** 一个实体及其关系的力导向可视化,从实体索引绘制。图谱随着自动拉取获取更多数据而增长——早期稀疏,几天内变得密集。 + +**Obsidian 存储库。** 一个**"在 Obsidian 中查看存储库"**按钮通过 `obsidian://open?path=...` 深度链接直接打开 `/wiki/`。你也可以在任何文件浏览器中打开该文件夹。 + +**摄入活动。** 一个显示摄入事件随时间分布的热力图,类似于 GitHub 的贡献图。可用于发现自动拉取空闲的时期(例如连接中断导致同步停止)。 + +**搜索与检索。** 记忆树上的搜索栏。支持源作用域、主题作用域或全局查询,任何结果都可以链接回底层块文件(在你的 Obsidian 存储库中)以获取完整来源追溯。 + +**路由。** 智能标签页还显示智能体每个任务使用的模型——见[自动模型路由](../model-routing/)。 + +## 交换后端 + +记忆树流水线(分块 → 评分 → 密封 → 摘要)是默认的。在多个智能体间自托管 [agentmemory](https://github.com/rohitg00/agentmemory) 且希望 OpenHuman 共享相同持久化存储的操作员可以通过 `MemoryConfig.backend = "agentmemory"` 选择外部后端——参见 [agentmemory 后端](agentmemory-backend.zh-CN.md) 了解配置 keys、字段映射、端点表、安全措施和故障模式。 diff --git a/gitbooks/features/platform.zh-CN.md b/gitbooks/features/platform.zh-CN.md new file mode 100644 index 000000000..d8dcf5a89 --- /dev/null +++ b/gitbooks/features/platform.zh-CN.md @@ -0,0 +1,75 @@ +--- +description: >- + OpenHuman 以什么形式交付(原生 React + Tauri v2 桌面应用,Rust core)、 + 支持的平台,以及当前范围内的功能。 +icon: layer-plus +--- + +# 平台与可用性 + +OpenHuman 是一个原生桌面应用,不是浏览器扩展,也不是 Electron 包装器。基于 **React + Tauri v2** 构建,搭载 **Rust core**,它体积小、启动快、不干扰你的工作流。 + +*** + +## 支持的平台 + +| 平台 | 架构 | 分发方式 | +| ---------- | ---------------------- | -------------------------- | +| **macOS** | Intel、Apple Silicon | `.dmg` 安装包、Homebrew | +| **Windows**| x64、ARM64 | `.msi` 安装包 | +| **Linux** | x64 | AppImage、`.deb` | + +*** + +## 为什么是原生应用 + +OpenHuman 作为原生应用构建而非 Web 包装器,有三个原因: + +**体积小。** 只有典型通信工具的几分之一。不到一秒启动,内存占用极少。 + +**启动快。** 无需初始化浏览器引擎。立即就绪接受请求。 + +**操作系统级安全。** 凭据保存在你平台的安全密钥链中:macOS Keychain、Windows Credential Manager、Linux Secret Service。敏感数据永不放在浏览器存储或明文文件中。本地记忆树的 SQLite 数据库位于你的工作区文件夹中,由你拥有。 + +*** + +## 架构概览 + +```text +┌──────────────────────────────────────────────────┐ +│ Tauri shell - windowing, OS integration │ +└──────────────────────────────────────────────────┘ + │ JSON-RPC ↕ +┌──────────────────────────────────────────────────┐ +│ Rust core(进程内 `openhuman` core)│ +│ • Memory Tree, integrations, auto-fetch │ +│ • Model router, TokenJuice, native tools │ +│ • Voice (STT in, TTS out, Meet agent) │ +└──────────────────────────────────────────────────┘ + │ +┌──────────────────────────────────────────────────┐ +│ React frontend - screens, navigation │ +└──────────────────────────────────────────────────┘ +```text + +Shell 是载体(负责窗口化、进程生命周期、IPC)。所有产品逻辑都在 Rust core 中。React 前端通过 JSON-RPC 与 core 通信。参见[架构](../developing/architecture/)获取完整图景。 + +*** + +## 实时通信 + +桌面应用与 OpenHuman 后端保持持久连接。响应在生成时流式输出;输出渐进出现,而非等待后的最终结果。如果网络断开,应用会自动重连,使用渐进退避。 + +*** + +## 离线行为 + +你的本地状态保存在设备上。偏好设置、设置和连接的源配置在离线时仍然可用。本地记忆树完全可访问,你可以浏览 [Obsidian 存储库](obsidian-wiki/),在无网络连接的情况下阅读你现有的笔记。 + +自动拉取和实时 LLM 调用需要网络连接。网络恢复时,下一个 20 分钟触发周期会从上次停止的地方继续。 + +*** + +## 自动更新 + +桌面 shell 通过 Tauri 的更新插件自动更新,针对 GitHub Releases 上发布的一份清单。进程内 OpenHuman core 打包在同一 bundle 中,所以 shell 更新会同时升级两者。 diff --git a/gitbooks/features/token-compression.zh-CN.md b/gitbooks/features/token-compression.zh-CN.md new file mode 100644 index 000000000..e0999e5f7 --- /dev/null +++ b/gitbooks/features/token-compression.zh-CN.md @@ -0,0 +1,51 @@ +--- +description: >- + TokenJuice - 一层规则叠加,在工具输出进入 LLM 上下文之前将其压缩。 + 处理成千上万封邮件依然成本低廉。 +icon: file-zipper +--- + +# 智能 Token 压缩 + +LLM Token 价格不菲,而冗长的工具输出是消耗大多数 Token 的地方。繁忙仓库里的 `git status`、一次 `cargo build` 日志、一个 600 条消息的邮件串,或者针对真实集群的 `docker ps -a`,这些都可能把上下文窗口撑得很大,却几乎不带多少有效信息。 + +OpenHuman 搭载 **TokenJuice**,这是 [vincentkoc/tokenjuice](https://github.com/vincentkoc/tokenjuice) 的移植版本,直接集成到工具执行路径中。在任何工具结果到达模型之前,TokenJuice 会将输出通过一层规则叠加进行处理,去除噪音、保留信号。 + +## 三层规则叠加 + +规则是 JSON,按以下顺序合并,后面的层级覆盖前面的: + +
层级路径用途
内置随二进制文件发布为 git、npm、cargo、docker、kubectl、ls 等提供的合理默认值
用户~/.config/tokenjuice/rules/你的个人覆盖,应用于所有项目
项目.tokenjuice/rules/仓库特定的覆盖,纳入版本控制,与团队共享
+ +每条规则命名一个工具/命令模式和一个压缩策略(截断、行去重、折叠空白、删除匹配的正则表达式、摘要分段等)。新规则就是 JSON 文件,无需重新编译。 + +## 为什么这和记忆有关 + +TokenJuice 是使[自动拉取](obsidian-wiki/auto-fetch.zh-CN.md)在经济上可行的原因。当 Gmail provider 同步一页 200 条消息时,TokenJuice 在每个规范化的邮件进入构建摘要的模型**之前**就将其压缩。GitHub diff、Slack 频道转储以及其他任何高流量来源同理。 + +具体来说:通过前沿模型摄入你最近六个月的邮件费用从数百美元降到个位数美元。 + +## 它在流水线中的位置 + +```text +工具调用结果 + │ + ▼ +TokenJuice(分类 → 匹配规则 → 压缩) + │ + ▼ +LLM 上下文 +```text + +实现:`src/openhuman/tokenjuice/`(`classify.rs`、`reduce.rs`、`rules/compiler.rs`、`tool_integration.rs`)。 + +## 检查和覆盖 + +* 在 `~/.config/tokenjuice/rules/` 中放入一个 JSON 文件来全局添加或覆写规则。 +* 在仓库内的 `.tokenjuice/rules/` 中放入一个来做同样的项目级设置。 +* 使用 `RUST_LOG=openhuman_core::openhuman::tokenjuice=debug` 启动 core,可以查看匹配了什么以及多少输出被裁剪了。 + +## 另见 + +* [原生工具](native-tools/README.zh-CN.md)。大多数重型工具输出都经过 TokenJuice。 +* [记忆树](obsidian-wiki/memory-tree.zh-CN.md)。压缩输出的下游消费者。 diff --git a/gitbooks/overview/getting-started.zh-CN.md b/gitbooks/overview/getting-started.zh-CN.md new file mode 100644 index 000000000..20c333373 --- /dev/null +++ b/gitbooks/overview/getting-started.zh-CN.md @@ -0,0 +1,78 @@ +--- +description: >- + 安装 OpenHuman,完成应用内入门引导(登录、连接 Gmail、 + 选择 AI 运行方式),然后对你的记忆树发出第一个请求。 +icon: play +--- + +# 快速入门 + +本文将引导你完成安装 OpenHuman、完成应用内入门引导,以及发出第一个请求。 + +OpenHuman 遵循 GNU GPL3 开源许可证,代码库位于 [github.com/tinyhumansai/openhuman](https://github.com/tinyhumansai/openhuman)。 + +*** + +## 系统要求 + +OpenHuman 支持 **macOS、Windows 和 Linux** 桌面端。建议 4 GB 以上内存;如果要摄入超大型邮箱或仓库,或在同一台机器上运行[本地模型](../features/model-routing/local-ai.md),建议 16 GB 以上。 + +### 权限 + +首次启动 OpenHuman 时,操作系统会提示授予应用所需的权限(macOS 上的 Accessibility、语音热键的 Input Monitoring,以及计划使用[会议智能体](../features/mascot/meeting-agents.md)时的相机/麦克风)。你随时可以在 **设置 → 自动化与渠道** 中查看和调整这些权限。 + +*** + +## 1. 下载并安装 + +从 [https://tinyhumans.ai/openhuman](https://tinyhumans.ai/openhuman) 或通过你平台的软件包管理器获取 OpenHuman 桌面应用。安装后打开应用。 + +## 2. 登录 + +第一个屏幕是**"登录!让我们开始吧"**。提供多种登录方式,包括社交登录。如果你要将应用指向自定义 core RPC URL(自建后端的情况),还有一个**高级**面板;大多数用户可以忽略它。 + +{% hint style="info" %} +**无永久锁定。** 登录不会授予 OpenHuman 对任何内容的持续访问权。所有第三方访问都需要在以下步骤中每个集成单独进行明确的 OAuth 批准。 +{% endhint %} + +## 3. 发出你的第一个请求 + +一旦 Gmail 完成摄入(首次自动拉取会在二十分钟内触发),可以尝试以下提示: + +**简报** + +* "过去 12 小时我需要了解什么?" +* "有什么在等着我?" + +**跨源查询** + +* "总结我今天错过了什么。" +* "这周有哪些关键决策?" +* "从我最近的对话中提取行动项。" +* "Sarah 在邮件和聊天中对这个项目说了什么?" + +OpenHuman 自动为每个任务选择合适的模型。参见[自动模型路由](../features/model-routing/)。 + +*** + +## 4. 打开 Obsidian 存储库 + +"记忆"标签页有一个**"在 Obsidian 中查看存储库"**按钮。点击它可以在 [Obsidian](https://obsidian.md) 中打开 `/wiki/`。你可以浏览智能体的摘要、放入你自己的笔记,甚至构建手动链接——智能体会在下一次摄入时获取你的编辑。参见 [Obsidian 风格的记忆](../features/obsidian-wiki/)。 + +*** + +## 5. 让吉祥物做更多 + +现在智能体有了记忆和一个模型,产品的其余部分就是给它更多发挥空间: + +* [**会议智能体**](../features/mascot/meeting-agents.md) —— 放入一个 Google Meet 链接,吉祥物作为真实参与者加入:它倾听、将笔记记入记忆树、在通话中说话,并实时使用工具。 +* [**从集成自动拉取**](../features/obsidian-wiki/auto-fetch.zh-CN.md) —— 从**设置**中连接更多源;每二十分钟调度器将新数据拉入你的树。 +* [**原生语音**](../features/native-tools/voice.md) —— 按键说话输入和 TTS 回复,这样你可以和 OpenHuman 对话而不是打字。 +* [**潜意识循环**](../features/subconscious.md) —— 让你离开时吉祥物继续处理待办任务。 + +## 加入社区 + +OpenHuman 处于早期测试阶段。在这个阶段,反馈和贡献能带来真正的改变。 + +* **GitHub:** [github.com/tinyhumansai/openhuman](https://github.com/tinyhumansai/openhuman) +* **Discord:** [discord.tinyhumans.ai](https://discord.tinyhumans.ai) diff --git a/scripts/i18n-doc-fix.sh b/scripts/i18n-doc-fix.sh new file mode 100755 index 000000000..d3eb61f90 --- /dev/null +++ b/scripts/i18n-doc-fix.sh @@ -0,0 +1,85 @@ +#!/bin/bash +# i18n-doc-fix.sh - 自动修复可识别的问题 +# 使用: ./scripts/i18n-doc-fix.sh [--dry-run] + +DRY_RUN=false +if [[ "$1" == "--dry-run" ]]; then + DRY_RUN=true + echo "🔍 Dry-run 模式,仅显示将要修改的内容" + echo "" +fi + +# 1. 修复裸代码块(``` → ```text) +echo "【1/3】修复裸代码块..." +for f in gitbooks/**/*.zh-CN.md; do + if [[ -f "$f" ]]; then + # 匹配孤立的 ``` 行(前后不是 ```text 这样的语言标识) + # 简单策略:在 ``` 后紧跟非字母字符的改为 ```text + if grep -q '^```$' "$f"; then + if $DRY_RUN; then + echo " [dry-run] would fix: $f" + else + # 使用 perl 做替换:单独的 ``` 行 → ```text + perl -i -pe 's/^(```)$/$1text/' "$f" + # 但要处理 ```text 已经存在的情况,我们再把 ```texttext 变回来 + perl -i -pe 's/```texttext/```text/' "$f" + echo " fixed: $f" + fi + fi + fi +done +echo "" + +# 2. 修复 http:// → https://(只改外部域名链接,不改内部路径) +echo "【2/3】修复 http:// → https://..." +for f in gitbooks/**/*.zh-CN.md; do + if [[ -f "$f" ]]; then + if grep -q 'http://' "$f"; then + if $DRY_RUN; then + echo " [dry-run] would fix: $f" + else + # 只替换 http:// 开头且后面不是 // 开头的(避免把 //path 变成 https:////path) + perl -i -pe 's|http://(?![/])|https://|g' "$f" + echo " fixed: $f" + fi + fi + fi +done +echo "" + +# 3. 修复 sidecar 术语 +echo "【3/3】移除 sidecar 术语(core 已内联)..." +for f in gitbooks/**/*.zh-CN.md; do + if [[ -f "$f" ]]; then + if grep -qi 'sidecar' "$f"; then + if $DRY_RUN; then + echo " [dry-run] would fix: $f" + else + # 替换 sidecar 相关描述为更准确的说法 + perl -i -pe 's/[Ss]idecar[^s]*(sidecar)?//g' "$f" + perl -i -pe 's/\bsidecar\b//g' "$f" + echo " fixed: $f" + fi + fi + fi +done +echo "" + +# 4. 添加末尾空行 +echo "【4/4】确保文件末尾有空行..." +for f in gitbooks/**/*.zh-CN.md; do + if [[ -f "$f" ]]; then + last=$(tail -c1 "$f" 2>/dev/null | xxd -p) + if [[ "$last" != "0a" && -s "$f" ]]; then + if $DRY_RUN; then + echo " [dry-run] would fix: $f" + else + echo "" >> "$f" + echo " fixed: $f" + fi + fi + fi +done +echo "" + +$DRY_RUN && echo "✅ Dry-run 完成,使用不带 --dry-run 参数运行以实际修改。" || echo "✅ 修复完成。" \ No newline at end of file diff --git a/scripts/i18n-doc-scan.sh b/scripts/i18n-doc-scan.sh new file mode 100755 index 000000000..946616881 --- /dev/null +++ b/scripts/i18n-doc-scan.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# i18n-doc-scan.sh - OpenHuman GitBook 中文文档问题扫描 + +set -e + +echo "=== OpenHuman i18n 文档扫描 ===" +echo "" + +# 1. 未本地化的内部链接 +echo "【1/5】检查未本地化的 .md 链接..." +UNLOCALIZED=$(find gitbooks -name "*.zh-CN.md" -exec grep -l '\.md)' {} \; 2>/dev/null | while read f; do + grep '\.md)' "$f" 2>/dev/null | grep -v '\.zh-CN\.md)' | sed "s|^|$f:|" || true +done) +if [[ -n "$UNLOCALIZED" ]]; then + COUNT=$(echo "$UNLOCALIZED" | grep -c ':' || echo 0) + echo "❌ 发现未本地化链接(共 $COUNT 处):" + echo "$UNLOCALIZED" | head -30 +else + echo "✅ 无未本地化链接" +fi +echo "" + +# 2. MD040 - 代码块缺少语言标识(查找孤立的 ``` 行) +echo "【2/5】检查代码块语言标识(MD040)..." +NO_LANG=$(find gitbooks -name "*.zh-CN.md" -exec sh -c ' + for f; do + line_num=0 + while IFS= read -r line; do + line_num=$((line_num + 1)) + if [[ "$line" == "\`\`\`" ]]; then + # 检查前一行是否也是 ``` 或空(是代码块开始) + # 简单判断:当前行是 ``` 且下一行不是以 ``` 开头(结尾没有语言标识) + prev_line=$(sed "$((line_num - 1))q;d" "$f" 2>/dev/null || echo "") + next_line=$(sed "$((line_num + 1))q;d" "$f" 2>/dev/null || echo "") + if [[ ! "$line" =~ ^\`\`\`[a-zA-Z] ]]; then + echo "$f:$line_num: $line" + fi + fi + done < "$f" + done +' sh {} + 2>/dev/null || true) +if [[ -n "$NO_LANG" && ${#NO_LANG} -gt 10 ]]; then + echo "❌ 发现裸代码块:" + echo "$NO_LANG" | head -20 +else + echo "✅ 所有代码块均有语言标识" +fi +echo "" + +# 3. http:// 外部链接 +echo "【3/5】检查 http:// 外部链接..." +HTTP_FILES=$(find gitbooks -name "*.zh-CN.md" -exec grep -l 'http://' {} \; 2>/dev/null || true) +if [[ -n "$HTTP_FILES" ]]; then + echo "❌ 发现 http:// 链接:" + find gitbooks -name "*.zh-CN.md" -exec grep -n 'http://' {} \; 2>/dev/null | head -10 +else + echo "✅ 无 http:// 链接" +fi +echo "" + +# 4. sidecar 术语 +echo "【4/5】检查 sidecar 术语..." +SIDECAR_FILES=$(find gitbooks -name "*.zh-CN.md" -exec grep -l -i 'sidecar' {} \; 2>/dev/null || true) +if [[ -n "$SIDECAR_FILES" ]]; then + echo "❌ 发现 sidecar 提及:" + find gitbooks -name "*.zh-CN.md" -exec grep -n -i 'sidecar' {} \; 2>/dev/null | head -10 +else + echo "✅ 无 sidecar 术语" +fi +echo "" + +# 5. 末尾空行检查 +echo "【5/5】检查文件末尾空行..." +MISSING_TRAILING=$(find gitbooks -name "*.zh-CN.md" -exec sh -c ' + for f; do + if [[ -s "$f" ]]; then + last=$(tail -c1 "$f" 2>/dev/null | xxd -p | tr -d " ") + if [[ "$last" != "0a" ]]; then + echo "$f" + fi + fi + done +' sh {} + 2>/dev/null || true) +if [[ -n "$MISSING_TRAILING" ]]; then + echo "❌ 文件缺少末尾空行(共 $(echo "$MISSING_TRAILING" | wc -l) 个):" + echo "$MISSING_TRAILING" | head -10 +else + echo "✅ 所有文件末尾有空行" +fi +echo "" + +echo "=== 扫描完成 ===" \ No newline at end of file